Elliott Wave Patterns in a JavaScript or React Financial Chart
Elliott wave theory describes market moves as repeating patterns of waves: five waves with the trend, then three against it. This page explains what Elliott waves are, the rules traders count them by and how they use them, and shows how to draw Elliott wave patterns on a JavaScript or React financial chart with SciTrader.js, by point and click or in code, with live examples you can edit in CodePen.
The Elliott wave patterns are five 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 Is Elliott Wave Theory?
Ralph Nelson Elliott proposed in the 1930s that crowd psychology moves markets in recognisable patterns of waves:
- An impulse is five waves in the direction of the trend, labelled 1 to 5. Waves 1, 3 and 5 move with the trend; waves 2 and 4 correct against it.
- A correction is three waves against the trend, labelled A, B and C.
- Patterns nest: each wave is itself made of smaller waves, so the same shapes appear on every interval.
SciTrader has an Elliott wave tool for each of the common patterns. Each is a poly line with a label at each point, in the Patterns group:
| Tool | Tool id | Points (labels) | Pattern |
|---|---|---|---|
| Elliott Impulse Wave (12345) | EDrawingPattern | 6: (0) to (5) | Five waves with the trend |
| Elliott Correction Wave (ABC) | EDrawingPattern | 4: 0, A, B, C | A three-wave zigzag correction |
| Elliott Triangle Wave (ABCDE) | EDrawingPattern | 6: (0), (A) to (E) | A five-wave contracting or expanding triangle |
| Elliott Double Combo (WXY) | EDrawingPattern | 4: (0), (W), (X), (Y) | Two corrections joined by an X wave |
| Elliott Triple Combo (WXYXZ) | EDrawingPattern | 6: (0), (W), (X), (Y), (X), (Z) | Three corrections joined by X waves |
The first point, (0), is where the pattern starts.
The Rules of an Elliott Wave Count
An Elliott wave pattern is drawn by the trader, not calculated. A valid impulse has to follow three rules:
- Wave 2 never retraces all of wave 1. It doesn't go below wave 1's start, in an up-move.
- Wave 3 is never the shortest of waves 1, 3 and 5. It is often the longest.
- Wave 4 doesn't overlap wave 1's price range, except in special patterns such as diagonals.
Traders also use Fibonacci ratios as guidelines: wave 2 often retraces 50% to 61.8% of wave 1, wave 3 often reaches 1.618 times wave 1, and wave 4 often retraces 38.2% of wave 3. Draw a Fibonacci Retracement over a wave to check.
How to Use Elliott Waves
These are the common uses. They are conventions traders use, not signals that hold every time:
- Where the trend is. In wave 3 of an impulse, the trend has room to run; after wave 5, a correction is expected.
- Corrections as entries. The end of wave 2 or wave 4, or of an ABC correction, is where trend traders look to join the trend.
- Invalidation. A count that breaks a rule is wrong; the price that would break it is a natural stop-loss level.
- More than one count. Waves are open to interpretation, so traders often keep a main count and an alternative.
Drawing Elliott Waves with Point and Click
Start a pattern tool with selectTool, from a button or the Drawing Toolbar. The user clicks each point in turn, (0) first, and the labels follow:
import { DrawingToolsController, EDrawingToolVariantId } from "scichart-scitrader";
const drawing = DrawingToolsController.attach(financeChartPanel.financeApi);
drawing.selectTool(EDrawingToolVariantId.PatternElliottImpulse); // (0) to (5)
drawing.selectTool(EDrawingToolVariantId.PatternElliottCorrection); // 0, A, B, C
Adding an Elliott Wave in Code
An Elliott wave 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, p1, p2, p3, p4, p5], ["(0)", "(1)", "(2)", "(3)", "(4)", "(5)"], "#7C3AED", "#7C3AED33")
);
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 Elliott Waves on a JavaScript Financial Chart
A daily BTC-USD chart from Coinbase with an Elliott impulse wave added in code, on a zigzag of alternating lows and highs over the last 180 candles. It shows how to draw one; it is not a wave count of this market. The buttons start the impulse and correction tools: click the chart to place each point.
The code for this example is found below.
- TS
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: "BTC-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 Elliott impulse wave, (0) to (5), on a zigzag of alternating lows and highs over the last
// 180 candles. It shows how to draw one; it is not a wave count of this market
pricePane.annotations.add(
new PolyLineAnnotation(
patternOptions(zigzag(candles, 180, 6), ["(0)", "(1)", "(2)", "(3)", "(4)", "(5)"], "#7C3AED", "#7C3AED33")
)
);
// 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 impulse wave (12345)", () => drawing.selectTool(EDrawingToolVariantId.PatternElliottImpulse)),
button("Draw a correction wave (ABC)", () => drawing.selectTool(EDrawingToolVariantId.PatternElliottCorrection)),
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 Elliott Waves 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.
- React
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 ElliottWaveChart() {
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: "BTC-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 Elliott impulse wave, (0) to (5), on a zigzag of alternating lows and highs over the last
// 180 candles. It shows how to draw one; it is not a wave count of this market
pricePane.annotations.add(
new PolyLineAnnotation(
patternOptions(zigzag(candles, 180, 6), ["(0)", "(1)", "(2)", "(3)", "(4)", "(5)"], "#7C3AED", "#7C3AED33")
)
);
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.PatternElliottImpulse)}>
Draw an impulse wave (12345)
</button>
<button style={buttonStyle} disabled={!controller} onClick={() => controller.selectTool(EDrawingToolVariantId.PatternElliottCorrection)}>
Draw a correction wave (ABC)
</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",
};
Elliott Wave Options: PolyLineAnnotation
| Option | What it does |
|---|---|
points | One point per label: 6 for an impulse, 4 for an ABC correction |
labels | The text at each point, and where it sits: anchor, point, vertical, y, text |
point | When the labels show. EAnnotation for patterns |
format | "Sci flips each label above its point at a peak. Registered by register |
stroke, stroke, stroke | The line |
fill | Shades the triangle between each pair of legs. null for none |
is | Whether 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;
}
Where to read next
- Add Drawing Tools to a Financial Chart: the other drawing tools, point-and-click drawing and drawings in code.
- Editing Drawings: the quick-edit toolbar, the full editor and edit events.
- Drawing Toolbar and Drawing List: ready-made tools and list for users.
- Elliott wave patterns: the poly line patterns, in the
scichart-financial-toolsdocs.