Skip to main content

Harmonic Patterns (XABCD) in a JavaScript or React Financial Chart

Harmonic patterns are price patterns whose legs are in particular Fibonacci ratios, such as the Gartley, Bat and Butterfly. Most are drawn as five points, X, A, B, C and D. This page explains what harmonic patterns are, the ratios that define them and how traders use them, and shows how to draw an XABCD pattern on a JavaScript or React financial chart with SciTrader.js, with live examples you can edit in CodePen.

info

The XABCD Pattern tool is one of the 34 drawing tools in the SciTrader SDK. For the full list, with each tool's id, annotation class and number of points, see Every Drawing Tool. To add drawing tools with point and click or in code, see Add Drawing Tools to a Financial Chart.

What Are Harmonic Patterns?​

A harmonic pattern is a zigzag of four legs between five points, X, A, B, C and D:

  • XA is the first move.
  • AB retraces part of XA, and BC retraces part of AB.
  • CD is the final leg, which ends at D, the point where the pattern "completes" and traders expect a reversal.

What makes a pattern harmonic is the ratio of each leg to the one before, from the Fibonacci sequence. The ABCD pattern is the same idea with only the last three legs.

SciTrader's XABCD Pattern tool, in the Patterns group, places six points, labelled 0, X, A, B, C and D: the leg into X, then the pattern's legs. It draws the legs and shades the triangles between them.

How Are Harmonic Pattern Ratios Calculated?​

Each ratio compares the length of a leg, in price, with the leg before it:

ABXA=∣B−A∣∣A−X∣BCAB=∣C−B∣∣B−A∣CDBC=∣D−C∣∣C−B∣\frac{AB}{XA} = \frac{|B - A|}{|A - X|} \qquad \frac{BC}{AB} = \frac{|C - B|}{|B - A|} \qquad \frac{CD}{BC} = \frac{|D - C|}{|C - B|}

and the depth of D is measured as a retracement of XA. The common patterns' ratios:

PatternAB / XABC / ABCD / BCD, as a retracement of XA
Gartley0.6180.382 to 0.8861.27 to 1.6180.786
Bat0.382 to 0.50.382 to 0.8861.618 to 2.6180.886
Butterfly0.7860.382 to 0.8861.618 to 2.241.27 to 1.618
Crab0.382 to 0.6180.382 to 0.8862.24 to 3.6181.618

These ratios are the usual published guidelines; sources differ slightly.

tip

The XABCD tool draws the pattern but doesn't measure its ratios. To check a leg's ratio, draw a Fibonacci Retracement over the leg before it.

How to Use Harmonic Patterns​

These are the common uses. They are conventions traders use, not signals that hold every time:

  • A reversal zone at D. When the legs match a pattern's ratios, the area around D is where traders look for price to turn.
  • Bullish and bearish. A pattern whose D is a low (an M shape) is bullish; one whose D is a high (a W shape) is bearish.
  • Stops past D. A move well past D invalidates the pattern, so stops usually go just beyond it.
  • Targets back up the legs. Common targets are the 38.2% and 61.8% retracements of the CD leg, or point A.

Drawing an XABCD Pattern with Point and Click​

Start the tool with selectTool, from a button or the Drawing Toolbar. The user clicks each of the six points in turn, and the labels follow:

import { DrawingToolsController, EDrawingToolVariantId } from "scichart-scitrader";

const drawing = DrawingToolsController.attach(financeChartPanel.financeApi);
drawing.selectTool(EDrawingToolVariantId.PatternXabcd);

Adding an XABCD Pattern in Code​

An XABCD pattern is a PolyLineAnnotation with a label at each point. The SDK's own pattern helpers aren't exported, so build the same options yourself:

import { Point } from "scichart";
import { EAnnotationVisibilityMode, EMultiPointLabelAnchorMode, PolyLineAnnotation } from "scichart-financial-tools";
import { EVerticalTextPosition } from "scichart";
import { PaneId, registerDrawingTools } from "scichart-scitrader";

registerDrawingTools(); // once, at startup: also registers the label style the patterns use

