# TradeCanvas > Trading chart library for the web, drawn with Canvas 2D or WebGL (TypeScript, no runtime dependencies): candlestick, Heikin-Ashi, Renko and other chart types, 134 indicators, drawing tools, live exchange feeds, orders and positions on the chart, alerts, replay and backtesting. npm: @tradecanvas/chart (Chart and the ChartWidget UI), @tradecanvas/react, @tradecanvas/vue, @tradecanvas/svelte, @tradecanvas/analytics. Use `ChartWidget` (`@tradecanvas/chart/widget`) for a complete trading UI, or `Chart` (`@tradecanvas/chart`) to build your own around it. Both need a sized DOM element and run in the browser only. ## Docs - [Getting started](https://tradecanvas.dev/docs/getting-started): install, the widget, the headless chart - [API reference](https://tradecanvas.dev/docs/api): Chart and ChartWidget methods, options and events - [Indicators](https://tradecanvas.dev/docs/indicators): every built-in indicator with its id, inputs and lines - [Styling](https://tradecanvas.dev/docs/styling): the widget's look as tokens: the Studio, Terminal and Capsule presets, your own theme, CSS variables - [Customization](https://tradecanvas.dev/docs/customization): every way to customise: themes, looks, style overrides, the widget's 114 feature switches, your own toolbar, sidebar, status bar, menu entries and shortcuts, your own CSS - [Chart types](https://tradecanvas.dev/docs/chart-types): candlestick, Heikin-Ashi, Renko, Kagi, point & figure and more - [Drawing tools](https://tradecanvas.dev/docs/drawing-tools): trend lines, Fibonacci, Gann, patterns, text - [Realtime and replay](https://tradecanvas.dev/docs/realtime): data adapters (Binance, Coinbase, Bybit, Kraken, WebSocket, polling) and replay - [Trading](https://tradecanvas.dev/docs/trading): orders, positions, brackets and execution adapters on the chart - [Frameworks](https://tradecanvas.dev/docs/frameworks): React, Vue and Svelte components - [Plugins](https://tradecanvas.dev/docs/plugins): custom indicators, drawings and chart types - [Analytics](https://tradecanvas.dev/docs/analytics): strategy backtester and risk metrics ## Agent skill - [SKILL.md](https://github.com/bonguynvan/tradecanvas/blob/main/skills/tradecanvas/SKILL.md): how to build with TradeCanvas, the rules that avoid most bugs, worked examples - [Indicator reference](https://github.com/bonguynvan/tradecanvas/blob/main/skills/tradecanvas/references/indicators.md): ids, default params, lines and levels - [Recipes](https://github.com/bonguynvan/tradecanvas/blob/main/skills/tradecanvas/references/recipes.md): live data, custom adapters, indicators on indicators, custom indicators, layouts ## Optional - [Everything in one file](https://tradecanvas.dev/llms-full.txt): the library README, the skill and its references - [Source](https://github.com/bonguynvan/tradecanvas) ============================================================================== skills/tradecanvas/SKILL.md ============================================================================== --- name: tradecanvas description: Build trading and financial charts with TradeCanvas (@tradecanvas/chart) — candlesticks and 17 other chart types, 134 indicators, drawing tools, live exchange data, orders and positions on the chart, signals and trade zones, alerts, replay, a full trading widget. Use when a project imports @tradecanvas/*, or when asked to add a price, candlestick or trading chart to a web app (vanilla TS/JS, React, Vue, Svelte). --- # TradeCanvas TradeCanvas draws trading charts in TypeScript, with no runtime dependencies: on Canvas 2D by default, or with the series, indicators and panes on WebGL 2 (`renderer: 'webgl'`, or `'auto'` where the GPU is a real one). One package, `@tradecanvas/chart`, holds both the chart engine and a complete trading UI around it. ## Pick the entry point | You need | Use | |---|---| | A full trading UI: toolbar, drawing sidebar, indicator legend, settings, status bar | `ChartWidget` from `@tradecanvas/chart/widget` | | A chart inside your own UI | `Chart` from `@tradecanvas/chart` | | React, Vue or Svelte components | `@tradecanvas/react`, `@tradecanvas/vue`, `@tradecanvas/svelte` | | Strategy backtests and risk metrics | `@tradecanvas/analytics` | `widget.getChart()` returns the `Chart` inside a widget, so everything below also works on a widget. ## Rules that avoid most bugs 1. **Give the container a size** (width and height) before creating the chart. The chart follows later size changes by itself. 2. **Browser only.** In SSR frameworks (Next, Nuxt, SvelteKit) create the chart in `useEffect` / `onMounted` / `onMount`, importing it there with `await import('@tradecanvas/chart')` if the module is also loaded on the server. 3. **Call `destroy()`** when the element goes away (component unmount). 4. **Bars** are `{ time, open, high, low, close, volume }`, ascending by time. `time` may be milliseconds or seconds (values up to 1e12 are read as seconds). Keep one unit per series. 5. **Indicator ids are short** — `'rsi'`, `'bb'`, `'psar'`, `'stochastic'` — see [references/indicators.md](references/indicators.md). `addIndicator` returns an instance id (`null` if the feature or id is turned off); later calls take that instance id, not the indicator id. 6. **Live updates:** `appendBar` for a new bar, `updateLastBar` for the forming one. Never `setData` on every tick: it resets the view and recomputes everything. 7. Read values with `chart.getIndicatorOutput(instanceId)?.series[i]` (an object keyed by the indicator's line keys), not by guessing from the drawing. 8. **Look names up when the TradeCanvas MCP server is connected** (tools named `get_indicator`, `validate_options`…, from `@tradecanvas/mcp`): an indicator's params and line keys with `get_indicator`, drawing types with `list_drawing_tools`, and an options object through `validate_options` before it goes to `new ChartWidget` or `new Chart`. ## Quick start: the widget with live data ```ts 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(), locale: 'en', // 'vi' too; 28 more from '@tradecanvas/chart/widget/locales' (`messages`) onReady: (chart) => { chart.addIndicator('ema', { period: 50 }); chart.addIndicator('rsi', { period: 14 }); }, }); // Later, when the page goes away: widget.destroy(); ``` ## Quick start: a chart with your own data ```ts import { Chart, type OHLCBar } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, { chartType: 'candlestick', theme: 'dark', features: { indicators: true, drawings: true, volume: true }, }); const bars: OHLCBar[] = await fetch('/api/bars?symbol=AAPL&tf=1d').then((r) => r.json()); chart.setData(bars); // A new bar opens, then the forming bar ticks: const last = bars[bars.length - 1]; chart.appendBar({ time: last.time + 86_400_000, open: last.close, high: last.close, low: last.close, close: last.close, volume: 0 }); chart.updateLastBar({ ...chart.getData()[chart.getData().length - 1], close: last.close + 1.25 }); ``` For a live feed, give the chart an adapter instead and it handles history, streaming and reconnects: `chart.connect({ adapter, symbol, timeframe })`. Built-in adapters: `BinanceAdapter`, `CoinbaseAdapter`, `BybitAdapter`, `KrakenAdapter`, `MockAdapter` (random data for demos), and two bases for your own feeds — `WebSocketAdapter` and `PollingAdapter` (see [references/recipes.md](references/recipes.md)). ## Indicators ```ts import { Chart, indicatorSource } from '@tradecanvas/chart'; declare const chart: Chart; const ema = chart.addIndicator('ema', { period: 21 })!; // on the price pane const rsi = chart.addIndicator('rsi', { period: 14 }, 'bottom')!; // a pane of its own chart.updateIndicator(ema, { period: 34 }); // change inputs chart.updateIndicator(ema, { source: 'hlc3' }); // compute from another price chart.setIndicatorLevels(rsi, [20, 50, 80]); // RSI's reference lines (null = defaults) chart.updateIndicatorStyle(rsi, { colors: ['#f2a93b'], lineWidths: [2] }); chart.setIndicatorVisible(ema, false); // An indicator on another indicator's line: an SMA of RSI is drawn in RSI's pane. const smoothed = chart.addIndicator('sma', { period: 9, source: indicatorSource(rsi, 'value') }); // Values at the latest bar, by line key: const series = chart.getIndicatorOutput(rsi)?.series ?? []; const latestRsi = series[series.length - 1]?.value; chart.removeIndicator(rsi); // also removes `smoothed`, which reads from it ``` - Panel indicators may share a pane: `addIndicator('stochastic', {}, 'bottom', { pane: rsi })`. - Each line's latest value is tagged on its axis; turn that off with `features.indicatorValueLabels: false`. - Custom indicators: extend `IndicatorBase`, declare `plots`, and register with `chart.registerIndicator(...)` — see [references/recipes.md](references/recipes.md). ## Drawings, alerts and orders ```ts import type { Chart } from '@tradecanvas/chart'; declare const chart: Chart; declare const t1: number, t2: number; // bar times chart.setDrawingTool('fibRetracement'); // the user clicks the points; null cancels const line = chart.addDrawing({ type: 'trendLine', anchors: [{ time: t1, price: 61_800 }, { time: t2, price: 63_400 }], options: { extendRight: true }, // each tool's own settings: chart.getDrawingOptionDefs(type) }); if (line && chart.canAddDrawingAlert(line)) chart.addDrawingAlert(line, { condition: 'crossingDown' }); chart.addAlert(65_000, 'crossingUp', 'BTC above 65k'); chart.setPositions([{ id: 'p1', side: 'buy', entryPrice: 62_500, quantity: 0.5, stopLoss: 61_000, takeProfit: 66_000 }]); chart.on('positionModify', (e) => console.log('stop or target dragged', e.payload)); ``` 69 drawing tools; each keeps its own settings (`setDrawingOptions`, `updateDrawing`), and drawings can be grouped (`groupDrawings`), reordered (`moveDrawing`) and erased (`setEraserMode`). `riskReward` is a Long/Short position sized from `accountSize` and `risk`. ## Signals, trades and replay ```ts import type { Chart } from '@tradecanvas/chart'; declare const chart: Chart; declare const t1: number, t2: number; // bar times chart.setSignalMarkers([{ id: 's1', time: t1, price: 62_000, direction: 'long', confidence: 0.8, source: 'my-strategy' }]); chart.setTradeZones([{ id: 'z1', direction: 'long', entryTime: t1, entryPrice: 62_000, exitTime: t2, exitPrice: 63_500 }]); // Replay from a time, only the replayed bars on the chart, the whole run in about 3.5 s. // Markers and zones appear as the replay reaches them. chart.replayStart({ startTime: t1, hideHistory: true, duration: 3500 }); chart.on('replayComplete', () => chart.replayStop()); // every bar and mark back ``` `replayPause`, `replayResume`, `replaySeekToTime` and `replaySeekToBar` steer it; `revealMarks: false` shows every mark from the start. ## Saving layouts ```ts import type { Chart } from '@tradecanvas/chart'; declare const chart: Chart; const json = chart.saveState(); // chart type, theme, drawings, indicators, alerts if (json) localStorage.setItem('layout', json); chart.loadState(localStorage.getItem('layout') ?? '{}'); chart.setAutoSave('layout', 1500); // or keep it saved as it changes ``` Saved indicators keep their inputs, sources, panes, colours, visibility and levels. ## Events `chart.on(event, (e) => …)` — the payload is `e.payload`. | Event | Payload | |---|---| | `crosshairMove` | `{ point, bar, barIndex }` (also over indicator panes) | | `crosshairLeave` | — | | `barClick` | `{ bar, barIndex, point }` | | `visibleRangeChange` | `{ from, to }` bar indices | | `dataUpdate` | the series | | `indicatorAdd` / `indicatorRemove` | `{ instanceId, id }` | | `indicatorChange` | `{ instanceId, change }`: `'visible' \| 'style' \| 'levels' \| 'params' \| 'pane'` | | `indicatorUpdate` | `{ from }`: values recomputed from that bar on (once per update) | | `drawingCreate` | `{ id, type, drawing }` (also when an undo brings a drawing back) | | `drawingUpdate`, `drawingRemove`, `drawingDoubleClick` | `{ id }` | | `drawingContextMenu` | `{ id, x, y }`: a drawing was right-clicked | | `toolModeChange` | `{ eraser }` or `{ zoomArea }` | | `orderModify`, `positionModify` | the object | | `replayStep` | `{ barIndex, time, until, total }`: a replay step | | `replayState` | `{ state }`: `'playing' \| 'paused' \| 'stopped'` | | `replayComplete` | `{ barIndex, time }`: the replay reached its last bar | | `styleChange` | `{ layer }`: `'host'` or `'user'` overrides changed | | `rendererChange` | `{ renderer, reason? }`: WebGL fell back to Canvas 2D, or came back | ## Theming `theme: 'dark' | 'light'` or a `Theme` object (`{ ...DARK_THEME, candleUp: '#1fa874' }`); `chart.setTheme(...)` at runtime. Numbers follow `numberLocale` (`'en-US'`, `'vi-VN'` …). One part of the look apart from the theme: `chart.applyOverrides({ 'grid.vertical.visible': false, 'series.candlestick.upColor': '#26a69a' })` (keys in `CHART_STYLE_KEYS`, `null` takes one away; `overrides` in the options and the React/Vue/Svelte props). `getTheme()` is the theme as set; what is drawn reads back from `getStyleValue(key)`. Orders, positions, signal markers, trade zones and drawing handles take keys too (`trading.buyColor`, `markers.longColor`, `tradeZones.activeColor`, `drawings.handleColor`). Each indicator plot's dash and visibility: `updateIndicatorStyle(id, { plots: { signal: { lineStyle: 'dashed' } } })`. The widget's Settings edit these keys on the user's layer: the Style tab the chart's parts and the colours of the chart type on view, the Trading tab the orders', markers' and zones' colours (Auto until one is picked). The widget's parts: `features: { 'toolbar.replay': false, hotkeys: false }` and `widget.setFeatures(patch)` at runtime (names in `WIDGET_FEATURES`; no dot = a whole capability everywhere, dotted = one place). They hide the widget's UI only; to stop drawing, trading or zoom on the chart, use `chartOptions.features`. Your own parts: `addToolbarButton`, `addToolbarDropdown`, `addSidebarButton`, `addStatusBarItem`, `getSlot('chart')`, `addHotkey`, and the `chartMenuItems` / `drawingMenuItems` / `indicatorMenuItems` options. Style the widget with your own CSS through `--tcw-*` variables and `[data-tcw-part~="name"]` (stable through 1.x). ## When something looks wrong | Symptom | Check | |---|---| | Blank chart | The container has no height (check its computed size) | | Garbled candles, wrong dates on the axis | Bars not ascending by time: `setData` does not sort them | | Dates in 1970 | `time` in seconds treated as ms (or the reverse) mixed in one series | | `addIndicator` returns `null` | `features.indicators` is off, or `features.indicatorIds` leaves it out | | Indicator line missing at the start | Warm-up: no value until its period has enough bars | | Chart stutters on a fast feed | `setData` called per tick instead of `updateLastBar` / `appendBar` | | A React/Vue/Svelte chart shows Binance bars before yours | Pass `data` at mount, or `stream={false}` | ## References - [references/indicators.md](references/indicators.md) — every built-in indicator: id, default params, lines, levels, source support (generated from the code). - [references/recipes.md](references/recipes.md) — worked examples: custom data feeds, frameworks, indicators on indicators, a custom indicator, multi-chart layouts. - Docs site: https://tradecanvas.dev/ · API: https://tradecanvas.dev/docs/api ============================================================================== skills/tradecanvas/references/recipes.md ============================================================================== # TradeCanvas recipes Worked examples. Every `ts` block here is type-checked against the library in CI (`pnpm docs:check`), so the calls are real. ## Your own data feed over REST `PollingAdapter` turns any "give me the last N bars" endpoint into a live feed: it loads history, polls, updates the forming bar and closes it on rollover. ```ts import { Chart, PollingAdapter, type OHLCBar, type TimeFrame } from '@tradecanvas/chart'; const feed = new PollingAdapter({ name: 'my-api', intervalMs: 5_000, fetchBars: async (symbol: string, timeframe: TimeFrame, limit: number): Promise => { const res = await fetch(`/api/candles?symbol=${symbol}&tf=${timeframe}&limit=${limit}`); const rows: [number, number, number, number, number, number][] = await res.json(); return rows.map(([time, open, high, low, close, volume]) => ({ time, open, high, low, close, volume })); }, }); const chart = new Chart(document.getElementById('chart')!, { theme: 'dark' }); await chart.connect({ adapter: feed, symbol: 'AAPL', timeframe: '1h', historyLimit: 500 }); ``` ## Your own data feed over WebSocket `WebSocketAdapter` handles connecting, reconnecting and the history request; you describe the URL, the subscribe message and how to read a frame. ```ts import { Chart, WebSocketAdapter, type OHLCBar } from '@tradecanvas/chart'; interface KlineFrame { t: number; o: string; h: string; l: string; c: string; v: string; final: boolean } const feed = new WebSocketAdapter({ name: 'my-exchange', wsUrl: () => 'wss://stream.example.com/ws', subscribeMessage: ({ symbol, timeframe }) => ({ op: 'subscribe', channel: `kline.${timeframe}.${symbol}` }), fetchHistory: async (symbol, timeframe, limit): Promise => { const res = await fetch(`https://api.example.com/klines?symbol=${symbol}&interval=${timeframe}&limit=${limit}`); return res.json(); }, parseMessage: (raw) => { const k = (raw as { kline?: KlineFrame }).kline; if (!k) return null; const bar = { time: k.t, open: +k.o, high: +k.h, low: +k.l, close: +k.c, volume: +k.v }; return { bar, closed: k.final }; }, }); const chart = new Chart(document.getElementById('chart')!, {}); await chart.connect({ adapter: feed, symbol: 'BTCUSDT', timeframe: '1m' }); ``` A feed that learns the symbol's details only after connecting (its decimals, price step or hours, say on its first message) sends a `symbolInfo` event with the `SymbolInfo`, as it sends its other events (`emitEvent('symbolInfo', info)` in a subclass of these adapters). The chart applies it: the price scale, legend and tags take the symbol's decimals, the scale's ticks its price step. ## React, Vue, Svelte The wrapper packages give a component with reactive props: ```tsx import { TradeCanvas } from '@tradecanvas/react'; import { BinanceAdapter } from '@tradecanvas/chart'; const adapter = new BinanceAdapter(); export function PriceChart({ symbol }: { symbol: string }) { return ( chart.setIndicatorValueLabelsVisible(true)} style={{ height: 480 }} /> ); } ``` `indicators` takes ids or `{ id, params, position }`; one whose inputs change is put back with them. With `data` at mount the component shows your bars and opens no stream; `stream={false}` never opens one (and closes one it had), so a chart mounted before its bars arrive doesn't start the default Binance feed. `features` follows its changes after mount. `@tradecanvas/vue` and `@tradecanvas/svelte` take the same data props (Vue emits `ready` and `crosshairMove` instead of the callback props). To drive the widget or a bare `Chart` from a framework, create it in the mount hook on a sized element and call `destroy()` in the unmount hook. ## A signal from an indicator on an indicator An SMA of RSI, drawn in RSI's pane, and a signal when RSI closes above it. It looks at the two last *closed* bars (the forming one still moves) and fires once per bar: ```ts import { Chart, BinanceAdapter, indicatorSource } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, { theme: 'dark' }); await chart.connect({ adapter: new BinanceAdapter(), symbol: 'ETHUSDT', timeframe: '15m' }); const rsi = chart.addIndicator('rsi', { period: 14 })!; const signal = chart.addIndicator('sma', { period: 9, source: indicatorSource(rsi, 'value') })!; let lastSignalBar = -1; chart.on('indicatorUpdate', () => { const r = chart.getIndicatorOutput(rsi)?.series ?? []; const s = chart.getIndicatorOutput(signal)?.series ?? []; const i = r.length - 2; // the bar that closed last if (i <= lastSignalBar) return; const [r0, r1, s0, s1] = [r[i - 1]?.value, r[i]?.value, s[i - 1]?.value, s[i]?.value]; if (r0 === undefined || r1 === undefined || s0 === undefined || s1 === undefined) return; if (r0 <= s0 && r1 > s1) { lastSignalBar = i; console.log('RSI closed above its average'); } }); ``` ## A backtest replayed on the chart ```ts import { Chart, type OHLCBar, type SignalMarker, type TradeZone } from '@tradecanvas/chart'; declare const bars: OHLCBar[]; // the whole lookback declare const signals: SignalMarker[]; // the backtest's buys and sells declare const trades: TradeZone[]; // and its trades const chart = new Chart(document.getElementById('chart')!, { theme: 'dark' }); chart.setData(bars); chart.setSignalMarkers(signals); chart.setTradeZones(trades); // Sweep the last 30 days in about 3.5 s: only those bars on the chart, each // mark appearing as the replay reaches it, each trade open until its exit. const DAY = 86_400_000; chart.replayStart({ startTime: bars[bars.length - 1].time - 30 * DAY, hideHistory: true, duration: 3500 }); chart.on('replayStep', (e) => console.log(`${e.payload.barIndex + 1} / ${e.payload.total}`)); chart.on('replayComplete', () => chart.replayStop()); // the whole series, every mark ``` `revealMarks: false` shows every mark through the replay. `duration` plays several bars a frame when it must; a frame that comes late slows the replay rather than skip ahead. The bars before `startTime` come back with `replayStop()`. ## Alerts beyond a price level A line crossing another line, a fast move, a close past a level, and an end date. They check on every price the feed sends, with the lines they watch taken at the same moment: ```ts import { Chart, BinanceAdapter } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, { theme: 'dark' }); await chart.connect({ adapter: new BinanceAdapter(), symbol: 'BTCUSDT', timeframe: '1h' }); const ema = chart.addIndicator('ema', { period: 50 })!; const rsi = chart.addIndicator('rsi', { period: 14 })!; chart.addAlert(NaN, 'crossingUp', 'above the 50 EMA', 'price', undefined, { target: `${ema}:value` }); chart.addAlert(NaN, 'movesDown', 'dump', 'price', undefined, { percent: 4, bars: 6 }); // bars: 2–500 chart.addAlert(70, 'greaterThan', 'RSI closed above 70', `${rsi}:value`, 'RSI', { onBarClose: true }); chart.addAlert(64_000, 'crossing', 'today only', 'price', undefined, { expiresAt: Date.now() + 86_400_000 }); chart.on('alertTriggered', (e) => console.log(e.payload.message, e.payload.channel, e.payload.target)); chart.on('alertExpired', (e) => console.log('expired', e.payload.id)); ``` ## Practising on a replay with a paper account Replay an hourly chart in 5-minute steps and trade it on paper: orders fill on the replayed prices and fills land on the replayed bars. Alerts keep watching the live market meanwhile. ```ts import { Chart, BinanceAdapter, PaperExecutionAdapter } from '@tradecanvas/chart'; const adapter = new BinanceAdapter(); const chart = new Chart(document.getElementById('chart')!, { theme: 'dark' }); await chart.connect({ adapter, symbol: 'BTCUSDT', timeframe: '1h' }); chart.connectExecution(new PaperExecutionAdapter()); const steps = await adapter.fetchHistory('BTCUSDT', '5m', 2000); chart.replayStart({ steps, startIndex: 300, paused: true, speed: 5 }); chart.replayResume(); // … place orders from the chart; chart.replaySeekToBar(i) jumps, chart.replayStop() goes back to live ``` ## Another symbol on its own scale, and the ratio of the two ETH beside BTC on a price scale of its own, and BTC ÷ ETH in a pane. The indicators ask for the bars of the symbol they read; fetch them again when the interval changes: ```ts import { Chart, BinanceAdapter } from '@tradecanvas/chart'; const adapter = new BinanceAdapter(); const chart = new Chart(document.getElementById('chart')!, { theme: 'dark' }); await chart.connect({ adapter, symbol: 'BTCUSDT', timeframe: '1h' }); const load = async (symbol: string) => chart.setSymbolSeries(symbol, await adapter.fetchHistory(symbol, '1h', 1000)); chart.on('symbolSeriesRequest', (e) => void load(e.payload.symbol)); chart.addIndicator('compareSymbol', { symbol: 'ETHUSDT' }, 'bottom', { scale: 'left' }); const ratio = chart.addIndicator('spread', { symbol: 'ETHUSDT', mode: 'ratio' })!; chart.setPaneScale(ratio, { percent: true }); ``` ## Prices in 32nds A bond future quoted in 32nds and half 32nds, on High-Low bars, regular session only: ```ts import { Chart } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, { chartType: 'hiLo', priceFormat: { denominator: 32, subDenominator: 2 }, // 110'165 = 110 and 16.5/32 extendedHours: false, }); chart.setSymbolInfo({ symbol: 'ZN', timezone: 'America/Chicago', sessions: [{ start: '07:20', end: '14:00' }] }); ``` ## A Vietnamese stock, and VN30 futures HOSE's rules on the chart: steps by price band, the day's ceiling and floor, lots of 100 in the order ticket, the price board's colours. Preset prices are in thousands of dong; `scaleMarketPrices(MARKET_HOSE, 1000)` reads dong. ```ts import { Chart, MARKET_HOSE, MARKET_VN30F, priceStepAt, vn30fContracts } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, { extendedHours: false }); chart.setMarket(MARKET_HOSE); chart.setPriceLimits(25.35); // the reference: yesterday's close console.log(chart.getPriceLimits()); // { ceiling: 27.1, floor: 23.6, reference: 25.35 } console.log(chart.roundPrice(25.37), priceStepAt(9.5, MARKET_HOSE)); // 25.35 0.01 // VN30 futures: the front contract and its last trading day const [front] = vn30fContracts(Date.now()); chart.setMarket(MARKET_VN30F); console.log(front.symbol, front.lastTradingDay); // e.g. VN30F2611 2026-11-19 ``` ## A dashboard: net flows, allocation and returns The finance charts take an element and their options, and size themselves to it. ```ts import { BarChart, DonutChart, HistogramChart } from '@tradecanvas/chart'; new BarChart(document.getElementById('flows')!, { categories: ['6/10', '7/10', '8/10'], series: [{ name: 'Net', values: [-660, 860, -280] }], colorBySign: true, onBarClick: ({ category, value }) => console.log(category, value), }); new DonutChart(document.getElementById('allocation')!, { data: [{ label: 'Stocks', value: 52 }, { label: 'Bonds', value: 23 }, { label: 'Cash', value: 25 }], centerLabel: 'Total', centerValue: '1.2B', }); new HistogramChart(document.getElementById('returns')!, { values: [-1.2, 0.4, 0.8, -0.3, 1.5, 0.2], binWidth: 0.5, colorBySign: true, markers: [{ value: 0.23, label: 'Mean' }], }); ``` Days on a calendar, correlations in a matrix, and a market map by sector: ```ts import { CalendarChart, MatrixChart, HeatmapChart, correlationMatrix } from '@tradecanvas/chart'; new CalendarChart(document.getElementById('sessions')!, { data: [{ date: '2025-01-02', value: 1.2 }, { date: '2025-01-03', value: -0.8 }], weekdays: 5, colorScale: { min: -2, max: 2 }, onDayClick: (day) => console.log(day.date, day.value), }); // Daily returns (not prices), one array per stock, the same days in each. const returns = [[0.4, -1.1, 0.8, 0.2], [0.6, -0.9, 1.1, -0.1], [-0.2, 0.5, -0.4, 0.3]]; new MatrixChart(document.getElementById('correlation')!, { rows: ['VCB', 'BID', 'HPG'], values: correlationMatrix(returns), triangle: 'lower', colorScale: { min: -1, max: 1 }, }); new HeatmapChart(document.getElementById('sectors')!, { data: [ { id: 'vcb', label: 'VCB', value: 0.8, weight: 480, group: 'Banks' }, { id: 'hpg', label: 'HPG', value: 1.9, weight: 160, group: 'Steel' }, ], grouped: true, }); ``` Price against volume as a counter-clock cycle, and indicators from Asian price boards: ```ts import { Chart, CounterClockChart } from '@tradecanvas/chart'; import type { OHLCBar } from '@tradecanvas/chart'; declare const bars: OHLCBar[]; declare const chart: Chart; const clock = new CounterClockChart(document.getElementById('clock')!, { data: bars, period: 25, points: 120 }); console.log(clock.getPhase()); // 1–8, counter-clockwise from volume rising chart.addIndicator('psy', { period: 10 }, 'bottom'); chart.addIndicator('vr', { period: 20 }, 'bottom'); chart.addIndicator('rainbow', { first: 10, step: 10, count: 10 }); ``` Candlestick patterns, on the chart or as data: ```ts import { Chart, detectCandlePatterns } from '@tradecanvas/chart'; import type { OHLCBar } from '@tradecanvas/chart'; declare const bars: OHLCBar[]; declare const chart: Chart; chart.addIndicator('candlepatterns'); // arrows and short names on the bars that end a pattern for (const m of detectCandlePatterns(bars, { patterns: ['bullishEngulfing', 'hammer', 'morningStar'] })) { console.log(new Date(m.time).toISOString(), m.pattern.name, m.pattern.direction); } ``` ## A custom indicator Extend `IndicatorBase` and declare what it draws (`plots`), its pane scale and levels; the chart then draws it, scales its pane, labels its values and lists it in the widget legend and settings with no rendering code of yours. ```ts import { Chart, IndicatorBase, IndicatorValueMap } from '@tradecanvas/chart'; import type { DataSeries, IndicatorConfig, IndicatorDescriptor, IndicatorOutput, IndicatorValue } from '@tradecanvas/chart'; /** Where the close sits in the last `period` bars' range, 0–100. */ class RangePosition extends IndicatorBase { descriptor: IndicatorDescriptor = { id: 'rangePosition', name: 'Range Position', shortName: 'RP', placement: 'panel', defaultConfig: { period: 20 }, inputs: { period: { min: 2, max: 500 } }, plots: [{ key: 'value', title: 'RP', color: 0 }], scale: { min: 0, max: 100 }, levels: [20, 80], }; calculate(data: DataSeries, config: IndicatorConfig): IndicatorOutput { const period = Math.max(2, Number(config.params.period) || 20); const values = new IndicatorValueMap(); const series: (IndicatorValue | null)[] = new Array(data.length).fill(null); for (let i = period - 1; i < data.length; i++) { let hi = -Infinity; let lo = Infinity; for (let j = i - period + 1; j <= i; j++) { hi = Math.max(hi, data[j].high); lo = Math.min(lo, data[j].low); } const point = { value: hi > lo ? ((data[i].close - lo) / (hi - lo)) * 100 : 50 }; values.set(data[i].time, point); series[i] = point; } return { values, series }; } } const chart = new Chart(document.getElementById('chart')!, {}); chart.registerIndicator(new RangePosition()); chart.addIndicator('rangePosition', { period: 30 }); ``` Declare `inputs: { source: { source: true } }` and read `data[i].close` to let users run it on another price or another indicator's line. ## An overlay of your own, on the price grid An overlay plugin draws on a layer each frame. `overlayScale(context)` places bars by time or index and prices by value exactly where the chart draws them (log and inverted scales too); `chart.getPriceStep()` is the symbol's price step, for rows on its grid. ```ts import { Chart, overlayScale } from '@tradecanvas/chart'; import type { OverlayPlugin } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, {}); /** A band one price step high around each bar's close. */ const closeBands: OverlayPlugin = { descriptor: { id: 'closeBands', name: 'Close bands', layer: 'main' }, render(ctx, context) { const s = overlayScale(context); const step = chart.getPriceStep(); ctx.fillStyle = 'rgba(242, 169, 59, 0.35)'; for (let i = s.visible.from; i <= s.visible.to; i++) { const close = chart.roundPrice(context.data[i].close); const top = s.priceToY(close + step / 2); const bottom = s.priceToY(close - step / 2); ctx.fillRect(s.indexToX(i) - s.barWidth / 2, Math.min(top, bottom), s.barWidth, Math.abs(bottom - top)); } }, }; chart.plugins.register({ kind: 'overlay', plugin: closeBands }); // A switch of the overlay's own: hide it without unregistering, and show the // change at once, even with no tick coming to redraw the chart. closeBands.enabled = false; chart.requestRender(); ``` An indicator's own `render` gets the viewport alone: `priceToY`, `barIndexToX`, `timeToX` and their inverses are exported for it. ## Several charts in a grid ```ts import { ChartGrid, BinanceAdapter } from '@tradecanvas/chart'; const grid = new ChartGrid(document.getElementById('grid')!, { layout: '2x2', syncCrosshair: true, syncTimeAxis: true, }); // An adapter keeps one stream: each chart needs its own. await grid.connectAll(() => new BinanceAdapter(), ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT'], '5m'); grid.getChart(0)?.addIndicator('ema', { period: 50 }); ``` With the full widget on each chart, `ChartWidgetGrid` adds a bar to pick the arrangement and the sync, and saves the whole grid as a named layout: ```ts import { ChartWidgetGrid } from '@tradecanvas/chart/widget'; import { BinanceAdapter } from '@tradecanvas/chart'; const workspace = new ChartWidgetGrid(document.getElementById('grid')!, { layout: '1x2', adapter: () => new BinanceAdapter(), // one per chart cells: [{ symbol: 'BTCUSDT' }, { symbol: 'ETHUSDT', timeframe: '1h' }], sync: { crosshair: true, interval: true }, }); workspace.getActiveWidget().getChart().addIndicator('rsi', { period: 14 }); ``` ## Named layouts on your own server ```ts import { ChartWidget, type LayoutStorage, type SavedLayout } from '@tradecanvas/chart/widget'; const server: LayoutStorage = { list: async () => (await fetch('/api/layouts')).json(), load: async (id) => { const res = await fetch(`/api/layouts/${encodeURIComponent(id)}`); return res.ok ? ((await res.json()) as SavedLayout) : null; }, save: async (layout) => { await fetch(`/api/layouts/${encodeURIComponent(layout.id)}`, { method: 'PUT', body: JSON.stringify(layout) }); }, remove: async (id) => { await fetch(`/api/layouts/${encodeURIComponent(id)}`, { method: 'DELETE' }); }, }; const widget = new ChartWidget(document.getElementById('chart')!, { symbol: 'BTCUSDT', layouts: { storage: server, openLast: true }, // Ctrl/Cmd+S saves, the open layout auto-saves }); await widget.getLayoutSession()?.saveAs('Swing BTC'); ``` Treat what the server returns as untrusted: the widget refuses content it cannot read, but your API should still check who owns a layout. ## The widget, saved per symbol and in Vietnamese ```ts import { ChartWidget } from '@tradecanvas/chart/widget'; import { BinanceAdapter } from '@tradecanvas/chart'; new ChartWidget(document.getElementById('chart')!, { adapter: new BinanceAdapter(), symbol: 'BTCUSDT', symbols: ['BTCUSDT', 'ETHUSDT', 'SOLUSDT'], persistLayouts: true, // indicators, drawings and chart type per symbol, in localStorage locale: 'vi', chartOptions: { numberLocale: 'vi-VN' }, }); ``` ## The widget in a look of your own ```ts import { ChartWidget, WIDGET_UI_PRESETS } from '@tradecanvas/chart/widget'; import { BinanceAdapter } from '@tradecanvas/chart'; // 'studio' (the default), 'terminal' (dense, square) or 'capsule' (pills, floating bars) const widget = new ChartWidget(document.getElementById('chart')!, { adapter: new BinanceAdapter(), symbol: 'BTCUSDT', ui: 'terminal', }); // Your theme over a preset: what you leave out stays the preset's. widget.setUI({ preset: 'studio', radius: { md: 10, lg: 14 }, // buttons and fields take md, menus and panels lg density: 'compact', font: { family: "'Manrope', system-ui, sans-serif", labelCase: 'uppercase' }, toolbar: 'floating', active: 'solid', // tint · solid · underline tagRadius: 999, // pill price tags on the chart }); console.log(widget.getUI().sizes.control, WIDGET_UI_PRESETS.capsule.components.control); ``` The look changes shapes and sizes only; colours stay with `theme`. The widget loads no fonts, so load the families a look names. Without `ui` the look's CSS variables (`--tcw-radius`, `--tcw-control-h`, …) stay the stylesheet's and your CSS can set them; with it the widget writes them inline. ## The chart's look by key ```ts import { Chart, CHART_STYLE_KEYS, type ChartStyleKey } from '@tradecanvas/chart'; const chart = new Chart(document.getElementById('chart')!, { theme: 'dark', overrides: { 'grid.vertical.visible': false }, // the host's layer, from the start }); // Any key of CHART_STYLE_KEYS; up/down colours fall back to series.candlestick.* chart.applyOverrides({ 'series.candlestick.upColor': '#26a69a', 'series.candlestick.downColor': '#ef5350', 'grid.horizontal.style': 'dotted', 'crosshair.vertical.style': 'solid', 'lastPrice.style': 'solid', }); chart.applyOverrides({ 'lastPrice.style': null }); // null takes a key away // What the user picks: kept with the theme it was picked on, saved with saveState() chart.applyOverrides({ 'background.color': '#0b0b0f' }, { layer: 'user' }); const macd = chart.addIndicator('macd', {}); if (macd) { chart.updateIndicatorStyle(macd, { plots: { signal: { lineStyle: 'dashed' }, histogram: { visible: false } } }); chart.setPaneStyle(macd, { separator: '#f2a93b' }); } chart.setIndicatorDefaults('ema', { colors: ['#f5a623'], lineWidths: [2] }); const keys = Object.keys(CHART_STYLE_KEYS) as ChartStyleKey[]; console.log(keys.length, chart.getStyleValue('series.bar.upColor'), chart.getStyle().grid.vertical.visible); chart.on('styleChange', (e) => console.log(e.payload.layer)); ``` `getTheme()` is the theme as set; the overrides go on it apart, so `setTheme(getTheme())` never bakes them in. The host's layer stays through theme switches and is never saved. A grid of charts takes them for every chart with `grid.applyOverrides(patch)`. Orders and positions (`trading.*`), signal markers (`markers.*`), trade zones (`tradeZones.*`) and a selected drawing's handles (`drawings.handleColor`) read back `null` until set: they keep their own colours. In the widget, Settings → Style and Settings → Trading edit every key on the user's layer, with Auto for those. ## The widget with only the parts you want, and parts of your own ```ts import { ChartWidget, WIDGET_FEATURES, type WidgetFeature } from '@tradecanvas/chart/widget'; const widget = new ChartWidget(document.getElementById('chart')!, { symbol: 'BTCUSDT', features: { 'toolbar.replay': false, // one button 'sidebar.patterns': false, // a section of drawing tools 'menu.chart.exportData': false, // one menu entry hotkeys: false, // every key of the widget's }, drawingMenuItems: ({ id, type }) => [{ label: `Copy ${type}`, icon: 'link', onSelect: () => void navigator.clipboard.writeText(id) }], indicatorMenuItems: ({ indicatorId }) => [{ label: 'About', icon: 'info', onSelect: () => console.log(indicatorId) }], }); // While it runs: a clean chart, and back widget.setFeatures({ sidebar: false, statusBar: false }); widget.setFeatures({ sidebar: true, statusBar: true }); // A menu of switches of your own on the toolbar const parts: WidgetFeature[] = ['sidebar', 'statusBar', 'navigation', 'indicatorLegend']; widget.addToolbarDropdown({ id: 'parts', label: 'Parts', icon: 'layers', items: () => parts.map((name) => ({ label: name, checked: widget.isFeatureOn(name), onSelect: () => widget.setFeatures({ [name]: !widget.isFeatureOn(name) }), })), }); const latency = widget.addStatusBarItem({ id: 'latency', text: '— ms', label: 'Latency' }); latency?.setText('12 ms'); // A shortcut of your own, listed in the shortcut sheet (?). It replaces a widget shortcut on the // same keys; the chart's own keys (arrows, Delete, Ctrl+Z) still run, and it warns of those. const shortcut = widget.addHotkey({ keys: 'Alt+L', label: 'Show the latency', onPress: () => latency?.setText('12 ms') }); shortcut?.remove(); const focus = widget.addSidebarButton({ id: 'focus', label: 'Focus', icon: 'eye', toggle: true, onClick: () => { const on = !widget.isFeatureOn('toolbar'); widget.setFeatures({ toolbar: on }); focus?.setActive(!on); }, }); const badge = document.createElement('div'); badge.textContent = 'Paper account'; badge.style.cssText = 'position:absolute;right:72px;top:8px;pointer-events:auto'; widget.getSlot('chart')?.append(badge); // a layer over the chart the pointer passes through console.log(WIDGET_FEATURES.length, widget.getFeatures().alerts); ``` A name without a dot (`alerts`, `settings`, `hotkeys`) turns a capability off wherever it shows; a dotted one (`toolbar.alerts`) one place. The older options (`toolbar: false`, `drawingTools: false`…) are the same switches. They hide the widget's own UI; `chartOptions.features` is what stops drawing, trading or zoom on the chart. A `ChartWidgetGrid` takes them for every chart with `grid.setFeatures(patch)`. ## Watchlists with live quotes, and a tick chart ```ts import { ChartWidget } from '@tradecanvas/chart/widget'; import { BinanceAdapter, marketStatus } from '@tradecanvas/chart'; const widget = new ChartWidget(document.getElementById('chart')!, { adapter: new BinanceAdapter(), // its subscribeQuotes fills the rows symbol: 'BTCUSDT', watchlist: { lists: [ { id: 'majors', name: 'Majors', symbols: ['BTCUSDT', 'ETHUSDT'] }, { id: 'alts', name: 'Alts', symbols: ['SOLUSDT', 'ADAUSDT'] }, ], persist: true, }, }); widget.addToWatchlist('BNBUSDT', 'alts'); widget.toggleSymbolInfo(true); // price, market status, the day, hours, news await widget.setTimeframe('100T'); // a bar per 100 trades, from Binance's trades // Your own feed's quotes instead widget.setQuotes([{ symbol: 'AAPL', last: 190.2, prevClose: 188.1 }]); console.log(marketStatus({ symbol: 'AAPL', timezone: 'America/New_York', sessions: [{ start: '09:30', end: '16:00', days: [1, 2, 3, 4, 5] }] }, Date.now()).state); ``` Quotes come from a feed's `subscribeQuotes` (or `watchlist.quotes`, or `setQuotes`); tick timeframes need a feed with `fetchTrades` and `subscribeTrades`. ============================================================================== skills/tradecanvas/references/indicators.md ============================================================================== # Built-in indicators 134 indicators. Add one with `chart.addIndicator(id, params)`; params you leave out take the defaults below. An indicator's values at bar `i` are `chart.getIndicatorOutput(instanceId).series[i]`, an object keyed by the line keys listed here. "Source ✓" means it has a `source` input: a price (`close`, `open`, `high`, `low`, `hl2`, `hlc3`, `ohlc4`, `hlcc4`) or another indicator's line (`indicatorSource(instanceId, key)`). ## On the price pane (48) Drawn over the candles, on the price scale. | id | Name | Default params | Lines (key: title) | Levels | Source | |---|---|---|---|---|---| | `sma` | Simple Moving Average | period: 20, source: "close" | value: SMA | — | ✓ | | `ema` | Exponential Moving Average | period: 20, source: "close" | value: EMA | — | ✓ | | `bb` | Bollinger Bands | period: 20, stdDev: 2, source: "close" | upper: Upper, middle: Basis, lower: Lower | — | ✓ | | `vwap` | Volume Weighted Average Price | — | value: VWAP | — | | | `ichimoku` | Ichimoku Cloud | tenkan: 9, kijun: 26, senkou: 52, displacement: 26 | tenkan: Conversion, kijun: Base, senkouA: Lead A, senkouB: Lead B | — | | | `psar` | Parabolic SAR | step: 0.02, max: 0.2 | value: SAR | — | | | `supertrend` | Supertrend | period: 10, multiplier: 3 | value: Supertrend | — | | | `keltner` | Keltner Channel | emaPeriod: 20, atrPeriod: 10, multiplier: 1.5 | upper: Upper, middle: Basis, lower: Lower | — | | | `donchian` | Donchian Channel | period: 20 | upper: Upper, middle: Basis, lower: Lower | — | | | `pivots` | Pivot Points | lookback: 24, type: "traditional" | r3: R3, r2: R2, r1: R1, pp: P, s1: S1, s2: S2, s3: S3 | — | | | `avwap` | Anchored VWAP | anchorTime: 0 | value: AVWAP | — | | | `zigzag` | ZigZag | deviation: 5 | pivot: Pivot | — | | | `lrc` | Linear Regression Channel | period: 100, stdDev: 2, source: "close" | upper: Upper, middle: Basis, lower: Lower | — | ✓ | | `hma` | Hull Moving Average | period: 21, source: "close" | value: HMA | — | ✓ | | `mtfma` | MTF Moving Average | period: 50, timeframe: "1d", source: "close" | value: MA | — | ✓ | | `svwap` | Session VWAP | bands: 1 | value: VWAP, u1: +1σ, l1: −1σ, u2: +2σ, l2: −2σ, u3: +3σ, l3: −3σ | — | | | `chandelier` | Chandelier Exit | period: 22, multiplier: 3 | long: Long, short: Short | — | | | `alligator` | Alligator | jaw: 13, teeth: 8, lips: 5, jawShift: 8, teethShift: 5, lipsShift: 3 | jaw: Jaw, teeth: Teeth, lips: Lips | — | | | `vwma` | Volume Weighted Moving Average | period: 20, source: "close" | value: VWMA | — | ✓ | | `wma` | Weighted Moving Average | period: 20, source: "close" | value: WMA | — | ✓ | | `envelope` | Moving Average Envelope | period: 20, percent: 2.5, source: "close" | upper: Upper, basis: Basis, lower: Lower | — | ✓ | | `tema` | Triple EMA | period: 20, source: "close" | value: TEMA | — | ✓ | | `volumeProfile` | Volume Profile | rows: 24 | — | — | | | `dema` | Double Exponential Moving Average | period: 20, source: "close" | value: DEMA | — | ✓ | | `smma` | Smoothed Moving Average | period: 14, source: "close" | value: SMMA | — | ✓ | | `alma` | Arnaud Legoux Moving Average | period: 9, offset: 0.85, sigma: 6, source: "close" | value: ALMA | — | ✓ | | `kama` | Kaufman Adaptive Moving Average | period: 10, fast: 2, slow: 30, source: "close" | value: KAMA | — | ✓ | | `lsma` | Least Squares Moving Average | period: 25, offset: 0, source: "close" | value: LSMA | — | ✓ | | `mcginley` | McGinley Dynamic | period: 14, source: "close" | value: McGinley | — | ✓ | | `macross` | MA Cross | fast: 9, slow: 21, type: "sma", source: "close" | fast: Fast, slow: Slow | — | ✓ | | `fractals` | Williams Fractals | period: 2 | up: Up, down: Down | — | | | `cks` | Chande Kroll Stop | p: 10, x: 1, q: 9 | long: Stop Long, short: Stop Short | — | | | `seb` | Standard Error Bands | period: 21, mult: 2, smooth: 3 | upper: Upper, middle: Middle, lower: Lower | — | | | `gmma` | Guppy Multiple Moving Average | — | s3: EMA 3, s5: EMA 5, s8: EMA 8, s10: EMA 10, s12: EMA 12, s15: EMA 15, l30: EMA 30, l35: EMA 35, l40: EMA 40, l45: EMA 45, l50: EMA 50, l60: EMA 60 | — | | | `maribbon` | Moving Average Ribbon | ma1: 20, ma2: 50, ma3: 100, ma4: 200, exponential: 0 | ma1: MA 1, ma2: MA 2, ma3: MA 3, ma4: MA 4 | — | | | `avgprice` | Average Price | — | value: Average | — | | | `medprice` | Median Price | — | value: Median | — | | | `typprice` | Typical Price | — | value: Typical | — | | | `machannel` | Moving Average Channel | upperLength: 20, lowerLength: 20, upperOffset: 0, lowerOffset: 0 | upper: Upper, lower: Lower | — | | | `hamming` | Moving Average Hamming | period: 10, source: "close" | value: MA | — | ✓ | | `madouble` | Moving Average Double | first: 20, second: 50, type: "sma", source: "close" | ma1: MA 1, ma2: MA 2 | — | ✓ | | `matriple` | Moving Average Triple | first: 10, second: 20, third: 50, type: "sma", source: "close" | ma1: MA 1, ma2: MA 2, ma3: MA 3 | — | ✓ | | `volindex` | Volatility Index | period: 7, mult: 3 | sar: Stop | — | | | `tsf` | Time Series Forecast | period: 14 | value: TSF | — | | | `wclose` | Weighted Close | — | value: Weighted | — | | | `demark` | DeMark Projected Range | — | high: Projected high, low: Projected low | — | | | `rainbow` | Moving Average Rainbow | first: 10, step: 10, count: 10, exponential: 0 | ma1: MA 1, ma2: MA 2, ma3: MA 3, ma4: MA 4, ma5: MA 5, ma6: MA 6, ma7: MA 7, ma8: MA 8, ma9: MA 9, ma10: MA 10, ma11: MA 11, ma12: MA 12, ma13: MA 13, ma14: MA 14, ma15: MA 15, ma16: MA 16, ma17: MA 17, ma18: MA 18, ma19: MA 19, ma20: MA 20 | — | | | `candlepatterns` | Candlestick Patterns | trendPeriod: 10, reversal: 1, continuation: 1, indecision: 0 | bull: Bullish, bear: Bearish, neutral: Indecision | — | | ## In a pane of their own (86) Drawn in a pane under the chart (`addIndicator(id, params, 'bottom' | 'top')`), with their own scale and levels. | id | Name | Default params | Lines (key: title) | Levels | Source | |---|---|---|---|---|---| | `rsi` | Relative Strength Index | period: 14, source: "close" | value: RSI | 30, 70 | ✓ | | `macd` | MACD | fast: 12, slow: 26, signal: 9, source: "close" | histogram: Histogram, macd: MACD, signal: Signal | — | ✓ | | `stochastic` | Stochastic Oscillator | kPeriod: 14, dPeriod: 3, smooth: 3 | k: %K, d: %D | 20, 80 | | | `atr` | Average True Range | period: 14 | value: ATR | — | | | `adx` | Average Directional Index | period: 14 | adx: ADX, plusDI: +DI, minusDI: −DI | — | | | `obv` | On Balance Volume | — | value: OBV | — | | | `williamsR` | Williams %R | period: 14 | value: %R | -80, -20 | | | `cci` | Commodity Channel Index | period: 20 | value: CCI | -100, 0, 100 | | | `mfi` | Money Flow Index | period: 14 | value: MFI | 20, 80 | | | `aroon` | Aroon | period: 25 | up: Up, down: Down | — | | | `roc` | Rate of Change | period: 12, source: "close" | value: ROC | 0 | ✓ | | `tsi` | True Strength Index | longPeriod: 25, shortPeriod: 13, signalPeriod: 7, source: "close" | tsi: TSI, signal: Signal | 0 | ✓ | | `cmf` | Chaikin Money Flow | period: 20 | value: CMF | — | | | `stddev` | Standard Deviation | period: 20, source: "close" | value: StdDev | — | ✓ | | `ad` | Accumulation/Distribution | — | value: A/D | — | | | `vroc` | Volume Rate of Change | period: 14 | value: VROC | 0 | | | `ao` | Awesome Oscillator | fast: 5, slow: 34 | value: AO | — | | | `chaikinOsc` | Chaikin Oscillator | fast: 3, slow: 10 | value: Chaikin | — | | | `voldelta` | Volume Delta | mode: 0 | value: Delta | — | | | `vortex` | Vortex Indicator | period: 14 | viPlus: VI+, viMinus: VI− | 1 | | | `chop` | Choppiness Index | period: 14 | value: CHOP | 38.2, 61.8 | | | `uo` | Ultimate Oscillator | fast: 7, mid: 14, slow: 28 | value: UO | 30, 70 | | | `fi` | Force Index | period: 13 | value: EFI | 0 | | | `crsi` | Connors RSI | rsiPeriod: 3, streakPeriod: 2, rankPeriod: 100, source: "close" | value: CRSI | 10, 90 | ✓ | | `coppock` | Coppock Curve | longRoc: 14, shortRoc: 11, wma: 10, source: "close" | value: Coppock | 0 | ✓ | | `kst` | Know Sure Thing | roc1: 10, roc2: 15, roc3: 20, roc4: 30, sma1: 10, sma2: 10, sma3: 10, sma4: 15, signal: 9, source: "close" | value: KST, signal: Signal | 0 | ✓ | | `elderray` | Elder Ray | period: 13 | bull: Bull, bear: Bear | — | | | `stc` | Schaff Trend Cycle | fast: 23, slow: 50, cycle: 10, source: "close" | value: STC | 25, 75 | ✓ | | `kvo` | Klinger Oscillator | fast: 34, slow: 55, signal: 13 | value: KVO, signal: Signal | 0 | | | `fisher` | Fisher Transform | period: 9 | value: Fisher, trigger: Trigger | 0 | | | `dpo` | Detrended Price Oscillator | period: 20, source: "close" | value: DPO | — | ✓ | | `bop` | Balance of Power | smooth: 14 | value: BOP | 0 | | | `massindex` | Mass Index | ema: 9, sum: 25 | value: Mass | 26.5, 27 | | | `cmo` | Chande Momentum | period: 9, source: "close" | value: CMO | -50, 0, 50 | ✓ | | `trix` | TRIX | period: 15, signal: 9, source: "close" | value: TRIX, signal: Signal | 0 | ✓ | | `emv` | Ease of Movement | period: 14 | value: EOM | 0 | | | `pvt` | Price Volume Trend | — | value: PVT | — | | | `wad` | Williams A/D | — | value: WAD | — | | | `chaikinvol` | Chaikin Volatility | ema: 10, roc: 10 | value: CV | 0 | | | `rvi` | Relative Vigor Index | period: 10 | value: RVGI, signal: Signal | 0 | | | `stochrsi` | Stochastic RSI | rsiPeriod: 14, stochPeriod: 14, k: 3, d: 3, source: "close" | k: %K, d: %D | 20, 80 | ✓ | | `ppo` | Percentage Price Oscillator | fast: 12, slow: 26, signal: 9, source: "close" | hist: Histogram, value: PPO, signal: Signal | 0 | ✓ | | `ac` | Accelerator Oscillator | — | value: AC | — | | | `rmi` | Relative Momentum Index | period: 20, momentum: 5, source: "close" | value: RMI | 30, 70 | ✓ | | `disparity` | Disparity Index | period: 14, source: "close" | value: DI | 0 | ✓ | | `qstick` | Qstick | period: 8 | value: Qstick | 0 | | | `pgo` | Pretty Good Oscillator | period: 14 | value: PGO | -3, 0, 3 | | | `bbpb` | Bollinger Bands %B | period: 20, stdDev: 2, source: "close" | value: %B | 0, 0.5, 1 | ✓ | | `bbw` | Bollinger BandWidth | period: 20, stdDev: 2, source: "close" | value: BBW | — | ✓ | | `mom` | Momentum | period: 10, source: "close" | value: MOM | 0 | ✓ | | `hv` | Historical Volatility | period: 10, annual: 365, source: "close" | value: HV | — | ✓ | | `vo` | Volume Oscillator | short: 5, long: 10 | value: Vol Osc | 0 | | | `ulcer` | Ulcer Index | period: 14, source: "close" | value: Ulcer | — | ✓ | | `smi` | Stochastic Momentum Index | period: 10, smooth: 3, signal: 3 | smi: SMI, signal: Signal | 40, -40 | | | `rvix` | Relative Volatility Index | period: 10, smooth: 14 | value: RVI | 80, 50, 20 | | | `trendstrength` | Trend Strength Index | period: 14 | value: Trend | 0 | | | `lrslope` | Linear Regression Slope | period: 14 | value: Slope | 0 | | | `stderror` | Standard Error | period: 14 | value: StdErr | — | | | `adr` | Average Day Range | period: 14 | value: ADR | — | | | `netvolume` | Net Volume | — | value: Net | 0 | | | `asi` | Accumulative Swing Index | limit: 10 | value: ASI | — | | | `advdecline` | Advance/Decline | length: 10 | value: Ratio | — | | | `majority` | Majority Rule | period: 14 | value: Majority | 50 | | | `chopzone` | Chop Zone | — | angle: Angle | — | | | `smiergodic` | SMI Ergodic | short: 5, long: 20, signal: 5, source: "close" | oscillator: Oscillator, indicator: Indicator, signal: Signal | — | ✓ | | `po` | Price Oscillator | short: 10, long: 21, source: "close" | value: PO | — | ✓ | | `volohlc` | Volatility O-H-L-C | period: 10, annual: 365 | value: Volatility | — | | | `volzt` | Volatility Zero Trend Close-to-Close | period: 10, annual: 365 | value: Volatility | — | | | `psy` | Psychological Line | period: 10 | value: PSY | 25, 75 | | | `newpsy` | New Psychological Line | period: 10 | value: New PSY | -50, 0, 50 | | | `vr` | Volume Ratio | period: 20 | value: VR | 70, 150, 450 | | | `abratio` | AB Ratio | period: 26 | a: A, b: B | 100 | | | `sonar` | Sonar | period: 20, lookback: 9, signal: 9 | sonar: Sonar, signal: Signal | 0 | | | `sigma` | Sigma | period: 20 | value: Sigma | -2, 0, 2 | | | `vhf` | Vertical Horizontal Filter | period: 28 | value: VHF | — | | | `rsquared` | R-Squared | period: 14 | value: R² | — | | | `rwi` | Random Walk Index | period: 14 | high: RWI High, low: RWI Low | 1 | | | `nvi` | Negative Volume Index | signal: 255 | value: NVI, signal: Signal | — | | | `pvi` | Positive Volume Index | signal: 255 | value: PVI, signal: Signal | — | | | `tr` | True Range | — | value: TR | — | | | `iii` | Intraday Intensity | period: 21 | value: III % | 0 | | | `volpct` | Volume vs Average | period: 20 | value: Vol % | 0 | | | `volchange` | Volume Change | — | value: Volume | — | | | `tradevalue` | Trade Value | — | value: Value | — | | | `binarywave` | Binary Wave | ma: 20, fast: 12, slow: 26, signal: 9, roc: 12, stoch: 14 | value: Wave | 0 | | | `sroc` | Smoothed Rate of Change | ema: 13, roc: 21 | value: S-ROC | 0 | | ============================================================================== packages/library/README.md ==============================================================================

