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() after hasValue()): BosNChochValue | undefined
    • trend: "BULLISH" | "BEARISH"
    • chochLevel: number
    • bosLevel: number
    • action: "BOS" | "CHOCH" | undefined
    • type: "BODY" | "WICK" | undefined
  • ChochType is 'body' | 'wick' | 'both'.
  • Warm-up: undefined for the first minExtremeCandlesChochRecalculation candles; action and type are 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() after hasValue()): "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() after hasValue()): FVGValue | null
    • type: "bullish" | "bearish"
    • upper: number
    • lower: number
  • Warm-up: null until 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() after hasValue()): OHLCValue | undefined
    • open: number
    • high: number
    • low: number
    • close: 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, .close are also exposed as number sub-indicators, so new 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() 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: 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'), or ha.close for 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.openTime or candles.closeTime).
  • Value (getValue() after hasValue()): 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() after hasValue()): MarketStructureValue
    • swingType?: SwingType | undefined
    • swingBarIndex?: number | undefined
    • bosType?: BosType | undefined
    • bosDirection?: BosDirection | undefined
    • bosBarIndex?: number | undefined
    • prevHigh?: number | undefined
    • prevLow?: number | undefined
    • prevHighIndex?: number | undefined
    • prevLowIndex?: number | undefined
    • highActive: boolean
    • lowActive: boolean
  • SwingType is 'HH' | 'LH' | 'HL' | 'LL'.
  • BosType is 'BOS' | 'CHoCH'.
  • BosDirection is 'bullish' | 'bearish'.
  • Warm-up: Ready from the first candle; events appear once swings are confirmed (swingSize candles 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() after hasValue()): OrderBlocksValue
    • bullish: OrderBlock | null
    • bearish: OrderBlock | null
  • OrderBlock is { type: "bullish" | "bearish"; upper: number; lower: number; timestamp: number; line25: number; line50: number; line75: number }.
  • Warm-up: Values from the third candle; bullish / bearish are null until 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() after hasValue()): PivotPointsValue
    • P: number | undefined
    • R1: number | undefined
    • R2: number | undefined
    • R3: number | undefined
    • R4: number | undefined
    • R5: number | undefined
    • S1: number | undefined
    • S2: number | undefined
    • S3: number | undefined
    • S4: number | undefined
    • S5: number | undefined
  • PivotType is 'Traditional' | 'Fibonacci' | 'Woodie' | 'Classic' | 'DM' | 'Camarilla'.
  • Timeframe is 'day' | 'week' | 'month'.
  • Warm-up: Levels are undefined until one full period has completed. Formulas that define fewer levels leave the others undefined.
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() after hasValue()): RenkoValue | undefined
    • open: number
    • close: number
    • direction: 1 | -1
    • count: number
  • Sub-indicators: .open, .close, .high, .low, .direction, .count, usable as inputs to other indicators.
  • Warm-up: undefined until the first brick forms. count is how many bricks formed on this candle (0 when none), so act on count > 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, .count are 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() after hasValue()): RenkoValue | undefined
    • open: number
    • close: number
    • direction: 1 | -1
    • count: number
  • Warm-up: undefined until the ATR is ready (period + 1 candles) 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() after hasValue()): StrongCandleStructureValue | undefined
    • index: number
    • trend?: StructureTrend | undefined
    • validHigh?: number | undefined
    • validLow?: number | undefined
    • waitingForValidHigh: boolean
    • waitingForValidLow: boolean
    • action?: StructureAction | undefined
    • actionType?: StructureBreakType | undefined
    • liquidityGrab?: "ABOVE_HIGH" | "BELOW_LOW" | undefined
    • liquidityGrabCount?: number | undefined
    • candleCloseDown: boolean
    • candleCloseUp: boolean
    • zone?: StructureZone | undefined
    • zones?: readonly StructureZone[] | undefined
    • brokenZones?: readonly StructureZone[] | undefined
  • BreakConfirmation is 'body' | 'wick'.
  • StructureAnchor is 'last-candle' | 'extreme-ccd'.
  • ZoneMemory is 'current' | 'historical'.
  • ZoneLimit is { above: number; below: number }.
  • StructureZone is { side: "DEMAND" | "SUPPLY"; top: number; bottom: number; fresh: boolean; testedDepth?: number | undefined; createdIndex: number }.
  • OHLC is { open: number; high: number; low: number; close: number }.
  • StructureTrend is 'BULLISH' | 'BEARISH'.
  • StructureAction is 'BOS' | 'CHOCH'.
  • StructureBreakType is 'BODY' | 'WICK'.
  • Warm-up: A value from the first candle. validHigh and validLow stay undefined until a first candle close down or up validates them, trend and zone until the first break. action, actionType and liquidityGrab are set only on the candle of the event.
  • A zone is fresh until price trades into it, and testedDepth is 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.
  • liquidityGrabCount numbers 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() after hasValue()): SupportResistanceValue
    • resistance: number | undefined
    • support: number | undefined
    • volumeOsc: number | undefined
    • breakType: BreakType | undefined
    • breakWithVolume: boolean
  • BreakType is 'support' | 'resistance' | 'bullWick' | 'bearWick'.
  • Warm-up: breakWithVolume is a boolean from the first candle; support / resistance stay undefined until a first pivot of that side is confirmed (at least leftBars + rightBars + 2 candles, often more: like LuxAlgo's script, a level starts the candle after its pivot is confirmed). breakType is 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() after hasValue()): SwingInfo
    • index: number
    • swings: Swing[]
  • Swing is { type: "top" | "bottom"; index: number; time: number; value: number }.
  • Warm-up: Ready from the first candle with an empty list; a pivot is confirmed period candles after it forms (non-repainting).
  • index is the candle count of the pivot, time its close time and value its price: the pivot candle's own low for a bottom and high for a top, or its close with source: '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() after hasValue()): WilliamsFractalValue | undefined
    • upFractal: boolean
    • downFractal: boolean
    • fractalHigh?: number | undefined
    • fractalLow?: number | undefined
  • Warm-up: undefined until 2 × period + 1 candles; a fractal is reported period candles 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() after hasValue()): ZigZagValue
    • points: readonly ZigZagPoint[]
    • direction: 0 | 1 | -1
  • ZigZagPoint is { value: number; barIndex: number; time: number }.
  • Warm-up: Ready from the first candle; points fills as pivots are confirmed, period / 2 candles 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);
});
Browse the documentation