StockSharp JS Trading Charts is a canvas trading-chart engine: one chart owns
native panes and a single time axis, with custom series, primitives, indicators,
exact order flow (footprint / volume profile / TPO) and a broker-agnostic trading
layer — published as a typed ESM package and the SSChart global.
The indicators it draws come from
@stocksharp/indicators — ~165 of
them, checked against the C# StockSharp platform. That package is this engine's only
runtime dependency, and it can be used on its own by anything that wants the numbers
without a chart.
Live demo · StockSharp website · GitHub repository · Issue tracker
npm install @stocksharp/chartimport { createChart, CandlestickSeries } from '@stocksharp/chart';
const chart = createChart(document.getElementById('chart'));
const candles = chart.addSeries(CandlestickSeries, {});
candles.setData([{ time: 1, open: 100, high: 102, low: 99, close: 101 }]);Or drop in the global build from a CDN — no bundler required:
<script src="https://unpkg.com/@stocksharp/chart"></script>
<script>
const chart = SSChart.createChart(document.getElementById('chart'));
</script>The engine draws; @stocksharp/chart/ui draws the controls around it — the crosshair
legend, the right-click menu, the chart-type switcher and the indicator picker.
It is a separate entry point because a page can want one without the other: a
report with a sparkline needs no menus.
import { createChart, CandlestickSeries } from '@stocksharp/chart';
import { ChartLegend, standaloneHost, fullscreenMenuLayer } from '@stocksharp/chart/ui';
import '@stocksharp/chart/ui.css';
const legend = new ChartLegend({
container: document.getElementById('legend'),
host: standaloneHost, // your translator, formatters and toasts
paneHost: paneManager,
chartTypes: [{ value: 'candle', label: 'Candles', icon: 'bi bi-bar-chart-fill' }],
menuLayer: fullscreenMenuLayer,
});
legend.init(chart);Every module here takes a ChartUiHost: the page words its own strings, formats
its own numbers and shows its own messages. Nothing in the layer reaches for a
global, which is what lets it render on a page that is not the terminal it grew
up in. standaloneHost answers all three itself — English as written,
magnitude-based numbers, messages to the console — and is where a first
integration starts.
In the browser it ships as dist/sschartui.js, publishing SSChartUI. Load the
engine first: the UI layer reads it off the SSChart global rather than carrying
a second copy, because a chart matches a series definition by identity and a
definition from a second copy is one it does not know.
<script src="https://unpkg.com/@stocksharp/indicators"></script>
<script src="https://unpkg.com/@stocksharp/chart"></script>
<script src="https://unpkg.com/@stocksharp/chart/dist/sschartui.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stocksharp/chart/styles/chart-ui.css" />The stylesheet is required — those modules emit class names and nothing else.
Every colour in it is a --chart-ui-* custom property with a dark default, so a
page restyles by declaring the tokens rather than by overriding rules.
src/index.ts public ESM entry (createChart, addSeries, timeScale, …)
src/core/ the chart engine (canvas render, panes, scales, hit-test)
src/chart/ the UI layer, published as @stocksharp/chart/ui:
ui.ts its public entry point
chart-host.ts what a module asks of its page: translate, format, notify, modal
engine.ts the engine, in one place, so the browser bundle can read it off
the global instead of carrying a second copy
indicators/ IndicatorEngine + IndicatorRenderer + IndicatorSettings +
painters/ (the drawing half; the maths is @stocksharp/indicators)
chart-legend.ts OHLCV + indicator-value legend (crosshair-driven)
chart-context-menu.ts right-click menu; a page contributes its own rows
chart-pane-manager.ts oscillator sub-panes over the engine's native panes
indicator-dialog.ts indicator picker (search / categories / params / active list)
chart-type-switcher.ts candle / bar / line / area / heikin / renko / P&F
app.ts demo wiring — mounts the modules through their public options
styles/chart-ui.css the layer's stylesheet, published as @stocksharp/chart/ui.css
demo/ the showcase (index.html + terminal CSS + seeded market data)
build.mjs esbuild -> dist/sschart.js (SSChart global), dist/sschartui.js
(SSChartUI) and dist/chart-app.js; also copies the indicator
package's dist/ssindicators.js beside them
The engine (src/index.ts / src/core/) is the shared source of truth; the demo
loads it as the SSChart global, then the UI layer on top.
demo/index.html is a live trading chart driven by the real modules:
- Main chart — candlestick with volume, trade markers and a crosshair legend; the chart-type dropdown switches the render between candlestick, bar, line, area, Heikin-Ashi, Renko, Point & Figure, cluster (footprint) and box.
- Indicators — a + Indicator button / right-click Add indicator… opens the real picker over the full StockSharp indicator catalog; overlays draw on the main chart, oscillators (RSI, MACD, …) get their own spine-synced sub-pane with a proper 0–100 scale; the legend shows each study's value at the crosshair. All recompute live on the streaming feed.
- Light / dark theme toggle re-colours the whole stack, and a play/pause Realtime feed streams new bars with live indicator recompute.
Load the indicator package and the engine bundle — in that order, the engine reads the
SSIndicators global the package publishes — then drive it with a small declarative
API. time is UNIX seconds:
<div id="chart" style="width:800px;height:400px"></div>
<script src="dist/ssindicators.js"></script>
<script src="dist/sschart.js"></script>
<script>
const chart = SSChart.createChart(document.getElementById('chart'), {
layout: { background: { type: 'solid', color: '#131820' }, textColor: '#8b97a7' },
grid: { vertLines: { color: '#1e2633' }, horzLines: { color: '#1e2633' } },
rightPriceScale: { borderColor: '#1e2633' },
timeScale: { borderColor: '#1e2633', timeVisible: true },
crosshair: { mode: SSChart.CrosshairMode.Normal },
});
// candlesticks
const candles = chart.addSeries(SSChart.CandlestickSeries, {
upColor: '#00c853', downColor: '#ff3d57', borderVisible: false,
});
candles.setData([
{ time: 1704153600, open: 120, high: 122, low: 119, close: 121 },
{ time: 1704240000, open: 121, high: 124, low: 120, close: 123 },
]);
// an overlay line (e.g. a moving average) on the same pane
const ma = chart.addSeries(SSChart.LineSeries, { color: '#f0b90b', lineWidth: 2 });
ma.setData([{ time: 1704153600, value: 120.5 }, { time: 1704240000, value: 122.0 }]);
// trade markers + a price line
SSChart.createSeriesMarkers(candles, [
{ time: 1704240000, position: 'belowBar', color: '#00c853', shape: 'arrowUp', text: 'BUY' },
]);
candles.createPriceLine({ price: 123, color: '#4a9eff', lineStyle: SSChart.LineStyle.Dashed, title: 'entry' });
// realtime: same time replaces the last bar, a newer time appends a new one
candles.update({ time: 1704326400, open: 123, high: 123.5, low: 122.8, close: 123.2 });
chart.timeScale().fitContent();
</script>Series types: CandlestickSeries, BarSeries, LineSeries, AreaSeries,
HistogramSeries, RenkoSeries, PointFigureSeries, FootprintSeries,
ExactVolumeProfileSeries, ClusterSeries, BoxSeries2. The legacy
VolumeProfileSeries is retained only as an unsupported migration marker and
does not distribute candle volume across price bins. In a TypeScript build you can instead
import { createChart, CandlestickSeries } from '@stocksharp/chart' and bundle it.
For the full terminal experience — indicator engine over the whole catalog,
crosshair legend, oscillator sub-panes, right-click menu and the picker dialog —
import them from @stocksharp/chart/ui; src/chart/app.ts
mounts every one of them through the same public options you get.
An indicator is drawn as one or more ordinary lines by default. To opt into a
special renderer, put a stable painter name in its catalog.json entry:
{
"kind": "MyIndicator",
"name": "My indicator",
"pane": "separate",
"painter": "my-histogram"
}Register a factory before adding that indicator. A fresh painter is created for
each indicator instance; paint creates and returns its series, while update
refreshes them on every live recalculation:
SSChart.registerIndicatorPainter('my-histogram', () => ({
paint(ctx) {
const color = ctx.nextColor();
const bars = ctx.addSeries('histogram', { color, title: 'My histogram' }, ctx.output('value'));
return { series: [bars], colors: [color] };
},
update(ctx, series) {
series[0].setData(ctx.output('value'));
},
}));TypeScript consumers can import the contract and registry from
src/chart/indicators/painters/index.ts. If a painter name is absent or is not
registered, the renderer safely falls back to normal lines.
Order flow uses an explicit FootprintBar contract. bidVolume is the volume of
aggressive sells executed against resting bids; askVolume is the volume of
aggressive buys executed against resting asks. Every level price and OHLC price
must align to the instrument tick size:
const bars = normalizeFootprintBars([{
dataMode: OrderFlowDataMode.Exact,
time: 1704240000,
open: 100.00,
high: 100.02,
low: 99.99,
close: 100.01,
levels: [
{ price: 99.99, bidVolume: 18, askVolume: 0, tradeCount: 2 },
{ price: 100.00, bidVolume: 12, askVolume: 20, tradeCount: 5 },
{ price: 100.01, bidVolume: 4, askVolume: 31, tradeCount: 6 },
{ price: 100.02, bidVolume: 0, askVolume: 9, tradeCount: 1 },
],
}], { tickSize: 0.01 });ApproximateFootprintBar is a separate discriminated type with only
totalVolume and a mandatory approximation reason. The library never invents a
bid/ask split or silently passes candle-volume distribution as exact footprint
data. Legacy ClusterData/VolumeProfileData remain explicitly deprecated
approximation contracts during compatibility migration.
Classified executions can be aggregated incrementally without rebuilding prior
bars. FootprintAggregator.push(trade) emits either a one-bar update patch for
the active interval or a one-bar append patch for the next interval. Bar
boundaries are aligned by barDuration and optional timeOrigin; OHLC and every
level are derived only from the supplied executions.
calculateFootprintMetrics is a pure calculation path for total bid/ask,
delta, POC, value area, diagonal/stacked imbalance, and auction completion. Buy
imbalance compares ask(P) to bid(P - tick); sell imbalance compares bid(P)
to ask(P + tick). Viewport zoom is deliberately absent from these options.
FootprintSeries is a regular CustomSeriesDefinition, so it is added with
chart.addSeries(FootprintSeries, { tickSize, mode }) and does not add a branch
to the chart core. It supports bid-ask, delta, total, and ladder modes.
Automatic detail switches only the presentation between numbers, heatmap cells,
and an OHLC/delta summary; all three use the same cached metrics.
calculateVolumeProfile and ExactVolumeProfileAccumulator aggregate the exact
levels themselves, including POC and value area; candle volume is never spread
between low and high. The accumulator supports append and replace-last through
level deltas. calculateDevelopingVolumeProfile exposes cumulative POC/VAH/VAL.
For heterogeneous input, resolveVolumeProfile returns an explicit
approximate or mixed unavailable result instead of producing a profile.
ExactVolumeProfileSeries renders that calculation as a non-time-scale custom
overlay. Its range is explicitly visible, fixed, or session; session mode
uses serializable half-open session ranges rather than runtime callbacks. Fixed
and session profiles remain visible outside their source viewport, and optional
developing POC/VAH/VAL paths share the same selected exact bars.
TpoSeries is a separate Market Profile custom series. Its TpoBar input has
an explicit serializable sessionId; calculation produces per-session TPO
counts, letters, POC, value area, initial balance, and single prints. Automatic
zoom switches only between letters and compact blocks and never changes those
session calculations.
npm install # once, to fetch esbuild and link ../JS-Indicators
npm run build # bundles src -> dist/
npm run serve # http://localhost:8791/demo/index.html
npm test # typecheck + public-API snapshot + the node:test suite
build-tests.mjs esbuild-bundles tests/**/*.test.js into tests/_dist, then
node --test runs them. What is covered here is the chart: series, panes, drawings,
order flow, persistence, and the indicator engine — how a computed series reaches a
pane, a painter and the legend.
Whether an indicator's value is right is settled in JS-Indicators, against a live C# build rather than against a fixture. A number that looks wrong on screen is a bug to open there, not here.
- Porting the stack surfaced and fixed a few latent engine bugs (chart-level
priceScale(id), whitespace/warm-up points poisoning the price-scale bounds, a fallback for ordinal sub-pane scales,scrollToRealTime). These live insrc/core/here; folding them back into the terminal's copy is a follow-up.





