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()afterhasValue()):CandleopenTime: numbercloseTime: numberopen: numberhigh: numberlow: numberclose: numbervolume: 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 (undefinedbefore 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 asnew 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 asnew LambdaIndicator(candles, () => 30). - Value (
getValue()afterhasValue()):"firstUp" | "secondUp" | null - Warm-up: Emits
nulluntil 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 ornull, 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()afterhasValue()):T - Warm-up: Updates on the first candle all dependencies have seen; its value is whatever
_fnreturns (returnundefinedwhile 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()afterhasValue()):ExtremeValue<T> ExtremeValueisT 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
periodcandles have passed. On a number stream that can beundefined,maxandminarenumber | undefined. - The window includes the current candle, so
close > maxcan never be true. For a breakout against the previous N candles, useHistoryand exclude the last element, or compare with the previous candle'sExtremethroughnew History(extreme, 2). .maxand.minare 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()afterhasValue()):readonly T[] - Warm-up:
hasValue()is true from the first candle, but the array only reachesperiodelements afterperiodcandles. Checkvalue.lengthbefore indexing. - The array is
readonly; type helper parameters asreadonly Candle[]orreadonly number[]. - For a number stream, wrap first:
new History(candles.close, 5)ornew History(new PartialIndicator(candles, 'close'), 5). Elements of aHistoryover anIndicator<number | undefined>can beundefined.
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()afterhasValue()):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()afterhasValue()):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()afterhasValue()):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()afterhasValue()):T - Methods:
setValue(value: T): void. - Warm-up: Has a value after the first
setValuecall. - Construct it at the root like any indicator; calling
setValueinside 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()afterhasValue()):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()afterhasValue()):number | undefined - Warm-up:
undefineduntilperioddefined 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()afterhasValue()):number PineIndicatorOptionsis{ 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 }.PineInputValueisstring | 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)isundefinedwhile the script plotsna. - Read on the current candle:
alert(title)(analertconditionheld),marker(title)(the value of aplotshape,plotcharorplotarrow,undefinedwhen it shows nothing),plot(title)(the value computed on the candle),plotColor(title)(the plot's color,#RRGGBBAAorundefined: 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 anoffsetshows there what it computed that many candles earlier, such as Ichimoku's cloud 25 candles ahead, andundefinedwhen drawn back),label(text)andlabels(), and for a strategynextPosition()(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) andorders()(the orders still working after the candle, itsstrategy.exitbrackets askind: "exit"). boxes()andlines()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'stopis above itsbottom, a line'spriceis its level on the current candle (nullpast its ends), colors are#RRGGBBAAornull, a box isactivewhile it spans the current candle (a zone that ended stays drawn), andidnames 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 itsleft,topandbottom, and a level by itstime1andprice1. Until the firstboxes()orlines()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 firsttables()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.securityon a lower timeframe of the symbol reads thelowerTimeframescandles given (nawithout them), on another symbol it givesna, and on a higher timeframe withlookahead_onit 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'));
});