Indicators are computed causally by the engine, drawn on the chart, and readable by a strategy. The four built ins below have their own short calls; every indicator of the library is read with ctx.ind; and anything else is a custom formula or a plugin.
Built in (streamed via ctx)
- SMA simple moving average. Params: period, source.
ctx.sma(period). - EMA exponential moving average. Params: period, source.
ctx.ema(period). - RSI relative strength index, 0 to 100. Params: period, source.
ctx.rsi(period). - ATR average true range. Params: period.
ctx.atr(period).
The library (ctx.ind)
Every indicator of the library, the ones the chart offers, is read by its id with ctx.ind(id, **inputs). Inputs go by name, and one left out takes its default. An indicator with one line gives a number, or None until warmed up. One with several lines gives a reading: each line by its name, None while that line warms up, and .value for the first. A reading is never None itself. Indicator library lists every one, with what it measures, its inputs and its lines.
pythonrsi = ctx.ind("rsi", length=14) # a number, or None
upper = ctx.ind("bb", length=20).upper # one line of several- MACD
ctx.ind("macd"), linesmacd,signalandhist. - Bollinger Bands
ctx.ind("bb"), linesupper,middleandlower. - Stochastic
ctx.ind("stoch"), lineskandd. - ADX
ctx.ind("dmi"), linesadx,plusandminus.
Each is streamed like the built ins: fed every bar released so far on its first read, then one bar at a time, so a first read late in the run gives what an early one would have. An id or an input the library does not have stops the run with a message naming it. A strategy that reads a library indicator exports to TradingView, MetaTrader 5 and cTrader: each file carries the indicator as code of its own, computed as the engine computes it, never the platform's built in. To MetaTrader 5 and cTrader it goes the way Pine and blocks do, compiled and run beside the original first, so a strategy somebody else wrote asks before it runs.
Market structure (ctx.market_structure)
The Market structure group holds the ICT elements and the Fibonacci tools: swings with breaks of structure (BoS) and changes of character (CHoCH) structure, fair value gaps fvg, inversion fair value gaps ifvg, order blocks order_block, breaker blocks breaker_block, fib_retracement, fib_extension, premium_discount and the optimal trade entry ote; then liquidity: sweeps liquidity_sweep, equal highs and lows equal_levels, untaken swing highs and lows liquidity, balanced price ranges bpr, mitigation_block, rejection_block and displacement; and the ICT clock on New York time: killzones, midnight_open and the opening gaps opening_gaps (NWOG, NDOG). Each reads with ctx.ind like any other indicator, and ctx.market_structure(**inputs) reads all nineteen in one reading.
pythonms = ctx.market_structure(length=3, min_size=0.25)
if ms.choch == 1 and ms.ote.inside == 1: # the trend just turned up, price in the OTE
ctx.signal("LONG", sl_pips=20, tp_pips=40)
target = ms.fib_extension.level_1618
gap = ms.fvg.bull_top # the nearest open bullish gap, or None
if ms.killzones.london_open == 1 and ms.liquidity_sweep.down == 1:
ctx.signal("LONG", sl_pips=15, tp_pips=30) # the Asian low swept in London- The top level has the lines of
structure:trend(+1 or -1, 0 before the first break),bosandchoch(+1 or -1 on the bar of the break, else 0),swing_high,swing_low,bos_levelandchoch_level. - The parts are
structure,fvg,ifvg,order_block,breaker_block,fib(the retracement),fib_extension,premium_discount,ote,liquidity_sweep,equal_levels,liquidity,bpr,mitigation_block,rejection_block,displacement,killzones,midnight_openandopening_gaps, each the readingctx.indgives for that element, with its lines by name. - The inputs are the group's own, one name for one setting in every element:
length(swing bars each side),break_on("close"or"wick"),zone("wick"or"body"),atr_length,min_size(in ATRs),fill("full"or"half"),max_zones(zones kept per side),tolerance(equal levels, in ATRs),min_wick(rejection blocks, a share of the candle),min_body(displacement, in ATRs) anddays("mon_fri"or"every_day"). Each element takes the ones it has. - Nothing repaints: a swing shows on the bar that confirms it,
lengthbars after its extreme, and a zone on the bar that makes it.
It is the same streams as the nineteen ctx.ind calls, not a second implementation, so it runs on the server and in the Rust core like them. C# reads it with Indicators.MarketStructure(new { length = 3 }) (ms.Trend, ms.Fvg["bull_top"]), Rust with ctx.market_structure(&[("length", 3.into())]) (ms.trend, ms.fvg["bull_top"]). In blocks the nineteen are the Market structure group of the indicator blocks.
Custom formulas
A custom indicator carries a formula the engine compiles and computes causally. The formula language supports: inputs open high low close volume and hl2 hlc3 ohlc4; series access closes[i] highs[i] lows[i] opens[i] where i is bars back (0 is current); and the functions sma(p) ema(p) rsi(p) atr(p) plus abs min max sqrt log. End in an expression, or assign to result.
textfast = ema(12)
slow = ema(26)
fast - slowAny indicator of the library is one call away: ind(id, output, inputs) reads one line of it at this bar, None while it warms up. The id and the line are in quotes, the inputs go by name, and an input left out takes its default. An indicator with a single line needs no line name.
textwidth = ind("bb", "upper", length=20, mult=2) - ind("bb", "lower", length=20, mult=2)
width / close * 100 if ind("rsi", length=7) > 50 else 0The id, the line and each input are written out, a number, text in quotes or True or False, never a name or a calculation, so Validate finds an unknown id, line or input, or a value out of range, without a run. An indicator with several lines, such as ind("bb"), is refused with the names of its lines. A formula reads at most 64 different indicators.
Read a custom indicator from a strategy with ctx.custom("name").