CoinScriptLanguage
Language
The CoinScript reference: data series from candles through order flow and company figures to on-chain and macro, other symbols, functions, plot, signal, hline, alertcondition, input, your own calculations and what a script may not do.
CoinScript is JavaScript with a fixed library. Most of what you know from JavaScript works: const and let, functions, arrow functions, if, loops, Math. What is blocked is listed below under “What a script may not do”. On top of that come the chart's data series and the functions on this page. In the editor you can find the same overview in short form on the Reference tab below the code.
How a script calculates
A script runs once over all the candles the chart has loaded, not candle by candle.
- Each data series is a list with exactly one value per candle. Index
0is the oldest candle, the last indexbars - 1the newest, meaning the running one. - Missing values are
NaN. - The functions take whole series and return whole series.
ema(close, 21)is a list with the EMA for each candle. - Instead of a series you can also pass a number anywhere, for example
cross(close, 85000).
// The last and the second-to-last close
const last = close[bars - 1]
const prev = close[bars - 2]
console.log('Letzter Schluss', last, 'davor', prev)
plot(close, { title: 'Close', overlay: true })The running candle changes until it closes. What a script calculates for the last candle can still change until then, and a signal can disappear again.
If the chart loads older candles as you scroll back, the list gets longer and the indices shift. So always calculate relatively, for example with i - 1 or bars - 1, never with fixed indices.
Data series
| Name | Content |
|---|---|
open, high, low, close | Open, high, low and close of the chart's candles |
time | Each candle's open time in milliseconds since 1970 (UTC), for weekdays, sessions and fixed cycles |
volume | Volume of the chart's candles, summed across all the chart's exchanges |
buyVol | Taker buy volume per candle |
sellVol | Taker sell volume per candle |
delta | Taker buy minus taker sell volume per candle |
cvd | Running sum of delta |
oi | Open interest in coin, sum of the exchanges |
funding | Funding rate in % per 8 hours, average of the exchanges |
liqLong, liqShort | Liquidated longs and shorts per candle in USD, sum of the exchanges |
trades | Number of trades per candle |
buyTrades, sellTrades | Number of buys and sells per candle (by taker side) |
bidDepth, askDepth | Order book: buy and sell orders within 1% around the middle of the book in USD |
bidWall, askWall | Order book: price of the largest buy wall below and the largest sell wall above the middle |
bidWallSize, askWallSize | Order book: amount of these walls in USD |
revenue, netIncome, eps | Stocks: revenue, earnings and earnings per share of the most recently reported quarter |
revenueTtm, netIncomeTtm, epsTtm | Stocks: the same figures over the last twelve months |
pe | Stocks: P/E, meaning close divided by epsTtm |
revenueEst, epsEst | Stocks: revenue and earnings per share that analysts had expected for the most recently reported quarter |
margin, marginTtm | Stocks: net margin in %, per quarter and over twelve months |
cash | Stocks: cash and equivalents at the last quarter end |
interest, interestTtm | Stocks: interest plus dividends on preferred stock, per quarter and over twelve months |
holdings, mnav | Crypto companies: holdings of the largest coin and mNAV |
bars | Number of candles (a number, not a series) |
All series have the same length as the chart's candles and apply to its symbol and timeframe. You read further data series from the terminal, such as on-chain values, yields, central banks, ETF flows or COT, with feed(), and the candles of other symbols with symbol(). A script does not know other timeframes or the time of day of the candles.
If a series does not exist for the symbol, such as company figures for Bitcoin or order flow for a stock, it contains only NaN, and the console says so.
Order flow series
buyVol, sellVol, delta and cvd are based on taker volume, meaning whether a trade went into the market as a buy or a sell.
buyVol + sellVol does not always equal volume, which can include several exchanges. A candle without a taker figure is NaN. Symbols without order flow, such as stocks or forex, have only NaN in these series, and the console says so.
cvd starts with the first loaded candle that has a taker figure. Its level therefore depends on how far back the chart reaches. Compare the course, not the absolute level.
Open interest and funding
oi sums the open interest of the exchanges, funding averages their funding rates. Each candle carries the last known value, so funding moves in steps. Before the first known value it is NaN.
oi and funding go back about 1,000 candles of the timeframe, but at least one day. If the chart has loaded more candles, they are NaN for the older ones. Both exist only for crypto perpetuals.
oi is in coin, not in dollars. In alerts, on the other hand, open interest is in $M.Liquidations and trades
liqLong and liqShort are the liquidated long and short positions per candle in US dollars, summed across all exchanges, as in the Liquidations pane with the source “Aggregated”. trades counts the trades per candle, buyTrades and sellTrades split them by whether the taker bought or sold.
A candle without a liquidation has the value 0. NaN means there is no figure for this candle. The series exist for crypto symbols, from the minute timeframe up.
// Liquidations: longs up, shorts down, in $M
plot(liqLong.map(v => v / 1e6), { title: 'Longs', style: 'hist', color: '#FF1919' })
plot(liqShort.map(v => -v / 1e6), { title: 'Shorts', style: 'hist', colorDn: '#00FFDA' })
hline(0)Order book
The order book series are what the order book heatmap and order book top levels show, as numbers per candle:
| Name | Content |
|---|---|
bidDepth | Buy orders within 1% around the middle of the book in USD, averaged over the candle |
askDepth | Sell orders within 1% around the middle of the book in USD, averaged over the candle |
bidWall | Price of the largest buy wall below the middle, up to 5% away |
askWall | Price of the largest sell wall above the middle, up to 5% away |
bidWallSize, askWallSize | Amount of these walls in USD |
- Middle of the book: the price between the candle's buy and sell orders.
- Wall: a price level of 0.05% of the price that holds at least twice as much as the levels around it. The one that stands out most is chosen, as with order book top levels. Without a wall, the value is
NaN. - Period: from the recorded order book, the running candle from the live book of the open chart. Before recording began it is
NaN. Minute candles go back three days, 5-minute candles 30 days and hourly candles one year. - Like the heatmap, the order book is part of Plus and available only for cryptocurrencies.
// Book imbalance: above 0 more buy orders, below 0 more sell orders
const imbalance = bidDepth.map((b, i) => (b - askDepth[i]) / (b + askDepth[i]))
plot(imbalance, { title: 'Ungleichgewicht', style: 'hist', color: '#00FFDA', colorDn: '#FF1919' })
hline(0)
// The largest walls as lines on the price chart
plot(bidWall, { title: 'Kaufwand', color: '#00FFDA', overlay: true })
plot(askWall, { title: 'Verkaufswand', color: '#FF1919', overlay: true })
// Alert when the price comes within 0.3% of the buy wall
alertcondition(close.map((c, i) => (c - bidWall[i]) / c < 0.003), { title: 'Kurs an der Kaufwand' })Company figures
On stocks, a script reads the figures the indicators under Companies also show:
| Name | Content |
|---|---|
revenue | Revenue of the most recently reported quarter |
netIncome | Earnings (net income) of the most recently reported quarter |
eps | Earnings per share (diluted) of the most recently reported quarter |
revenueTtm, netIncomeTtm, epsTtm | the same figures over the last twelve months |
pe | P/E: close divided by epsTtm |
revenueEst | Revenue that analysts had expected for the most recently reported quarter |
epsEst | Earnings per share that analysts had expected for the most recently reported quarter |
margin, marginTtm | Net margin in % (netIncome divided by revenue), per quarter and over twelve months; na where earnings exceed revenue or there is hardly any revenue |
cash | Cash and equivalents: cash and short-term investments at the last quarter end, for Strategy the weekly reported USD reserve |
interest, interestTtm | Interest plus dividends on preferred stock, per quarter and over twelve months |
- From the report day: Each figure stands from the day the company published it and stays until the next report. The series moves in steps. Before that it is
NaN. - A new report is a candle where the value changes from the candle before, for example
revenue[i] !== revenue[i - 1]. - Unit:
revenueandnetIncomein the company's reporting currency, usually US dollars. For billions, divide by1e9. - Twelve months: The values with
Ttmgo back further than the quarterly values and smooth seasonal swings. - P/E:
pedivides each close by the most recently reported earnings per share over twelve months. Without positive earnings it isNaN. - Expectations:
revenueEstandepsEstare the expectations the market went into the respective report with.epsEstis usually estimated on an adjusted basis,epsis earnings under accounting rules. - For crypto, funds, indices and forex, these series are
NaN.
// Twelve-month revenue in billions in its own pane
plot(revenueTtm.map(v => v / 1e9), { title: 'Umsatz 12 M (Mrd.)', color: '#00FFDA' })A complete example that marks reports with rising revenue and a falling price is under Examples.
Crypto companies
| Name | Content |
|---|---|
holdings | Holdings of the largest coin in units (BTC for Strategy and Metaplanet, ETH for BitMine), from each buy or sale |
mnav | Enterprise value (market cap plus debt and preferred stock, minus cash and equivalents) divided by the value of the coins per candle, as Strategy reports it; 1 means exactly the value of the coins |
For companies without coins on their balance sheet, both are NaN. For miners, you read the network data with feed():
// Hashprice in USD per PH/s per day
const einnahmen = feed('btc-miner-revenue')
const hashrate = feed('btc-hashrate')
plot(einnahmen.map((v, i) => v / (hashrate[i] / 1000)), { title: 'Hashprice', color: '#00FFDA' })More data series with feed
feed('name') returns another data series from the terminal as a series with one value per candle, for example feed('btc-sopr') or feed('us10y').
- Alignment: Each candle carries the last known value. Daily, weekly and monthly values stand from the day they apply to. Before the first value it is
NaN. - Stale values: If no new value follows for a long time,
NaNappears instead of the old value: for yields and other trading-day values after five days, for the crypto market's daily values after two to three days, for weekly values after two weeks, for monthly values after about three months. An ETF net inflow applies only to its day. On-chain values and policy rates apply until the next value. - Independent of the chart: The series are the same no matter which symbol the script runs on.
// SOPR with 1 as the boundary and an alert condition
const sopr = feed('btc-sopr')
plot(sopr, { title: 'SOPR', color: '#FFEB3B' })
hline(1)
alertcondition(sopr.map((v, i) => i > 0 && sopr[i - 1] < 1 && v >= 1), 'SOPR über 1')us-cpi or us-m2 only appear weeks after the month they apply to. In the backtest they still stand from the first day of their month. Keep this in mind if a strategy relies on them.These names exist:
On-chain
| Name | Content | Unit |
|---|---|---|
btc-mvrv-z | Bitcoin MVRV z-score | – |
eth-mvrv-z | Ethereum MVRV z-score | – |
btc-sopr | Bitcoin SOPR | – |
btc-netflow | Bitcoin exchange netflow per day | USD |
eth-netflow | Ethereum exchange netflow per day | USD |
btc-exchange-supply | Bitcoin on exchanges | BTC |
eth-exchange-supply | Ether on exchanges | ETH |
btc-active-addresses | Bitcoin active addresses | addresses |
eth-active-addresses | Ethereum active addresses | addresses |
btc-hashrate | Bitcoin hashrate | TH/s |
btc-difficulty | Bitcoin mining difficulty | – |
btc-supply | Bitcoin in circulation | BTC |
btc-tx-fees | Bitcoin transaction fees per day | USD |
btc-miner-revenue | Bitcoin miner revenue per day | USD |
Crypto market
| Name | Content | Unit |
|---|---|---|
btc-marketcap | Bitcoin market cap | USD |
usdt-marketcap | USDT market cap | USD |
stablecoin-cap | Stablecoin market cap | USD |
btc-dominance | Bitcoin dominance | % |
usdt-dominance | USDT dominance | % |
fear-greed | Fear & Greed Index | 0–100 |
ETF flows
| Name | Content | Unit |
|---|---|---|
btc-etf-flow | Bitcoin ETFs net inflow per day | USD |
eth-etf-flow | Ether ETFs net inflow per day | USD |
btc-etf-assets | Bitcoin ETFs assets under management | USD |
Rates and bonds (US)
| Name | Content | Unit |
|---|---|---|
us1m | US 1-month yield | % |
us3m | US 3-month yield | % |
us6m | US 6-month yield | % |
us1y | US 1-year yield | % |
us2y | US 2-year yield | % |
us3y | US 3-year yield | % |
us5y | US 5-year yield | % |
us7y | US 7-year yield | % |
us10y | US 10-year yield | % |
us20y | US 20-year yield | % |
us30y | US 30-year yield | % |
us10y2y | Yield curve 10 years minus 2 years | % |
us10y-inflation | 10-year inflation expectation | % |
us10y-real | 10-year real yield | % |
hy-spread | High-yield spread | % |
baa-spread | Baa spread over 10 years | % |
Money and central banks
| Name | Content | Unit |
|---|---|---|
us-m2 | US money supply M2 | B USD |
fed-balance | Fed balance sheet total | M USD |
tga | Treasury General Account | M USD |
rrp | Overnight reverse repo | B USD |
ecb-balance | ECB balance sheet total | M EUR |
boj-balance | BoJ balance sheet total | 100M JPY |
fed-funds | Fed funds rate, monthly average | % |
fed-target-upper | Fed target range, upper bound | % |
fed-target-lower | Fed target range, lower bound | % |
policy-rate-us | Policy rate US | % |
policy-rate-eur | Policy rate eurozone | % |
policy-rate-jp | Policy rate Japan | % |
policy-rate-au | Policy rate Australia | % |
Economy (US)
| Name | Content | Unit |
|---|---|---|
us-cpi | US consumer price index | index |
us-core-cpi | US core consumer price index, not seasonally adjusted | index |
us-unemployment | US unemployment rate | % |
us-industrial | US industrial production | index |
us-dollar-broad | US dollar index, broad | index |
COT positioning
cot-<markt>-<gruppe> is the net position of a trader group (long minus short) in contracts, from the weekly COT report, for example feed('cot-btc-lev'). Each value stands from the report's reference date (Tuesdays), and it is published the following Friday.
| Markets | Groups |
|---|---|
btc, mbt, eth, met, sol, xrp, es, nq, zn, zt, zq, eur, jpy, gbp, dxy | dealer Dealers / intermediaries, am Asset managers, lev Leveraged funds, oth Other reportables, nr Non-reportables |
gc Gold, si Silver, hg Copper, cl WTI crude oil, ng Natural gas | pm Producers / merchants, sd Swap dealers, mm Managed money, oth Other reportables, nr Non-reportables |
The markets are named as in the COT widget: btc CME Bitcoin, mbt CME Micro Bitcoin, eth CME Ether, met CME Micro Ether, sol CME Solana, xrp CME XRP, es S&P 500 E-mini, nq Nasdaq-100 E-mini, zn 10Y T-Note, zt 2Y T-Note, zq Fed Funds, eur Euro FX, jpy Japanese yen, gbp British pound, dxy US dollar index.
Other symbols with symbol
symbol('name') returns the candles of another symbol in the chart's timeframe as an object with the series open, high, low, close and volume.
// Ether against Bitcoin: the price of ETH divided by that of the chart
const eth = symbol('ETH')
plot(eth.close.map((c, i) => c / close[i]), { title: 'ETH / Chart', color: '#B28CFF' })- Which names: any symbol from the symbol search, just as you type it there: crypto (
'ETH','SOL'), stocks ('AAPL'), indices ('SPX'), forex ('EURUSD'), commodities ('XAU'), bonds ('US10Y'), economic data ('USIRYY'), companies before their IPO ('OPENAI','ANTHROPIC': their valuation in billions of US dollars, per day; after the IPO, the stock price), ratios ('ETH/BTC') and spreads ('BTC-ETH'). - Alignment by time: Each candle of the chart carries the symbol's last candle that began at the same time or earlier. If the symbol is not trading, such as a stock on the weekend, it carries the values of its last candle before that, including volume. Before its first candle it is
NaN. - How far back: as far as the chart, as long as the symbol has candles going back that far.
What feed and symbol need
A script runs without a network. The terminal therefore loads the data for feed() and symbol() before the run, and for that the name must be in the code:
- Write the name as text in quotation marks directly in the call:
feed('btc-sopr'), notfeed(name). A calculated name is an error. - A script reads at most 8 different names in
feed()andsymbol()combined. - An unknown name is an error with the line. The message suggests similar names.
- Store the result in a constant, for example
const eth = symbol('ETH'), instead of calling the function in a loop.
const eps = 1e-9, yours applies in the whole script. Give it a different name if you need the series.Functions
| Function | Result |
|---|---|
sma(serie, länge) | Simple moving average |
ema(serie, länge) | Exponential moving average |
rsi(serie, länge) | Relative Strength Index after Wilder, länge defaults to 14 |
stdev(serie, länge) | Standard deviation over the window |
zscore(serie, länge) | Distance from the average in standard deviations |
highest(serie, länge) | Highest value of the last länge candles |
lowest(serie, länge) | Lowest value of the last länge candles |
change(serie, n) | Difference from the value n candles earlier, n defaults to 1 |
cross(a, b) | true at every candle where a and b cross |
feed('name') | Another data series from the terminal, see above |
symbol('name') | Candles of another symbol, see above |
In more detail:
smaisNaNuntil there are enough values. If the window contains a gap (NaN), the average isNaNtoo, and the next full window returns a number again.emastarts with the first value of the series and smooths with the factor2 / (länge + 1). The first values are therefore still imprecise. The EMA skips gaps.rsireturns the first value afterlängeprice changes. A gap counts as an unchanged value.stdevdivides bylänge(population standard deviation).zscoreis(wert − sma) / stdevover the same window.highestandlowestinclude the current candle and skipNaN. At the start of the series, they take the candles that already exist.changeisNaNfor the firstncandles.nmust be a whole number.crossdetects crossovers in both directions and returns a list oftrueandfalse. If the two are exactly level at a candle, that does not count as a crossover.
For one direction, compare yourself:
// Crossover upward only or downward only
const fast = ema(close, 9)
const slow = ema(close, 21)
const up = fast.map((f, i) => i > 0 && fast[i - 1] <= slow[i - 1] && f > slow[i])
const down = fast.map((f, i) => i > 0 && fast[i - 1] >= slow[i - 1] && f < slow[i])
signal(up, { title: 'Nach oben', color: '#00FFDA', pos: 'below' })
signal(down, { title: 'Nach unten', color: '#FF1919', pos: 'above' })Drawing and signals
plot
plot(serie, { title, color, colorDn, overlay, style }) draws a series.
| Option | Meaning | Default |
|---|---|---|
title | Name in the legend, up to 60 characters | “Plot 1”, “Plot 2” … |
color | Color of the line or the positive bars | '#FFFCF0' |
colorDn | Color of the negative bars in the histogram | '#FF1919' |
overlay | true draws on the price chart | own pane |
style | 'hist' draws a histogram | line |
- All plots without
overlay: trueshare one pane below the chart. Its scale adjusts to the visible values. - On the price chart, plots are always lines and use the price scale. The scale still follows the candles: a series in a different unit, such as
cvd, then lies outside the visible range. Put such series in their own pane. - The first plot's color appears in the legend. Script alerts that compare with a value also use the first plot.
You specify colors as hex values. The terminal's house colors are #00FFDA (buy, rising), #FF1919 (sell, falling), #FFEB3B (note) and #FFFCF0 (neutral).
hline
hline(wert, { color }) draws a dashed horizontal line in the script's pane, for example at 0, 30 or 70. It does not appear on the price chart, so it needs at least one plot without overlay.
signal
signal(bedingung, { title, color, pos }) places a triangle at every candle where bedingung is true.
| Option | Meaning | Default |
|---|---|---|
title | Name of the signal, also for alerts | 'Signal' |
color | Color of the triangle | '#FFEB3B' |
pos | 'below' under the low, 'above' over the high | 'below' |
In the backtest, signals with pos: 'below' are long entries and signals with pos: 'above' are short entries.
alertcondition
alertcondition(bedingung, { title }) draws nothing. It sets a condition you can put a script alert on in the alert dialog. Instead of the object, the title alone also works: alertcondition(bedingung, 'Ausbruch'). Without a title, it is called “Condition 1”, “Condition 2” …
Any list of true and false or 1 and 0 works as a condition, such as the result of cross() or of close.map(…).
Adjustable values with input
input(name, standard) makes a number adjustable and returns its current value.
const len = input('Länge', 21)
const k = input('Faktor', 2.5)
const mid = ema(close, len)
const sd = stdev(close, len)
plot(mid, { title: 'EMA', overlay: true })
plot(mid.map((v, i) => v + k * sd[i]), { title: 'Oben', overlay: true })sd once before the map, not inside it. A stdev() inside map would recalculate the whole series for every candle and blow the time limit.- On the chart, the values appear in the indicator's settings under Inputs. You set whole numbers in steps of one, numbers with decimals in the precision of the default (for
2.5, in steps of 0.1). - The name is also the label, up to 40 characters. Each name counts once.
- Only numbers become settings. Any other default, such as a text, comes back unchanged.
- Script alerts always calculate with the default. The optimization varies the first two inputs.
input('Länge', 21). Only then can the optimization write the best value into your code with Apply.Output to the console
console.log(…) writes to the editor's console, console.warn(…) in yellow, console.error(…) in red. A series appears with its last five values and its length, for example [… 125, 126, 127, 128, 129] (30). The console shows up to 50 outputs of 300 characters each per run.
The output only appears with Run. When the script runs on the chart, it writes nothing to the console.
Calculating your own series
Arithmetic operators do not work on whole series: close - open does not give a list, but NaN. For your own series, go over each candle with map. The second parameter is the index:
// Candle body in percent: (close − open) / open
const body = close.map((c, i) => (c - open[i]) / open[i] * 100)
plot(body, { title: 'Körper %', style: 'hist', color: '#00FFDA', colorDn: '#FF1919' })
hline(0)map on a data series returns a list of numbers again, with true and false becoming 1 and 0. This is how conditions for signal() and alertcondition() are made. If you access close[i - 1] at the first candle, you get undefined, and the calculation gives NaN. That is fine, the candle then shows nothing.
For calculations that need the value of the previous candle, a loop works well:
// Average True Range, simple average
const tr = new Array(bars).fill(NaN)
for (let i = 1; i < bars; i++) {
tr[i] = Math.max(high[i] - low[i], Math.abs(high[i] - close[i - 1]), Math.abs(low[i] - close[i - 1]))
}
plot(sma(tr, input('Länge', 14)), { title: 'ATR', color: '#FFEB3B' })plot(), signal() and alertcondition() a list with one value per candle. A shorter list, for example from close.slice(-50), starts at the oldest candle and then no longer lines up with the candles.You write your own helper functions with function or as arrow functions. Also available are Math, Number, String, Boolean, Object, Array, JSON, Date, Map, Set and the typed arrays such as Float64Array.
What a script may not do
Scripts run isolated in the browser. The terminal loads the data on this page itself before the run. The editor rejects:
importandexport,- its own network and its own storage:
fetch,XMLHttpRequest,WebSocket,localStorageand the like do not exist in a script, - timers and concurrency:
setTimeout,setInterval,Promise,asyncfunctions and generators, thisoutside a function,with,debuggerand classes,- the names
globalThis,self,window,eval,Function,constructor,prototypeand__proto__, also as a property or in a text.
Common messages
| Message | Meaning |
|---|---|
| “name” is not defined | A typo, or the name is not part of CoinScript |
| Syntax error: Unexpected character | A bracket, comma or quotation mark is missing or there is one too many |
| “window” is not allowed in CoinScript | A blocked name, see above |
| fetch is not a function | There is no network access of your own in CoinScript. You read data with the series above, feed() and symbol() |
| Unknown data feed “…” in feed() | A typo in the name. The message names similar names, and all of them are listed above |
| Unknown symbol “…” in symbol() | The symbol does not exist in the symbol search. The message names similar ones |
| feed() needs the name as text in quotation marks | The name is calculated, for example feed(name). Write it in directly: feed('btc-sopr') |
| At most 8 different names in feed() and symbol() per script | Split up the script |
| No data for revenue on BTC: the values stay empty. | The series does not exist for this symbol, or no value came for these candles. The script still runs |
| Assignment to a constant | A const was given a new value, use let |
| Too many plot() calls (at most 24) | Usually a plot() in a loop |
| Memory limit: at most 5,000,000 values per array | An array has become too large |
| Script aborted: time limit exceeded | The script calculates for longer than 250 ms, for example because of an endless loop |
| CoinScript could not be started | The calculation environment did not load. Reload the page |
Errors in the code name the line and column. Clicking the message in the console puts the cursor at that spot.