@tradecanvas/chart
High-performance canvas trading chart with built-in indicators, drawing tools, and real-time streaming. Zero external dependencies.
Live Demo | GitHub | npm
Why TradeCanvas?
Most chart libraries make you choose: pretty charts with no trading features, or trading features with an ugly API. TradeCanvas gives you both.
- 33 built-in indicators — SMA, EMA, Hull MA, RSI, MACD, Bollinger, Ichimoku, Pivot Points, Anchored VWAP, ZigZag, Linear Regression Channel, Awesome / Chaikin Oscillator, and more. No separate calculation library needed.
- 24 drawing tools — Trendlines, Fibonacci (retracement, extension, time zones), horizontal/vertical lines, channels, Elliott waves, Gann fans / boxes, Pitchfork, Volume Profile range. With undo/redo and full serialization.
- 17 chart types — Candlestick, line, area, bar, hollow candle, baseline, Heikin-Ashi, Renko, Kagi, Line Break, Point & Figure, Range Bars, Volume Candles, Equivolume, HLC Area, Step Line, Line+Markers.
- Trading overlay — Render open positions with entry line, P&L zone, and SL/TP markers. Orders as dashed lines. Drag SL/TP to modify. Cleanly opt-out via
features.trading: false for non-trading projects.
- Real-time streaming — Built-in Binance adapter. Plug in your own data source with the adapter interface.
- Strategy backtester (new in 0.8) —
@tradecanvas/analytics ships a bar-by-bar Backtester with virtual fills, commission/slippage models, portfolio tracking, and risk metrics (Sharpe, Sortino, Calmar, max drawdown).
- Replay mode (new in 0.8) —
ReplayController drives historical bars forward at controlled speed with start / pause / step / seek / setSpeed; decoupled from Chart so it can power both UI playback and headless backtests.
- Multi-chart grid —
ChartGrid for synchronized 2×2 / 2×3 layouts with linked crosshairs and shared time axis.
- Signal markers & trade zones — render bot/algorithm output (directional arrows, entry→exit rectangles) as a first-class chart layer.
- Save/load chart state — Persist drawings, indicators, theme, and chart type to JSON. Restore with one call.
- Zero dependencies — The entire library is self-contained. No
d3, no chart.js, no fancy-canvas.
Install
npm install @tradecanvas/chart
pnpm add @tradecanvas/chart
yarn add @tradecanvas/chart
Quick Start
The fastest path is ChartWidget — drop-in component with a full TradingView-like UI (toolbar, drawing sidebar, settings dialog, status bar). Zero framework dependency.
import { ChartWidget } from '@tradecanvas/chart/widget'
import { BinanceAdapter } from '@tradecanvas/chart'
const widget = new ChartWidget(document.getElementById('chart')!, {
symbol: 'BTCUSDT',
timeframe: '5m',
theme: 'dark',
adapter: new BinanceAdapter(),
trading: true,
})
That's it. Live data, all 33 indicators, all 24 drawing tools, command palette (Ctrl+K).
Headless Chart
For projects that want to own the surrounding UI (custom toolbar, framework-specific controls), use the lower-level Chart class directly:
import { Chart, BinanceAdapter } from '@tradecanvas/chart'
const chart = new Chart(document.getElementById('chart')!, {
theme: 'dark',
autoScale: true,
features: {
drawings: true,
indicators: true,
trading: true,
tradingContextMenu: true,
volume: true,
},
})
const adapter = new BinanceAdapter()
chart.connect({ adapter, symbol: 'BTCUSDT', timeframe: '5m', historyLimit: 300 })
Widget Options
symbol | string | 'BTCUSDT' | Initial trading symbol |
timeframe | TimeFrame | '5m' | Initial timeframe |
theme | 'dark' | 'light' | Theme | 'dark' | Chart theme |
adapter | DataAdapter | — | Data source adapter |
toolbar | boolean | true | Show top toolbar |
drawingTools | boolean | true | Show left drawing sidebar |
settings | boolean | true | Show settings button |
trading | boolean | true | Enable trading overlay |
statusBar | boolean | true | Show bottom status bar |
symbols | string[] | BTC/ETH/SOL/BNB | Available symbols |
timeframes | TimeFrame[] | 1m to 1d | Available timeframes |
chartTypes | ChartType[] | 11 types | Available chart types |
onSymbolChange | (symbol) => void | — | Symbol change callback |
onTimeframeChange | (tf) => void | — | Timeframe change callback |
onReady | (chart) => void | — | Fired when chart is ready |
Widget vs Headless
| Import | @tradecanvas/chart | @tradecanvas/chart/widget |
| UI included | None — build your own | Complete toolbar, sidebar, settings |
| Bundle impact | ~50 KB gzip | ~65 KB gzip (includes UI) |
| Framework | Any (React, Vue, Svelte, vanilla) | Vanilla JS DOM (works everywhere) |
| Customization | Full control | Toggle sections on/off |
| Advanced access | Direct API | widget.getChart() for direct API |
Features
Chart Types
| Candlestick | Standard OHLC candles |
| Hollow Candle | Open/close determines fill |
| Bar (OHLC) | Classic open-high-low-close bars |
| Line | Close price line |
| Area | Filled area below close |
| Baseline | Two-tone area split at a reference price |
| Heikin-Ashi | Smoothed candles for trend identification |
| Renko | Fixed-size bricks that ignore time |
| Kagi | Reversal-based line chart |
| Point & Figure | X/O columns for supply/demand analysis |
| Line Break | Three-line break charts |
| Range Bars | Fixed price-range bars — each bar's high − low equals a configured range |
| Volume Candles | Candlesticks with width proportional to volume |
| Equivolume | Full-range boxes with width proportional to volume share (Richard Arms style) |
| HLC Area | High-low-close area band with close line |
| Step Line | Staircase/step pattern from close prices |
| Line with Markers | Close line with circular markers at each data point |
Multi-Chart Grid
Display multiple synchronized charts side-by-side with linked crosshairs and time axis:
import { ChartGrid, BinanceAdapter } from '@tradecanvas/chart'
const grid = new ChartGrid(document.getElementById('grid')!, {
layout: '2x2',
syncCrosshair: true,
syncTimeAxis: true,
})
const adapter = new BinanceAdapter()
grid.connectAll(adapter, ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT'], '5m')
Supported layouts: '1x1', '1x2', '2x1', '2x2', '1x3', '3x1', '2x3', '3x2'.
Command Palette
Press Ctrl+K (or Cmd+K) inside ChartWidget to open a searchable command palette. Quickly find and toggle indicators, change chart types, activate drawing tools, switch timeframes, or trigger actions (screenshot, theme toggle, settings).
Finance Charts
| SparklineChart | Tiny inline line/area chart from a number array — for dashboards and KPI cards |
| DepthChart | Bid/ask order book visualization with cumulative volume areas |
| EquityCurveChart | Portfolio equity line with drawdown shading and benchmark comparison |
| HeatmapChart | Colored cell grid with treemap layout — for sector/market performance |
| WaterfallChart | Running cumulative bars — P&L attribution, revenue bridge, cash flow |
| GaugeChart | Speedometer-style gauge — KPIs, risk scores, Fear & Greed index |
import {
SparklineChart, DepthChart, EquityCurveChart, HeatmapChart,
WaterfallChart, GaugeChart,
} from '@tradecanvas/chart'
new SparklineChart(el, { data: [100, 102, 98, 105, 103], mode: 'area', color: '#26A69A' })
new EquityCurveChart(el, { data: equityPoints, drawdown: true, benchmark: spyData })
new DepthChart(el, { data: { bids, asks }, crosshair: true })
new HeatmapChart(el, { data: cells, weighted: true })
new WaterfallChart(el, {
data: [
{ label: 'Start', value: 10000, type: 'total' },
{ label: 'Gain', value: 1850 },
{ label: 'Loss', value: -620 },
{ label: 'End', value: 11230, type: 'total' },
],
})
const gauge = new GaugeChart(el, {
value: 72,
zones: [
{ from: 0, to: 25, color: '#ef4444' },
{ from: 75, to: 100, color: '#10b981' },
],
})
gauge.setValue(85)
Indicators (built-in)
Overlay (drawn on the price chart):
SMA, EMA, Hull MA, Bollinger Bands, Keltner Channel, Donchian Channel, Ichimoku Cloud, Parabolic SAR, Supertrend, VWAP, Anchored VWAP, Pivot Points (Classic), ZigZag, Linear Regression Channel
Panel (separate sub-chart):
RSI, MACD, Stochastic, ATR, ADX, CCI, CMF, MFI, OBV, ROC, TSI, Williams %R, Awesome Oscillator, Chaikin Oscillator, Volume Profile, VROC, Standard Deviation, Accumulation/Distribution, Aroon
All indicator parameters are validated at runtime — invalid values (NaN, Infinity, non-numeric strings, missing keys) fall back to documented defaults instead of silently propagating to calculations.
Drawing Tools
Trendline, Horizontal Line, Vertical Line, Ray, Extended Line, Parallel Channel, Fibonacci Retracement, Fibonacci Extension, Fibonacci Time Zones, Rectangle, Ellipse, Triangle, Arrow, Pitchfork, Gann Fan, Gann Box, Elliott Wave, Regression Channel, Date Range, Price Range, Measure, Anchored VWAP, Volume Profile Range, Text Annotation
All drawing tools support:
- Click-to-place with magnet snapping to OHLC values
- Undo / redo (Ctrl+Z / Ctrl+Y)
- Serialization for save/load
- Custom styles (color, width, dash pattern)
Trading Overlay
Render open positions and pending orders directly on the chart, like MT4/MT5.
import type { TradingPosition, TradingOrder } from '@tradecanvas/chart'
chart.setPositions([{
id: 'pos-1',
side: 'buy',
entryPrice: 3500,
quantity: 1.5,
closedQuantity: 0.5,
stopLoss: 3400,
takeProfit: 3700,
}])
chart.setOrders([{
id: 'order-1',
side: 'sell',
type: 'limit',
price: 3800,
quantity: 0.5,
label: 'TP',
draggable: true,
}])
chart.setTradingConfig({
pnlThresholds: [
{ pnl: -Infinity, color: '#b91c1c' },
{ pnl: 0, color: '#94a3b8' },
{ pnl: 50, color: '#16a34a' },
{ pnl: 200, color: '#15803d' },
],
positionLabel: '{side} {openQty}/{qty} @ {entry} | {pnlSign}{pnl} ({pnlPct})',
})
chart.on('positionModify', (e) => console.log('SL/TP moved:', e.payload))
chart.on('orderModify', (e) => console.log('Order moved:', e.payload))
Signal Markers
Visualize buy/sell signals from bots, indicators, or manual analysis.
chart.addSignalMarker({
time: 1715692800000,
price: 62500,
direction: 'long',
confidence: 0.85,
source: 'ema-crossover',
label: 'EMA Cross',
})
chart.setSignalMarkerStyle({
sourceColors: {
'ema-crossover': '#2196F3',
'rsi-divergence': '#FF9800',
'whale-flow': '#9C27B0',
},
})
Trade Zones
Render entry→exit rectangles with P&L coloring for executed trades.
const zoneId = chart.addTradeZone({
entryTime: 1715692800000,
entryPrice: 62500,
exitTime: 1715700000000,
exitPrice: 63200,
direction: 'long',
pnl: 140,
pnlPercent: 1.12,
})
chart.updateTradeZone(zoneId, {
exitTime: Date.now(),
exitPrice: 63500,
pnl: 200,
})
Real-Time Streaming
chart.connect({
adapter: new BinanceAdapter(),
symbol: 'ETHUSDT',
timeframe: '1m',
historyLimit: 500,
})
chart.setData(historicalBars)
chart.appendBar(newBar)
chart.updateLastBar(updatedBar)
chart.setCurrentPrice(3500.42)
Web Worker indicator pipeline
Heavy charts (1,000+ bars × 10+ indicators) can stutter when calculate() runs on the main thread. IndicatorWorkerHost offloads calculation to a worker so the render loop stays smooth.
import { IndicatorWorkerHost } from '@tradecanvas/core'
const worker = new Worker(
new URL('@tradecanvas/core/dist/indicator.worker.js', import.meta.url),
{ type: 'module' },
)
const host = new IndicatorWorkerHost(worker, { timeoutMs: 30_000 })
const output = await host.calculate(
'rsi',
{ id: 'rsi', instanceId: 'rsi-1', params: { period: 14 } },
bars,
)
await host.ping()
host.terminate()
No worker available (SSR, tests, or as a safety net)? Pass null and register fallback plugins for synchronous calculation:
import { IndicatorWorkerHost, RSIIndicator } from '@tradecanvas/core'
const host = new IndicatorWorkerHost(null)
host.registerFallbackPlugin(new RSIIndicator())
const output = await host.calculate('rsi', config, bars)
Render still happens on the main thread (it needs CanvasRenderingContext2D). Only the heavy compute moves off-thread.
Save / Load
const json = chart.saveState()
localStorage.setItem('my-chart', json!)
chart.loadState(localStorage.getItem('my-chart')!)
chart.downloadState('my-chart.json')
await chart.loadStateFromFile()
Themes
import { DARK_THEME, LIGHT_THEME, DARK_TERMINAL } from '@tradecanvas/chart'
chart.setTheme(DARK_TERMINAL)
chart.setTheme({
...DARK_THEME,
candleUp: '#26A69A',
candleDown: '#EF5350',
background: '#0a0a0f',
})
Events
chart.on('crosshairMove', (e) => { })
chart.on('barClick', (e) => { })
chart.on('visibleRangeChange', (e) => { })
chart.on('drawingCreate', (e) => { })
chart.on('orderModify', (e) => { })
chart.on('positionModify', (e) => { })
Replay Mode
ReplayController plays a historical DataSeries forward at controlled speed. Decoupled from Chart — wire it into any sink (chart for UI playback, or a strategy fn for headless backtests).
import { ReplayController } from '@tradecanvas/chart'
const replay = new ReplayController({
data: historicalBars,
speed: 10,
startIndex: 0,
})
chart.setData(replay.getPrefix())
replay.on('bar', ({ bar }) => chart.appendBar(bar))
replay.on('finished', () => console.log('done'))
replay.start()
Backtesting (@tradecanvas/analytics)
Bar-by-bar strategy backtester with virtual fills, commission/slippage models, and a full risk-metrics report.
import { Backtester, PercentCommission, PercentSlippage } from '@tradecanvas/analytics'
const bt = new Backtester({
initialCash: 10_000,
commission: new PercentCommission(0.0005),
slippage: new PercentSlippage(0.0003),
})
const result = bt.run(historicalBars, (ctx) => {
if (!ctx.position && smaFast > smaSlow) {
ctx.placeOrder({ side: 'long', type: 'market', quantity: 1 })
} else if (ctx.position && smaFast < smaSlow) {
ctx.close()
}
})
console.log(result.metrics.sharpe)
console.log(result.metrics.maxDrawdownPct)
console.log(result.equityCurve)
Returns: fills, closed trades, equityCurve, metrics (Sharpe, Sortino, Calmar, CAGR, max drawdown, win rate, profit factor, expectancy). See the live backtest demo.
Comparison
| Chart types | 17 + 6 finance | 4 | 8 (non-financial) | 10+ |
| Finance charts | Sparkline, Depth, Equity, Heatmap, Waterfall, Gauge | None | None | Some |
| Built-in indicators | 33 | 0 | 0 | ~30 |
| Drawing tools | 24 | 0 | 0 | Some |
| Trading overlay | Full (pos + orders + drag) | None | None | None |
| Real-time streaming | Built-in (Binance) | Manual | Manual | Built-in |
| Save/load state | Yes | No | No | Yes |
| Replay mode | Yes (ReplayController) | No | No | No |
| Backtester | Yes (@tradecanvas/analytics) | No | No | No |
| Multi-chart grid | Yes (ChartGrid) | No | No | Yes |
| Bundle (gzip) | ~56 KB core | ~45 KB | ~70 KB | ~200 KB |
| Dependencies | 0 | 1 | 0 | 0 |
| Widget (complete UI) | Yes (ChartWidget) | No | No | No |
| License | MIT | Apache 2.0 | MIT | Commercial |
API Overview
new Chart(container, options)
const chart = new Chart(element, {
chartType: 'candlestick',
theme: DARK_THEME,
autoScale: true,
rightMargin: 5,
numberLocale: 'en-US',
crosshair: { mode: 'magnet' },
features: { drawings: true, indicators: true, trading: true, volume: true },
})
chart.setNumberLocale('de-DE')
Key Methods
setData(bars) | Load historical OHLCV data |
appendBar(bar) | Append a new candle |
appendBars(bars) | Bulk append (reconnect catch-up) |
updateLastBar(bar) | Update the in-progress candle |
setCurrentPrice(price, pulseColor?) | Show a live price line |
connect(config) | Connect to a real-time data source |
setTimeframe(tf) | Switch timeframe on active stream |
setChartType(type) | Switch chart type |
setTheme(theme) | Apply a theme (DARK_THEME, LIGHT_THEME, DARK_TERMINAL) |
setNumberLocale(locale) | Set number format locale (en-US, de-DE, vi-VN) |
setStatusText(text) | Show status in legend area ("LIVE · 8ms") |
addIndicator(id, params?) | Add a technical indicator |
removeIndicator(instanceId) | Remove an indicator |
setDrawingTool(tool) | Activate a drawing tool |
setPositions(positions) | Render trading positions |
setOrders(orders) | Render pending orders |
saveState(key?) | Serialize chart state |
loadState(json) | Restore chart state |
screenshot() | Download chart as image |
on(event, handler) | Subscribe to events |
destroy() | Clean up all resources |
Data Format
interface OHLCBar {
time: number
open: number
high: number
low: number
close: number
volume: number
}
Examples
Browser Support
Chrome 80+, Firefox 80+, Safari 14+, Edge 80+
Framework Integration
TradeCanvas is framework-agnostic. The Chart class takes a DOM element and manages its own canvas layers.
React:
import { useEffect, useRef } from 'react'
import { Chart, BinanceAdapter } from '@tradecanvas/chart'
function TradingChart() {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const chart = new Chart(ref.current!, {
theme: 'dark',
features: { indicators: true, drawings: true },
})
chart.connect({
adapter: new BinanceAdapter(),
symbol: 'BTCUSDT',
timeframe: '5m',
})
return () => chart.destroy()
}, [])
return <div ref={ref} style={{ width: '100%', height: 500 }} />
}
Svelte:
<script lang="ts">
import { onMount, onDestroy } from 'svelte'
import { Chart, BinanceAdapter, DARK_THEME } from '@tradecanvas/chart'
import type { TimeFrame } from '@tradecanvas/chart'
interface Props { symbol?: string; timeframe?: TimeFrame }
let { symbol = 'BTCUSDT', timeframe = '5m' }: Props = $props()
let container: HTMLDivElement
let chart: Chart | null = null
onMount(() => {
chart = new Chart(container, {
chartType: 'candlestick',
theme: DARK_THEME,
autoScale: true,
features: { indicators: true, drawings: true, volume: true },
})
chart.connect({ adapter: new BinanceAdapter(), symbol, timeframe })
})
onDestroy(() => chart?.destroy())
$effect(() => {
if (!chart) return
chart.disconnectStream()
chart.connect({ adapter: new BinanceAdapter(), symbol, timeframe })
})
</script>
<div bind:this={container} style="width: 100%; height: 600px" />
Vue:
<template>
<div ref="chartContainer" style="width: 100%; height: 600px" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Chart, BinanceAdapter, DARK_THEME } from '@tradecanvas/chart'
const chartContainer = ref<HTMLDivElement>()
let chart: Chart | null = null
onMounted(() => {
if (!chartContainer.value) return
chart = new Chart(chartContainer.value, {
chartType: 'candlestick',
theme: DARK_THEME,
autoScale: true,
features: { indicators: true, drawings: true, volume: true },
})
chart.connect({ adapter: new BinanceAdapter(), symbol: 'BTCUSDT', timeframe: '5m' })
})
onUnmounted(() => chart?.destroy())
</script>
Architecture
Multi-layer canvas for optimal rendering — only dirty layers repaint each frame:
UI Layer (price axis, legend, live price) z=3
Overlay Layer (drawings, trading positions/orders) z=2
Main Layer (candles, indicators, volume) z=1
Background (grid, watermark) z=0
License
MIT