@chart-engine/core
v0.1.25
Published
Financial charting engine (WASM). Host-driven data — no market APIs. Candles, indicators, drawings, pan/zoom.
Downloads
301
Maintainers
Readme
@chart-engine/core
Your servers, your data — our charts.
Embeddable financial charting engine: WebAssembly core, WebGL renderer (Canvas2D fallback), optional full terminal UI. The library does not contain market data and never calls market APIs. Connect your backend via ExchangeAdapter, embed via iframe/postMessage, or use the built-in mock adapter to verify the install.
npm install @chart-engine/coreLive demo · Embed protocol ce:* 1.5 · Integration guide · MIT
Contents
- Overview
- Installation
- Quick start
- Widget constructor
- Featuresets
- Datafeed API (
ExchangeAdapter) - Resolutions, chart types, indicators
- Methods
- Host embed protocol (
ce:*1.5) - React + Vite
- Mobile (WebView)
- URL / query flags
- Troubleshooting
- Versioning
- License
- Reference UI
0. Reference UI
Target look for parity work (NSE intraday, light theme). These ship in assets/reference/:
Clean 3m candles + volume — HDFCBANK 3m NSE, OHLC legend, BUY/SELL tickets, price line @ 2037.88, volume pane:

Indicator stack + RSI pane — RELIANCE 3m NSE with RSI, EMA, Bollinger Bands, Parabolic SAR, Donchian overlays, volume pane, RSI-14 pane:

