Building blocks

The classes you combine to feed and shape other indicators: field selection, custom formulas, look-back buffers, crossings and rolling extremes. See the indicator reference for how to read a signature, and Indicators for the construction and guarding rules.

CandleIndicator

The candle series returned by manager.getIntervalData(symbol, interval). You never construct it yourself.

new CandleIndicator(getPendingCandle: () => Candle | undefined)
Parameter Type Default Meaning
getPendingCandle () => Candle | undefined required Internal. Do not construct this class; call manager.getIntervalData instead.
  • Input: None: the engine creates and feeds it.
  • Value (getValue() after hasValue()): Candle
    • openTime: number
    • closeTime: number
    • open: number
    • high: number
    • low: number
    • close: number
    • volume: number
  • Sub-indicators: .openTime, .closeTime, .open, .high, .low, .close, .volume, .hl2, .hlc3, .ohlc4, .hlcc4, usable as inputs to other indicators.
  • Methods: getPending(): Candle | undefined.
  • Warm-up: Ready from the first closed candle. getValue() is the last CLOSED candle; getPending() is the candle still forming (undefined before the first tick of a new candle).
  • Its sub-indicators (candles.close, candles.hl2, …) are ready-made number streams: new EMA(candles.close, 20) is the same as new EMA(new PartialIndicator(candles, 'close'), 20).
  • A candle is { openTime, closeTime, open, high, low, close, volume }; times are epoch milliseconds.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);

manager.onIntervalCandle(symbol, 60, () => {
  if (!candles.hasValue()) return;
  const candle = candles.getValue(); // the candle that just closed
  const forming = candles.getPending(); // the candle in progress, or undefined
  manager.debugLog('closed', candle.close, 'range', candle.high - candle.low, 'forming', forming?.close);
});

Crossover

Detects the candle on which one series crosses another. Emits 'firstUp' when source1 crosses above source2, 'secondUp' when source2 crosses above source1, and null on every other candle.

new Crossover(source1: Indicator<number | undefined>, source2: Indicator<number | undefined>)
Parameter Type Default Meaning
source1 Indicator<number | undefined> required The first series.
source2 Indicator<number | undefined> required The second series.
  • Input: Two number streams (for example two moving averages). Fields of a composite indicator work through PartialIndicator: new Crossover(new PartialIndicator(macd, 'macd'), new PartialIndicator(macd, 'signal')). To cross a fixed level, feed a constant series such as new LambdaIndicator(candles, () => 30).
  • Value (getValue() after hasValue()): "firstUp" | "secondUp" | null
  • Warm-up: Emits null until both inputs have values and each side has been seen at least once; the first cross can only be reported after that. The value is a string or null, never a number: compare it with 'firstUp' / 'secondUp'.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Crossover(new EMA(candles.close, 20), new EMA(candles.close, 50));

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  if (value === 'firstUp') manager.debugLog('EMA20 crossed above EMA50');
  if (value === 'secondUp') manager.debugLog('EMA20 crossed below EMA50');
});

DerivedIndicator

Computes a value from several indicators each time all of them have updated. Use it to combine indicators into one series that other indicators can consume.

new DerivedIndicator<T>(_fn: () => T, _dependencies: Indicator<unknown>[])
Parameter Type Default Meaning
_fn () => T required A function computing the value from the dependencies' current values (read them with getValue() inside).
_dependencies Indicator<unknown>[] required The indicators it depends on. It updates once all of them have updated for the candle.
  • Input: A list of indicators it depends on.
  • Value (getValue() after hasValue()): T
  • Warm-up: Updates on the first candle all dependencies have seen; its value is whatever _fn returns (return undefined while an input is warming up).
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const fast = new EMA(candles.close, 12);
const slow = new EMA(candles.close, 26);
const spread = new DerivedIndicator(() => {
  const f = fast.getValue();
  const s = slow.getValue();
  return f === undefined || s === undefined ? undefined : f - s;
}, [fast, slow]);
const smoothed = new SMA(spread, 9); // a derived series feeds other indicators

manager.onIntervalCandle(symbol, 60, () => {
  if (!smoothed.hasValue()) return;
  const value = smoothed.getValue();
  if (value === undefined) return;
  manager.debugLog('smoothed spread', value);
});

Extreme

The highest and lowest value over the last period updates, the current one included. On candles it uses high and low; on numbers, the numbers themselves.

