Skip to content
ChartDeck ChartScript docs Account Open the terminal

Strings and formatting

Text in ChartScript: string literals, template literals, joining text, the str.* functions, and formatting numbers and times.

Strings are the text your script shows: label text, table cells, log lines, alert messages. Most of the work is turning numbers into readable text, so this page spends most of its time on str.tostring and str.format.

#Writing strings

ChartScriptFragmentOpen in editor
const a = "Double quotes"
const b = "Single quotes work the same"
const c = "Line one\nLine two"  // \n starts a new line
const d = "Say \"hi\""  // escape the quote you opened with
const e = "Arrow \u2191"  // a character by its Unicode number
plot(str.length(a + b + c + d + e))

Escapes: \\ (backslash), \", \', \n (new line), \t (tab) and \uXXXX. A normal string must end on the line where it starts (CS1021).

For text over several lines, use a template literal between backticks (next section). It may span lines, and its value is exactly the text between the backticks, line breaks and spaces included:

ChartScriptFragmentOpen in editor
const help = `Fast line: EMA of the close.
Slow line: SMA of the close.`
plot(str.length(help))

Because the indentation of the following lines is part of the text, start them at the left edge.

A string can be at most 40,960 characters. A longer literal is refused when you compile (CS1023); a longer string built while the script runs stops it (CS5104).

All text a script outputs is shown as plain text. Markup such as HTML is displayed as typed, never interpreted.

#Template literals

A template literal is text between backticks, `…`. Inside it, ${expression} inserts the value of any expression as text:

