@stocksharp/chart
v2.4.0
Published
StockSharp JS Trading Charts — a dependency-free browser trading-chart engine: native panes, custom series, primitives, incremental indicators, exact order flow (footprint / volume profile / TPO) and a broker-agnostic trading layer, with a TypeScript API
Maintainers
Readme
StockSharp JS Trading Charts
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
Screenshots
| Order flow — footprint | Trading from the chart |
| --- | --- |
|
|
|
| Order flow — volume profile (visible range) | Order flow — per-bar volume profiles |
|
|
|
Quick start
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 UI layer
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.
What's here
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 themThe 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
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.
Usage
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.
Custom indicator painters
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.
Exact order-flow data
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.
Build & view
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 suiteTests
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.
Notes
- 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.