// The options the SDK's pattern tools use: a poly line with a label at each point
const pattern = (points: Point[], labels: string[], stroke: string, fill: string) =>
new PolyLineAnnotation({
points,
stroke,
fill, // shades the triangles between consecutive legs
strokeThickness: 3,
isEditable: true,
pointLabelVisibility: EAnnotationVisibilityMode.Always,
labels: labels.map((text, pointIndex) => ({
anchorMode: EMultiPointLabelAnchorMode.Point,
pointIndex,
verticalTextPosition: EVerticalTextPosition.Below,
yOffset: 10,
text,
})),
formatLabelStyle: "SciTraderPatternLabelStyle", // puts each label above its point at a peak
});

financeChartPanel.financeApi.chartPanes.get(PaneId.DEFAULT).surface.annotations.add(
pattern([p0, x, a, b, c, d], ["0", "X", "A", "B", "C", "D"], "#0F766E", "#0F766E15")
);

The label text identifies the pattern: with the same labels, the drawing list and resolveToolIdForAnnotation name it as the pattern tool's drawing, not a plain poly line.

How to Draw an XABCD Pattern on a JavaScript Financial Chart​

A daily ETH-USD chart from Coinbase with an XABCD pattern added in code, on a zigzag of alternating lows and highs over the last 150 candles. It shows how to draw one; it is not a harmonic pattern found in this market. The button starts the XABCD tool: click the chart to place each of its six points.

The code for this example is found below.

import "scichart-scitrader/scitrader.css";
import { EVerticalTextPosition, Point } from "scichart";
import {
EAnnotationVisibilityMode,
EMultiPointLabelAnchorMode,
IPolyLineAnnotationOptions,
PolyLineAnnotation,
} from "scichart-financial-tools";
import {
CandleInterval,
CoinbaseExchange,
DEFAULT_FEED_ID,
defaultExchangeProvider,
DrawingToolsController,
EDataSourceStringId,
EDrawingToolVariantId,
idbPriceStore,
OHLCStudy,
PaneId,
registerDrawingTools,
SciFinanceChart,
SciFinanceChartPanel,
VolumeStudy,
} from "scichart-scitrader";

// This example expects this HTML:
// <div style="display: flex; flex-direction: column; height: 100%">
// <div id="toolbar"></div>
// <div id="chart" style="flex: 1; min-height: 0"></div>
// </div>

// Once, at app startup: register a data source, and the drawing tools' annotation types.
// registerDrawingTools() makes drawings added in code count as user drawings, listed and saved with the chart
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);
registerDrawingTools();

async function createChart() {
const toolbar = document.getElementById("toolbar");
Object.assign(toolbar.style, toolbarStyle);

const financeChartPanel = await SciFinanceChartPanel.create(document.getElementById("chart"), {
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: "ETH-USD",
interval: CandleInterval.ONE_DAY,
period: "1Y",
studies: [new OHLCStudy(), new VolumeStudy()],
});
const chart = financeChartPanel.financeApi;

// 1. Add a drawing in code: create its annotation with its points in data coordinates
// (x: a candle's time in seconds, y: a price), and add it to the price pane's surface
const candles = getCandles(chart);
const pricePane = chart.chartPanes.get(PaneId.DEFAULT).surface;
// An XABCD pattern, 0 to D, on a zigzag of alternating lows and highs over the last 150 candles.
// It shows how to draw one; it is not a harmonic pattern found in this market
pricePane.annotations.add(
new PolyLineAnnotation(
patternOptions(zigzag(candles, 150, 6), ["0", "X", "A", "B", "C", "D"], "#0F766E", "#0F766E15")
)
);

// 2. Point-and-click drawing: the controller adds the drawing modifiers to every pane.
// selectTool starts a tool, and the user clicks the chart to place its points
const drawing = DrawingToolsController.attach(chart);
toolbar.append(
button("Draw an XABCD pattern", () => drawing.selectTool(EDrawingToolVariantId.PatternXabcd)),
button("Remove all", () => drawing.remove()) // every drawing, the ones added in code too
);

// When the page goes away: drawing.detach(); financeChartPanel.cleanup();
return { financeChartPanel, drawing };
}

