Market structure and patterns
Swings, structure breaks, order blocks, gaps, pivots, candle patterns and alternative chart types. See the indicator reference for how to read a signature, and Indicators for the construction and guarding rules.
BosNChoch
Trend state machine that tracks the last break of structure and the change-of-character level.
new BosNChoch(candleIndicator: Indicator<Candle>, chochType: ChochType, minExtremeCandlesChochRecalculation: number, minCounterTrendCandlesBos: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<Candle> |
required | The candle series. |
chochType |
ChochType |
required | What breaks the CHoCH level: 'body', 'wick' or 'both'. |
minExtremeCandlesChochRecalculation |
number |
required | Candles the extreme must hold before the CHoCH level is recomputed. |
minCounterTrendCandlesBos |
number |
required | Counter-trend candles required before a new BOS counts. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):BosNChochValue | undefinedtrend: "BULLISH" | "BEARISH"chochLevel: numberbosLevel: numberaction: "BOS" | "CHOCH" | undefinedtype: "BODY" | "WICK" | undefined
ChochTypeis'body' | 'wick' | 'both'.- Warm-up:
undefinedfor the firstminExtremeCandlesChochRecalculationcandles;actionandtypeare set only on the candle of a BOS or CHoCH.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new BosNChoch(candles, 'body', 3, 2);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('trend', value.trend, 'action', value.action, 'CHoCH level', value.chochLevel);
});
Engulfing
Two-candle engulfing pattern: 'bullish' when an up candle's body engulfs the previous down candle's body, 'bearish' for the mirror case, 'none' otherwise.
new Engulfing(candleIndicator: Indicator<Candle>)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<Candle> |
required | The candle series. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):"none" | "bullish" | "bearish" - Warm-up:
'none'on the first candle.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Engulfing(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value !== 'none') manager.debugLog('engulfing', value);
});
FVG
Fair value gap on the last three candles: bullish when the first candle's high is below the third candle's low, bearish for the mirror case.
new FVG(candleIndicator: ICandleIndicator)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):FVGValue | nulltype: "bullish" | "bearish"upper: numberlower: number
- Warm-up:
nulluntil three candles have been seen, and on every candle without a gap.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new FVG(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === null) return;
manager.debugLog(value.type, 'gap', value.lower, value.upper);
});
Heikinashi
Heikin-Ashi candles: close = (o + h + l + c) / 4, open = (previous HA open + previous HA close) / 2, high/low extended to include them.
new Heikinashi(candleIndicator: Indicator<OHLCValue | undefined>)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<OHLCValue | undefined> |
required | The candle series (or any OHLC series). |
- Input: An indicator of
{ open, high, low, close }objects: the candle series itself works. - Value (
getValue()afterhasValue()):OHLCValue | undefinedopen: numberhigh: numberlow: numberclose: number
- Sub-indicators:
.open,.high,.low,.close, usable as inputs to other indicators. - Warm-up: A candle from the first update (its open seeds from the first real candle).
.open,.high,.low,.closeare also exposed as number sub-indicators, sonew EMA(ha.close, 20)averages Heikin-Ashi closes.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Heikinashi(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
manager.debugLog('HA', value.open, value.close, value.close > value.open ? 'green' : 'red');
});
HeikinashiCandles
The candles of a Heikin Ashi chart: each candle keeps its times and volume, with the Heikin-Ashi open, high, low and close. Any indicator that takes candles runs on it, as a Heikin Ashi chart computes its indicators on these candles.
new HeikinashiCandles(candleIndicator: ICandleIndicator)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
- Input: The candle series.
- 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: A candle from the first update (its open seeds from the first real candle).
- Pass it where an indicator takes candles:
new ADX(ha, 14),new KeltnerChannels(ha),new VWAP(ha, ha.hlc3, 'day'), orha.closefor a price stream (new RSI(ha.close, 14)). getPending()gives the forming candle in Heikin Ashi, after the last closed one.- Orders still fill at real prices: compare a level computed on these candles with
ha.close, not with the real close.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new HeikinashiCandles(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
manager.debugLog('HA', value.open, value.close, 'volume', value.volume);
});
Lunar
Moon phase from a timestamp series: 0 new moon, 2 first quarter, 4 full moon, 6 last quarter (8 phases).
new Lunar(timeIndicator: Indicator<number>)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
timeIndicator |
Indicator<number> |
required | Epoch-millisecond timestamps. |
- Input: A timestamp stream (
candles.openTimeorcandles.closeTime). - Value (
getValue()afterhasValue()):number - Warm-up: Ready from the first candle.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Lunar(candles.closeTime);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === 4) manager.debugLog('full moon');
});
MarketStructure
Market structure (swing highs/lows labelled HH, LH, HL, LL) with break of structure (BOS) and change of character (CHoCH) events.
new MarketStructure(candleIndicator: ICandleIndicator, swingSize: number = 20, bosConfType: 'close' | 'wick' = 'close', showChoch: boolean = true)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
swingSize |
number |
20 |
Candles on each side that confirm a swing. |
bosConfType |
'close' | 'wick' |
'close' |
What confirms a break: the candle 'close' or its 'wick'. |
showChoch |
boolean |
true |
Label the first counter-trend break as 'CHoCH' (true) or report every break as 'BOS' (false). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):MarketStructureValueswingType?: SwingType | undefinedswingBarIndex?: number | undefinedbosType?: BosType | undefinedbosDirection?: BosDirection | undefinedbosBarIndex?: number | undefinedprevHigh?: number | undefinedprevLow?: number | undefinedprevHighIndex?: number | undefinedprevLowIndex?: number | undefinedhighActive: booleanlowActive: boolean
SwingTypeis'HH' | 'LH' | 'HL' | 'LL'.BosTypeis'BOS' | 'CHoCH'.BosDirectionis'bullish' | 'bearish'.- Warm-up: Ready from the first candle; events appear once swings are confirmed (
swingSizecandles after they form). Event fields are only set on the candle where the event happens.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new MarketStructure(candles, 10, 'close', true);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.bosType === undefined || value.bosDirection === undefined) return;
manager.debugLog(value.bosType, value.bosDirection, 'previous high', value.prevHigh);
});
OrderBlocks
The most recent bullish and bearish order blocks (a down candle followed by an up candle closing above its high, and the mirror case), with 25/50/75% lines.
new OrderBlocks(candleIndicator: ICandleIndicator, maxBoxSet: number = 5)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
maxBoxSet |
number |
5 |
How many blocks of each side are tracked. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):OrderBlocksValuebullish: OrderBlock | nullbearish: OrderBlock | null
OrderBlockis{ type: "bullish" | "bearish"; upper: number; lower: number; timestamp: number; line25: number; line50: number; line75: number }.- Warm-up: Values from the third candle;
bullish/bearisharenulluntil a block exists.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new OrderBlocks(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
const block = value.bullish;
if (block === null) return;
manager.debugLog('bullish block', block.lower, block.upper, 'mid', block.line50);
});
PivotPoints
Classic floor pivot levels (P, R1 to R5, S1 to S5) computed from the previous day, week or month.
new PivotPoints(candleIndicator: ICandleIndicator, pivotType: PivotType = 'Traditional', timeframe: Timeframe = 'day')
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
pivotType |
PivotType |
'Traditional' |
Formula: 'Traditional', 'Fibonacci', 'Woodie', 'Classic', 'DM' or 'Camarilla'. |
timeframe |
Timeframe |
'day' |
The period the levels are computed from: 'day', 'week' or 'month' (UTC boundaries). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):PivotPointsValueP: number | undefinedR1: number | undefinedR2: number | undefinedR3: number | undefinedR4: number | undefinedR5: number | undefinedS1: number | undefinedS2: number | undefinedS3: number | undefinedS4: number | undefinedS5: number | undefined
PivotTypeis'Traditional' | 'Fibonacci' | 'Woodie' | 'Classic' | 'DM' | 'Camarilla'.Timeframeis'day' | 'week' | 'month'.- Warm-up: Levels are
undefineduntil one full period has completed. Formulas that define fewer levels leave the othersundefined.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new PivotPoints(candles, 'Traditional', 'day');
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.P === undefined || value.R1 === undefined || value.S1 === undefined) return;
manager.debugLog('P', value.P, 'R1', value.R1, 'S1', value.S1);
});
Renko
Renko bricks from a price series: a new brick forms when price travels brickSize in the trend direction; a reversal needs 2 × brickSize.
new Renko(sourceIndicator: Indicator<number | undefined>, brickSize: number | Indicator<number | undefined>, gridAligned: boolean = false)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
sourceIndicator |
Indicator<number | undefined> |
required | The price stream. |
brickSize |
number | Indicator<number | undefined> |
required | Brick height in price units, or an indicator giving a dynamic size. |
gridAligned |
boolean |
false |
Anchor bricks on multiples of brickSize (true) instead of the first price (false). |
- Input: A number stream (usually closes).
- Value (
getValue()afterhasValue()):RenkoValue | undefinedopen: numberclose: numberdirection: 1 | -1count: number
- Sub-indicators:
.open,.close,.high,.low,.direction,.count, usable as inputs to other indicators. - Warm-up:
undefineduntil the first brick forms.countis how many bricks formed on this candle (0 when none), so act oncount > 0. - Build Renko on a small base timeframe (1 or 5 minutes); the brick size is in the traded instrument's price units.
.open,.close,.high,.low,.direction,.countare also exposed as sub-indicators.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Renko(candles.close, 50);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined || value.count === 0) return;
manager.debugLog(value.count, 'new brick(s), direction', value.direction, 'last close', value.close);
});
RenkoAtr
Renko bricks whose size is the period ATR (Wilder), either live or frozen at its first value (anchored).
new RenkoAtr(candleIndicator: Indicator<Candle>, period: number = 14, anchored: boolean = false, gridAligned: boolean = false)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<Candle> |
required | The candle series. |
period |
number |
14 |
ATR length. |
anchored |
boolean |
false |
Freeze the brick size at the first ATR value (true) or follow the ATR (false). |
gridAligned |
boolean |
false |
Anchor bricks on multiples of the brick size. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):RenkoValue | undefinedopen: numberclose: numberdirection: 1 | -1count: number
- Warm-up:
undefineduntil the ATR is ready (period + 1candles) and a first brick forms.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new RenkoAtr(candles, 14);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined || value.count === 0) return;
manager.debugLog('brick', value.direction, value.close);
});
StrongCandleStructure
Market structure by the Strong Candle method: valid highs and lows, break of structure (BOS) and change of character (CHoCH) on candle closes, and the demand or supply zone of each break. A swing high only becomes valid once a candle closes below the previous candle's low (a candle close down), a swing low once a candle closes above the previous candle's high (a candle close up); each break anchors the new CHoCH level and its zone on the last strong candle before it.
new StrongCandleStructure(candleIndicator: Indicator<OHLC | undefined>, breakConfirmation: BreakConfirmation = 'body', anchor: StructureAnchor = 'last-candle', zoneMemory: ZoneMemory = 'current', zoneLimit: ZoneLimit = { above: 5, below: 5 })
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<OHLC | undefined> |
required | The candle series (or any OHLC series). |
breakConfirmation |
BreakConfirmation |
'body' |
What breaks a valid high or low: a candle close beyond it ('body', where a wick alone is a liquidity grab and the level holds) or any wick through it ('wick'). |
anchor |
StructureAnchor |
'last-candle' |
Where a break anchors the new CHoCH level and its zone: 'last-candle' on the last strong candle before the break, with the level at the extreme reached since it; 'extreme-ccd' at the most extreme low (high) of the range that a strong candle opening above (below) it produced, with the zone on that candle, falling back to 'last-candle' when the range has no such extreme. |
zoneMemory |
ZoneMemory |
'current' |
'current' publishes only the zone of the current structure, replaced at every break; 'historical' also keeps every zone until a candle breaks its outer edge by the breakConfirmation rule (a close beyond it, or a wick with 'wick'), in zones and brokenZones. |
zoneLimit |
ZoneLimit |
{ above: 5, below: 5 } |
With 'historical': how many kept zones are published above (supply) and below (demand) the close. The current zone is always published on top of them; hidden zones are still tracked and come back when price nears them. |
- Input: The candle series, or
new Heikinashi(candles)for the Heikin Ashi variant the method is usually traded on. - Value (
getValue()afterhasValue()):StrongCandleStructureValue | undefinedindex: numbertrend?: StructureTrend | undefinedvalidHigh?: number | undefinedvalidLow?: number | undefinedwaitingForValidHigh: booleanwaitingForValidLow: booleanaction?: StructureAction | undefinedactionType?: StructureBreakType | undefinedliquidityGrab?: "ABOVE_HIGH" | "BELOW_LOW" | undefinedliquidityGrabCount?: number | undefinedcandleCloseDown: booleancandleCloseUp: booleanzone?: StructureZone | undefinedzones?: readonly StructureZone[] | undefinedbrokenZones?: readonly StructureZone[] | undefined
BreakConfirmationis'body' | 'wick'.StructureAnchoris'last-candle' | 'extreme-ccd'.ZoneMemoryis'current' | 'historical'.ZoneLimitis{ above: number; below: number }.StructureZoneis{ side: "DEMAND" | "SUPPLY"; top: number; bottom: number; fresh: boolean; testedDepth?: number | undefined; createdIndex: number }.OHLCis{ open: number; high: number; low: number; close: number }.StructureTrendis'BULLISH' | 'BEARISH'.StructureActionis'BOS' | 'CHOCH'.StructureBreakTypeis'BODY' | 'WICK'.- Warm-up: A value from the first candle.
validHighandvalidLowstayundefineduntil a first candle close down or up validates them,trendandzoneuntil the first break.action,actionTypeandliquidityGrabare set only on the candle of the event. - A zone is
freshuntil price trades into it, andtestedDepthis the deepest price reached inside it. A demand zone runs from the CHoCH level up to the open of the candle close down that anchors it; a supply zone from the CHoCH level down to the open of its anchoring candle close up. liquidityGrabCountnumbers the candles that swept the same level (1, 2, 3), per side, and restarts when the level changes: count one setup per level, not one per candle. A candle that sweeps both levels is reported as'ABOVE_HIGH'and counts on both sides.- Heikin Ashi and standard candles give different structures: pick one and keep it.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new StrongCandleStructure(candles, 'body', 'last-candle');
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined || value.action === undefined) return;
manager.debugLog(value.action, value.trend, 'zone', value.zone?.bottom, value.zone?.top);
});
SupportResistance
Support and resistance levels from pivot lows/highs, with break detection confirmed by a volume oscillator.
new SupportResistance(candleIndicator: ICandleIndicator, leftBars: number = 15, rightBars: number = 15, volumeThreshold: number = 20)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
leftBars |
number |
15 |
Candles left of a pivot. |
rightBars |
number |
15 |
Candles right of a pivot (confirmation delay). |
volumeThreshold |
number |
20 |
Volume oscillator level (percent) a break needs to count as confirmed. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):SupportResistanceValueresistance: number | undefinedsupport: number | undefinedvolumeOsc: number | undefinedbreakType: BreakType | undefinedbreakWithVolume: boolean
BreakTypeis'support' | 'resistance' | 'bullWick' | 'bearWick'.- Warm-up:
breakWithVolumeis a boolean from the first candle;support/resistancestayundefineduntil a first pivot of that side is confirmed (at leastleftBars + rightBars + 2candles, often more: like LuxAlgo's script, a level starts the candle after its pivot is confirmed).breakTypeis set only on the candle that breaks a level, so do not early-return on it.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new SupportResistance(candles);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value.breakType === 'resistance' && value.breakWithVolume) manager.debugLog('resistance broken with volume', value.resistance);
});
Swings
Swing highs and lows: a candle is a top (bottom) when it is the highest (lowest) of the 2 × period + 1 candles centred on it.
new Swings(candleIndicator: Indicator<Candle>, source: 'close' | 'high/low', side: 'top' | 'bottom' | 'both', period: number, maxPivots: number, maxPivotAge: number)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
Indicator<Candle> |
required | The candle series. |
source |
'close' | 'high/low' |
required | 'close' or 'high/low'. |
side |
'top' | 'bottom' | 'both' |
required | Pivots to track: 'top', 'bottom' or 'both'. |
period |
number |
required | Candles required on each side. |
maxPivots |
number |
required | Maximum pivots kept (oldest dropped first). |
maxPivotAge |
number |
required | Pivots older than this many candles are dropped. |
- Input: The candle series.
- Value (
getValue()afterhasValue()):SwingInfoindex: numberswings: Swing[]
Swingis{ type: "top" | "bottom"; index: number; time: number; value: number }.- Warm-up: Ready from the first candle with an empty list; a pivot is confirmed
periodcandles after it forms (non-repainting). indexis the candle count of the pivot,timeits close time andvalueits price: the pivot candle's own low for a bottom and high for a top, or its close withsource: 'close'.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new Swings(candles, 'high/low', 'both', 5, 10, 100);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
const lastTop = value.swings.filter((s) => s.type === 'top').pop();
if (lastTop === undefined) return;
manager.debugLog('last swing high at candle', lastTop.index, 'time', lastTop.time);
});
WilliamsFractal
Williams fractals: an up fractal is a high with period lower highs on each side, a down fractal a low with period higher lows.
new WilliamsFractal(candleIndicator: ICandleIndicator, period: number = 2)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
period |
number |
2 |
Candles on each side (minimum 2). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):WilliamsFractalValue | undefinedupFractal: booleandownFractal: booleanfractalHigh?: number | undefinedfractalLow?: number | undefined
- Warm-up:
undefineduntil2 × period + 1candles; a fractal is reportedperiodcandles after the pivot candle.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new WilliamsFractal(candles, 2);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
if (value === undefined) return;
if (value.downFractal) manager.debugLog('bullish fractal at', value.fractalLow);
});
ZigZag
TradingView's Zig Zag: alternating swing highs and lows. A pivot is confirmed period / 2 candles after it (at least 2), and a swing in the other direction starts a new leg once it is deviation percent away from the newest vertex.
new ZigZag(candleIndicator: ICandleIndicator, period: number = 10, maxPoints: number = 5, deviation: number = 0)
| Parameter | Type | Default | Meaning |
|---|---|---|---|
candleIndicator |
ICandleIndicator |
required | The candle series. |
period |
number |
10 |
TradingView's Pivot legs: the candles that confirm a pivot, half on each side of it (10 by default). |
maxPoints |
number |
5 |
How many vertices are kept (newest first). |
deviation |
number |
0 |
TradingView's Price deviation for reversals, in percent (0 by default: every confirmed swing; TradingView uses 5). |
- Input: The candle series.
- Value (
getValue()afterhasValue()):ZigZagValuepoints: readonly ZigZagPoint[]direction: 0 | 1 | -1
ZigZagPointis{ value: number; barIndex: number; time: number }.- Warm-up: Ready from the first candle;
pointsfills as pivots are confirmed,period / 2candles after their own.points[0]is the newest vertex and still moves while its leg extends.
const symbol = 'BTC';
const candles = manager.getIntervalData(symbol, 60);
const indicator = new ZigZag(candles, 10, 5, 5);
manager.onIntervalCandle(symbol, 60, () => {
if (!indicator.hasValue()) return;
const value = indicator.getValue();
const last = value.points[0];
if (last === undefined) return;
manager.debugLog('last zigzag point', last.value, 'direction', value.direction);
});