Skip to content

Strings and formatting ​

How do you turn a number into text that reads the way you want? Pass it to str.tostring, with a format when the default isn't right, and join the pieces with +. When the text holds several values, str.format fills a template in one call.

pine
//@version=6
indicator("Close as text", overlay = true)
var table panel = table.new(position.top_right, 1, 2)
float change = (close / close[1] - 1) * 100
if barstate.islast
    table.cell(panel, 0, 0, "Close " + str.tostring(close, "#,###.00"))
    // format.percent adds "%" without scaling: hence the * 100 above
    table.cell(panel, 0, 1, "Change " +
         str.tostring(change, format.percent))

What str.tostring writes ​

Without a format, str.tostring rounds a number to at most nine decimals and drops the zeros at the end. A float that holds a whole number gets no decimal point. A bool writes true or false, a string comes back as it is, and an array writes its elements between brackets.

ValueText
0.1 + 0.20.3
1.0 / 30.333333333
1.01
4242
1e-70.0000001
1e211E21
truetrue
array.from(1, 2.5)[1, 2.5]

+ joins two strings, so turn a number into text with str.tostring before you join it. Our engine also joins "a" + 1 as a1, but TradingView refuses + on a string and a number, so a script that leans on it won't compile there.

Decimals, groups and units ​

The second argument of str.tostring is a format: a pattern you write, or one of the format.* constants.

A pattern spells out the digits. 0 always writes a digit, # writes one only when the number needs it, . is the decimal point and , groups thousands. The last digit rounds half away from zero, on the number as it's written, so 1.005 becomes 1.01 even though the float stored for it sits a hair below 1.005.

CallText
str.tostring(3.14159, "#.##")3.14
str.tostring(1.005, "#.##")1.01
str.tostring(2.0 / 3, "#.####")0.6667
str.tostring(0.5, "0.000")0.500
str.tostring(1234567.891, "#,###.00")1,234,567.89
str.tostring(-2.5, "#")-3

The constants read the symbol, or name a unit. This is what str.tostring writes into text. The chart shows a plot's value with the same constants a little differently, as TradingView does: see Plots.

FormatWritesExample
format.mintickAs many decimals as the symbol's tick has1234.5678 on a tick of 0.01: 1234.57
format.priceAs many decimals as the symbol's price scale has1234.5678 at a scale of 100: 1234.57
format.percentThe value as it is, to two decimals, then %12.3: 12.30%
format.volumeK, M, B or T, with up to three decimals1534567: 1.535M, 1.5e12: 1.5T, 2.5e15: 2500T
format.inheritThe number in full, with no rounding

Filling a template ​

str.format takes a template and the values to put in it. {0} is the first value after the template, {1} the second, and so on, so one value can appear twice.

pine
//@version=6
indicator("Template", overlay = true)
var label tag = label.new(bar_index, high, "",
     style = label.style_label_down)
float hi = ta.highest(high, 20)
if barstate.islast
    tag.set_xy(bar_index, hi)
    // {0} and {1} are the first and second values after the template
    string msg = "20-bar high {0,number,#.##}, {1,number,percent} above"
    tag.set_text(str.format(msg, hi, hi / close - 1))

A bare {0} writes a number with thousands groups and up to three decimals. A style after the index changes that:

PlaceholderWritesExample
{0}Groups, up to three decimals1234567.891: 1,234,567.891
{0,number,#.#}The pattern, read as str.tostring reads it2.25: 2.3
{0,number,percent}The value times 100, rounded, then %0.256: 26%
{0,number,integer}The value rounded to a whole number1.5: 2
{0,number,currency}US dollars, two decimals1234.5: $1,234.50
{0,date,yyyy-MM-dd}A timestamp as a date, in UTC2024-03-09
{0,time,HH:mm}A timestamp as a time of day, in UTC14:05

To write a time in a zone of your choice, use str.format_time(time, "yyyy-MM-dd HH:mm", "America/New_York"). With no zone it takes the symbol's exchange zone. Time and sessions lists the letters a pattern takes.

What na and an infinity write ​

A number that is na writes NaN, and so does an infinity, which a division by zero gives. A few formats tell them apart:

Valuestr.tostring(x)format.percentformat.volumeformat.mintickstr.format("{0}", x)
naNaNNaN%NaNNaNNaN
1 / 0NaNNaN%NaNTInfinity∞
-1 / 0NaNNaN%NaNT-Infinity-∞

So a label that reads NaN doesn't tell you which one you have. Test with na(x) first and write a word of your own: na(x) ? "no value" : str.tostring(x).

A string can be na too, and a na string is the empty string. na("") is true, str.length of a na string is 0, + adds nothing for it, and a label or a table cell given one shows nothing. Only str.tostring of a na number writes NaN.

The str tools ​

FunctionGives
str.length(s)The number of characters
str.contains(s, part), str.startswith(s, part), str.endswith(s, part)Whether s holds part, starts with it or ends with it
str.pos(s, part)Where part first starts, counting from 0, or na
str.substring(s, from, to)The characters from from up to, not including, to
str.replace(s, target, with)s with the first target replaced
str.replace_all(s, target, with)s with every target replaced
str.split(s, separator)An array of the pieces between separators
str.upper(s), str.lower(s)The same text in one case
str.trim(s)s without white space at either end
str.repeat(s, n, separator)s written n times, with the separator between
str.match(s, regex)The first match of a regular expression, or ""
str.tonumber(s)The number s spells, or na
str.format_time(time, pattern, zone)A timestamp as text
array.join(a, separator)An array's elements as one string

AmenoScript adds str.trim_left, str.trim_right, str.char and str.concat, which strict Pine refuses.

Traps ​

  • format.percent doesn't multiply: str.tostring(0.05, format.percent) is 0.05%. {0,number,percent} in str.format does, and writes 5%.
  • `str.replace` here always replaces the first match. TradingView's takes a fourth argument, `occurrence`, which our engine ignores, so `str.replace("a-b-c", "-", "+", 1)` reads `a+b-c` where TradingView's reads `a-b+c`.
  • {0,date} and {0,time} in str.format write UTC, and str.format_time with no zone writes the exchange's, so one time can read two ways. Give str.format_time a zone, or write "UTC", when the two have to agree.
  • A na number and an infinity both write NaN.
  • na("") is true, so a test for an empty string and a test for na are the same test.

See also ​

AmenoScript is compatible with Pine Script v6. Pine Script is a trademark of TradingView, Inc., which has no part in AmenoScript.