TradeCanvas, the chart engine for trading apps

npm version npm downloads CI status pkg.pr.new previews No third-party dependencies TypeScript MIT license GitHub stars GitHub Sponsors

Live demo · Docs · Examples · System audit · Changelog

English · Tiếng Việt · 简体中文 · 日本語 · 한국어 · Español

**A complete trading chart for the web.** Candlesticks to Renko, 134 indicators, 69 drawing tools, live feeds and orders on the chart, on Canvas 2D or WebGL, with no third-party dependencies. Use the full `ChartWidget`, or build your own interface on the headless `Chart`, in TypeScript, React, Vue or Svelte.

ChartWidget on a live market: EMA 21 and 55 and RSI, the crosshair, and a trend line drawn with the mouse

## Highlights - **18 chart types, 134 indicators, 69 drawing tools**, indicators on indicators, alerts on lines, undo and redo. - **Trading on the chart**: positions with live P&L, orders and stops you drag, a paper broker or your own. - **Live data** from Binance, Bybit, Coinbase and Kraken, or your own feed in about twenty lines. - **Fast**: a million bars panned at 60 fps with WebGL; a live tick in about a microsecond. - **A workspace**: grids of charts, layouts, bar replay, backtesting. - **Yours to shape**: themes, three looks, 114 switches, plugins; 30 languages; phones and apps.
Dragging the take-profit and stop-loss of a long position; its P&L updates as the price moves
Orders you drag: stops, targets and limits move with the mouse.
Bar replay playing a market back with Bollinger Bands
Bar replay: play a market back, then trade it on paper.
Four charts in a two-by-two grid on different intervals, the crosshair following across all of them
Grids: up to six charts, linked by symbol, interval, crosshair or time.
One chart switching between the Terminal, Capsule and Studio looks, dark and light
Your look: Studio, Terminal, Capsule, light or dark, or your own tokens.
The widget on a phone: a finger pans the chart and draws a trend line
On phones: a finger pans, zooms, draws and drags orders, and the widget lays itself out for a small screen. It also runs in a WebView, React Native, Capacitor or Tauri.
## Quick start ```bash npm install @tradecanvas/chart ``` ```typescript import { ChartWidget } from '@tradecanvas/chart/widget' import { BinanceAdapter } from '@tradecanvas/chart' new ChartWidget(document.getElementById('chart')!, { symbol: 'BTCUSDT', timeframe: '5m', theme: 'dark', adapter: new BinanceAdapter(), // live data, no API key trading: true, }) ``` The headless `Chart` takes your own bars (`chart.setData(bars)`) and leaves the interface to you. For React, Vue and Svelte, `@tradecanvas/react`, `/vue` and `/svelte` wrap it in a component with the same props. ## Documentation [Getting started](https://tradecanvas.dev/docs/getting-started/) · [API](https://tradecanvas.dev/docs/api/) · [Indicators](https://tradecanvas.dev/docs/indicators/) · [Drawing tools](https://tradecanvas.dev/docs/drawing-tools/) · [Realtime](https://tradecanvas.dev/docs/realtime/) · [Trading](https://tradecanvas.dev/docs/trading/) · [Styling](https://tradecanvas.dev/docs/styling/) · [Frameworks](https://tradecanvas.dev/docs/frameworks/) · [Plugins](https://tradecanvas.dev/docs/plugins/) · [Phones and apps](https://tradecanvas.dev/docs/embed/#mobile-app) · [Performance](https://tradecanvas.dev/docs/performance/) The docs are in English, Tiếng Việt, 简体中文, 日本語, 한국어 and Español. Measurements, limits and how it is tested are on the [system audit](https://tradecanvas.dev/audit/). ## AI coding tools The agent skill in [`skills/tradecanvas`](https://github.com/bonguynvan/tradecanvas/blob/main/skills/tradecanvas/SKILL.md) teaches coding agents the API, with examples that CI type-checks. In Claude Code: ```text /plugin marketplace add bonguynvan/tradecanvas /plugin install tradecanvas@tradecanvas ``` Other agents (Cursor, Codex, Copilot and more): `npx skills add bonguynvan/tradecanvas`. Without a skill, [`llms.txt`](https://tradecanvas.dev/llms.txt) and [`llms-full.txt`](https://tradecanvas.dev/llms-full.txt) give an assistant the docs in one file. Changing TradeCanvas itself with an agent? [AGENTS.md](https://github.com/bonguynvan/tradecanvas/blob/main/AGENTS.md) holds the repo's rules. The MCP server `@tradecanvas/mcp` looks up indicators, drawing tools, options, feature switches and style keys in the library's own catalog, and checks an options object before it is used ([set it up in other clients](https://tradecanvas.dev/docs/getting-started/#ai)): ```bash claude mcp add tradecanvas -- npx -y @tradecanvas/mcp ``` On Windows outside WSL: `claude mcp add tradecanvas -- cmd /c npx -y @tradecanvas/mcp`. In Git Bash, which runs the `!` commands of Claude Code on Windows, write `//c`: Git Bash turns `/c` into a path. ## More - **Packages**: [`@tradecanvas/chart`](https://www.npmjs.com/package/@tradecanvas/chart) (the chart and the widget), [`react`](https://www.npmjs.com/package/@tradecanvas/react) · [`vue`](https://www.npmjs.com/package/@tradecanvas/vue) · [`svelte`](https://www.npmjs.com/package/@tradecanvas/svelte), and [`analytics`](https://www.npmjs.com/package/@tradecanvas/analytics) for backtests, and [`mcp`](https://www.npmjs.com/package/@tradecanvas/mcp) for coding agents. - **Previews**: every pull request and commit on `main` is on [pkg.pr.new](https://pkg.pr.new/~/bonguynvan/tradecanvas), installable before a release. - **Browsers**: Chrome 80+, Edge 80+, Firefox 80+, Safari 14+. - **For businesses**: no logo to show, nothing to apply for; the [For businesses](https://tradecanvas.dev/business/) page answers an IT review. business@tradecanvas.dev - **Related**: [bo-grid](https://github.com/bonguynvan/bo-grid), a fast Svelte data grid for the table half of a trading desk. - **Contributing**: questions in [Discussions](https://github.com/bonguynvan/tradecanvas/discussions), bugs in the issues, and [CONTRIBUTING.md](https://github.com/bonguynvan/tradecanvas/blob/main/CONTRIBUTING.md) for pull requests. Security issues: [SECURITY.md](https://github.com/bonguynvan/tradecanvas/blob/main/SECURITY.md). - **Sponsor**: TradeCanvas is free and built in the open; [GitHub Sponsors](https://github.com/sponsors/bonguynvan) keeps it going. [MIT](https://github.com/bonguynvan/tradecanvas/blob/main/LICENSE)