Appearance
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.
| Value | Text |
|---|---|
0.1 + 0.2 | 0.3 |
1.0 / 3 | 0.333333333 |
1.0 | 1 |
42 | 42 |
1e-7 | 0.0000001 |
1e21 | 1E21 |
true | true |
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.
| Call | Text |
|---|---|
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.
| Format | Writes | Example |
|---|---|---|
format.mintick | As many decimals as the symbol's tick has | 1234.5678 on a tick of 0.01: 1234.57 |
format.price | As many decimals as the symbol's price scale has | 1234.5678 at a scale of 100: 1234.57 |
format.percent | The value as it is, to two decimals, then % | 12.3: 12.30% |
format.volume | K, M, B or T, with up to three decimals | 1534567: 1.535M, 1.5e12: 1.5T, 2.5e15: 2500T |
format.inherit | The 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:
| Placeholder | Writes | Example |
|---|---|---|
{0} | Groups, up to three decimals | 1234567.891: 1,234,567.891 |
{0,number,#.#} | The pattern, read as str.tostring reads it | 2.25: 2.3 |
{0,number,percent} | The value times 100, rounded, then % | 0.256: 26% |
{0,number,integer} | The value rounded to a whole number | 1.5: 2 |
{0,number,currency} | US dollars, two decimals | 1234.5: $1,234.50 |
{0,date,yyyy-MM-dd} | A timestamp as a date, in UTC | 2024-03-09 |
{0,time,HH:mm} | A timestamp as a time of day, in UTC | 14: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:
| Value | str.tostring(x) | format.percent | format.volume | format.mintick | str.format("{0}", x) |
|---|---|---|---|---|---|
| na | NaN | NaN% | NaN | NaN | NaN |
1 / 0 | NaN | NaN% | NaNT | Infinity | ∞ |
-1 / 0 | NaN | NaN% | 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
| Function | Gives |
|---|---|
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.percentdoesn't multiply:str.tostring(0.05, format.percent)is0.05%.{0,number,percent}instr.formatdoes, and writes5%.- `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}instr.formatwrite UTC, andstr.format_timewith no zone writes the exchange's, so one time can read two ways. Givestr.format_timea 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
- The str reference, every function's parameters
- Drawings and tables, where text goes on the chart
- Time and sessions, timestamps and their patterns
- Operators, where an infinity comes from