Frost Script
tradoor's own language for indicators: readable lines that work out a value on every bar and draw it — lines, histograms, levels, marks, shapes, labels and a table, on the chart or in a pane of their own — with settings of every kind and the same numbers as the built-in indicators.
A script reads the chart's candles and gives back what to draw. That is all it can do: it has no word for the network, your account, your browser or the page, so a script someone else wrote can draw wrong lines at worst, and nothing more. See Safety.
Every built-in indicator that can be written in Frost Script shows its own source: open its settings and the Source tab — the EMA ribbon, Ichimoku, the swing map and the key levels among them. Each gives exactly the numbers the built-in does, and they are the best place to start.
Your first script
- On a chart, open Add an indicator and press New Frost Script. Writing a script needs you signed in with Telegram.
- The editor opens on a small script. Change it, or start from one of the examples on the left.
- Under the text it says ✓ Checks, or which line and column a mistake is on — click the message to go there.
- Save and add to chart. The script is now one of your indicators, in the picker under My scripts, with its own settings, style and source.
// A moving average, and a mark where price crosses above it
input length = 20 min 2 max 500 "Length"
average = ema(close, length)
plot(average, "Average", blue)
mark(crossover(close, average), "Up", below, green)
How a script runs
The script is worked out once for every bar on the chart, from the oldest to the newest, top to bottom. On each bar, close is that bar's close, a variable holds what it was set to on that bar, and plot puts that bar's point on the line.
- It runs over the chart's closed bars, as the built-in indicators do, and again as each new bar closes. The bar still forming is not part of it: the newest closed bar is the last, and
islastis true on it. - It runs in your browser, in tradoor's own engine for Frost Script. Nothing of it runs on the server.
Writing it down
- One statement a line.
;also ends one, so two may share a line. Inside round brackets a statement may run on over several lines. //starts a note, to the end of the line.- Numbers are written
14,2.5,.5: digits and one point, 15 digits at most. Texts go in double quotes:"RSI";"\n"is a line break in one. - Colours are written by name, as
#rrggbb, or withrgb(): see Colours. - Names are letters, digits and
_, starting with a letter or_, up to 40 long. Capitals count:Lengthis notlength. - These words belong to the language:
input state if else for to while break continue fn return and or not pane na true false, and so do the bar values, function, colour and style names below. A variable cannot take them. - What a function is given goes in order, or by name:
plot(x, "X", width = 2),line(a, b, c, d, color = red, style = dashed).
Bar values
| Name | On each bar |
|---|---|
open high low close | Its prices. |
volume | What traded in it. |
hl2 hlc3 ohlc4 | (high + low) / 2, (high + low + close) / 3, (open + high + low + close) / 4 |
tr | True range: the larger of high − low and how far either reaches from the close before. On the first bar, high − low. |
bar | Its place: 0 for the first bar the chart holds, then 1, 2, … lastbar is the newest's. |
isfirst islast | True on the first bar, and on the newest closed one. |
time | When it opened, in milliseconds since 1970, UTC. interval is a bar's length, in milliseconds. |
day week month year | Which UTC day, week (from Monday), month and year it is in, as a number that changes when a new one begins: changed(week) is true on a week's first bar. year is also the year itself. |
hour minute dayofweek dayofmonth monthofyear | Its time, UTC. dayofweek is 1 on a Sunday, 2 on a Monday, as Pine counts; the words sunday to saturday stand for those numbers. |
tick decimals | The market's price step, and how many decimals its prices are written with. |
na | No value. See na. |
true false | 1 and 0. |
Settings
input length = 14 min 2 max 500 "Length"
input mult = 2 min 0.1 max 10 step 0.1 "Multiplier"
input showLabels = true "Show labels"
input lineColor = #2962ff "Line colour"
input size = "small" from "tiny", "small", "normal", "large" "Label size"
input start = date(2024, 1, 1) "Range start"
- A setting is a number, a tick (
trueorfalse), a colour, a choice of texts, or a moment (date(year, month, day, hour, minute), UTC). Its settings dialog shows a number field, a tick box, a colour picker, a list or a date to match. - A number takes any of
min,maxandstep. It takes whole numbers when its default is whole and its step, if it has one, is whole too. - A choice's value is the text chosen:
size == "small". - The label in quotes is what the settings dialog shows; without one it shows the name.
- A setting is the same on every bar: use it anywhere a number goes, and as a length. Up to 32, at the top level.
Variables and state
range = high - low // set on every bar
state count = 0 // starts at 0 the first time…
count = count + 1 // …and keeps its value from bar to bar
name = valuesets a variable for this bar. A plain variable starts every bar with no value, until a line sets it.state name = startstarts once, the first time the line is reached, and keeps its value from one bar to the next: a running total, a trend, a stop that trails, the line it drew. Its start is a plain value — a number, a text, a setting,na— not a function over bars.- A variable holds a number or a text, and keeps to the one it was first set to.
- A variable can be read before the line that sets it, as
average[1]in its own definition. - A variable may share a function's name —
sum,label,rising— as a call has brackets; not a bar value's, a colour's or a word of the language's.
Looking back: x[n]
gap = open - close[1] // from the close a bar ago
climbing = close > close[1] and close[1] > close[2]
x[n]is whatxended bar n bars ago on.x[0]isxnow.- It works on number variables and on the bar values:
close[1],high[i]in a loop. To look back at a sum, name it first. nis from 0 to 5,000, and can be worked out:close[length]. Before the first bar there is no value.
Arithmetic and logic
| Write | Means |
|---|---|
+ - * / % | Add, subtract, multiply, divide, remainder. -x turns the sign. + also joins texts. |
== != < <= > >= | Compare: 1 when true, 0 when not. Texts compare with == and !=. |
and or not | Logic. Any number other than 0 is true; 0 and na are not. |
c ? a : b | a where c is true, else b: both numbers, or both texts. |
( ) | First. Then -x; then * / %; then + -; then comparisons; then not, and, or; then ? :. |
No value: na
A value can be missing: an average before it has enough bars, close[1] on the first bar, a division by zero. Frost Script calls it na, and a line simply has a gap where its value is na.
- Arithmetic with
nagivesna; so does a division by zero. - A comparison with
nais not true, so anifon it does not run — andx != yis not true wherexisna: writeisna(x) or x != y. max(na, 0)isna, not 0: no value in, no value out.isna(x)asks;nz(x)gives 0 forna,nz(x, y)givesy. As a colour,nais none: nothing is drawn.
Texts
tag = close > open ? "Up" : "Down"
line1 = tag + " " + str(round(change(close), 2))
note = "Since " + duration(time - time[10]) + "\nat " + str(close, decimals)
| Write | Gives |
|---|---|
"text" + "text" | The two joined. A number joined to a text is written plainly. |
str(x) | A number as a text: whole numbers bare, others to up to 8 decimals. |
str(x, 2) | To 2 decimals. str(x, decimals) writes it as the market writes a price. |
duration(ms) | A stretch of time: 3h, 2d 4h. |
Texts go into titles, labels, the table and settings' choices; a text a script makes is cut at 1,000 characters.
if, for and while
input length = 10 min 1 max 100
if close > open {
colour = 1
} else if close < open {
colour = -1
} else {
colour = 0
}
total = 0
for i = 0 to length - 1 {
total = total + volume[i]
}
for k = 9 to 0 by -1 { // down, by any step
if close[k] > open[k] {
break
}
}
- Blocks go in braces.
elsemay start the line after the closing brace. for i = a to bcounts up fromatob, both included; ifbis belowait does not run.by -1counts down,by 2in twos.while condition { }goes round while the condition holds.breakleaves a loop,continuegoes on to its next round. One loop goes round 10,000 times at most.- Inside
if,forandwhile: setting variables,state, maths, texts, lists, shapes, the table's cells, functions of your own. Not there: plots and marks, settings,pane, and the functions over bars below.
Lists
state swings = list() // kept from bar to bar
ph = pivothigh(5, 5)
if not isna(ph) {
swings.push(ph)
}
average = swings.avg()
plot(swings.size() > 0 ? swings.last() : na, "Last swing")
name = list()makes an empty list of numbers;state name = list()keeps it from bar to bar.push(v) pop() shift() unshift(v) get(i) set(i, v) insert(i, v) remove(i) clear() size() first() last() sum() avg() min() max() indexof(v) includes(v) sort() reverse(), each written after the list's name:swings.get(0).get(-1)is the last. A place outside the list is an error that says so.- A list holds numbers: lines and labels by the number they are given, a kind by 1 or 0. Up to 5,000 values — pushed when full, its oldest goes — and 32 lists.
Functions of your own
fn middle(length) {
return (highest(high, length) + lowest(low, length)) / 2
}
fn band(x, width) {
return x + width, x - width
}
base = middle(26)
upper, lower = band(base, atr(14))
fn name(a, b) { … }at the top level;returngives back a value, or several:p, q = f(x). One that gives back nothing is called on a line of its own.- A function's names are its own. It reads the script's names, but setting one inside makes a new one of the function's: to change the script's, give the value back, or keep it in a list.
- Each call has its own
stateand its own memory forema()and the rest, as Pine's do. A function may call others, but not itself. Plots, settings and the pane are not inside functions.
Functions over bars
These remember the bars before: an average needs the last length values. So each one is worked out on every bar, which is why they go outside if, for and while — and why both sides of c ? a : b are always worked out. A length is from 1 to 5,000: a number, a setting, or maths on them such as length * 2 or round(length / 2), rounded to a whole number. It cannot be a variable.
| Function | Gives |
|---|---|
sma ema rma wma vwma hma(x, length) | Simple, exponential (seeded with the simple average), Wilder's (as RSI and ATR use), weighted, volume-weighted and Hull averages. |
stdev dev median (x, length) | Standard deviation of the whole set (÷ length), mean absolute deviation, median. |
highest lowest sum (x, length) | The highest, the lowest, and the sum of the last length. highest(n) reads the highs, lowest(n) the lows. |
highestbars lowestbars (x, length) | How many bars back the highest or lowest is: 0 or less, as Pine gives it. |
rsi(x, length), atr(length) | Relative Strength Index, 0 to 100; average true range, rma(tr, length). |
change(x), change(x, n), roc(x, n) | x − x[1], x − x[n], and that as a percentage of x[n]. |
linreg cci mfi percentrank (x, length) | Linear regression at the newest bar; commodity channel index; money flow index (with volume); percent of the values before that are at or below this one. |
stoch(x, high, low, length), correlation(a, b, length) | Stochastic, 0 to 100; correlation, −1 to 1. |
rising falling (x, n) | True where x is above (below) each of its n values before. |
pivothigh pivotlow (left, right) | The high (low) right bars back, where it is higher (lower) than the left bars before it and the right bars after; na elsewhere. Known right bars late, as in Pine. A value may come first: pivothigh(close, 5, 5). |
crossover crossunder cross (a, b) | True on the bar a goes from at or below b to above it, the other way, or either. |
barssince(c), valuewhen(c, x) | Bars since c was last true (0 on that bar), and x as it was then. |
changed(x), cum(x) | True on a bar where x is not what it was a bar ago; the sum of every x so far. |
An average that meets na starts over after it, as the built-in indicators do.
Maths
These work out a value from what they are given, and go anywhere.
| Function | Gives |
|---|---|
abs sqrt log log10 exp sign (x) | Without its sign; square root (na below zero); logarithms (na at zero and below); e to the x; −1, 0 or 1. |
pow(x, y) | x to the power y. |
round(x), round(x, digits), floor ceil | To the nearest whole number or to 0–10 decimals; down; up. |
min max avg (a, b, …) | The least, the greatest, and the average of 2 to 12 values; na if any is. |
clamp(x, low, high) | x kept between the two. |
nz(x), nz(x, y), isna(x) | x, or 0 — or y — where it is na; whether it is. |
sin cos tan atan (x), atan2(y, x), pi | In radians. |
date(year, month, day, hour, minute) | A moment in milliseconds, UTC; the hour and minute may be left out. |
barat(time) | The place of the bar holding a moment; na before the first. |
Colours
green
lime
red
orange
yellow
blue
aqua
teal
purple
pink
white
gray
black
silver
maroon
navy
olive
fuchsia
- Any colour:
#ff9800,#f90,#ff980080(its last two the opacity),rgb(255, 152, 0),rgb(255, 152, 0, 0.5). alpha(colour, opacity)sets how solid it is, 0 to 1:alpha(blue, 0.2).gradient(value, low, high, colourLow, colourHigh)mixes between two.- A colour is a value like any other:
c = close > open ? green : red, a setting, a list's value.nais no colour. - A line with no colour takes the next of blue, orange, purple, aqua, red, green, yellow and pink. Every colour can also be changed in the indicator's Style tab.
Plots, marks and shading
These draw along the bars, at the top level, on every bar. Each line needs a title of its own (up to 40 characters): it is what the legend and the Style tab show.
| Write | Draws |
|---|---|
plot(value, "Title", color, width, style, offset) | A line through each bar's value, with gaps where it is na. style: line steps dots dashed dotted. Width 0.5 to 10. offset moves it by bars: 26 draws it 26 bars ahead, past the newest. A colour that changes from bar to bar colours the line bar by bar. |
histogram(value, "Title", color, offset) | A bar from zero for each value, in a colour of its own bar by bar if it is given one; with none, above zero and below take the chart's up and down colours. |
hline(value, "Title", color, width, style) | A level, dashed unless another style is named; gray unless another colour is. |
fill("Upper", "Lower", color) | Shades between two lines drawn above it, named by their titles. |
mark(condition, "Text", above or below, color) | A mark at the bar where the condition is true: below its low (green unless named) or above its high (red unless named). Text up to 12 characters, or none. |
background(color) | Shades the bar's whole height, in its pane; na leaves it. |
barcolor(color) | Colours the bar's candle; na leaves it as it is. |
pane own | Draws the script in a pane of its own below the chart, with its own scale. pane price, the default, draws on the price chart. |
fill and background, so the candles show through; give one an opacity of its own with alpha(). plot(x, "X", color = na) keeps a line's values, for a fill, without drawing it. Up to 32 lines, 16 fills, 16 marks and 4 backgrounds.Lines, labels and boxes
state last = na
ph = pivothigh(5, 5)
if not isna(ph) {
delete(last)
last = line(bar - 5, ph, bar + 20, ph, color = red, style = dashed)
tag = label(bar - 5, ph, "High\n" + str(ph, decimals), color = red, place = above)
}
Shapes are drawn anywhere — in if, loops and functions — and stay until they are deleted. A shape's x is a bar's place (bar, bar - 5, bar + 10 ahead of the newest), its y a price. Each is given back as a number to keep, move or delete it by.
| Write | Draws |
|---|---|
line(x1, y1, x2, y2, color, width, style, extend) | A straight line. style: solid dashed dotted. extend: none left right both, on past its points. |
label(x, y, text, color, textcolor, place, size, box) | A text at a point, in a box pointing at it. place: above below left right center; size: tiny small normal large; box = false draws the text alone. Its text may run over several lines. |
box(x1, y1, x2, y2, color, border, width) | A rectangle, filled and outlined. |
delete(id) | Lets one go. na, or one already gone, is left alone. |
set_x1 set_y1 set_x2 set_y2 (id, v), set_xy1 set_xy2 (id, x, y) | Moves one end. For a label, set_x set_y set_xy. |
set_text set_color set_textcolor set_width | Changes it. |
get_x1 get_y1 get_x2 get_y2 (id), get_text(id) | Reads it back. |
Up to 500 lines, 500 labels and 200 boxes: past these, the oldest goes as a new one is drawn.
The table
table("top right")
averageRange = sma(high - low, 20)
if islast {
cell(0, 0, "Average range", gray)
cell(1, 0, str(averageRange, decimals), white, alpha(blue, 0.4))
}
table("top right"), once at the top level, says where the table stands:top middle bottom, thenleft center right. It may be a choice setting:table(position).cell(column, row, text, color, background)sets one cell, anywhere: columns 0 to 3, rows 0 to 15. Set them on the last bar, withif islast.
Examples
The editor's examples are tradoor's built-in indicators written in Frost Script, each giving exactly the built-in's numbers: SMA, EMA, the EMA 12/25 ribbon and EMA 200, Bollinger Bands, Keltner Channels, Ichimoku, RSI, Stochastic RSI, MACD, ATR, VWAP, anchored VWAP, the historical key levels, the swing structure map, trading sessions, moon phases and volume, and a SuperTrend. Open any of them, or any built-in's Source tab, to read it.
SuperTrend, with a mark at each turn
input length = 10 min 1 max 1000 "ATR length"
input mult = 3 min 0.1 max 10 step 0.1 "Multiplier"
band = mult * atr(length)
state trend = 1
state stop = na
if trend == 1 {
stop = isna(stop) ? hl2 - band : max(hl2 - band, stop)
if close < stop {
trend = -1
stop = hl2 + band
}
} else {
stop = isna(stop) ? hl2 + band : min(hl2 + band, stop)
if close > stop {
trend = 1
stop = hl2 - band
}
}
turned = changed(trend)
plot(stop, "Stop", trend == 1 ? green : red)
mark(turned and trend == 1, "Buy", below, green)
mark(turned and trend == -1, "Sell", above, red)
The day's range so far, labelled at its high
state dayHigh = na
state dayLow = na
state tag = na
if changed(day) or isna(dayHigh) {
dayHigh = high
dayLow = low
} else {
dayHigh = max(dayHigh, high)
dayLow = min(dayLow, low)
}
plot(dayHigh, "Day high", green, 1, steps)
plot(dayLow, "Day low", red, 1, steps)
if islast {
tag = label(bar, dayHigh, "Range " + str(dayHigh - dayLow, decimals), color = alpha(gray, 0.6), place = above, size = small)
}
From Pine Script, with an AI
An indicator written in TradingView's Pine Script v6 can be turned into Frost Script by an AI chat — Claude, ChatGPT, Gemini or another. The instructions below teach it the whole language and how each part of Pine Script maps onto it: its arrays, functions, lines, labels, boxes and tables included.
- Copy the instructions: the button below, or Pine Script → Frost: copy AI instructions in the Frost Script editor.
- Paste them into the AI chat, and then the Pine Script v6 source of the indicator.
- Paste the script it answers with into the editor. It is checked at once: if a line is wrong, give the AI the message and the line, and ask it to put it right.
- Before you rely on it, put it on a chart beside the original and compare the numbers on a few bars.
Read the instructions
Loading…
request.security), strategies, alerts, the clock (timenow), polylines, matrices and maps. The AI is told to say what it left out, and to turn a strategy's entries into marks.Mistakes and messages
A script is checked as it is typed. A mistake says its line and column and what is wrong, and the line's number turns red. Some you will meet:
| Message | What to do |
|---|---|
foo is not known here | A name not set anywhere, or spelt differently (capitals count). |
ema(value, length) | The function takes other arguments: here a value and a length. |
a length is a number or a setting | A length cannot change from bar to bar: use a number, a setting, or maths on them. |
… call it outside if, for and while | A function over bars, or a plot, inside a block. Work the value out above the block, and use it inside. |
close is built in | A variable named as a bar value, a colour or a word of the language: pick another. A function's name is free: sum = 0 is fine. |
… keeps it nowhere: name it | A line that works out a value and keeps it nowhere. |
two lines are called "A" | Give each line its own title. |
x holds a number: it cannot be set to a text | A variable keeps to the kind it was first set to: give the text another name. |
… is not a place in this list of 3 | A list read or set past its end: check its size() first. |
f(…) calls itself | A function cannot call itself: write it as a loop. |
this script takes too long to work out | Loops that together ask too much: fewer, or shorter. See Limits. |
A script that runs into a problem on the chart says so in the chart's legend, beside its name.
Limits
Every script ends, and quickly. One that asks for more than these stops with a message saying where.
| Limit | At most |
|---|---|
| Length of a script | 40,000 characters, 12,000 words and signs |
| Brackets and blocks inside each other | 40 deep |
| Settings · a choice's options | 32 · 16 |
| Variables, counting each function call's own | 1,000 |
| Functions · lists · a list's values | 64 · 32 · 5,000 |
| Lines, histograms and levels · fills · marks · backgrounds | 32 · 16 · 16 · 4 |
| Shapes kept: lines · labels · boxes | 500 · 500 · 200 |
| Table | 4 columns, 16 rows |
| A length (sma, ema, highest…) | 5,000 |
| Looking back with x[n] | 5,000 bars |
| One loop | 10,000 rounds |
| All the work of one run over the chart | 40 million steps |
| Titles and names · mark text · a text in quotes · a text made | 40 · 12 · 200 · 1,000 characters |
Safety
Frost Script is safe by what it is, not by what it forbids. A script is read and worked out by tradoor's own engine for it, written in Rust. It is never turned into JavaScript or any other code a browser runs.
- Nothing to reach. The language has no word for the network, storage, your account, your browser, the page or the clock. In the browser it runs in a module that imports nothing at all: it is given candles and gives back numbers and short texts — what to draw, and where.
- It always ends. Every limit is held, so a script cannot freeze a chart.
- Checked twice. The server checks a script with the same engine when it is saved; your browser checks it again before it runs.
- Text is text. Titles, labels and mark text are shown as plain text, never as page code.
- Moderated. Scripts are looked after by tradoor's moderator, who can remove one that misleads. A removed script runs for no one, its author is told why, and it can be restored.
Your scripts and your account
- Writing and keeping scripts needs you signed in with Telegram: Settings, Account. Signed out, you can read the examples and every built-in's source.
- Your scripts are kept on tradoor's server with your account, so they follow you to every device you sign in on. Up to 100.
- A script on a chart is part of that chart's layout, with its settings and style, like any indicator.
- Deleting a script takes it off the charts of the layout you are in, and it draws on no chart anywhere after.
- A script the moderator removed shows in your list with the reason. It runs for no one and cannot be changed, but you can still read and delete it.
Public scripts and the Community
The indicator picker has three tabs: Built-in, tradoor's own; Community, the scripts their authors made public; and My scripts. One search runs through all three, and each tab says how many it matches.
- A script is private until you make it public: yours alone, on your charts.
- Set it to Public beside its name in the editor, write its description — what it shows, and how to read it, in 30 characters at least — and save. It is listed in the Community under your username, for anyone to add to a chart, signed in or not, and the chart's legend shows it beside the script's name. A public script must check.
- Every public script is on the Community showcase, a page of its own with a link for each to share. Its chart there is drawn live by tradoor's engine, on the market and timeframe of the chart you shared it from, with your settings: open the editor from that chart before you save. No picture is kept.
- Your username is yours to choose, in place of your Telegram name, so you can stay anonymous: Settings, Account, or the editor the first time you share. It is 3 to 20 letters a to z, digits and
_, starting with a letter, and one no one else has. You choose it once: it can never be changed, and stays with your Telegram account, so a name on a script always means the same author. - A change you save to a public script reaches every chart it is on.
- Set it back to Private, or delete it, and it leaves the Community and draws on no one else's chart.
- On a Community script, Copy opens a copy of your own in the editor, to read or to change. Its Source tab shows its text on the chart, too.
- The moderator can remove a public script that misleads: it then runs for no one, and its author is told why.