ChartScriptFragmentOpen in editor
const rsi = ta.rsi(close, 14)
const a = `RSI ${rsi}`  // the same as "RSI " + str.tostring(rsi)
const b = `RSI ${str.tostring(rsi, "0.0")} on ${market.symbol}`  // with a number format
const c = `Range ${high - low}, ${close > open ? "up" : "down"} bar`
log.info(b)
plot(rsi)
  • ${x} converts the value with str.tostring(x) in its default format: up to 10 decimals for numbers, NaN for na. For another format, call str.tostring inside the braces, as in b above.
  • Any expression can go between the braces: arithmetic, a ternary, a function call.
  • A template literal may span lines (see above). Escapes are the same as in other strings, plus \` for a backtick and \$ for a dollar sign that should not start ${.
  • A template literal without ${…} is simply a string.

Like any text built from values, a template literal cannot be used where fixed text is required, such as a plot title (CS2103).

#Joining text

+ joins two strings, and += appends to a string variable:

ChartScriptFragmentOpen in editor
let msg = "Close: "
msg += str.tostring(close)
msg += close > open ? " (up)" : " (down)"
plot(str.length(msg))

+ only joins strings. A number has to be converted first (CS2008):

ChartScriptError: CS2008
//@chartscript 1
indicator("Missing conversion")
const txt = "RSI is " + ta.rsi(close, 14)
plot(close)

Write "RSI is " + str.tostring(ta.rsi(close, 14), "#.0"), or `RSI is ${str.tostring(ta.rsi(close, 14), "#.0")}`.

#Numbers to text: str.tostring

str.tostring(value) works for numbers, bools, strings and enum values. For numbers, a second argument controls the format:

Format1234.5678 becomesNotes
(none)1234.5678up to 10 decimals, no trailing zeros
"#.##"1234.57# = a digit only if needed
"0.00"1234.570 = always a digit: 42 gives 42.00
"#,###.00"1,234.57, adds thousands separators
"#"1235rounds to a whole number
format.mintickrounded to the market's tick size102.456 on a 0.1-tick market gives 102.5
format.volume1.235Kshort form with K, M, B
format.percent1234.57%adds a % sign; it does not multiply by 100

A few other cases: str.tostring(true) is "true", an int prints without decimals, and na prints as NaN. To show something friendlier for missing values, test first: na(x) ? "–" : str.tostring(x, "0.00").

ChartScriptFragmentOpen in editor
const rsi = ta.rsi(close, 14)
const txt = na(rsi) ? "RSI: warming up" : "RSI: " + str.tostring(rsi, "0.0")
log.info(txt)
plot(rsi)

#Placeholders: str.format and str.tpl

str.format fills numbered placeholders {0}, {1} … with the arguments that follow. A placeholder can carry a number format:

PlaceholderExample output
{0}the value as str.tostring would write it
{0,number,#.##}3.14
{0,number,0.000}0.123
{0,number,percent}0.12345 becomes 12% (here the value is multiplied by 100)
{0,date,yyyy-MM-dd}a time in milliseconds as a date, in UTC
ChartScriptFragmentOpen in editor
const line1 = str.format("{0}: {1,number,#.##} ({2,number,percent})", market.symbol, close, (close - open) / open)
log.info(line1)
plot(close)

The same placeholder may appear more than once, and the arguments can be any type. str.tpl does the same job with the same {0} syntax. For short text a template literal is often easier to read; str.format is handy when each value needs its own format.

#Times to text: str.format_time

str.format_time(time, format, timezone) writes a timestamp as text. The default format is ISO style (2026-03-14T09:00:00+0000) in UTC.

PatternMeaning
yyyyyear
MMmonth, 01–12
ddday of the month
HHhour, 00–23
mmminute
sssecond
ChartScriptFragmentOpen in editor
const stamp = str.format_time(time, "yyyy-MM-dd HH:mm")
log.info("Bar opened " + stamp + " UTC")
plot(close)

#The str.* functions

FunctionDoesExample
str.length(s)number of charactersstr.length("BTC") is 3
str.upper(s), str.lower(s)change case"BTC" / "btc"
str.trim(s)remove spaces at both ends" x " gives "x"
str.contains(s, part)does s contain part?true / false
str.startswith, str.endswithprefix / suffix teststr.endswith(market.symbol, "USDT")
str.pos(s, part)first position of part, from 0str.pos("hello", "l") is 2
str.substring(s, from, to)part of a string; to is not included and may be left outstr.substring("hello", 1, 3) is "el"
str.replace(s, old, new, n)replace one occurrence (the nth, from 0)
str.replace_all(s, old, new)replace every occurrence"a-b-c" → "a+b+c"
str.split(s, sep)split into a string[]str.split("a,b,c", ",") has 3 elements
str.repeat(s, n, sep)repeat with an optional separatorstr.repeat("ab", 3, "|") is "ab|ab|ab"
str.match(s, pattern)the first match of a regular expression, or ""str.match("abc123", "[0-9]+") is "123"
str.tonumber(s)text to a float, na if it is not a numberstr.tonumber("3.5")

To join an array of strings, use arr.join(", ").

#A complete example

This script puts a summary label on the newest bar: the symbol, the price rounded to the tick size and the change over a chosen number of bars.

ChartScriptOpen in editor
//@chartscript 1
indicator("Price summary", { overlay: true })
const len = input.int(24, "Compare with N bars ago", { minval: 1 })
const chg = close - close[len]
const chgPct = chg / close[len] * 100
state info: label = na
if (barstate.islast) {
    label.delete(info)
    const sign = chg >= 0 ? "+" : ""
    const txt = str.format(
        "{0}  {1}\n{2}{3,number,0.00}% over {4} bars",
        market.symbol,
        str.tostring(close, format.mintick),
        sign,
        chgPct,
        len
    )
    info = label.new(
        bar_index,
        high,
        txt,
        { style: label.style_label_down, color: chg >= 0 ? color.up : color.down, textcolor: color.white }
    )
}
plot(close, "Close", color.muted)

The text is built only on the last bar (barstate.islast), because it is only shown there. That keeps the script fast on long charts.

#Good to know

  • Titles of plots, inputs and alert conditions are fixed text and cannot be built from values (CS2103). Changing text belongs in labels, tables, logs and alert.send messages.
  • Building long strings on every bar of a long chart costs time. Build display text inside if (barstate.islast) when only the last bar shows it.
  • log.info("close {0}", close) takes the same {0} placeholders as str.format, which is handy while debugging; the output appears in the editor's Console tab.

The ChartScript documentation is in English for now; translations will follow.