@tradecanvas/chart 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,545 +1,570 @@
1
- # @tradecanvas/chart
2
-
3
- High-performance canvas trading chart with built-in indicators, drawing tools, and real-time streaming. Zero external dependencies.
4
-
5
- **[Live Demo](https://bonguynvan.github.io/tradecanvas/)** | **[GitHub](https://github.com/bonguynvan/tradecanvas)** | **[npm](https://www.npmjs.com/package/@tradecanvas/chart)**
6
-
7
- ## Why TradeCanvas?
8
-
9
- Most chart libraries make you choose: pretty charts with no trading features, or trading features with an ugly API. TradeCanvas gives you both.
10
-
11
- - **33+ built-in indicators** — MA, 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.
12
- - **10+ drawing tools** — Trendlines, Fibonacci retracement, horizontal/vertical lines, rectangles, channels, Elliott waves, Gann fans. With undo/redo.
13
- - **Trading overlay** — Render open positions with entry line, P&L zone, and SL/TP markers. Orders as dashed lines. Users can drag SL/TP to modify.
14
- - **Real-time streaming** — Built-in Binance adapter. Plug in your own data source with the adapter interface.
15
- - **Save/load chart state** — Persist drawings, indicators, theme, and chart type to JSON. Restore with one call.
16
- - **Replay mode** — Step through historical data bar-by-bar for backtesting visualization.
17
- - **Zero dependencies** — The entire library is self-contained. No `d3`, no `chart.js`, no `fancy-canvas`.
18
-
19
- ## Install
20
-
21
- ```bash
22
- npm install @tradecanvas/chart
23
- # or
24
- pnpm add @tradecanvas/chart
25
- # or
26
- yarn add @tradecanvas/chart
27
- ```
28
-
29
- ## Quick Start
30
-
31
- ```typescript
32
- import { Chart, BinanceAdapter } from '@tradecanvas/chart'
33
-
34
- // Create a chart
35
- const chart = new Chart(document.getElementById('chart')!, {
36
- theme: 'dark',
37
- autoScale: true,
38
- features: {
39
- drawings: true,
40
- indicators: true,
41
- trading: true,
42
- volume: true,
43
- },
44
- })
45
-
46
- // Connect to live Binance data
47
- const adapter = new BinanceAdapter()
48
- chart.connect({
49
- adapter,
50
- symbol: 'BTCUSDT',
51
- timeframe: '5m',
52
- historyLimit: 300,
53
- })
54
- ```
55
-
56
- That's it. A full-featured trading chart with live data in 15 lines.
57
-
58
- ## Widget (Complete UI)
59
-
60
- For a complete TradingView-like experience with built-in toolbar, drawing tools, and settings — no UI code needed:
61
-
62
- ```typescript
63
- import { ChartWidget } from '@tradecanvas/chart/widget'
64
- import { BinanceAdapter } from '@tradecanvas/chart'
65
-
66
- const widget = new ChartWidget(document.getElementById('chart')!, {
67
- symbol: 'BTCUSDT',
68
- timeframe: '5m',
69
- adapter: new BinanceAdapter(),
70
- theme: 'dark',
71
- })
72
- ```
73
-
74
- That's it. Full toolbar, drawing sidebar, settings modal, and status bar — all included.
75
-
76
- ### Widget Options
77
-
78
- | Option | Type | Default | Description |
79
- |---|---|---|---|
80
- | `symbol` | `string` | `'BTCUSDT'` | Initial trading symbol |
81
- | `timeframe` | `TimeFrame` | `'5m'` | Initial timeframe |
82
- | `theme` | `'dark' \| 'light' \| Theme` | `'dark'` | Chart theme |
83
- | `adapter` | `DataAdapter` | — | Data source adapter |
84
- | `toolbar` | `boolean` | `true` | Show top toolbar |
85
- | `drawingTools` | `boolean` | `true` | Show left drawing sidebar |
86
- | `settings` | `boolean` | `true` | Show settings button |
87
- | `trading` | `boolean` | `true` | Enable trading overlay |
88
- | `statusBar` | `boolean` | `true` | Show bottom status bar |
89
- | `symbols` | `string[]` | BTC/ETH/SOL/BNB | Available symbols |
90
- | `timeframes` | `TimeFrame[]` | 1m to 1d | Available timeframes |
91
- | `chartTypes` | `ChartType[]` | 7 types | Available chart types |
92
- | `onSymbolChange` | `(symbol) => void` | — | Symbol change callback |
93
- | `onTimeframeChange` | `(tf) => void` | — | Timeframe change callback |
94
- | `onReady` | `(chart) => void` | — | Fired when chart is ready |
95
-
96
- ### Widget vs Headless
97
-
98
- | | `Chart` (headless) | `ChartWidget` |
99
- |---|---|---|
100
- | Import | `@tradecanvas/chart` | `@tradecanvas/chart/widget` |
101
- | UI included | None — build your own | Complete toolbar, sidebar, settings |
102
- | Bundle impact | ~50 KB gzip | ~65 KB gzip (includes UI) |
103
- | Framework | Any (React, Vue, Svelte, vanilla) | Vanilla JS DOM (works everywhere) |
104
- | Customization | Full control | Toggle sections on/off |
105
- | Advanced access | Direct API | `widget.getChart()` for direct API |
106
-
107
- ## Features
108
-
109
- ### Chart Types
110
-
111
- | Type | Description |
112
- |---|---|
113
- | Candlestick | Standard OHLC candles |
114
- | Hollow Candle | Open/close determines fill |
115
- | Bar (OHLC) | Classic open-high-low-close bars |
116
- | Line | Close price line |
117
- | Area | Filled area below close |
118
- | Baseline | Two-tone area split at a reference price |
119
- | Heikin-Ashi | Smoothed candles for trend identification |
120
- | Renko | Fixed-size bricks that ignore time |
121
- | Kagi | Reversal-based line chart |
122
- | Point & Figure | X/O columns for supply/demand analysis |
123
- | Line Break | Three-line break charts |
124
- | Range Bars | Fixed price-range bars — each bar's high − low equals a configured range |
125
-
126
- ### Finance Charts
127
-
128
- | Chart | Description |
129
- |---|---|
130
- | SparklineChart | Tiny inline line/area chart from a number array — for dashboards and KPI cards |
131
- | DepthChart | Bid/ask order book visualization with cumulative volume areas |
132
- | EquityCurveChart | Portfolio equity line with drawdown shading and benchmark comparison |
133
- | HeatmapChart | Colored cell grid with treemap layout — for sector/market performance |
134
- | WaterfallChart | Running cumulative bars — P&L attribution, revenue bridge, cash flow |
135
- | GaugeChart | Speedometer-style gauge — KPIs, risk scores, Fear & Greed index |
136
-
137
- ```typescript
138
- import {
139
- SparklineChart, DepthChart, EquityCurveChart, HeatmapChart,
140
- WaterfallChart, GaugeChart,
141
- } from '@tradecanvas/chart'
142
-
143
- // Sparkline in a 120x48 container
144
- new SparklineChart(el, { data: [100, 102, 98, 105, 103], mode: 'area', color: '#26A69A' })
145
-
146
- // Equity curve with drawdown
147
- new EquityCurveChart(el, { data: equityPoints, drawdown: true, benchmark: spyData })
148
-
149
- // Order book depth
150
- new DepthChart(el, { data: { bids, asks }, crosshair: true })
151
-
152
- // Market heatmap (treemap weighted by market cap)
153
- new HeatmapChart(el, { data: cells, weighted: true })
154
-
155
- // P&L waterfall
156
- new WaterfallChart(el, {
157
- data: [
158
- { label: 'Start', value: 10000, type: 'total' },
159
- { label: 'Gain', value: 1850 },
160
- { label: 'Loss', value: -620 },
161
- { label: 'End', value: 11230, type: 'total' },
162
- ],
163
- })
164
-
165
- // Fear & Greed gauge
166
- const gauge = new GaugeChart(el, {
167
- value: 72,
168
- zones: [
169
- { from: 0, to: 25, color: '#ef4444' },
170
- { from: 75, to: 100, color: '#10b981' },
171
- ],
172
- })
173
- gauge.setValue(85) // animates smoothly
174
- ```
175
-
176
- ### Indicators (built-in)
177
-
178
- **Overlay** (drawn on the price chart):
179
- 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
180
-
181
- **Panel** (separate sub-chart):
182
- 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
183
-
184
- 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.
185
-
186
- ### Drawing Tools
187
-
188
- Trendline, Horizontal Line, Vertical Line, Ray, Extended Line, Parallel Channel, Fibonacci Retracement, Fibonacci Extension, 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
189
-
190
- All drawing tools support:
191
- - Click-to-place with magnet snapping to OHLC values
192
- - Undo / redo (Ctrl+Z / Ctrl+Y)
193
- - Serialization for save/load
194
- - Custom styles (color, width, dash pattern)
195
-
196
- ### Trading Overlay
197
-
198
- Render open positions and pending orders directly on the chart, like MT4/MT5.
199
-
200
- ```typescript
201
- import type { TradingPosition, TradingOrder } from '@tradecanvas/chart'
202
-
203
- chart.setPositions([{
204
- id: 'pos-1',
205
- side: 'buy',
206
- entryPrice: 3500,
207
- quantity: 1.5,
208
- closedQuantity: 0.5, // partial close — visualized as a left-edge dim band
209
- stopLoss: 3400,
210
- takeProfit: 3700,
211
- }])
212
-
213
- chart.setOrders([{
214
- id: 'order-1',
215
- side: 'sell',
216
- type: 'limit',
217
- price: 3800,
218
- quantity: 0.5,
219
- label: 'TP',
220
- draggable: true,
221
- }])
222
-
223
- // Customize the position zone color via P&L thresholds
224
- chart.setTradingConfig({
225
- pnlThresholds: [
226
- { pnl: -Infinity, color: '#b91c1c' },
227
- { pnl: 0, color: '#94a3b8' },
228
- { pnl: 50, color: '#16a34a' },
229
- { pnl: 200, color: '#15803d' },
230
- ],
231
- // Custom label template — tokens: {side} {qty} {openQty} {closedQty} {entry} {price} {pnl} {pnlPct} {pnlSign}
232
- positionLabel: '{side} {openQty}/{qty} @ {entry} | {pnlSign}{pnl} ({pnlPct})',
233
- })
234
-
235
- // Listen for user drag-to-modify
236
- chart.on('positionModify', (e) => console.log('SL/TP moved:', e.payload))
237
- chart.on('orderModify', (e) => console.log('Order moved:', e.payload))
238
- ```
239
-
240
- ### Real-Time Streaming
241
-
242
- ```typescript
243
- // Built-in Binance adapter (free, no API key)
244
- chart.connect({
245
- adapter: new BinanceAdapter(),
246
- symbol: 'ETHUSDT',
247
- timeframe: '1m',
248
- historyLimit: 500,
249
- })
250
-
251
- // Or manual data feed
252
- chart.setData(historicalBars)
253
- chart.appendBar(newBar)
254
- chart.updateLastBar(updatedBar)
255
- chart.setCurrentPrice(3500.42)
256
- ```
257
-
258
- ### Web Worker indicator pipeline
259
-
260
- 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.
261
-
262
- ```typescript
263
- import { IndicatorWorkerHost } from '@tradecanvas/core'
264
-
265
- // Bundler-supported worker URL (Vite, webpack 5, esbuild, etc.)
266
- const worker = new Worker(
267
- new URL('@tradecanvas/core/dist/indicator.worker.js', import.meta.url),
268
- { type: 'module' },
269
- )
270
- const host = new IndicatorWorkerHost(worker, { timeoutMs: 30_000 })
271
-
272
- const output = await host.calculate(
273
- 'rsi',
274
- { id: 'rsi', instanceId: 'rsi-1', params: { period: 14 } },
275
- bars,
276
- )
277
-
278
- // Health check / cleanup
279
- await host.ping()
280
- host.terminate()
281
- ```
282
-
283
- No worker available (SSR, tests, or as a safety net)? Pass `null` and register fallback plugins for synchronous calculation:
284
-
285
- ```typescript
286
- import { IndicatorWorkerHost, RSIIndicator } from '@tradecanvas/core'
287
-
288
- const host = new IndicatorWorkerHost(null)
289
- host.registerFallbackPlugin(new RSIIndicator())
290
- const output = await host.calculate('rsi', config, bars) // runs synchronously
291
- ```
292
-
293
- Render still happens on the main thread (it needs `CanvasRenderingContext2D`). Only the heavy compute moves off-thread.
294
-
295
- ### Save / Load
296
-
297
- ```typescript
298
- const json = chart.saveState()
299
- localStorage.setItem('my-chart', json!)
300
-
301
- chart.loadState(localStorage.getItem('my-chart')!)
302
-
303
- // Download / upload files
304
- chart.downloadState('my-chart.json')
305
- await chart.loadStateFromFile()
306
- ```
307
-
308
- ### Themes
309
-
310
- ```typescript
311
- import { DARK_THEME, LIGHT_THEME, DARK_TERMINAL } from '@tradecanvas/chart'
312
-
313
- // Built-in presets: DARK_THEME, LIGHT_THEME, DARK_TERMINAL
314
- chart.setTheme(DARK_TERMINAL) // fintech terminal: #0E0E0E bg, #00FF87/#FF3B4D candles, monospace
315
-
316
- // Or customize any preset
317
- chart.setTheme({
318
- ...DARK_THEME,
319
- candleUp: '#26A69A',
320
- candleDown: '#EF5350',
321
- background: '#0a0a0f',
322
- })
323
- ```
324
-
325
- ### Events
326
-
327
- ```typescript
328
- chart.on('crosshairMove', (e) => { /* { point, bar, barIndex, indicatorValues } */ })
329
- chart.on('barClick', (e) => { /* { bar, barIndex, point } */ })
330
- chart.on('visibleRangeChange', (e) => { /* { from, to } */ })
331
- chart.on('drawingCreate', (e) => { /* ... */ })
332
- chart.on('orderModify', (e) => { /* ... */ })
333
- chart.on('positionModify', (e) => { /* ... */ })
334
- ```
335
-
336
- ### Replay Mode
337
-
338
- ```typescript
339
- chart.replayStart({ data: historicalBars, speed: 2, startIndex: 100 })
340
- chart.replayPause()
341
- chart.replayResume()
342
- chart.replayStop()
343
- const { current, total, percent } = chart.getReplayProgress()
344
- ```
345
-
346
- ## Comparison
347
-
348
- | Feature | @tradecanvas/chart | lightweight-charts | chart.js | Highcharts Stock |
349
- |---|---|---|---|---|
350
- | Chart types | 12 + 6 finance | 4 | 8 (non-financial) | 10+ |
351
- | Finance charts | Sparkline, Depth, Equity, Heatmap, Waterfall, Gauge | None | None | Some |
352
- | Built-in indicators | 33+ | 0 | 0 | ~30 |
353
- | Drawing tools | 23 | 0 | 0 | Some |
354
- | Trading overlay | Full (pos + orders + drag) | None | None | None |
355
- | Real-time streaming | Built-in (Binance) | Manual | Manual | Built-in |
356
- | Save/load state | Yes | No | No | Yes |
357
- | Replay mode | Yes | No | No | No |
358
- | Multi-panel | Yes | No | No | Yes |
359
- | Bundle (gzip) | ~50 KB | ~45 KB | ~70 KB | ~200 KB |
360
- | Dependencies | 0 | 1 | 0 | 0 |
361
- | Widget (complete UI) | Yes (`ChartWidget`) | No | No | No |
362
- | License | MIT | Apache 2.0 | MIT | Commercial |
363
-
364
- ## API Overview
365
-
366
- ### `new Chart(container, options)`
367
-
368
- ```typescript
369
- const chart = new Chart(element, {
370
- chartType: 'candlestick',
371
- theme: DARK_THEME,
372
- autoScale: true,
373
- rightMargin: 5,
374
- numberLocale: 'en-US', // or 'de-DE', 'vi-VN', etc. — BCP 47 locale
375
- crosshair: { mode: 'magnet' },
376
- features: { drawings: true, indicators: true, trading: true, volume: true },
377
- })
378
-
379
- // Change locale at runtime
380
- chart.setNumberLocale('de-DE') // 65.234,00
381
- ```
382
-
383
- ### Key Methods
384
-
385
- | Method | Description |
386
- |---|---|
387
- | `setData(bars)` | Load historical OHLCV data |
388
- | `appendBar(bar)` | Append a new candle |
389
- | `appendBars(bars)` | Bulk append (reconnect catch-up) |
390
- | `updateLastBar(bar)` | Update the in-progress candle |
391
- | `setCurrentPrice(price, pulseColor?)` | Show a live price line |
392
- | `connect(config)` | Connect to a real-time data source |
393
- | `setTimeframe(tf)` | Switch timeframe on active stream |
394
- | `setChartType(type)` | Switch chart type |
395
- | `setTheme(theme)` | Apply a theme (DARK_THEME, LIGHT_THEME, DARK_TERMINAL) |
396
- | `setNumberLocale(locale)` | Set number format locale (en-US, de-DE, vi-VN) |
397
- | `setStatusText(text)` | Show status in legend area ("LIVE · 8ms") |
398
- | `addIndicator(id, params?)` | Add a technical indicator |
399
- | `removeIndicator(instanceId)` | Remove an indicator |
400
- | `setDrawingTool(tool)` | Activate a drawing tool |
401
- | `setPositions(positions)` | Render trading positions |
402
- | `setOrders(orders)` | Render pending orders |
403
- | `saveState(key?)` | Serialize chart state |
404
- | `loadState(json)` | Restore chart state |
405
- | `screenshot()` | Download chart as image |
406
- | `on(event, handler)` | Subscribe to events |
407
- | `destroy()` | Clean up all resources |
408
-
409
- ### Data Format
410
-
411
- ```typescript
412
- interface OHLCBar {
413
- time: number // Unix timestamp (seconds)
414
- open: number
415
- high: number
416
- low: number
417
- close: number
418
- volume: number
419
- }
420
- ```
421
-
422
- ## Examples
423
-
424
- | Example | Description |
425
- |---|---|
426
- | [Live Demo](https://bonguynvan.github.io/tradecanvas/) | Full-featured demo with live Binance data |
427
- | [examples/basic](./examples/basic/) | Vanilla JS + live Binance streaming |
428
- | [examples/vanilla-static](./examples/vanilla-static/) | Vanilla JS + static data (offline) |
429
- | [examples/react](./examples/react/) | React 19 integration |
430
- | [examples/svelte](./examples/svelte/) | Svelte 5 integration |
431
- | [examples/vue](./examples/vue/) | Vue 3 integration |
432
-
433
- ## Browser Support
434
-
435
- Chrome 80+, Firefox 80+, Safari 14+, Edge 80+
436
-
437
- ## Framework Integration
438
-
439
- TradeCanvas is framework-agnostic. The `Chart` class takes a DOM element and manages its own canvas layers.
440
-
441
- **React:**
442
-
443
- ```tsx
444
- import { useEffect, useRef } from 'react'
445
- import { Chart, BinanceAdapter } from '@tradecanvas/chart'
446
-
447
- function TradingChart() {
448
- const ref = useRef<HTMLDivElement>(null)
449
-
450
- useEffect(() => {
451
- const chart = new Chart(ref.current!, {
452
- theme: 'dark',
453
- features: { indicators: true, drawings: true },
454
- })
455
- chart.connect({
456
- adapter: new BinanceAdapter(),
457
- symbol: 'BTCUSDT',
458
- timeframe: '5m',
459
- })
460
- return () => chart.destroy()
461
- }, [])
462
-
463
- return <div ref={ref} style={{ width: '100%', height: 500 }} />
464
- }
465
- ```
466
-
467
- **Svelte:**
468
-
469
- ```svelte
470
- <script lang="ts">
471
- import { onMount, onDestroy } from 'svelte'
472
- import { Chart, BinanceAdapter, DARK_THEME } from '@tradecanvas/chart'
473
- import type { TimeFrame } from '@tradecanvas/chart'
474
-
475
- interface Props { symbol?: string; timeframe?: TimeFrame }
476
- let { symbol = 'BTCUSDT', timeframe = '5m' }: Props = $props()
477
-
478
- let container: HTMLDivElement
479
- let chart: Chart | null = null
480
-
481
- onMount(() => {
482
- chart = new Chart(container, {
483
- chartType: 'candlestick',
484
- theme: DARK_THEME,
485
- autoScale: true,
486
- features: { indicators: true, drawings: true, volume: true },
487
- })
488
- chart.connect({ adapter: new BinanceAdapter(), symbol, timeframe })
489
- })
490
-
491
- onDestroy(() => chart?.destroy())
492
-
493
- $effect(() => {
494
- if (!chart) return
495
- chart.disconnectStream()
496
- chart.connect({ adapter: new BinanceAdapter(), symbol, timeframe })
497
- })
498
- </script>
499
-
500
- <div bind:this={container} style="width: 100%; height: 600px" />
501
- ```
502
-
503
- **Vue:**
504
-
505
- ```vue
506
- <template>
507
- <div ref="chartContainer" style="width: 100%; height: 600px" />
508
- </template>
509
-
510
- <script setup lang="ts">
511
- import { ref, onMounted, onUnmounted } from 'vue'
512
- import { Chart, BinanceAdapter, DARK_THEME } from '@tradecanvas/chart'
513
-
514
- const chartContainer = ref<HTMLDivElement>()
515
- let chart: Chart | null = null
516
-
517
- onMounted(() => {
518
- if (!chartContainer.value) return
519
- chart = new Chart(chartContainer.value, {
520
- chartType: 'candlestick',
521
- theme: DARK_THEME,
522
- autoScale: true,
523
- features: { indicators: true, drawings: true, volume: true },
524
- })
525
- chart.connect({ adapter: new BinanceAdapter(), symbol: 'BTCUSDT', timeframe: '5m' })
526
- })
527
-
528
- onUnmounted(() => chart?.destroy())
529
- </script>
530
- ```
531
-
532
- ## Architecture
533
-
534
- Multi-layer canvas for optimal rendering — only dirty layers repaint each frame:
535
-
536
- ```
537
- UI Layer (price axis, legend, live price) z=3
538
- Overlay Layer (drawings, trading positions/orders) z=2
539
- Main Layer (candles, indicators, volume) z=1
540
- Background (grid, watermark) z=0
541
- ```
542
-
543
- ## License
544
-
545
- [MIT](./LICENSE)
1
+ # @tradecanvas/chart
2
+
3
+ High-performance canvas trading chart with built-in indicators, drawing tools, and real-time streaming. Zero external dependencies.
4
+
5
+ **[Live Demo](https://bonguynvan.github.io/tradecanvas/)** | **[GitHub](https://github.com/bonguynvan/tradecanvas)** | **[npm](https://www.npmjs.com/package/@tradecanvas/chart)**
6
+
7
+ ## Why TradeCanvas?
8
+
9
+ Most chart libraries make you choose: pretty charts with no trading features, or trading features with an ugly API. TradeCanvas gives you both.
10
+
11
+ - **33+ built-in indicators** — MA, 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.
12
+ - **10+ drawing tools** — Trendlines, Fibonacci retracement, horizontal/vertical lines, rectangles, channels, Elliott waves, Gann fans. With undo/redo.
13
+ - **Trading overlay** — Render open positions with entry line, P&L zone, and SL/TP markers. Orders as dashed lines. Users can drag SL/TP to modify.
14
+ - **Real-time streaming** — Built-in Binance adapter. Plug in your own data source with the adapter interface.
15
+ - **Save/load chart state** — Persist drawings, indicators, theme, and chart type to JSON. Restore with one call.
16
+ - **Replay mode** — Step through historical data bar-by-bar for backtesting visualization.
17
+ - **Zero dependencies** — The entire library is self-contained. No `d3`, no `chart.js`, no `fancy-canvas`.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @tradecanvas/chart
23
+ # or
24
+ pnpm add @tradecanvas/chart
25
+ # or
26
+ yarn add @tradecanvas/chart
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```typescript
32
+ import { Chart, BinanceAdapter } from '@tradecanvas/chart'
33
+
34
+ // Create a chart
35
+ const chart = new Chart(document.getElementById('chart')!, {
36
+ theme: 'dark',
37
+ autoScale: true,
38
+ features: {
39
+ drawings: true,
40
+ indicators: true,
41
+ trading: true,
42
+ volume: true,
43
+ },
44
+ })
45
+
46
+ // Connect to live Binance data
47
+ const adapter = new BinanceAdapter()
48
+ chart.connect({
49
+ adapter,
50
+ symbol: 'BTCUSDT',
51
+ timeframe: '5m',
52
+ historyLimit: 300,
53
+ })
54
+ ```
55
+
56
+ That's it. A full-featured trading chart with live data in 15 lines.
57
+
58
+ ## Widget (Complete UI)
59
+
60
+ For a complete TradingView-like experience with built-in toolbar, drawing tools, and settings — no UI code needed:
61
+
62
+ ```typescript
63
+ import { ChartWidget } from '@tradecanvas/chart/widget'
64
+ import { BinanceAdapter } from '@tradecanvas/chart'
65
+
66
+ const widget = new ChartWidget(document.getElementById('chart')!, {
67
+ symbol: 'BTCUSDT',
68
+ timeframe: '5m',
69
+ adapter: new BinanceAdapter(),
70
+ theme: 'dark',
71
+ })
72
+ ```
73
+
74
+ That's it. Full toolbar, drawing sidebar, settings modal, and status bar — all included.
75
+
76
+ ### Widget Options
77
+
78
+ | Option | Type | Default | Description |
79
+ |---|---|---|---|
80
+ | `symbol` | `string` | `'BTCUSDT'` | Initial trading symbol |
81
+ | `timeframe` | `TimeFrame` | `'5m'` | Initial timeframe |
82
+ | `theme` | `'dark' \| 'light' \| Theme` | `'dark'` | Chart theme |
83
+ | `adapter` | `DataAdapter` | — | Data source adapter |
84
+ | `toolbar` | `boolean` | `true` | Show top toolbar |
85
+ | `drawingTools` | `boolean` | `true` | Show left drawing sidebar |
86
+ | `settings` | `boolean` | `true` | Show settings button |
87
+ | `trading` | `boolean` | `true` | Enable trading overlay |
88
+ | `statusBar` | `boolean` | `true` | Show bottom status bar |
89
+ | `symbols` | `string[]` | BTC/ETH/SOL/BNB | Available symbols |
90
+ | `timeframes` | `TimeFrame[]` | 1m to 1d | Available timeframes |
91
+ | `chartTypes` | `ChartType[]` | 11 types | Available chart types |
92
+ | `onSymbolChange` | `(symbol) => void` | — | Symbol change callback |
93
+ | `onTimeframeChange` | `(tf) => void` | — | Timeframe change callback |
94
+ | `onReady` | `(chart) => void` | — | Fired when chart is ready |
95
+
96
+ ### Widget vs Headless
97
+
98
+ | | `Chart` (headless) | `ChartWidget` |
99
+ |---|---|---|
100
+ | Import | `@tradecanvas/chart` | `@tradecanvas/chart/widget` |
101
+ | UI included | None — build your own | Complete toolbar, sidebar, settings |
102
+ | Bundle impact | ~50 KB gzip | ~65 KB gzip (includes UI) |
103
+ | Framework | Any (React, Vue, Svelte, vanilla) | Vanilla JS DOM (works everywhere) |
104
+ | Customization | Full control | Toggle sections on/off |
105
+ | Advanced access | Direct API | `widget.getChart()` for direct API |
106
+
107
+ ## Features
108
+
109
+ ### Chart Types
110
+
111
+ | Type | Description |
112
+ |---|---|
113
+ | Candlestick | Standard OHLC candles |
114
+ | Hollow Candle | Open/close determines fill |
115
+ | Bar (OHLC) | Classic open-high-low-close bars |
116
+ | Line | Close price line |
117
+ | Area | Filled area below close |
118
+ | Baseline | Two-tone area split at a reference price |
119
+ | Heikin-Ashi | Smoothed candles for trend identification |
120
+ | Renko | Fixed-size bricks that ignore time |
121
+ | Kagi | Reversal-based line chart |
122
+ | Point & Figure | X/O columns for supply/demand analysis |
123
+ | Line Break | Three-line break charts |
124
+ | Range Bars | Fixed price-range bars — each bar's high − low equals a configured range |
125
+ | Volume Candles | Candlesticks with width proportional to volume |
126
+ | HLC Area | High-low-close area band with close line |
127
+ | Step Line | Staircase/step pattern from close prices |
128
+ | Line with Markers | Close line with circular markers at each data point |
129
+
130
+ ### Multi-Chart Grid
131
+
132
+ ```typescript
133
+ import { ChartGrid, BinanceAdapter } from '@tradecanvas/chart'
134
+
135
+ const grid = new ChartGrid(document.getElementById('grid')!, {
136
+ layout: '2x2',
137
+ syncCrosshair: true,
138
+ syncTimeAxis: true,
139
+ })
140
+
141
+ const adapter = new BinanceAdapter()
142
+ grid.connectAll(adapter, ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT'], '5m')
143
+ ```
144
+
145
+ Layouts: `'1x1'`, `'1x2'`, `'2x1'`, `'2x2'`, `'1x3'`, `'3x1'`, `'2x3'`, `'3x2'`.
146
+
147
+ ### Command Palette
148
+
149
+ Press `Ctrl+K` / `Cmd+K` inside ChartWidget to open the command palette. Search indicators, chart types, drawing tools, timeframes, and actions.
150
+
151
+ ### Finance Charts
152
+
153
+ | Chart | Description |
154
+ |---|---|
155
+ | SparklineChart | Tiny inline line/area chart from a number array — for dashboards and KPI cards |
156
+ | DepthChart | Bid/ask order book visualization with cumulative volume areas |
157
+ | EquityCurveChart | Portfolio equity line with drawdown shading and benchmark comparison |
158
+ | HeatmapChart | Colored cell grid with treemap layout — for sector/market performance |
159
+ | WaterfallChart | Running cumulative bars — P&L attribution, revenue bridge, cash flow |
160
+ | GaugeChart | Speedometer-style gauge — KPIs, risk scores, Fear & Greed index |
161
+
162
+ ```typescript
163
+ import {
164
+ SparklineChart, DepthChart, EquityCurveChart, HeatmapChart,
165
+ WaterfallChart, GaugeChart,
166
+ } from '@tradecanvas/chart'
167
+
168
+ // Sparkline in a 120x48 container
169
+ new SparklineChart(el, { data: [100, 102, 98, 105, 103], mode: 'area', color: '#26A69A' })
170
+
171
+ // Equity curve with drawdown
172
+ new EquityCurveChart(el, { data: equityPoints, drawdown: true, benchmark: spyData })
173
+
174
+ // Order book depth
175
+ new DepthChart(el, { data: { bids, asks }, crosshair: true })
176
+
177
+ // Market heatmap (treemap weighted by market cap)
178
+ new HeatmapChart(el, { data: cells, weighted: true })
179
+
180
+ // P&L waterfall
181
+ new WaterfallChart(el, {
182
+ data: [
183
+ { label: 'Start', value: 10000, type: 'total' },
184
+ { label: 'Gain', value: 1850 },
185
+ { label: 'Loss', value: -620 },
186
+ { label: 'End', value: 11230, type: 'total' },
187
+ ],
188
+ })
189
+
190
+ // Fear & Greed gauge
191
+ const gauge = new GaugeChart(el, {
192
+ value: 72,
193
+ zones: [
194
+ { from: 0, to: 25, color: '#ef4444' },
195
+ { from: 75, to: 100, color: '#10b981' },
196
+ ],
197
+ })
198
+ gauge.setValue(85) // animates smoothly
199
+ ```
200
+
201
+ ### Indicators (built-in)
202
+
203
+ **Overlay** (drawn on the price chart):
204
+ 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
205
+
206
+ **Panel** (separate sub-chart):
207
+ 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
208
+
209
+ 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.
210
+
211
+ ### Drawing Tools
212
+
213
+ Trendline, Horizontal Line, Vertical Line, Ray, Extended Line, Parallel Channel, Fibonacci Retracement, Fibonacci Extension, 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
214
+
215
+ All drawing tools support:
216
+ - Click-to-place with magnet snapping to OHLC values
217
+ - Undo / redo (Ctrl+Z / Ctrl+Y)
218
+ - Serialization for save/load
219
+ - Custom styles (color, width, dash pattern)
220
+
221
+ ### Trading Overlay
222
+
223
+ Render open positions and pending orders directly on the chart, like MT4/MT5.
224
+
225
+ ```typescript
226
+ import type { TradingPosition, TradingOrder } from '@tradecanvas/chart'
227
+
228
+ chart.setPositions([{
229
+ id: 'pos-1',
230
+ side: 'buy',
231
+ entryPrice: 3500,
232
+ quantity: 1.5,
233
+ closedQuantity: 0.5, // partial close — visualized as a left-edge dim band
234
+ stopLoss: 3400,
235
+ takeProfit: 3700,
236
+ }])
237
+
238
+ chart.setOrders([{
239
+ id: 'order-1',
240
+ side: 'sell',
241
+ type: 'limit',
242
+ price: 3800,
243
+ quantity: 0.5,
244
+ label: 'TP',
245
+ draggable: true,
246
+ }])
247
+
248
+ // Customize the position zone color via P&L thresholds
249
+ chart.setTradingConfig({
250
+ pnlThresholds: [
251
+ { pnl: -Infinity, color: '#b91c1c' },
252
+ { pnl: 0, color: '#94a3b8' },
253
+ { pnl: 50, color: '#16a34a' },
254
+ { pnl: 200, color: '#15803d' },
255
+ ],
256
+ // Custom label template — tokens: {side} {qty} {openQty} {closedQty} {entry} {price} {pnl} {pnlPct} {pnlSign}
257
+ positionLabel: '{side} {openQty}/{qty} @ {entry} | {pnlSign}{pnl} ({pnlPct})',
258
+ })
259
+
260
+ // Listen for user drag-to-modify
261
+ chart.on('positionModify', (e) => console.log('SL/TP moved:', e.payload))
262
+ chart.on('orderModify', (e) => console.log('Order moved:', e.payload))
263
+ ```
264
+
265
+ ### Real-Time Streaming
266
+
267
+ ```typescript
268
+ // Built-in Binance adapter (free, no API key)
269
+ chart.connect({
270
+ adapter: new BinanceAdapter(),
271
+ symbol: 'ETHUSDT',
272
+ timeframe: '1m',
273
+ historyLimit: 500,
274
+ })
275
+
276
+ // Or manual data feed
277
+ chart.setData(historicalBars)
278
+ chart.appendBar(newBar)
279
+ chart.updateLastBar(updatedBar)
280
+ chart.setCurrentPrice(3500.42)
281
+ ```
282
+
283
+ ### Web Worker indicator pipeline
284
+
285
+ 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.
286
+
287
+ ```typescript
288
+ import { IndicatorWorkerHost } from '@tradecanvas/core'
289
+
290
+ // Bundler-supported worker URL (Vite, webpack 5, esbuild, etc.)
291
+ const worker = new Worker(
292
+ new URL('@tradecanvas/core/dist/indicator.worker.js', import.meta.url),
293
+ { type: 'module' },
294
+ )
295
+ const host = new IndicatorWorkerHost(worker, { timeoutMs: 30_000 })
296
+
297
+ const output = await host.calculate(
298
+ 'rsi',
299
+ { id: 'rsi', instanceId: 'rsi-1', params: { period: 14 } },
300
+ bars,
301
+ )
302
+
303
+ // Health check / cleanup
304
+ await host.ping()
305
+ host.terminate()
306
+ ```
307
+
308
+ No worker available (SSR, tests, or as a safety net)? Pass `null` and register fallback plugins for synchronous calculation:
309
+
310
+ ```typescript
311
+ import { IndicatorWorkerHost, RSIIndicator } from '@tradecanvas/core'
312
+
313
+ const host = new IndicatorWorkerHost(null)
314
+ host.registerFallbackPlugin(new RSIIndicator())
315
+ const output = await host.calculate('rsi', config, bars) // runs synchronously
316
+ ```
317
+
318
+ Render still happens on the main thread (it needs `CanvasRenderingContext2D`). Only the heavy compute moves off-thread.
319
+
320
+ ### Save / Load
321
+
322
+ ```typescript
323
+ const json = chart.saveState()
324
+ localStorage.setItem('my-chart', json!)
325
+
326
+ chart.loadState(localStorage.getItem('my-chart')!)
327
+
328
+ // Download / upload files
329
+ chart.downloadState('my-chart.json')
330
+ await chart.loadStateFromFile()
331
+ ```
332
+
333
+ ### Themes
334
+
335
+ ```typescript
336
+ import { DARK_THEME, LIGHT_THEME, DARK_TERMINAL } from '@tradecanvas/chart'
337
+
338
+ // Built-in presets: DARK_THEME, LIGHT_THEME, DARK_TERMINAL
339
+ chart.setTheme(DARK_TERMINAL) // fintech terminal: #0E0E0E bg, #00FF87/#FF3B4D candles, monospace
340
+
341
+ // Or customize any preset
342
+ chart.setTheme({
343
+ ...DARK_THEME,
344
+ candleUp: '#26A69A',
345
+ candleDown: '#EF5350',
346
+ background: '#0a0a0f',
347
+ })
348
+ ```
349
+
350
+ ### Events
351
+
352
+ ```typescript
353
+ chart.on('crosshairMove', (e) => { /* { point, bar, barIndex, indicatorValues } */ })
354
+ chart.on('barClick', (e) => { /* { bar, barIndex, point } */ })
355
+ chart.on('visibleRangeChange', (e) => { /* { from, to } */ })
356
+ chart.on('drawingCreate', (e) => { /* ... */ })
357
+ chart.on('orderModify', (e) => { /* ... */ })
358
+ chart.on('positionModify', (e) => { /* ... */ })
359
+ ```
360
+
361
+ ### Replay Mode
362
+
363
+ ```typescript
364
+ chart.replayStart({ data: historicalBars, speed: 2, startIndex: 100 })
365
+ chart.replayPause()
366
+ chart.replayResume()
367
+ chart.replayStop()
368
+ const { current, total, percent } = chart.getReplayProgress()
369
+ ```
370
+
371
+ ## Comparison
372
+
373
+ | Feature | @tradecanvas/chart | lightweight-charts | chart.js | Highcharts Stock |
374
+ |---|---|---|---|---|
375
+ | Chart types | 12 + 6 finance | 4 | 8 (non-financial) | 10+ |
376
+ | Finance charts | Sparkline, Depth, Equity, Heatmap, Waterfall, Gauge | None | None | Some |
377
+ | Built-in indicators | 33+ | 0 | 0 | ~30 |
378
+ | Drawing tools | 23 | 0 | 0 | Some |
379
+ | Trading overlay | Full (pos + orders + drag) | None | None | None |
380
+ | Real-time streaming | Built-in (Binance) | Manual | Manual | Built-in |
381
+ | Save/load state | Yes | No | No | Yes |
382
+ | Replay mode | Yes | No | No | No |
383
+ | Multi-panel | Yes | No | No | Yes |
384
+ | Bundle (gzip) | ~50 KB | ~45 KB | ~70 KB | ~200 KB |
385
+ | Dependencies | 0 | 1 | 0 | 0 |
386
+ | Widget (complete UI) | Yes (`ChartWidget`) | No | No | No |
387
+ | License | MIT | Apache 2.0 | MIT | Commercial |
388
+
389
+ ## API Overview
390
+
391
+ ### `new Chart(container, options)`
392
+
393
+ ```typescript
394
+ const chart = new Chart(element, {
395
+ chartType: 'candlestick',
396
+ theme: DARK_THEME,
397
+ autoScale: true,
398
+ rightMargin: 5,
399
+ numberLocale: 'en-US', // or 'de-DE', 'vi-VN', etc. — BCP 47 locale
400
+ crosshair: { mode: 'magnet' },
401
+ features: { drawings: true, indicators: true, trading: true, volume: true },
402
+ })
403
+
404
+ // Change locale at runtime
405
+ chart.setNumberLocale('de-DE') // 65.234,00
406
+ ```
407
+
408
+ ### Key Methods
409
+
410
+ | Method | Description |
411
+ |---|---|
412
+ | `setData(bars)` | Load historical OHLCV data |
413
+ | `appendBar(bar)` | Append a new candle |
414
+ | `appendBars(bars)` | Bulk append (reconnect catch-up) |
415
+ | `updateLastBar(bar)` | Update the in-progress candle |
416
+ | `setCurrentPrice(price, pulseColor?)` | Show a live price line |
417
+ | `connect(config)` | Connect to a real-time data source |
418
+ | `setTimeframe(tf)` | Switch timeframe on active stream |
419
+ | `setChartType(type)` | Switch chart type |
420
+ | `setTheme(theme)` | Apply a theme (DARK_THEME, LIGHT_THEME, DARK_TERMINAL) |
421
+ | `setNumberLocale(locale)` | Set number format locale (en-US, de-DE, vi-VN) |
422
+ | `setStatusText(text)` | Show status in legend area ("LIVE · 8ms") |
423
+ | `addIndicator(id, params?)` | Add a technical indicator |
424
+ | `removeIndicator(instanceId)` | Remove an indicator |
425
+ | `setDrawingTool(tool)` | Activate a drawing tool |
426
+ | `setPositions(positions)` | Render trading positions |
427
+ | `setOrders(orders)` | Render pending orders |
428
+ | `saveState(key?)` | Serialize chart state |
429
+ | `loadState(json)` | Restore chart state |
430
+ | `screenshot()` | Download chart as image |
431
+ | `on(event, handler)` | Subscribe to events |
432
+ | `destroy()` | Clean up all resources |
433
+
434
+ ### Data Format
435
+
436
+ ```typescript
437
+ interface OHLCBar {
438
+ time: number // Unix timestamp (seconds)
439
+ open: number
440
+ high: number
441
+ low: number
442
+ close: number
443
+ volume: number
444
+ }
445
+ ```
446
+
447
+ ## Examples
448
+
449
+ | Example | Description |
450
+ |---|---|
451
+ | [Live Demo](https://bonguynvan.github.io/tradecanvas/) | Full-featured demo with live Binance data |
452
+ | [examples/basic](./examples/basic/) | Vanilla JS + live Binance streaming |
453
+ | [examples/vanilla-static](./examples/vanilla-static/) | Vanilla JS + static data (offline) |
454
+ | [examples/react](./examples/react/) | React 19 integration |
455
+ | [examples/svelte](./examples/svelte/) | Svelte 5 integration |
456
+ | [examples/vue](./examples/vue/) | Vue 3 integration |
457
+
458
+ ## Browser Support
459
+
460
+ Chrome 80+, Firefox 80+, Safari 14+, Edge 80+
461
+
462
+ ## Framework Integration
463
+
464
+ TradeCanvas is framework-agnostic. The `Chart` class takes a DOM element and manages its own canvas layers.
465
+
466
+ **React:**
467
+
468
+ ```tsx
469
+ import { useEffect, useRef } from 'react'
470
+ import { Chart, BinanceAdapter } from '@tradecanvas/chart'
471
+
472
+ function TradingChart() {
473
+ const ref = useRef<HTMLDivElement>(null)
474
+
475
+ useEffect(() => {
476
+ const chart = new Chart(ref.current!, {
477
+ theme: 'dark',
478
+ features: { indicators: true, drawings: true },
479
+ })
480
+ chart.connect({
481
+ adapter: new BinanceAdapter(),
482
+ symbol: 'BTCUSDT',
483
+ timeframe: '5m',
484
+ })
485
+ return () => chart.destroy()
486
+ }, [])
487
+
488
+ return <div ref={ref} style={{ width: '100%', height: 500 }} />
489
+ }
490
+ ```
491
+
492
+ **Svelte:**
493
+
494
+ ```svelte
495
+ <script lang="ts">
496
+ import { onMount, onDestroy } from 'svelte'
497
+ import { Chart, BinanceAdapter, DARK_THEME } from '@tradecanvas/chart'
498
+ import type { TimeFrame } from '@tradecanvas/chart'
499
+
500
+ interface Props { symbol?: string; timeframe?: TimeFrame }
501
+ let { symbol = 'BTCUSDT', timeframe = '5m' }: Props = $props()
502
+
503
+ let container: HTMLDivElement
504
+ let chart: Chart | null = null
505
+
506
+ onMount(() => {
507
+ chart = new Chart(container, {
508
+ chartType: 'candlestick',
509
+ theme: DARK_THEME,
510
+ autoScale: true,
511
+ features: { indicators: true, drawings: true, volume: true },
512
+ })
513
+ chart.connect({ adapter: new BinanceAdapter(), symbol, timeframe })
514
+ })
515
+
516
+ onDestroy(() => chart?.destroy())
517
+
518
+ $effect(() => {
519
+ if (!chart) return
520
+ chart.disconnectStream()
521
+ chart.connect({ adapter: new BinanceAdapter(), symbol, timeframe })
522
+ })
523
+ </script>
524
+
525
+ <div bind:this={container} style="width: 100%; height: 600px" />
526
+ ```
527
+
528
+ **Vue:**
529
+
530
+ ```vue
531
+ <template>
532
+ <div ref="chartContainer" style="width: 100%; height: 600px" />
533
+ </template>
534
+
535
+ <script setup lang="ts">
536
+ import { ref, onMounted, onUnmounted } from 'vue'
537
+ import { Chart, BinanceAdapter, DARK_THEME } from '@tradecanvas/chart'
538
+
539
+ const chartContainer = ref<HTMLDivElement>()
540
+ let chart: Chart | null = null
541
+
542
+ onMounted(() => {
543
+ if (!chartContainer.value) return
544
+ chart = new Chart(chartContainer.value, {
545
+ chartType: 'candlestick',
546
+ theme: DARK_THEME,
547
+ autoScale: true,
548
+ features: { indicators: true, drawings: true, volume: true },
549
+ })
550
+ chart.connect({ adapter: new BinanceAdapter(), symbol: 'BTCUSDT', timeframe: '5m' })
551
+ })
552
+
553
+ onUnmounted(() => chart?.destroy())
554
+ </script>
555
+ ```
556
+
557
+ ## Architecture
558
+
559
+ Multi-layer canvas for optimal rendering — only dirty layers repaint each frame:
560
+
561
+ ```
562
+ UI Layer (price axis, legend, live price) z=3
563
+ Overlay Layer (drawings, trading positions/orders) z=2
564
+ Main Layer (candles, indicators, volume) z=1
565
+ Background (grid, watermark) z=0
566
+ ```
567
+
568
+ ## License
569
+
570
+ [MIT](./LICENSE)