// ---------------------------------------------------------------------------------------------
// Not part of the SDK: helpers that read the loaded candles, to choose where to place drawings
// ---------------------------------------------------------------------------------------------

// The loaded candles: times in seconds (the x values of a SciTrader chart), and prices
function getCandles(chart: SciFinanceChart) {
const dm = chart.dataManager;
const values = (name: EDataSourceStringId) => dm.getValues<number>(dm.getSourceIdByName(DEFAULT_FEED_ID, name));
return {
times: dm.xValues,
highs: values(EDataSourceStringId.HIGH),
lows: values(EDataSourceStringId.LOW),
closes: values(EDataSourceStringId.CLOSE),
};
}
type TCandles = ReturnType<typeof getCandles>;

// The lowest low, or highest high, of candles [from, to), as a point on the chart
function lowest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.lows[j] < c.lows[i]) i = j;
return new Point(c.times[i], c.lows[i]);
}
function highest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.highs[j] > c.highs[i]) i = j;
return new Point(c.times[i], c.highs[i]);
}

// A zigzag of `count` points over the last `candleCount` candles: the lowest low of the first
// stretch, the highest high of the next, and so on
function zigzag(c: TCandles, candleCount: number, count: number): Point[] {
const n = c.times.length;
const size = Math.floor(candleCount / count);
return Array.from({ length: count }, (_, k) => {
const from = n - candleCount + k * size;
return k % 2 === 0 ? lowest(c, from, from + size) : highest(c, from, from + size);
});
}

// The options the SDK's pattern tools use: a poly line with a label at each point.
// The label text identifies the pattern, e.g. in the drawing list
function patternOptions(points: Point[], labels: string[], stroke: string, fill: string): IPolyLineAnnotationOptions {
return {
points,
isEditable: true, // the user can select, drag and edit it
stroke,
fill, // shades the triangles between consecutive legs
strokeThickness: 3,
pointLabelVisibility: EAnnotationVisibilityMode.Always,
labels: labels.map((text, pointIndex) => ({
anchorMode: EMultiPointLabelAnchorMode.Point,
pointIndex,
verticalTextPosition: EVerticalTextPosition.Below,
yOffset: 10,
text,
})),
// Registered by registerDrawingTools(): puts each label above its point at a peak, below at a trough
formatLabelStyle: "SciTraderPatternLabelStyle",
};
}

function button(text: string, onClick: () => void) {
const b = document.createElement("button");
b.textContent = text;
b.onclick = onClick;
Object.assign(b.style, buttonStyle);
return b;
}

// The toolbar uses SciTrader's UI colours: its --st-ui-* CSS variables, with the stylesheet's defaults
const toolbarStyle: Partial<CSSStyleDeclaration> = {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
gap: "8px",
padding: "8px",
background: "var(--st-ui-background, #2b2839)",
borderBottom: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
};
const buttonStyle: Partial<CSSStyleDeclaration> = {
padding: "4px 10px",
border: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
borderRadius: "6px",
background: "var(--st-ui-surface, #373447)",
color: "var(--st-ui-text, #ffffff)",
font: "inherit",
fontSize: "13px",
cursor: "pointer",
};

createChart();

How to Draw an XABCD Pattern on a React Financial Chart​

The same chart in React. The drawing is added in onInit, once the chart has loaded its candles, and useDrawingTools gives the buttons their controller.

The code for this example is found below.

import "scichart-scitrader/scitrader.css";
import * as React from "react";
import { EVerticalTextPosition, Point } from "scichart";
import {
EAnnotationVisibilityMode,
EMultiPointLabelAnchorMode,
IPolyLineAnnotationOptions,
PolyLineAnnotation,
} from "scichart-financial-tools";
import {
CandleInterval,
CoinbaseExchange,
DEFAULT_FEED_ID,
defaultExchangeProvider,
EDataSourceStringId,
EDrawingToolVariantId,
idbPriceStore,
ISciFinanceChartPanelApi,
ISciFinanceChartPanelConfig,
OHLCStudy,
PaneId,
registerDrawingTools,
SciFinanceChart,
VolumeStudy,
} from "scichart-scitrader";
import { SciFinanceChartView, useDrawingTools } from "scichart-scitrader-react";

