GitHub - StockSharp/JS-Charts: JavaScript candle charts · GitHub
Skip to content

Latest commit

 

History

105 Commits

Folders and files

Repository files navigation

StockSharp JS Trading Charts

Build and test npm version License

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.

StockSharp JS Trading Charts terminal — candlesticks with Bollinger Bands, an Ichimoku cloud, Fractals, a moving average and an RSI sub-pane

Live demo · StockSharp website · GitHub repository · Issue tracker

Screenshots

Order flow — footprint Trading from the chart
Exact footprint: bid/ask volume at every price with imbalance highlighting Broker-agnostic trading layer: order lines, a long position with live P&L, bracket and bid/ask/last quotes
Order flow — volume profile (visible range) Order flow — per-bar volume profiles
Exact volume profile with point-of-control and value area, recomputed over the visible range Per-bar exact volume profiles, each with its own point-of-control and value area

Quick start

npm install @stocksharp/chart
import { 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 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

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 suite

Tests

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 in src/core/ here; folding them back into the terminal's copy is a follow-up.

About

JavaScript candle charts

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages