| name | openalgo-charts |
| description | Use when working with openalgo-charts - creating canvas charts, adding series (candlestick/bar/line/area/baseline/histogram and 13 more), configuring price and time scales, panes, indicators, drawing tools, primitives and custom renderers, volume/market profile and footprint, on-chart trading and order lines, DOM ladder, OpenAlgo REST history and WebSocket live ticks, chart state persistence, themes, keyboard shortcuts, or React/Next.js integration. Also market replay, multi-symbol comparison, linked chart grids (synced crosshair, viewport and symbol), copy/cut/paste of drawings, warm-load bar caching for any feed, the interval registry for calendar/tick/volume timeframes, rebasing price scales, the chart timezone, reference price levels (previous close, session high/low, bid/ask), axis chrome (session clock, bar-close countdown), the settings schema with its paired up/down colour control, and context menus including one raised on a price axis. Covers the six-tier bundle model, the UI standard for host chrome, and the time, scale, registry, indicator, drawing, linking, caching, trading and bundling foot-guns. |
OpenAlgo Charts skill
openalgo-charts is a from-scratch, dependency-free HTML5-canvas charting engine: one canvas pipeline, no SVG, no DOM per bar, six lazy-loaded bundle tiers, zero runtime dependencies.
Works the same whether the project is a downstream npm consumer app or an upstream openalgo-charts source checkout. Detect which one you are in and resolve every API name from whatever typings are locally available.
Source lookup order
Do not assume you are inside the upstream source repository.
- In a consumer app, inspect the installed package first:
node_modules/openalgo-charts/package.json for the actual version.
node_modules/openalgo-charts/dist/index.d.ts for the base API surface, and dist/{trade,draw,indicators,transform,profile}/index.d.ts for each tier.
- In the upstream repo, inspect
dist/index.d.ts first, then src/ if generated output is unavailable.
ARCHITECTURE.md and website/pages/docs/*.mdx are supporting evidence, but local typings win when they disagree.
Verify before answering (copy-paste):
node -p "require('./node_modules/openalgo-charts/package.json').version"
rg -n "createChart|addSeries|addIndicator|DrawingController|OrderEngine" node_modules/openalgo-charts/dist/index.d.ts
rg -n "createChart|addSeries|addIndicator" dist/index.d.ts src/index.ts
rg -n "from 'openalgo-charts" src app
If the relevant file is unavailable, say what could not be verified. Do not invent option names, methods, exports, event names, or indicator ids.
Mental model
Eight layers, in dependency order. Most bugs come from confusing one for another.
- Chart -
createChart(container, options) returns a Chart. One chart per container element. It owns everything below.
- DataLayer - one per chart. Merges every series by time onto a single shared logical index space
0..N-1. This is the load-bearing idea; see rule 2 below.
- Scales -
chart.timeScale maps logical index to x; each pane's PriceScale maps price to y. Panes autoscale independently.
- Panes - vertically stacked drawing regions, each with two canvases (base + overlay) and up to three price scales (
'right', 'left', '' overlay).
- Series -
chart.addSeries(type, options) returns a SeriesApi. The type names an entry in the chart-type registry; the core never switches on type.
- Registries - chart types, indicators, and drawing tools are all descriptors in a Map. Adding one is a registration, never a core change.
- Primitives - the extension point. Anything that draws but is not a series: price lines, markers, legends, profiles, trading pills, drawings.
- Tiers -
indicators, draw, transform, profile, trade are separate bundles that register into the base engine's registries on import.
Install and tiers
npm install openalgo-charts
Import only what you use. Each tier is a separate entry point that registers into the base engine, so a feature you do not load costs zero bytes.
| Import | Contents | Brotli limit |
|---|
openalgo-charts | Engine, 13 chart types, panes and scales, primitives, registries, chart state and settings schema, market replay, symbol comparison, chart linking, warm-load bar cache, interval registry, chart timezone, trading visualization, OpenAlgo feeds, EMA/RSI/ATR/Supertrend calculators | 55 KB |
openalgo-charts/indicators | 91 built-in indicators + the Tier-2 external-data contract | 27 KB |
openalgo-charts/draw | 43 drawing tools + a headless DrawingController and its clipboard | 14 KB |
openalgo-charts/transform | Heikin Ashi, Renko, Range bars, Line Break, Point and Figure, Kagi | 5 KB |
openalgo-charts/profile | Volume Profile, Market Profile (TPO), Footprint, order flow | 11 KB |
openalgo-charts/trade | Order engine, state machine, order/position/bracket lines, DOM ladder | 62 KB with base |
Limits are the CI-enforced budgets in .size-limit.json; the whole package measures 110.8 KB Brotli against a 120 KB budget. Nothing is excluded from them because there are no runtime dependencies to exclude.
The clipboard lives in the draw tier, not the base one, because it needs the drawing-tool registry. DrawingClipboard and friends come from openalgo-charts/draw.
The 60-second chart
import { createChart } from 'openalgo-charts';
const chart = createChart(document.getElementById('chart')!);
const series = chart.addSeries('candlestick');
series.setData([
{ time: 1705286700, open: 100, high: 101, low: 99.5, close: 100.6, volume: 1200 },
{ time: 1705286760, open: 100.6, high: 101.4, low: 100.2, close: 101.1, volume: 900 },
]);
chart.fitContent();
time is UTC seconds. The container must have a non-zero size before the chart can lay out.
Non-negotiable rules
- Time is UTC seconds everywhere, never milliseconds.
Math.floor(Date.now() / 1000), not Date.now(). Feed adapters convert broker formats at the edge. Which wall clock those seconds are labelled in is ChartOptions.timezone, an IANA name defaulting to Asia/Kolkata; never offset the timestamps themselves to fake a zone, or every session anchor and gap moves with them.
- The time axis is gapless and index-based, not timestamp-proportional. x is
logicalIndex * barSpacing, so weekends, holidays and session breaks have no index and collapse to nothing. Never compute an x from a timestamp difference; use chart.timeToCoordinate(t) or chart.timeScale.
- One bar per time per series, ascending. Duplicate times collapse to the last one written.
- Nothing crosses a chart boundary as a logical index. Sync by instant. Because rule 2 makes the index a property of that chart's own bars, index 300 is a different moment on every chart in a grid. Any value passed between charts (a linked crosshair, a shared viewport, a highlighted bar) converts index to time on the sender with
indexToTimeFloat and time back to index on the receiver with timeToIndex / timeToIndexFloat, against that chart's own DataLayer. createLinkGroup, followerIndex and followerRange do this; a hand-rolled b.setVisibleLogicalRange(a.getVisibleLogicalRange()) does not, and it looks correct until the two charts hold different bars. An instant outside the receiver's first or last bar is an absence: draw nothing rather than clamping to an edge bar. See chart-linking.
- Never cache the forming bar. A closed bar is immutable; the last bar is alive until its interval ends. Serving a snapshot of it puts a stale close on the last-price line, the axis tag, the header LTP and every indicator computed off it, with no spinner and no staleness badge.
withBarCache drops trailing unclosed bars for this reason. A fast wrong price is a worse failure than a slow right one on a chart that draws Buy and Sell buttons.
- Never deep-import.
import { X } from 'openalgo-charts' or a published tier specifier only. A deep path into dist/ internals inlines a second copy of the registry Map, and will never see what your tier registered. See .
References
Detailed reference for each topic is in references/. Read the one that matches the task before writing code.
| Reference | Topic |
|---|
| core-api | createChart, ChartOptions, SeriesApi, lifecycle, coordinate conversion, the render/invalidation model |
| chart-types | All 13 base series types, their styles and autoscale rules, runtime type switching |
| scales-and-panes | Price/time scale options, log and inverted modes, left/right/overlay scales, per-axis state and moves, axis chrome (corner clock, bar countdown, tick priority), pane weights and layout |
| themes-and-styling | ChartTheme keys, dark/light, gradients, SeriesStyle precedence, price formatting, and the UI standard for host chrome |
| data-and-time | Bar shape, UTC seconds, the chart timezone and the time helpers, setData/update/prependData, the logical-index model, history paging, tick and volume bars |
| feeds-and-live | DataFeed contract, OpenAlgo REST/WS/live feeds, CandleBuilder, the interval registry, withBarCache warm loading, writing a custom feed |
| events-and-state | The full event catalogue with payloads, getState/restoreState, saved layouts |
| indicators | The 91 built-ins with exact ids, placements and input defaults, the settings model, levels/ranges/fills, signal markers, registerIndicator, the Tier-2 external-data contract |
Triage
| User asks about | First check | Answer with | Avoid |
|---|
| First chart / blank chart | container size, dist present | createChart + addSeries + setData | assuming a CSS import or web component |
| Bars in the wrong place | units of time | UTC seconds | Date.now() milliseconds |
| Axis shows the wrong hours | chart.timezone() | timezone: 'America/New_York' on createChart, or setTimezone | shifting bar timestamps, or a timeFormatter that only relabels |
| Gaps for weekends | the gapless-axis rule | it is intended; whitespace points if you want a gap | shifting timestamps |
| Realtime ticks | last-bar vs full replace | series.update(bar) | setData on every tick |
| Loading older history | setHistoryLoader | prependData + historyLoadComplete | rebuilding and re-fitting |
| Indicator not found | is the tier imported | import 'openalgo-charts/indicators' | registering it by hand |
| Which indicator id to use | the catalogue in indicators | the exact id from the 91-row table, guarded with hasIndicator(id) | guessing an id from the display name |
| Indicator settings UI | descriptor inputs + generated style keys | build the form from the descriptor, apply with setSettings | expecting a built-in dialog |
| Drawing tools | DrawingController | headless controller + host toolbar | expecting a built-in toolbar |
| Volume in its own pane | paneIndex and |
Core API cheat sheet
Verified names. Get these wrong and nothing works.
const chart = createChart(el, options);
chart.addSeries(type, { paneIndex, style, priceScaleId, priceFormat });
chart.addIndicator(id, settings, { paneIndex });
chart.addPriceLine(opts, paneIndex);
chart.addPrimitive(primitive, paneIndex);
chart.fitContent();
chart.applyOptions({ theme, grid, canvas, statusLine, priceScale, priceFormatter, timeFormatter, timezone, crosshairMode });
chart.setTheme(theme);
chart.setTimezone('America/New_York') / chart.timezone();
chart.panes();
chart.primarySeries() / chart.primarySeriesInfo();
chart.setCanvasOptions(opts) / chart.setStatusLineOptions(opts) / chart.setPriceScaleOptions(opts);
chart.setAxisChromeOptions({ sessionClock: true, barCountdown: true }) / chart.axisChromeOptions();
chart.priceAxisState(paneIndex, scaleId);
chart.setPriceAxisOptions(paneIndex, scaleId, patch);
chart.setPriceAxisAutoFit(paneIndex, scaleId, on);
chart.setPriceAxisLockRatio(paneIndex, scaleId, on);
chart.movePriceAxis(paneIndex, , );
;
chart.() / chart.(state);
chart.(event, cb);
chart.(cb);
chart.(t) / chart.(x);
chart.(p, paneIndex) / chart.(y, paneIndex);
chart.();
chart.;
chart.;
chart.;
chart.;
chart.;
series.(items);
series.(item);
series.(items);
series.();
series.({ : });
series.();
series.();
series.();
(chart, { bars, startIndex, barMs });
(chart, { , bars });
(chart) / (chart) / (chart, patch);
group = ({ : , : , : , : });
group.(chart, { : , : (s, c) });
group.(chart) / group.(patch) / group.(chart, s) / group.();
group.() / group.(chart) / group.() / group.(chart);
(followerDataLayer, timeSec, | );
(leaderDataLayer, followerDataLayer, range);
feed = (sourceFeed, { ttlMs, max, maxBars, storage, now, intervalSeconds });
feed.({ , exchange, interval, , to, : });
feed.({ , exchange, interval }) / feed.() / feed.() / feed.;
({ : , : { : , : , : } });
(code)
(code) / (code) / () / (code);
(bucketing, timeSec, zone?) / (...) / (bucketing);
draw.(target?) / draw.(target?) / draw.();
draw.().();
levels = ({ : { : { : , : } } });
chart.(levels, );
levels.(kind, patch) / levels.(q) / levels.() / levels.(kind);
({ bars, anchorTime });
chart.fitContent() takes no arguments; chart.timeScale.fitContent(barCount) does.
Code-generation rules
- Verify option names against local typings before writing them. Many similarly named options exist at chart, series, pane and scale level. Confirm which level owns the option.
- Minimal snippets. One feature per code block. Combining an indicator, a drawing tool and a trading line in one snippet hides which API does what.
- Import from the package entry or a published tier specifier. Never a deep path.
- Match the user's host. A React user wants the effect lifecycle; a vanilla user does not; a no-bundler user needs the standalone build.
- State which tier a feature needs whenever the answer uses one.
- Do not invent. If a name does not appear in the installed typings or upstream source, it does not exist.
Answer contract
When answering an openalgo-charts question:
- Name the API and the tier it lives in.
- Show one minimal snippet, not a mega-demo.
- Call out the main foot-gun for that task, from pitfalls.
- Say what local source was checked (version, typings), or state that it could not be verified.