// Once, at app startup: register a data source, and the drawing tools' annotation types.
// registerDrawingTools() makes drawings added in code count as user drawings, listed and saved with the chart
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);
registerDrawingTools();

export function HarmonicPatternChart() {
const [chartApi, setChartApi] = React.useState<ISciFinanceChartPanelApi>();
// useDrawingTools attaches a DrawingToolsController once the chart is ready, and detaches it on unmount
const { controller } = useDrawingTools(chartApi);

// Create the config once per mounted chart: studies are live objects bound to one chart
const config = React.useMemo<ISciFinanceChartPanelConfig>(
() => ({
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: "ETH-USD",
interval: CandleInterval.ONE_DAY,
period: "1Y",
studies: [new OHLCStudy(), new VolumeStudy()],
}),
[]
);

// Once the chart has loaded its candles, add a drawing in code: create its annotation with its
// points in data coordinates (x: a candle's time in seconds, y: a price), and add it to the price pane
const onInit = (api: ISciFinanceChartPanelApi) => {
const chart = api.financeApi;
const candles = getCandles(chart);
const pricePane = chart.chartPanes.get(PaneId.DEFAULT).surface;
// An XABCD pattern, 0 to D, on a zigzag of alternating lows and highs over the last 150 candles.
// It shows how to draw one; it is not a harmonic pattern found in this market
pricePane.annotations.add(
new PolyLineAnnotation(
patternOptions(zigzag(candles, 150, 6), ["0", "X", "A", "B", "C", "D"], "#0F766E", "#0F766E15")
)
);
setChartApi(api);
};

// Fills its parent, so give the parent a height
return (
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
{/* Point-and-click drawing: selectTool starts a tool, and the user clicks the chart to place its points */}
<div style={toolbarStyle}>
<button style={buttonStyle} disabled={!controller} onClick={() => controller.selectTool(EDrawingToolVariantId.PatternXabcd)}>
Draw an XABCD pattern
</button>
<button style={buttonStyle} disabled={!controller} onClick={() => controller.remove()}>
Remove all
</button>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<SciFinanceChartView config={config} onInit={onInit} />
</div>
</div>
);
}

// ---------------------------------------------------------------------------------------------
// Not part of the SDK: helpers that read the loaded candles, to choose where to place drawings
// ---------------------------------------------------------------------------------------------

// The loaded candles: times in seconds (the x values of a SciTrader chart), and prices
function getCandles(chart: SciFinanceChart) {
const dm = chart.dataManager;
const values = (name: EDataSourceStringId) => dm.getValues<number>(dm.getSourceIdByName(DEFAULT_FEED_ID, name));
return {
times: dm.xValues,
highs: values(EDataSourceStringId.HIGH),
lows: values(EDataSourceStringId.LOW),
closes: values(EDataSourceStringId.CLOSE),
};
}
type TCandles = ReturnType<typeof getCandles>;

// The lowest low, or highest high, of candles [from, to), as a point on the chart
function lowest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.lows[j] < c.lows[i]) i = j;
return new Point(c.times[i], c.lows[i]);
}
function highest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.highs[j] > c.highs[i]) i = j;
return new Point(c.times[i], c.highs[i]);
}

// A zigzag of `count` points over the last `candleCount` candles: the lowest low of the first
// stretch, the highest high of the next, and so on
function zigzag(c: TCandles, candleCount: number, count: number): Point[] {
const n = c.times.length;
const size = Math.floor(candleCount / count);
return Array.from({ length: count }, (_, k) => {
const from = n - candleCount + k * size;
return k % 2 === 0 ? lowest(c, from, from + size) : highest(c, from, from + size);
});
}

