Skip to content

Latest commit

 

History

106 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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