new Extreme<T extends Candle | number | undefined = Candle>(sourceIndicator: Indicator<T>, period: number)
Parameter Type Default Meaning
sourceIndicator Indicator<T> required Candles or a number stream.
period number required The window length, current candle included.
  • Input: The candle series, or a number stream.
  • Value (getValue() after hasValue()): ExtremeValue<T>
  • ExtremeValue is T extends undefined ? { max: number | undefined; min: number | undefined } : { max: number; min: number }.
  • Sub-indicators: .max, .min, usable as inputs to other indicators.
  • Warm-up: Ready from the first candle; the window is partial until period candles have passed. On a number stream that can be undefined, max and min are number | undefined.
  • The window includes the current candle, so close > max can never be true. For a breakout against the previous N candles, use History and exclude the last element, or compare with the previous candle's Extreme through new History(extreme, 2).
  • .max and .min are also exposed as sub-indicators.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Extreme(candles, 20);

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  manager.debugLog('20-candle high', value.max, 'low', value.min);
});

History

Keeps the last period values of another indicator as an array, oldest first and newest last.

new History<T>(source: Indicator<T>, period: number)
Parameter Type Default Meaning
source Indicator<T> required The indicator to record. Pass the whole indicator (candles, rsi), not a property of its value.
period number required How many values to keep. Must be a finite number.
  • Input: Any indicator (candles, numbers, objects).
  • Value (getValue() after hasValue()): readonly T[]
  • Warm-up: hasValue() is true from the first candle, but the array only reaches period elements after period candles. Check value.length before indexing.
  • The array is readonly; type helper parameters as readonly Candle[] or readonly number[].
  • For a number stream, wrap first: new History(candles.close, 5) or new History(new PartialIndicator(candles, 'close'), 5). Elements of a History over an Indicator<number | undefined> can be undefined.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new History(candles, 20);

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  if (value.length < 20) return; // shorter while filling up
  const previous = value.slice(0, -1); // exclude the current candle
  const highest = Math.max(...previous.map((c) => c.high));
  const last = value[value.length - 1];
  if (last === undefined) return;
  manager.debugLog('breakout', last.close > highest);
});

Index

The zero-based update count of another indicator (0 on its first value).

new Index(anyIndicator: Indicator<unknown>)
Parameter Type Default Meaning
anyIndicator Indicator<unknown> required The indicator whose update count to expose.
  • Input: Any indicator.
  • Value (getValue() after hasValue()): number
  • Warm-up: Same as its source.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Index(candles);

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  manager.debugLog('candles seen', value + 1);
});

IteratorIndicator

Wraps an iterator that yields one value per update of its dependencies. This is how most built-in indicators are written; prefer DerivedIndicator for your own formulas.

new IteratorIndicator<T>(iterator: Iterator<T>, dependencies: Indicator<unknown>[])
Parameter Type Default Meaning
iterator Iterator<T> required An iterator (for example a generator object) whose next() is called once per update.
dependencies Indicator<unknown>[] required The indicators it depends on.
  • Input: A list of indicators it depends on.
  • Value (getValue() after hasValue()): T
  • Warm-up: Updates on the first candle all dependencies have seen.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
function* countUpCloses(): Generator<number> {
  let count = 0;
  while (true) {
    const candle = candles.getValue();
    if (candle !== undefined && candle.close > candle.open) count++;
    yield count;
  }
}
const upCloses = new IteratorIndicator(countUpCloses(), [candles]);

manager.onIntervalCandle(symbol, 60, () => {
  if (!upCloses.hasValue()) return;
  manager.debugLog('up closes so far', upCloses.getValue());
});

LambdaIndicator

Applies a pure function to each value of another indicator, without storing anything. Use it for a custom per-candle formula.

new LambdaIndicator<T, U>(_indicator: Indicator<T>, _lambda: (value: T) => U)
Parameter Type Default Meaning
_indicator Indicator<T> required The source indicator.
_lambda (value: T) => U required A pure function (value) => result, called when the value is read. It must not call manager or keep state.
  • Input: Any indicator.
  • Value (getValue() after hasValue()): U
  • Warm-up: Same as its source.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new LambdaIndicator(candles, (candle) => candle.close - candle.open);

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  manager.debugLog('body', value);
});

ManualIndicator

An indicator you feed yourself with setValue(value). Useful to run a built-in indicator over values you compute in a callback.

new ManualIndicator<T>()
  • Input: Values you push with setValue.
  • Value (getValue() after hasValue()): T
  • Methods: setValue(value: T): void.
  • Warm-up: Has a value after the first setValue call.
  • Construct it at the root like any indicator; calling setValue inside a callback is allowed and is how it is fed. Feed it exactly once per update, or indicators built on it count too many samples.
  • It only holds what your callbacks fed it: the start-up history replay runs no callbacks, so after a restart it starts empty. When the value can be computed from other indicators, prefer a DerivedIndicator, which is rebuilt from history like any indicator.
const symbol = 'BTC';
const other = 'ETH';
const btc = manager.getIntervalData(symbol, 60);
const eth = manager.getIntervalData(other, 60);
const ratio = new ManualIndicator<number>();
const ratioAverage = new SMA(ratio, 20);

manager.onIntervalCandle(symbol, 60, () => {
  if (!btc.hasValue() || !eth.hasValue()) return;
  ratio.setValue(btc.getValue().close / eth.getValue().close); // feed once per candle
  if (!ratioAverage.hasValue()) return;
  const value = ratioAverage.getValue();
  if (value === undefined) return;
  manager.debugLog('BTC/ETH ratio average', value);
});

PartialIndicator

Selects one field of an indicator's object value, producing a new indicator. The standard way to turn a candle series into a number stream.

new PartialIndicator<T, K extends keyof T>(indicator: Indicator<T>, key: K)
Parameter Type Default Meaning
indicator Indicator<T> required The source indicator.
key K required The field to read, checked against the source value type ('close', 'high', 'volume', …).
  • Input: Any indicator whose value is an object, typically the candle series.
  • Value (getValue() after hasValue()): T[K]
  • Warm-up: Same as its source (the first closed candle for a candle series).
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new PartialIndicator(candles, 'close');

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  manager.debugLog('close', value);
});

Percentile

The percentile rank (0 to 100) of the current value among the last period values: the share of those values strictly below it.

new Percentile(sourceIndicator: Indicator<number | undefined>, period: number)
Parameter Type Default Meaning
sourceIndicator Indicator<number | undefined> required The number stream to rank.
period number required How many past values to rank against, current included.
  • Input: A number stream.
  • Value (getValue() after hasValue()): number | undefined
  • Warm-up: undefined until period defined values have been seen.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Percentile(candles.close, 100);

manager.onIntervalCandle(symbol, 60, () => {
  if (!indicator.hasValue()) return;
  const value = indicator.getValue();
  if (value === undefined) return;
  manager.debugLog('close percentile', value);
});

PineIndicator

Runs a TradingView Pine Script indicator or strategy as-is, with TradingView's series semantics: on each candle it gives what the script shows on a chart of the same candles (plots, markers, alertconditions, labels, boxes, lines, tables and strategy orders).

