acttrader-charts 1.0.0

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 ADDED
@@ -0,0 +1,676 @@
1
+ # @acttrader/stockchart
2
+
3
+ A performant, zero-dependency stock charting library built on Canvas 2D.
4
+ Dual ESM/CJS output, TypeScript-first.
5
+
6
+ **Features:** 13 chart series types · 30+ indicators (overlay + sub-pane) · 80+ drawing tools · Trade-From-Chart (TFC) with drag-to-modify levels · Real-time WebSocket streaming · Dark & Light themes · Fully configurable labels for i18n · Fullscreen support
7
+
8
+ ---
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @acttrader/stockchart
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Basic Usage
19
+
20
+ ```ts
21
+ import { ChartEngine } from '@acttrader/stockchart';
22
+
23
+ const chart = new ChartEngine({
24
+ container: document.getElementById('chart')!,
25
+ theme: 'dark',
26
+ series: 'candlestick',
27
+ symbol: 'XAUUSD',
28
+ timeframe: '1D',
29
+ duration: '1Y',
30
+ });
31
+
32
+ chart.loadData(ohlcvBars); // OHLCVBar[]
33
+ ```
34
+
35
+ Load OHLCV data and the chart is ready. All UI (top bar, bottom bar, drawing toolbar) is rendered automatically unless `showUI: false`.
36
+
37
+ ### Respond to duration / timeframe changes
38
+
39
+ ```ts
40
+ chart.on('durationChange', ({ duration, timeframe }) => {
41
+ fetchBars(timeframe, duration).then(bars => chart.loadData(bars));
42
+ });
43
+ ```
44
+
45
+ ### Programmatic data loading via `dataLoader`
46
+
47
+ ```ts
48
+ const chart = new ChartEngine({
49
+ container,
50
+ dataLoader: async ({ start, end, interval }) => {
51
+ const res = await fetch(`/api/bars?from=${start.getTime()}&to=${end.getTime()}&tf=${interval}`);
52
+ return res.json(); // OHLCVBar[]
53
+ },
54
+ });
55
+ // No manual loadData() needed — the engine calls dataLoader on start and on timeframe/duration changes.
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Advanced Usage
61
+
62
+ ### Adding Indicators
63
+
64
+ ```ts
65
+ import { SMA, EMA, RSI } from '@acttrader/stockchart';
66
+
67
+ chart.addIndicator(new SMA(20, '#f59e0b')); // overlay
68
+ chart.addIndicator(new RSI(14, '#818cf8')); // sub-pane
69
+ chart.removeIndicator('SMA');
70
+ ```
71
+
72
+ Indicator pills appear inline above the chart (up to 2 visible; beyond that collapses into a dropdown).
73
+
74
+ ### Drawing Tools
75
+
76
+ ```ts
77
+ chart.setDrawingTool('trendLine'); // activate
78
+ chart.setDrawingTool(null); // back to pointer mode
79
+ ```
80
+
81
+ Keyboard shortcuts: `Escape` cancel / deselect · `Delete`/`Backspace` remove selected · `Ctrl+Z` undo · `Ctrl+Y` redo
82
+
83
+ ### Real-time Streaming
84
+
85
+ ```ts
86
+ import { WebSocketAdapter } from '@acttrader/stockchart';
87
+
88
+ const adapter = new WebSocketAdapter(
89
+ 'wss://feed.example.com/ticks',
90
+ (msg) => ({ time: msg.t, bid: msg.p, ask: msg.p, volume: msg.v }),
91
+ );
92
+
93
+ chart.connectStream(adapter);
94
+ // chart.disconnectStream();
95
+ ```
96
+
97
+ Auto-reconnects with exponential backoff (2 s → 30 s). Stream status is reflected in the top-bar dot and emitted via `streamStatus`.
98
+
99
+ To push ticks directly without an adapter (e.g. from your own WebSocket handler):
100
+
101
+ ```ts
102
+ chart.pushTick({ time: Date.now(), bid: 1.2055, ask: 1.2057, volume: 120 });
103
+ ```
104
+
105
+ ### Trade From Chart (TFC) — `setLevels`
106
+
107
+ TFC renders draggable price levels for open positions and pending orders.
108
+ The library is broker-agnostic: it draws the lines and emits typed events after the user confirms; your app owns the API calls.
109
+
110
+ **Pending order** (dashed line, draggable with optional SL/TP brackets):
111
+
112
+ ```ts
113
+ chart.setLevels(
114
+ [{
115
+ label: 'ORD-1',
116
+ price: 1.2050,
117
+ side: 'buy',
118
+ orderType: 'limit',
119
+ lots: 1.0,
120
+ stopLossPrice: 1.1980,
121
+ takeProfitPrice: 1.2150,
122
+ text: 'Buy Limit',
123
+ }],
124
+ 'label', // unique key field
125
+ 'price', // price field
126
+ 'pending', // level type
127
+ );
128
+ ```
129
+
130
+ **Open position** (solid line + live P&L + optional SL/TP brackets):
131
+
132
+ ```ts
133
+ chart.setLevels(
134
+ [{
135
+ label: 'POS-1',
136
+ price: 1.2010,
137
+ pnl: 42.5,
138
+ pnlText: '+$42.50',
139
+ side: 'buy',
140
+ stopLossPrice: 1.1960,
141
+ takeProfitPrice: 1.2120,
142
+ }],
143
+ 'label', 'price', 'position',
144
+ );
145
+ ```
146
+
147
+ **Bracket orders via `ToClose`** — link a separate SL/TP pending order to a parent level:
148
+
149
+ ```ts
150
+ chart.setLevels([
151
+ { label: 'POS-1', price: 1.2010, side: 'buy', type: 'position' },
152
+ { label: 'ORD-SL', price: 1.1960, side: 'sell', orderType: 'stop', ToClose: 'POS-1' },
153
+ ], 'label', 'price', 'pending');
154
+ ```
155
+
156
+ **Remove levels:**
157
+
158
+ ```ts
159
+ chart.removeLevelByLabel('ORD-1');
160
+ chart.setLevels([], 'label', 'price', 'pending'); // clear all of one type
161
+ ```
162
+
163
+ **TFC events:**
164
+
165
+ ```ts
166
+ // Confirmed drag of a pending order entry
167
+ chart.on('tradeLevelEdit', ({ label, type, changes }) => {
168
+ for (const c of changes) {
169
+ if (c.field === 'MAIN') myApi.modifyOrder(label, { price: c.newPrice });
170
+ if (c.field === 'SL') myApi.modifyOrder(label, { stopLoss: c.newPrice });
171
+ if (c.field === 'TP') myApi.modifyOrder(label, { takeProfit: c.newPrice });
172
+ }
173
+ });
174
+
175
+ // User clicked × on a level
176
+ chart.on('tradeLevelClose', ({ label, type, action }) => {
177
+ if (type === 'pending') myApi.cancelOrder(label);
178
+ if (type === 'position') myApi.closePosition(label);
179
+ });
180
+ ```
181
+
182
+ **Drag constraints (enforced live):**
183
+
184
+ | Order side | Stop Loss | Take Profit |
185
+ |---|---|---|
186
+ | `buy` | must stay ≤ order price | must stay ≥ order price |
187
+ | `sell` | must stay ≥ order price | must stay ≤ order price |
188
+
189
+ ---
190
+
191
+ ## Default Configuration
192
+
193
+ ### `ChartConfig`
194
+
195
+ | Property | Required | Default | Description |
196
+ |---|:---:|---|---|
197
+ | `container` | ✓ | — | Host `HTMLElement` |
198
+ | `theme` | | `"dark"` | `"dark"` or `"light"` |
199
+ | `series` | | `"candlestick"` | Initial chart type |
200
+ | `showVolume` | | `true` | Show volume overlay |
201
+ | `showUI` | | `true` | Render top / bottom / left bars |
202
+ | `timeframe` | | `"1D"` | Initial timeframe label |
203
+ | `duration` | | — | Initial active duration button |
204
+ | `symbol` | | — | Symbol name shown in the top bar |
205
+ | `padding` | | `{top:8,right:0,bottom:0,left:0}` | Canvas padding (px) |
206
+ | `minLots` | | `1` | Default lot size in the trade popover |
207
+ | `tickActivityMs` | | `30000` | ms the stream dot stays green after last tick |
208
+ | `maxCandles` | | `200` | Max bars fetched per data-load request |
209
+ | `durationTimeframeMap` | | *(see below)* | Override duration → timeframe pairings |
210
+ | `dataLoader` | | — | `(params) => Promise<OHLCVBar[]>` auto-called on load / change |
211
+ | `uiConfig` | | `DEFAULT_UI_CONFIG` | Deep-partial size / font overrides per component |
212
+ | `labels` | | `DEFAULT_LABELS` | Deep-partial string overrides for i18n/translation |
213
+ | `onOrderSubmit` | | — | Called when user submits a trade via the floating button |
214
+ | `onLevelEdit` | | — | Called when user confirms a TFC level edit |
215
+ | `onLevelDragEnd` | | — | *(deprecated — use `onLevelEdit`)* |
216
+
217
+ ### `OHLCVBar`
218
+
219
+ | Field | Required | Type | Description |
220
+ |---|:---:|---|---|
221
+ | `time` | ✓ | `number` | Unix ms timestamp |
222
+ | `open` | ✓ | `number` | Open price |
223
+ | `high` | ✓ | `number` | High price |
224
+ | `low` | ✓ | `number` | Low price |
225
+ | `close` | ✓ | `number` | Close price |
226
+ | `volume` | | `number` | Volume (omit to hide volume overlay) |
227
+
228
+ ### Duration → Auto Timeframe Map
229
+
230
+ | Duration | Default timeframe |
231
+ |---|---|
232
+ | `1D` | `5m` |
233
+ | `5D` | `15m` |
234
+ | `1M` | `30m` |
235
+ | `3M` | `1h` |
236
+ | `6M` | `4h` |
237
+ | `1Y` | `1D` |
238
+ | `5Y` | `1W` |
239
+ | `All` | `1M` |
240
+
241
+ Override individual entries via `durationTimeframeMap`:
242
+
243
+ ```ts
244
+ new ChartEngine({ ..., durationTimeframeMap: { '1Y': '4h', '5Y': '1D' } });
245
+ ```
246
+
247
+ ### `UiConfig` — sizes & fonts
248
+
249
+ All values are optional; unset keys fall back to `DEFAULT_UI_CONFIG`.
250
+
251
+ ```ts
252
+ import { DEFAULT_UI_CONFIG } from '@acttrader/stockchart';
253
+ ```
254
+
255
+ | Component key | Configurable properties |
256
+ |---|---|
257
+ | `drawingToolbar` | `iconBtnSize`, `iconFontSize`, `iconFontFamily`, `flyoutLabelFontSize`, `flyoutLabelFontFamily`, `shortcutFontSize`, `soonBadgeFontSize`, `flyoutHeadingFontSize`, `flyoutHeadingLetterSpacing`, `scrollBtnHeight`, `scrollBtnFontSize`, `barPadding`, `btnGap` |
258
+ | `topBar` | `height`, `dropBtnFontSize`, `dropBtnFontFamily`, `drawBtnSize`, `drawBtnIconFontSize`, `flyoutRowFontSize`, `flyoutRowFontFamily`, `flyoutCategoryFontSize`, `flyoutCategoryLetterSpacing`, `flyoutCheckFontSize`, `streamDotSize` |
259
+ | `bottomBar` | `height`, `btnFontSize`, `btnFontFamily` |
260
+ | `priceAxis` | `fontSize`, `fontFamily` |
261
+ | `timeAxis` | `fontSize`, `fontFamily` |
262
+ | `crosshair` | `labelHeight`, `labelPaddingX`, `fontSize`, `fontFamily` |
263
+ | `indicatorOverlay` | `pillFontSize`, `pillFontFamily`, `dotFontSize`, `pillIconFontSize`, `collapseBtnFontSize`, `collapseBtnFontFamily`, `dropdownRowFontSize`, `dropdownRowFontFamily`, `dropdownIconFontSize` |
264
+
265
+ **Example:**
266
+
267
+ ```ts
268
+ import { resolveUiConfig } from '@acttrader/stockchart';
269
+
270
+ new ChartEngine({
271
+ container,
272
+ uiConfig: {
273
+ topBar: { height: '40px', dropBtnFontSize: '13px' },
274
+ priceAxis: { fontSize: '12px', fontFamily: 'Inter, sans-serif' },
275
+ },
276
+ });
277
+ ```
278
+
279
+ You can also import individual section defaults:
280
+
281
+ ```ts
282
+ import {
283
+ DEFAULT_UI_CONFIG,
284
+ DEFAULT_TOP_BAR_CONFIG,
285
+ DEFAULT_PRICE_AXIS_CONFIG,
286
+ DEFAULT_CROSSHAIR_CONFIG,
287
+ // ...
288
+ } from '@acttrader/stockchart';
289
+ ```
290
+
291
+ ### `ChartLabels` — i18n / translation
292
+
293
+ All user-visible strings can be overridden for translation. Only keys you supply are replaced; the rest fall back to `DEFAULT_LABELS` (English).
294
+
295
+ ```ts
296
+ import { DEFAULT_LABELS, resolveLabels } from '@acttrader/stockchart';
297
+ ```
298
+
299
+ Pass overrides directly in `ChartConfig`:
300
+
301
+ ```ts
302
+ new ChartEngine({
303
+ container,
304
+ labels: {
305
+ trade: { buy: 'Acheter', sell: 'Vendre', limit: 'Limite', stop: 'Stop' },
306
+ ohlc: { open: 'O', high: 'H', low: 'L', close: 'C', volume: 'V' },
307
+ chart: { loading: 'Chargement…', scrollToLatest: 'Aller à la fin' },
308
+ },
309
+ });
310
+ ```
311
+
312
+ | Section | Key | Default | Description |
313
+ |---|---|---|---|
314
+ | `topBar.series` | `candlestick` | `'Candles'` | Series dropdown label |
315
+ | `topBar.series` | `hollow` | `'Hollow'` | |
316
+ | `topBar.series` | `ohlc` | `'OHLC'` | |
317
+ | `topBar.series` | `line` | `'Line'` | |
318
+ | `topBar.series` | `area` | `'Area'` | |
319
+ | `topBar` | `indicatorsBtn` | `'Indicators'` | Indicators button (no active) |
320
+ | `topBar` | `indicatorsBtnActive` | `'Indicators ({count})'` | `{count}` replaced at runtime |
321
+ | `topBar` | `toggleDrawingTitle` | `'Toggle Drawing Tools'` | Toolbar toggle tooltip |
322
+ | `topBar` | `toggleFullscreenTitle` | `'Toggle Fullscreen'` | Fullscreen button tooltip |
323
+ | `topBar` | `exitFullscreenTitle` | `'Exit Fullscreen'` | |
324
+ | `topBar` | `settingsTitle` | `'Settings'` | Settings button tooltip |
325
+ | `topBar.timeframes` | `'1m'`…`'1Y'` | same as key | Timeframe dropdown labels |
326
+ | `bottomBar.durations` | `'1D'`…`'All'` | same as key | Duration button labels |
327
+ | `drawingToolbar.categories` | `trendLines` | `'Trend Lines & Channels'` | Category flyout heading |
328
+ | `drawingToolbar.categories` | `fibonacci` | `'Fibonacci & Gann Tools'` | |
329
+ | `drawingToolbar.categories` | `shapes` | `'Geometric Shapes'` | |
330
+ | `drawingToolbar.categories` | `measurements` | `'Measurement Tools'` | |
331
+ | `drawingToolbar.categories` | `pitchforks` | `'Pitchfork Tools'` | |
332
+ | `drawingToolbar.categories` | `volume` | `'Volume & Statistical Tools'` | |
333
+ | `drawingToolbar.categories` | `annotations` | `'Annotation Tools'` | |
334
+ | `drawingToolbar.categories` | `patterns` | `'Pattern Recognition'` | |
335
+ | `drawingToolbar.categories` | `elliott` | `'Elliott Wave Tools'` | |
336
+ | `drawingToolbar.categories` | `brushes` | `'Brush & Highlighter'` | |
337
+ | `drawingToolbar.categories` | `advanced` | `'Advanced Tools'` | |
338
+ | `drawingToolbar.categories` | `priceProjection` | `'Price Projection Tools'` | |
339
+ | `drawingToolbar.tools` | *(DrawingToolType)* | English name per tool | One entry per `DrawingToolType` |
340
+ | `drawingToolbar.actions` | `undo` | `'Undo (Ctrl+Z)'` | Action button tooltips |
341
+ | `drawingToolbar.actions` | `redo` | `'Redo (Ctrl+Y)'` | |
342
+ | `drawingToolbar.actions` | `deleteSelected` | `'Delete Selected (Del)'` | |
343
+ | `drawingToolbar.actions` | `clearAll` | `'Clear All'` | |
344
+ | `drawingToolbar.actions` | `showAll` | `'Show All'` | |
345
+ | `drawingToolbar.actions` | `hideAll` | `'Hide All'` | |
346
+ | `drawingToolbar.actions` | `lockAll` | `'Lock All'` | |
347
+ | `drawingToolbar.actions` | `unlockAll` | `'Unlock All'` | |
348
+ | `drawingToolbar` | `soonBadge` | `'soon'` | Badge on unimplemented tools |
349
+ | `drawingToolbar` | `scrollUp` | `'Scroll up'` | Scroll indicator tooltip |
350
+ | `drawingToolbar` | `scrollDown` | `'Scroll down'` | |
351
+ | `ohlc` | `open` | `'O'` | OHLC strip label |
352
+ | `ohlc` | `high` | `'H'` | |
353
+ | `ohlc` | `low` | `'L'` | |
354
+ | `ohlc` | `close` | `'C'` | |
355
+ | `ohlc` | `volume` | `'V'` | |
356
+ | `trade` | `buy` | `'Buy'` | Trade popover label |
357
+ | `trade` | `sell` | `'Sell'` | |
358
+ | `trade` | `limit` | `'Limit'` | Order type label |
359
+ | `trade` | `stop` | `'Stop'` | |
360
+ | `trade` | `market` | `'Market'` | |
361
+ | `trade` | `qty` | `'Qty'` | Draft order qty input label |
362
+ | `trade` | `placeOrderTitle` | `'Place order at this price'` | Trade button tooltip |
363
+ | `dialogs.settings` | `title` | `'Chart Settings'` | Dialog title |
364
+ | `dialogs.settings` | `tfcSection` | `'Trade from Charts'` | Section heading |
365
+ | `dialogs.settings` | `showLabel` | `'Show'` | Radio group label |
366
+ | `dialogs.settings` | `positionStyleLabel` | `'Position Style'` | |
367
+ | `dialogs.settings` | `showAll` | `'All'` | Filter option |
368
+ | `dialogs.settings` | `showPositions` | `'Positions Only'` | |
369
+ | `dialogs.settings` | `showOrders` | `'Orders Only'` | |
370
+ | `dialogs.settings` | `styleLine` | `'Full Line'` | Style option |
371
+ | `dialogs.settings` | `styleDot` | `'Dot'` | |
372
+ | `dialogs.indicator` | `titleSuffix` | `'Settings'` | Appended to indicator name in dialog title |
373
+ | `dialogs.indicator` | `noParams` | `'No configurable parameters.'` | Empty-state message |
374
+ | `chart` | `loading` | `'Loading…'` | Loading overlay text |
375
+ | `chart` | `scrollToLatest` | `'Scroll to latest'` | Jump-to-end button tooltip |
376
+ | `chart` | `nIndicators` | `'{count} indicators'` | Collapse button; `{count}` replaced at runtime |
377
+
378
+ ### `ChartTheme` — colors
379
+
380
+ ```ts
381
+ import { DARK_THEME, LIGHT_THEME } from '@acttrader/stockchart';
382
+ import type { ChartTheme } from '@acttrader/stockchart';
383
+ ```
384
+
385
+ Pass a full or partial theme object to customize colors:
386
+
387
+ ```ts
388
+ const myTheme: ChartTheme = {
389
+ ...DARK_THEME,
390
+ background: '#0a0a0a',
391
+ candle: { up: '#00c853', down: '#ff1744', wickUp: '#00c853', wickDown: '#ff1744' },
392
+ };
393
+
394
+ new ChartEngine({ container, theme: 'dark' });
395
+ // Theme override is applied via setTheme() after construction:
396
+ chart.setTheme('light');
397
+ ```
398
+
399
+ | Top-level key | Type | Description |
400
+ |---|---|---|
401
+ | `background` | `string` | Canvas background color |
402
+ | `grid` | `string` | Grid line color |
403
+ | `axisText` | `string` | Axis tick label color |
404
+ | `axisBorder` | `string` | Axis border / separator color |
405
+ | `crosshair` | `string` | Crosshair line color |
406
+ | `tooltip` | `{background, text, border}` | Floating label colors |
407
+ | `candle` | `{up, down, wickUp, wickDown}` | Candlestick colors |
408
+ | `volume` | `{up, down}` | Volume bar colors |
409
+ | `ui` | `ChartThemeUi` | Accent and interactive-state colors |
410
+ | `drawingToolbar` | `DrawingToolbarColors` | Icon and flyout colors |
411
+ | `topBar` | `TopBarColors` | Top bar button and flyout colors |
412
+ | `bottomBar` | `BottomBarColors` | Duration button colors |
413
+ | `indicatorOverlay` | `IndicatorOverlayColors` | Pill and dropdown colors |
414
+ | `tradeLevels` | `TradeLevelColors` | Level line and info box colors |
415
+ | `tradePanel` | `TradePanelColors` | Trade panel side-drawer colors |
416
+
417
+ ---
418
+
419
+ ## API Reference
420
+
421
+ ### Data
422
+
423
+ ```ts
424
+ chart.loadData(bars: OHLCVBar[]): this
425
+ chart.prependData(bars: OHLCVBar[]): this // prepend historical bars (infinite scroll back)
426
+ chart.correctBar(barTime: number, bar: OHLCVBar): void // replace a bar with authoritative data after close
427
+ chart.setLoading(loading: boolean): this
428
+ ```
429
+
430
+ ### Series & Appearance
431
+
432
+ ```ts
433
+ chart.setSeries(series: SeriesType): this
434
+ chart.setTheme('dark' | 'light'): this
435
+ chart.setVolume(show: boolean): this
436
+ ```
437
+
438
+ `SeriesType` values: `candlestick` · `hollow` · `line` · `area` · `ohlc` · `heikinashi` · `volumecandles` · `linemarkers` · `step` · `hlcarea` · `baseline` · `columns` · `highlow`
439
+
440
+ ### Indicators
441
+
442
+ ```ts
443
+ chart.addIndicator(indicator: IIndicator): this
444
+ chart.removeIndicator(name: string): this
445
+ ```
446
+
447
+ ### Drawing Tools
448
+
449
+ ```ts
450
+ chart.setDrawingTool(tool: DrawingToolType | null): this
451
+ ```
452
+
453
+ ### Trade Levels (TFC)
454
+
455
+ ```ts
456
+ chart.setLevels(levels, labelField, priceField, type): this
457
+ chart.removeLevelByLabel(label: string): this
458
+ ```
459
+
460
+ ### Trade Button
461
+
462
+ ```ts
463
+ chart.setOrderLots(lots: number): void // update default qty at runtime
464
+ ```
465
+
466
+ ### Viewport
467
+
468
+ ```ts
469
+ chart.isAtLiveEdge(): boolean
470
+ ```
471
+
472
+ ### Streaming
473
+
474
+ ```ts
475
+ chart.connectStream(adapter: IWebSocketAdapter): this
476
+ chart.disconnectStream(): this
477
+ chart.pushTick(tick: { time: number; bid: number; ask: number; volume?: number }): void // push a tick directly without an adapter
478
+ ```
479
+
480
+ ### State Persistence
481
+
482
+ ```ts
483
+ // Capture the full current configuration as a serializable object
484
+ const state: ChartState = chart.getState();
485
+ // → { symbol, timeframe, duration, series, showVolume, theme, indicators[], drawings[] }
486
+
487
+ // Restore a previously saved state (call after construction, before loadData)
488
+ chart.setState(state);
489
+ ```
490
+
491
+ Use together with the `stateChange` event to persist chart config per symbol:
492
+
493
+ ```ts
494
+ import type { ChartState } from '@acttrader/stockchart';
495
+
496
+ chart.on('stateChange', ({ symbol, state }) => {
497
+ localStorage.setItem(`chart:${symbol}`, JSON.stringify(state));
498
+ });
499
+
500
+ // On init — restore if available
501
+ const saved = localStorage.getItem(`chart:${symbol}`);
502
+ if (saved) chart.setState(JSON.parse(saved) as ChartState);
503
+ ```
504
+
505
+ ### Lifecycle
506
+
507
+ ```ts
508
+ chart.destroy(): void
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Built-in Indicators
514
+
515
+ ### Trend / Price Overlay
516
+
517
+ | Class | Constructor defaults |
518
+ |---|---|
519
+ | `SMA(period?, color?)` | period 20 |
520
+ | `EMA(period?, color?)` | period 20 |
521
+ | `WMA(period?, color?)` | period 20 |
522
+ | `HMA(period?, color?)` | period 20 — Hull MA |
523
+ | `BollingerBands(period?, mult?, color?)` | 20, 2 |
524
+ | `KeltnerChannels(period?, mult?, color?)` | 20, 2 |
525
+ | `DonchianChannels(period?, color?)` | 20 |
526
+ | `IchimokuCloud(tenkan?, kijun?, senkou?, color?)` | 9, 26, 52 |
527
+ | `ParabolicSAR(step?, max?, color?)` | 0.02, 0.2 |
528
+ | `Supertrend(period?, mult?, color?)` | 10, 3 |
529
+ | `MARibbon(color?)` | 8 EMAs (10–80) |
530
+ | `PivotPoints(color?)` | Standard pivot levels |
531
+ | `FibRetracementIndicator(color?)` | Auto swing high/low |
532
+ | `HeikinAshi(color?)` | Heikin-Ashi candle overlay |
533
+ | `LinearRegression(period?, color?)` | Linear regression line overlay |
534
+
535
+ ### Sub-pane Oscillators
536
+
537
+ | Class | Constructor defaults |
538
+ |---|---|
539
+ | `RSI(period?, color?)` | 14 |
540
+ | `MACD(fast?, slow?, signal?, color?)` | 12, 26, 9 |
541
+ | `Stochastic(kPeriod?, dPeriod?, color?)` | 14, 3 |
542
+ | `StochRSI(rsiPeriod?, stochPeriod?, kPeriod?, dPeriod?, color?)` | 14, 14, 3, 3 |
543
+ | `CCI(period?, color?)` | 20 |
544
+ | `WilliamsR(period?, color?)` | 14 |
545
+ | `ROC(period?, color?)` | 12 |
546
+ | `ATR(period?, color?)` | 14 |
547
+ | `ADX(period?, color?)` | 14 — includes +DI / -DI |
548
+ | `OBV(color?)` | On Balance Volume |
549
+ | `VWAP(color?)` | Intra-day weighted average |
550
+ | `AccDistribution(color?)` | Accumulation/Distribution |
551
+ | `MFI(period?, color?)` | 14 — Money Flow Index |
552
+ | `CMF(period?, color?)` | 20 — Chaikin Money Flow |
553
+ | `AwesomeOscillator(color?)` | 5 / 34 SMA difference |
554
+ | `VOL(color?)` | Volume oscillator sub-pane |
555
+
556
+ Sub-pane heights are user-resizable by dragging the separator between panes.
557
+
558
+ ---
559
+
560
+ ## Drawing Tools
561
+
562
+ ### Lines & Channels
563
+
564
+ | Key | Tool | Placement |
565
+ |---|---|---|
566
+ | `horizontalLine` | Horizontal Line | Single click |
567
+ | `verticalLine` | Vertical Line | Single click |
568
+ | `trendLine` | Trend Line | 2 clicks |
569
+ | `trendAngle` | Trend Angle | 2 clicks (shows angle) |
570
+ | `crossLine` | Cross Line | Single click (H+V) |
571
+ | `parallelChannel` | Parallel Channel | 3 clicks |
572
+ | `flatChannel` | Flat Channel | 2 clicks |
573
+ | `disjointChannel` | Disjoint Channel | 3 clicks |
574
+
575
+ ### Fibonacci & Gann
576
+
577
+ `fibRetracement` · `fibExtension` · `fibChannel` · `fibTimezone` · `fibCircles` · `fibSpiral` · `fibFan` · `fibProjection` · `gannFan` · `gannSquare` · `gannBox`
578
+
579
+ ### Shapes
580
+
581
+ `rectangle` · `rotatedRectangle` · `ellipse` · `triangle` · `circle` · `arc` · `polyline` · `path` · `brush` · `highlighter`
582
+
583
+ ### Measurements & Annotations
584
+
585
+ `priceRange` · `dateRange` · `datePriceRange` · `ruler` · `priceLabel` · `priceNote` · `priceProjection` · `projection` · `arrowUp` · `arrowDown` · `arrowMarker` · `text` · `callout` · `anchoredNote` · `flag` · `sineLine` · `regressionTrend` · `ghostFeed`
586
+
587
+ ### Volume Profile
588
+
589
+ `volumeProfile` · `anchoredVP` · `fixedRangeVP`
590
+
591
+ ### Cyclic & Time
592
+
593
+ `cyclicLines` · `timeCycles`
594
+
595
+ ### Harmonic & Elliott Patterns
596
+
597
+ `abcdPattern` · `headShoulders` · `trianglePattern` · `batPattern` · `butterflyPattern` · `crabPattern` · `gartleyPattern` · `cypherPattern` · `sharkPattern` · `threeDrives` · `elliottImpulse` · `elliottCorrective` · `elliottTriangle` · `elliottCombination` · `elliottWxy`
598
+
599
+ ### Pitchforks
600
+
601
+ `pitchfork` · `schiffPitchfork` · `modifiedSchiff` · `insidePitchfork`
602
+
603
+ ---
604
+
605
+ ## Events
606
+
607
+ ```ts
608
+ chart.on('crosshair', ({ x, y, barIndex, price, bar }) => {});
609
+ chart.on('click', ({ x, y, barIndex, price, bar }) => {});
610
+ chart.on('zoom', ({ viewport }) => {});
611
+ chart.on('pan', ({ viewport }) => {});
612
+ chart.on('timeframeChange',({ timeframe }) => {});
613
+ chart.on('durationChange', ({ duration, timeframe }) => {});
614
+ chart.on('seriesChange', ({ series }) => {});
615
+ chart.on('streamStatus', ({ status }) => {}); // 'connected' | 'reconnecting' | 'disconnected'
616
+ chart.on('dataLoaded', ({ timeframe, interval, start, end }) => {});
617
+ chart.on('newBar', ({ completedBar, openingBar, intervalMs }) => {});
618
+ chart.on('stateChange', ({ symbol, state }) => {
619
+ // Fires after every user-driven config change (timeframe, series, theme,
620
+ // indicators, drawings, volume). `state` is a full serializable snapshot.
621
+ localStorage.setItem(`chart:${symbol}`, JSON.stringify(state));
622
+ });
623
+
624
+ // TFC events
625
+ chart.on('tradeLevelEdit', ({ label, type, data, changes }) => {});
626
+ chart.on('tradeLevelClose', ({ label, type, action, data }) => {});
627
+ chart.on('tradeLevelDragEnd', ({ label, type, newPrice, data }) => {}); // deprecated
628
+ chart.on('tradeLevelBracketDrag', ({ label, bracketType, newPrice, data }) => {}); // deprecated
629
+ chart.on('tradeLevelEditOpen', ({ label, type, price, side, stopLossPrice, takeProfitPrice, data }) => {});
630
+ chart.on('tradeLevelConfirmed',({ label, type }) => {});
631
+ ```
632
+
633
+ ---
634
+
635
+ ## Custom Indicator
636
+
637
+ Implement `IIndicator` and pass it to `addIndicator()`:
638
+
639
+ ```ts
640
+ import type { IIndicator, OHLCVBar, IndicatorResult } from '@acttrader/stockchart';
641
+
642
+ class MyIndicator implements IIndicator {
643
+ name = 'MyInd';
644
+ color = '#e91e63';
645
+ pane = 'sub' as const; // 'main' = overlay, 'sub' = dedicated pane
646
+ paneHeight = 80;
647
+
648
+ compute(data: OHLCVBar[]): IndicatorResult[] {
649
+ return data.map((bar, i) => ({ index: i, value: bar.close }));
650
+ }
651
+
652
+ render(ctx, results, scale, viewport, chartHeight) {
653
+ // draw with ctx (Canvas 2D)
654
+ }
655
+ }
656
+
657
+ chart.addIndicator(new MyIndicator());
658
+ ```
659
+
660
+ ---
661
+
662
+ ## Build & Dev
663
+
664
+ ```bash
665
+ npm run dev # Vite dev server with demo chart
666
+ npm run build # ESM + CJS via tsup → dist/
667
+ npm run test # Vitest unit tests
668
+ npm run typecheck # tsc --noEmit
669
+ npm run lint # ESLint
670
+ ```
671
+
672
+ ---
673
+
674
+ ## License
675
+
676
+ MIT