str.tostring in Pine Script: Formats, Percentages, and a Working Dashboard
str.tostring converts values to text for labels, tables, and messages. Keep the calculations numeric until you need a display string. Formatting changes how a value looks; it does not change the numeric series used by the rest of the script.
Choose a format that matches the unit
| Input meaning | Format choice | Check |
|---|---|---|
| Price | format.mintick | Uses the current symbol's minimum tick, not a fixed number of decimals for every FX pair |
| Percentage points | format.percent | Pass 5.23 to represent 5.23%, rather than the fractional return 0.0523 |
| Fractional return | Custom "0.00%" | The trailing percent token scales the representation by 100 |
| Large quantity | format.volume | Abbreviates magnitude; does not identify the unit or data source |
| Fixed precision | "0.000" | Includes trailing zeros |
| Optional decimals | "#.###" | Omits unnecessary trailing zeros |
TradingView's string formatting documentation distinguishes the percent constant from custom percent patterns. Mixing them can produce a display off by a factor of 100. Test a known fractional return before using the result in a dashboard.
Boolean values can be converted to text. Supported collections also have string representations, but this does not make the function a general serializer for every Pine object. Use an explicit fallback for unavailable numeric data so a reader can distinguish missing from zero.
A complete Pine v6 dashboard
This example calculates price change, RSI, MACD, and Bollinger values on every bar. It updates one persistent label on the last bar instead of creating a new historical label each time the chart advances.
//@version=6
indicator("Formatted market dashboard", overlay=true)
float changePct = not na(close[1]) and close[1] != 0 ? 100 * (close - close[1]) / close[1] : na
float rsiValue = ta.rsi(close, 14)
[macdLine, signalLine, macdHistogram] = ta.macd(close, 12, 26, 9)
[bbBasis, bbUpper, bbLower] = ta.bb(close, 20, 2)
float average = ta.sma(close, 20)
bool aboveAverage = close > average
formatValue(float value, string pattern) =>
na(value) ? "N/A" : str.tostring(value, pattern)
var label dashboard = label.new(na, na, "", style=label.style_label_left, color=color.blue, textcolor=color.white)
if barstate.islast
string displayText = "Price: " + formatValue(close, format.mintick)
displayText += "\nVolume: " + formatValue(volume, format.volume)
displayText += "\nChange: " + formatValue(changePct, format.percent)
displayText += "\nRSI: " + formatValue(rsiValue, "0.00")
displayText += "\nMACD: " + formatValue(macdLine, "0.0000")
displayText += "\nBB upper: " + formatValue(bbUpper, format.mintick)
displayText += "\nAbove SMA: " + (na(average) ? "N/A" : str.tostring(aboveAverage))
label.set_xy(dashboard, bar_index, high)
label.set_text(dashboard, displayText)
ta.macd and ta.bb each return three values, so the example receives them with tuple declarations and selects the intended member. Assigning either whole tuple to a single numeric variable is not a valid substitute.
The change calculation multiplies the fractional return by 100 before applying format.percent. It also checks the previous close for missing or zero data. Indicator values remain unavailable during relevant warmup periods, and the display uses N/A rather than inventing a value.
Version and display boundaries
The v5 migration guide maps v4 tostring to namespaced str.tostring. The namespaced function was not the v4 spelling. Use the version declared by the complete script and review other migration changes rather than changing only the version number.
An open realtime bar can update the label as price changes. This example is a display indicator, not a trade signal or strategy. It does not report historical returns, submit orders, or guarantee that displayed values are final before bar close.
For a larger dashboard, consider a table and profile the actual script before making performance claims. Repeated formatting in a large loop can add work, but there is no measured speed benchmark attached to this example.
Pineify's Coding Agent can help draft display logic. Review units and tuple types as well as compiler messages; a successful syntax check does not catch every misleading label or calculation assumption.
Why does my percentage look 100 times too small?
Check the units. With format.percent, convert a fractional return to percentage points first. A custom pattern ending in % performs scaling in the representation.
Does format.mintick always use four decimals for EUR/USD?
No. It follows the selected symbol's tick size, which can vary by feed and instrument.
Why does assigning ta.macd to one variable fail?
It returns a tuple. Receive the MACD line, signal, and histogram separately, then format the member you need.