new PineIndicator(candles: ICandleIndicator, source: string, options: PineIndicatorOptions = {}, libraries: Readonly<Record<string, string>> = {})
Parameter Type Default Meaning
candles ICandleIndicator required The candle series the script runs on.
source string required The Pine source, any version from 1 to 6 (//@version=N).
options PineIndicatorOptions {} inputs: overrides by input title ("Title #2" for a repeated title). heikinAshi: run on Heikin Ashi candles, as on a Heikin Ashi chart. ticker and mintick: the script's syminfo. timeframe: read from the candles when omitted. warmupBars: candles kept for a first read after a long warm-up (5,000 by default, at most 50,000). visibleBars: for a script that reads the chart's visible range, how many candles ending at the current one the chart shows (the number the user gives, else 150). Such a script runs from scratch on every read, as TradingView recomputes it whenever the range moves, over its last 2,000 candles (twice visibleBars when more, within the 5,000 kept; warmupBars when set), about 9 ms a read and paid on every candle of a backtest; its zones are rebuilt each time, so watch the close entering one rather than remembering a zone. lowerTimeframes: for a script that reads intrabars (request.security_lower_tf, or request.security on a lower timeframe), candles of the same symbol on each lower timeframe it reads, with its Pine timeframe: [{ timeframe: '1', candles: manager.getIntervalData(symbol, 1) }], got before the candles the script runs on so that, on a tick closing both, the lower ones are in first. The last 50,000 candles are kept, split between the timeframes; without them those values are na.
libraries Readonly<Record<string, string>> {} The sources of the Pine libraries the script imports, by import path (user/name/version): only the ones it calls into are needed. A Pine import on the Smart Chart passes the libraries it keeps.
  • Input: The candle series. The script runs on its interval.
  • Value (getValue() after hasValue()): number
  • PineIndicatorOptions is { plotTransparency?: "source" | "legacy" | undefined; namedColors?: "source" | "uniform-v6" | undefined; inputs?: Record<string, PineInputValue> | undefined; timeframe?: string | undefined; heikinAshi?: boolean | undefined; ticker?: string | undefined; mintick?: number | undefined; warmupBars?: number | undefined; visibleBars?: number | undefined; lowerTimeframes?: { timeframe: string; candles: ICandleIndicator; }[] | undefined }.
  • PineInputValue is string | number | boolean.
  • Methods: output(): PineIndicatorOutput | undefined, alert(title: string): boolean, marker(title: string): number | undefined, plot(title: string): number | undefined, plotColor(title: string): string | undefined, plotDrawn(title: string): number | undefined, labels(): string[], label(text: string): boolean, boxes(): PineIndicatorBox[], lines(): PineIndicatorLine[], tables(): PineIndicatorTable[], orders(): PineIndicatorOrder[], fills(): PineIndicatorFill[], position(): number, nextPosition(): number.
  • Warm-up: Each read runs the script on the candles fed since the previous read. plot(title) is undefined while the script plots na.
  • Read on the current candle: alert(title) (an alertcondition held), marker(title) (the value of a plotshape, plotchar or plotarrow, undefined when it shows nothing), plot(title) (the value computed on the candle), plotColor(title) (the plot's color, #RRGGBBAA or undefined: some scripts show a state by a color only, such as Squeeze Momentum's squeeze, a dot plotted at 0), plotDrawn(title) (the value the chart draws under the candle: a plot with an offset shows there what it computed that many candles earlier, such as Ichimoku's cloud 25 candles ahead, and undefined when drawn back), label(text) and labels(), and for a strategy nextPosition() (its signed position once the market orders it decided on the candle fill at the next open, every fill of the candle included: follow its direction to trade like the strategy), position() (before those orders fill), fills() (the orders it filled on the candle) and orders() (the orders still working after the candle, its strategy.exit brackets as kind: "exit").
  • boxes() and lines() give the zones and levels the script shows on the current candle, oldest first. A box is { id, active, left, right, top, bottom, extend, text, color, borderColor } and a line { id, time1, price1, time2, price2, extend, color, style, width, price }: times are candle open times in ms (an edge past the current candle is projected at the candle interval), a box's top is above its bottom, a line's price is its level on the current candle (null past its ends), colors are #RRGGBBAA or null, a box is active while it spans the current candle (a zone that ended stays drawn), and id names the drawing: a script may move a drawing to a newer zone (Order Block Detector keeps a few boxes per side), so remember a zone by its left, top and bottom, and a level by its time1 and price1. Until the first boxes() or lines() call, the script skips the work only drawings show.
  • tables() gives the tables the script shows on the current candle, one per position: its dashboards as { id, position, columns, rows, cells }, a cell being { column, row, text, textColor, bgColor }, row by row (a merged cell is listed once, at its top-left). Many dashboards tell states apart by color only. Until the first tables() call, the script skips the work only its tables show.
  • namedColors: "source" follows each Pine module's RGB palette. Omitted or "uniform-v6" retains the uniform v6 named RGB of existing agents. Independently, plotTransparency: "source" follows the source version's plot/marker alpha; omitted or "legacy" retains historical alpha. Imported classes fix both saved policies across agent edits. Named RGB does not determine rendered alpha: compare a state with the complete sampled RGBA in the import's description, accounting for the source's plotting transparency and any overridden color input.
  • A Pine script imported on the Smart Chart is a custom indicator: new <ClassName>(candles, { inputs }) runs it without pasting its source.
  • In agents, request.security on a lower timeframe of the symbol reads the lowerTimeframes candles given (na without them), on another symbol it gives na, and on a higher timeframe with lookahead_on it reads the last completed bar.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const pine = new PineIndicator(candles, `//@version=6
indicator("Cross", overlay = true)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
plot(fast, "fast")
alertcondition(ta.crossover(fast, slow), "Buy", "fast crossed above slow")`);

manager.onIntervalCandle(symbol, 60, () => {
  if (pine.alert('Buy')) manager.debugLog('Buy signal, fast EMA at', pine.plot('fast'));
});
Browse the documentation