1. Overview
Two integration paths, one engine:
| Path | API | Renders | Best for |
|------|-----|---------|----------|
| Full terminal UI | mountChartApp(container, options) | Iframe (dist/hosted or /ce-hosted) | Apps that want toolbar, drawings, tickets, widgets |
| Canvas only | new ChartEngine({ container, … }) | Direct canvas in your page | Apps that draw their own chrome |
Rules:
- The chart is a rendering surface. Symbols, history, ticks, depth, option chain, session levels, and OMS stay in the host app.
- Standalone runs on a local mock adapter. Embedded, the host answers
ce:request-candlesand streamsce:tick. - Container must have non-zero width and height before mount, otherwise the chart is empty.
2. Installation
npm install @chart-engine/coreRequires a bundler that supports import.meta.url (Vite / webpack 5 / modern esbuild). ESM only:
import { mountChartApp, ChartEngine, registerAdapter } from "@chart-engine/core";
import { chartEngineHosted } from "@chart-engine/core/vite";Package contents: dist/ (ESM + types), assets/, vite-plugin.mjs, and dist/hosted/ (built terminal UI, copied from apps/web at build time).
3. Quick start
A. Full UI (recommended)
import { mountChartApp } from "@chart-engine/core";
const widget = mountChartApp(document.getElementById("chart"), {
symbol: "RELIANCE",
interval: "5m",
theme: "dark",
autosize: true,
disabled_features: ["widget_bar", "option_chain"],
});
// later
widget.destroy();No OHLCV to pass for the mock check. Provide a sized container and the chart synthesizes a series for symbol.
B. Canvas only (your chrome)
import { ChartEngine } from "@chart-engine/core";
const engine = new ChartEngine({
container: document.getElementById("chart"),
initialAdapter: "mock",
initialSymbol: "RELIANCE",
initialTimeframe: "5m",
onReady: (e) => e.fitAll(),
});Full terminal UI = mountChartApp. No header/toolbar with bare ChartEngine — that is expected.
C. Your own datafeed
import { registerAdapter, mountChartApp } from "@chart-engine/core";
registerAdapter({
key: "venue",
label: "Venue",
capabilities: { historical: true, live: true, intradayTimeframes: ["1m", "5m", "15m"], orders: "paper" },
async searchSymbols(query) { return []; },
async fetchHistory({ symbol, timeframe, limit }) { return []; },
subscribe(symbol, timeframe, onCandle) {
return { unsubscribe() {} };
},
});
mountChartApp(el, { initialAdapter: "venue", symbol: "RELIANCE", interval: "5m" });4. Widget constructor (mountChartApp)
mountChartApp(container, options) — mirrors the TradingView widget-constructor pattern: one container, one options object, feature flags to show/hide UI.
Returns { engine: null, destroy: () => void }. The UI runs in an iframe, so there is no direct engine handle — drive it via ce:* messages, query flags, or remount. Always call destroy() on unmount.
4.1 Options reference
| Name | Type | Default | Description |
|------|------|---------|-------------|
| container | HTMLElement \| string | — (required, first arg) | Mount node. Must have non-zero width × height. |
| symbol | string | "RELIANCE" | Default symbol. Alias: initialSymbol. |
| interval | string | "1m" | Resolution (see §7). Alias: initialTimeframe. Also sent as ?interval= + ?tf=. |
| theme | "dark" \| "light" | "dark" | Chart theme. |
| locale | string | — | Locale code, passed through to the shell (?locale=). |
| autosize | boolean | true | Fill the container (width/height: 100%). |
| fullscreen | boolean | false | Fullscreen layout (?fullscreen=1). |
| initialAdapter | string | "mock" | Datafeed id (canvas path; iframe uses host protocol). |
| features | Partial<Record<Featureset, boolean>> | all on | Map form: { order_panel: false } disables. |
| disabled_features | string[] | [] | Featuresets to turn off (canonical or alias). |
| enabled_features | string[] | [] | Featuresets to force on (removes from disabled set). |
| hostedUrl | string | /ce-hosted/index.html | Override UI URL (advanced; options are appended as query flags). |
| timezone | string | — | Accepted for TV parity; passed through where the shell supports it. |
Aliases: initialSymbol → symbol, initialTimeframe → interval.
new ChartEngine() takes instead: container, adapters, initialSymbol, initialTimeframe, initialAdapter, theme, accent, onReady.
5. Featuresets (disabled_features / enabled_features)
Same idea as TradingView disabled_features / enabled_features. Every block is on unless listed in disabled_features. enabled_features re-enables (wins over disabled).
Header
| Featureset | UI |
|------------|-----|
| header_widget | Entire top toolbar (alias: toolbar) |
| header_symbol_search | Symbol search |
| header_compare | Compare / overlay |
| header_resolutions | Interval control |
| header_chart_type | Chart type |
| header_indicators | Indicators (alias: indicators) |
| header_undo_redo | Undo / redo |
| header_screenshot | Screenshot |
| header_fullscreen_button | Fullscreen |
| header_settings | Settings |
Chart
| Featureset | UI |
|------------|-----|
| left_toolbar | Drawing tools (alias: drawings) |
| legend_widget | OHLC / study legend |
| control_bar | Scale / VP / L2 strip |
| timeframes_toolbar | Favorite intervals |
| replay | Bar replay |
| use_webgl | GPU renderer (alias: webgl; disabling appends ?webgl=0 → Canvas2D) |
Trading
| Featureset | UI |
|------------|-----|
| order_panel | Order ticket (alias: trade) |
| buy_sell_buttons | Buy / sell controls |
Side panels
| Featureset | UI |
|------------|-----|
| widget_bar | Right widget dock (alias: widgets) |
| watchlist | Watchlist |
| data_window | Data window |
| volume_profile | Volume profile (alias: volumeProfile) |
| footprint | Footprint |
| depth_of_market | L2 depth (alias: depth) |
| option_chain | Options strip (alias: options) |
| session_levels | PDH / PDL / VWAP (alias: levels) |
Alias table: toolbar → header_widget, drawings → left_toolbar, trade → order_panel, indicators → header_indicators, widgets → widget_bar, volumeProfile → volume_profile, depth → depth_of_market, options → option_chain, levels → session_levels, webgl → use_webgl.
Example — research chart, no trading:
mountChartApp(el, {
disabled_features: ["order_panel", "buy_sell_buttons", "widget_bar", "option_chain"],
});Example — map form (equivalent):
mountChartApp(el, {
features: { order_panel: false, buy_sell_buttons: false },
});6. Datafeed API (ExchangeAdapter)
Neither the widget nor the canvas build ships quotes. The library calls you; you return data asynchronously. This is the single contract every exchange implements — the engine never talks to a broker directly.
{ time, open, high, low, close, volume } // time = Unix ms| Method | Required | Signature | Description |
|--------|----------|-----------|-------------|
| searchSymbols | yes | (query: string, limit?: number) => Promise<SymbolInfo[]> | Symbol search. |
| fetchHistory | yes | (req: { symbol, timeframe, start?, end?, limit? }) => Promise<Candle[]> | Historical bars. end pages older history (infinite scroll left). |
| subscribe | yes | (symbol, timeframe, onCandle: (c: Candle) => void) => { unsubscribe: () => void } | Live stream. Call onCandle for every in-progress and closed candle. |
| submitOrder | no | (order: OrderRequest) => Promise<OrderResult> | Live/paper orders (only for order-capable adapters). |
| cancelOrder | no | (orderId: string) => Promise<void> | Cancel a pending order. |
| getPositions | no | (symbol?: string) => Promise<Position[]> | Open positions. |
| onPositionUpdate | no | (cb: (ps: Position[]) => void) => () => void | Position stream. |
Adapter metadata:
{
key: "venue", label: "Venue",
capabilities: { historical: true, live: true, intradayTimeframes: ["1m", "5m"], orders: "paper" },
session?: { sessions?: Array<[number, number]>; timezone?: string; aroundTheClock?: boolean },
priceFormat?: { decimals: number; grouping: "thousand" | "lakh" | "none"; prefix?: string; suffix?: string },
}Helpers: registerAdapter, unregisterAdapter, getAdapter, listAdapters, mockAdapter. setIndianExchange / getIndianExchange configure lakh grouping. The mock adapter is pre-registered — keep it for install checks, switch via initialAdapter / setExchange(key).
7. Resolutions, chart types, indicators
7.1 Resolutions
1t 1s 5s 15s 30s 1m 3m 5m 15m 30m 1h 2h 4h 1d 1w 1M — 1t is the tick timeframe (host streams ce:tick); the rest are time-based.
7.2 Chart types (setChartType / ce:set-chart-type)
candles bars line area heikin-ashi (heikinashi) hollow-candles (hollow) baseline volume-candles hlc-area (hlc) renko kagi point-and-figure (pnf) line-break (linebreak) range-bars (range).
7.3 Indicators (addIndicator / ce:indicator)
Keys (aliases with -/_ accepted): sma (20), ema (20), ema-ribbon, ma-ribbon, rsi (14), macd (12, 26, 9), bollinger / bollinger_bands (20, 2), keltner (14, 2), donchian (20), supertrend (10, 3), parabolic-sar (0.02, 0.2), vwap, atr (14), stochastic (14, 3, 3), stoch-rsi, adx (14), momentum (10), roc (12), cci (20), williams-r (14), mfi (14), aroon (14), vortex (14), trix (15), ultimate, std-dev (14), volume-profile (24 bins), obv, ichimoku (9, 26, 52, 26), pivot-points, zigzag (5%), vwma (20), hma (16), alma (9, 0.85, 6), alligator (13, 8, 5), fractals (2), dpo (20), force-index (13), tsi (25, 13, 7), chaikin (3, 10).
engine.addIndicator("sma", { period: 20 });
engine.addIndicator("supertrend", { period: 10, multiplier: 3 });Over ce:*: { action: "add"|"remove"|"clear"|"update", key?, index?, params? }.
8. Methods
mountChartApp exposes only destroy() (iframe boundary). All chart control below is the canvas ChartEngine handle (or the equivalent ce:* message in §9).
| Group | Method | Description |
|-------|--------|-------------|
| Symbol / feed | setSymbol(symbol) | Load a symbol |
| | setTimeframe(tf) | Change interval |
| | setExchange(key) | Switch adapter |
| | onCandle(cb) | Live-candle hook → () => void |
| Chart | setChartType(type) | Series style (§7.2) |
| | setTheme("dark"\|"light"), setAccent(color) | Theme |
| | fitAll() | Fit data |
| | undo() / redo() | History |
| Indicators | addIndicator(key, params?) / updateIndicator(i, key, params?) / removeIndicator(i) / listIndicators() | Studies (§7.3) |
| Drawings | setDrawingTool(tool) / cancelDrawingTool() | Arm a drawing |
| | listDrawings() / deleteDrawing(id) / deleteSelectedDrawing() / selectDrawing(id) | Manage |
| | setDrawingColor/Locked/Group/Visible, setMagnetMode, saveDrawings/loadDrawings, saveLayout/loadLayout | Style + persist |
| View | toggleGrid/Crosshair/LogScale/Volume/OrderBook/SessionLevels | Appearance |
| Replay | startReplay(n) / stopReplay() / replaySeek/StepForward/StepBackward / isReplayActive() | Bar replay |
| Backtest | runBacktest(json) / optimizeBacktest(json, fastLo, fastHi, slowLo, slowHi, step) | SMA-cross + optimizer |
| Trading | buy(qty?) / sell(qty?) / closePosition() / getPositions() | Paper unless adapter is order-capable |
| | placeBuyTrade/placeSellTrade/clearTradeMarkers/getTradeSummary/setOrderBook | Markers |
| Data | detectPatterns(window?) | Candlestick patterns |
| Lifecycle | screenshot() / getContainer/getCanvas / destroy() | Capture + dispose |
Keyboard (terminal UI): B buy · S sell · 1–9 qty · Esc close ticket.
9. Host embed protocol (ce:* 1.5, iframe / WebView)
For iframe / WebView the parent implements the datafeed over postMessage. Chart asks; host answers. Never put API tokens in the iframe.
Chart Your app
ce:ready ────────────────► { version: "1.5" }
◄──────────────── ce:init { symbol, timeframe }
ce:request-candles ──────►
◄──────────────── ce:candles { requestId, candles }
(live) ◄──────────────── ce:tick { price, time }Always reply to ce:request-candles with the same requestId. Shapes: candle { time, open, high, low, close, volume } (epoch ms); tick { price, time?, volume? }.
9.1 You → chart
| type | payload |
|------|---------|
| ce:init | { symbol?, timeframe?, candles?, theme?, chartType? } |
| ce:pong | {} |
| ce:candles | { requestId?, symbol, timeframe, candles } |
| ce:tick | { price, time?, volume? } |
| ce:candle | { candle } (pre-aggregated) |
| ce:set-symbol | { symbol, timeframe? } |
| ce:set-timeframe | { timeframe } |
| ce:set-theme | { theme: "dark" \| "light" } |
| ce:set-chart-type | { chartType } (§7.2) |
| ce:indicator | { action: "add"\|"update"\|"remove"\|"clear", key?, index?, params? } |
| ce:search-results | { requestId?, results } (reply to ce:request-search) |
| ce:drawings | { symbol, json } (restore) |
| ce:info | { requestId?, symbol, info? } |
| ce:error | { requestId?, code, message } (timeout \| no_data \| invalid \| host_lost \| unknown) |
| ce:fill / ce:order | { symbol?, side, price?, qty?, label? } markers |
| ce:positions | { positions: [{ symbol, side, qty, avgPrice, stopLoss?, takeProfit?, sl?, tp?, unrealizedPnl? }] } |
| ce:brackets | { symbol?, sl?, tp?, qty? } SL/TP lines |
| ce:depth | { levels: [{ price, bid, ask }] } |
| ce:footprint | { cells: [{ t, price, buy, sell }] } |
| ce:chain / ce:option-chain | { rows: [{ strike, ce, pe, oi? }] } |
| ce:levels | { symbol?, pdh?, pdl?, open?, vwap?, prevClose? } |
9.2 Chart → you
| type | you should |
|------|------------|
| ce:ready | send ce:init (version: "1.5") |
| ce:ping | send ce:pong |
| ce:open-search | your picker → ce:set-symbol |
| ce:request-search | ce:search-results (optional) |
| ce:request-candles | ce:candles (honor before) |
| ce:request-info | ce:info (optional) |
| ce:request-drawings | ce:drawings (optional) |
| ce:trade | place order in your OMS: { symbol, side, orderType, price, qty, sl?, tp? } |
| ce:brackets | SL/TP moved |
| ce:alert | { title, body, price?, dir? } — persist/notify |
| ce:request-depth | ce:depth |
| ce:request-footprint | ce:footprint |
| ce:request-chain | ce:chain |
| ce:request-levels | ce:levels |
| ce:select-strike | optional — user tapped options strip |
| ce:notify | optional persist ({ symbol, timeframe, chartType, theme, indicators }) |
| ce:drawings | persist; restore on request |
| ce:error | timeout / no_data / host_lost |
Minimal host:
const ORIGIN = "https://chart-engine-ruby.vercel.app";
const w = document.getElementById("ce").contentWindow;
const send = (type, payload) => w.postMessage({ type, payload }, ORIGIN);
window.addEventListener("message", async (e) => {
if (e.origin !== ORIGIN) return;
const { type, payload } = e.data || {};
if (type === "ce:ready") send("ce:init", { symbol: "RELIANCE", timeframe: "15m", theme: "dark" });
if (type === "ce:ping") send("ce:pong", {});
if (type === "ce:request-candles") {
const candles = await yourApi.getBars(payload.symbol, payload.timeframe, payload.bars);
send("ce:candles", { requestId: payload.requestId, symbol: payload.symbol, timeframe: payload.timeframe, candles });
}
if (type === "ce:open-search") {
const symbol = await openYourSymbolPicker();
if (symbol) send("ce:set-symbol", { symbol });
}
if (type === "ce:trade") await yourOms.place(payload);
});Full executable reference: USING.md.
10. React + Vite
Self-host the UI (avoids CDN iframe) with the bundled Vite plugin, which copies dist/hosted → public/ce-hosted:
// vite.config.ts
import { chartEngineHosted } from "@chart-engine/core/vite";
import react from "@vitejs/plugin-react";
export default { plugins: [react(), chartEngineHosted()] };useEffect(() => {
const w = mountChartApp(ref.current!, {
symbol: "RELIANCE",
interval: "5m",
disabled_features: ["widget_bar"],
});
return () => w.destroy();
}, []);404 on /ce-hosted/ means the plugin is missing. Canvas-only alternative: new ChartEngine({ container, initialAdapter: "mock" }).
11. Mobile (WebView)
This package is JavaScript; it does not compile to Dart/Swift. Load the chart in a WebView and bridge postMessage:
WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse(
'https://chart-engine-ruby.vercel.app/?symbol=RELIANCE&interval=5m',
));- React Native:
react-native-webviewonMessage+injectedJavaScript - iOS:
WKWebView+WKScriptMessageHandler - Android:
WebView+@JavascriptInterface
Do not set chromeless=1 if you need the header/toolbar. Prefer ?disabled_features=order_panel,widget_bar for chrome control.
12. URL / query flags
mountChartApp flags (appended automatically): symbol, interval, tf (= interval), theme, locale, fullscreen=1, disabled_features=a,b, webgl=0 (when use_webgl disabled).
Hosted-shell flags: ?chromeless=1 (chart pane only) · ?pro=1 (reduced chrome) · ?hide_drawings=1 · ?webgl=0 (Canvas2D) · ?origin=https://your.app · ?symbol= ?interval=/?tf=.
13. Troubleshooting
| Issue | Reason / fix |
|-------|--------------|
| Empty chart | Container height is 0 — give it real width × height; keep autosize: true |
| 404 /ce-hosted/ | Add chartEngineHosted() to Vite config |
| No header with ChartEngine | Expected — full UI is mountChartApp |
| No tools in WebView | chromeless=1 is on — remove it or use disabled_features |
| No bars / overlay stays empty | Host did not reply to ce:request-candles (match requestId); orderflow overlays need ce:depth/ce:footprint/ce:chain/ce:levels |
| timeout / no_data / host_lost | See ce:error codes; check adapter fetchHistory and ping/pong |
14. Versioning
Semver. Protocol is versioned separately (PROTOCOL_VERSION, exported from the package; current 1.5). Check ce:ready.version / ce:ping in hosts and reject mismatches you cannot handle.
15. License
MIT