// The options the SDK's pattern tools use: a poly line with a label at each point.
// The label text identifies the pattern, e.g. in the drawing list
function patternOptions(points: Point[], labels: string[], stroke: string, fill: string): IPolyLineAnnotationOptions {
return {
points,
isEditable: true, // the user can select, drag and edit it
stroke,
fill, // shades the triangles between consecutive legs
strokeThickness: 3,
pointLabelVisibility: EAnnotationVisibilityMode.Always,
labels: labels.map((text, pointIndex) => ({
anchorMode: EMultiPointLabelAnchorMode.Point,
pointIndex,
verticalTextPosition: EVerticalTextPosition.Below,
yOffset: 10,
text,
})),
// Registered by registerDrawingTools(): puts each label above its point at a peak, below at a trough
formatLabelStyle: "SciTraderPatternLabelStyle",
};
}

// The toolbar uses SciTrader's UI colours: its --st-ui-* CSS variables, with the stylesheet's defaults
const toolbarStyle: React.CSSProperties = {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
gap: 8,
padding: 8,
background: "var(--st-ui-background, #2b2839)",
borderBottom: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
};
const buttonStyle: React.CSSProperties = {
padding: "4px 10px",
border: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
borderRadius: 6,
background: "var(--st-ui-surface, #373447)",
color: "var(--st-ui-text, #ffffff)",
font: "inherit",
fontSize: 13,
cursor: "pointer",
};

XABCD Pattern Options: PolyLineAnnotation​

OptionWhat it does
pointsSix points: 0, X, A, B, C and D
labelsThe text at each point, and where it sits
formatLabelStyle"SciTraderPatternLabelStyle" flips each label above its point at a peak. Registered by registerDrawingTools()
stroke, strokeThickness, fillThe line, and the shading between the legs
isEditableWhether the user can select, drag and edit it
The PolyLineAnnotation class, with its TSDoc
/**
* Native WebGL polyline annotation defined by two or more points.
*
* `PolyLineAnnotation` is the simplest concrete {@link MultiPointAnnotationBase}
* implementation. It renders straight segments through its `points` array and
* inherits the shared multi-point behavior: editable vertex grips, whole-shape
* dragging, data-point snapping, point/segment/axis labels, label formatters and
* placement-modifier support. Set `fill` to draw the closed polygon as a triangle
* strip; leave it `null` for line-only drawing.
*/
class PolyLineAnnotation extends MultiPointAnnotationBase {
/** @inheritDoc */
readonly type: EAnnotationType;
/** @inheritDoc */
readonly surfaceTypes: ESurfaceType[];
constructor(options?: IPolyLineAnnotationOptions);
/** Gets or sets stroke color. */
get stroke(): string;
set stroke(value: string);
/** Gets or sets stroke thickness. */
get strokeThickness(): number;
set strokeThickness(value: number);
/** Gets or sets stroke dash array. */
get strokeDashArray(): number[];
set strokeDashArray(value: number[]);
/** Gets or sets fill color. */
get fill(): string | null;
set fill(value: string | null);
/** Gets or sets the number of points required during placement. */
get placementPointCount(): number;
set placementPointCount(value: number);
/** @inheritDoc */
delete(): void;
/** @inheritDoc */
onAttach(scs: SciChartSurface): void;
/** @inheritDoc */
drawWithContext(renderContext: IRenderContext2D, xCalc: CoordinateCalculatorBase, yCalc: CoordinateCalculatorBase, seriesViewRect: Rect, surfaceViewRect: Rect, chartViewRect: Rect): void;
/** @inheritDoc */
onDpiChanged(args: TDpiChangedEventArgs): void;
/** @inheritDoc */
toJSON(): {
type: EAnnotationType;
options: Required<Omit<IAnnotationBaseOptions, never>>;
};
}
The IPolyLineAnnotationOptions interface, with its TSDoc
/** Options for {@link PolyLineAnnotation}. */
interface IPolyLineAnnotationOptions extends IMultiPointAnnotationBaseOptions {
/**
* Number of points required when using {@link MultiPointAnnotationPlacementModifier}.
* Default `2`.
*/
placementPointCount?: number;
/** Stroke color. */
stroke?: string;
/** Stroke thickness of the line annotation.
* Default `2`
*/
strokeThickness?: number;
/**
* The strokeDashArray for the {@link LineAnnotation}
*/
strokeDashArray?: number[];
/**
* The primitive triangle fill as an HTML color string.
* Default `null` -> triangles do NOT draw at all.
*/
fill?: string | null;
}