Release notes
The full, authoritative changelog lives in
CHANGELOG.md.
2.3.2
2026-09-16
Added
- Stream-driven repair. Three opt-in
DataLoadingControlleroptions let history repair follow what the stream reports instead of a clock.refreshOnBarCloseruns one refresh a moment after a pushed bar opens a new bucket, which is when the bar before it closed, and retries a bounded number of times while history has not published that bar yet; nothing fires while the market is quiet.refreshOnGaprefreshes at once when a pushed bar skips whole buckets, the shape a dropped socket, a hidden tab or a sleeping machine leaves behind.refreshWindowBarsmakes every refresh a tail request instead of re-fetching the whole load window. Together they cost about one small request per bar instead of two full-window requests a minute, and a skipped bucket is repaired the moment the stream resumes.pollIntervalMsis unchanged and still serves as a slow backstop for silent drift. - Provisional bars.
CandleBuildernow says when a bar’s open, high, low and volume cover only the ticks it saw.CandleUpdate.provisionalis true for a bucket the builder opened from a tick without having streamed the bar before it: a cold start, or a seed from an older bucket, both of which mean the trades between the bucket’s true open and the first tick were missed.CandleBuilder.reconcile(bar)adopts an authoritative bar for that bucket, taking the true open for a provisional bar, the union of the extremes and the larger volume for any bar, and keeping the close with the ticks;isProvisional()reads the flag.DataLoadingController.pushBar(bar, { provisional: true })keeps the open history already holds for that bucket instead of replacing it, so a repair that found the true open is not undone by the next tick.subscribeBarscallbacks receive the sameLiveBarMeta, andOpenAlgoLiveDataFeedpasses it through. - A refresh keeps the extremes and volume the stream observed on the bar that was forming when the request went out, as it already did for bars pushed while the request was in flight.
Changed
examples/liveruns onDataLoadingControllerwith the new options in place of its own reconcile loop, seeds its builder from history, and reseeds it after a stream resync so the bucket opened after the gap is provisional.- Two budgets raised deliberately: the base engine from 77 to 78 KB (76.46 to 77.23 KB measured) and base + trade from 85 to 86 KB (84.07 to 84.84 KB). The widget terminal (182.37 KB) and everything (215.76 KB) stay inside their budgets.
Notes
- Every new option is off by default. A controller built the old way makes exactly the requests it made before, and a test pins that.
- A rollover repair fires only on a push, so a host streaming whole candles from an exchange gets the same cadence as one building candles from ticks, and a host with no stream keeps polling.
2.3.1
2026-09-16
Fixed
- A refresh no longer deletes a bar the stream completed and REST has not
caught up to.
DataLoadingController.refresh(), which the repair poll, the resume after a hidden tab and a stream resync all go through, fetches the window from the oldest loaded bar to now and treated the reply as authoritative for all of it. A broker’s REST history runs a few seconds behind its stream, so a refresh fired just after a candle closed came back one bar short, and that candle was removed from the series until a later refresh found it in REST. On screen: the current candle became the previous candle, vanished, then came back. The window REST is allowed to overwrite now ends at the newest bar REST actually returned. A bar REST has not published yet is left alone; a bar it does return is still corrected by it.
Notes
- Bars pushed while a refresh was in flight were already protected by the buffer. The gap was bars that completed before the refresh started, which lived only in the held series and so were not in the buffer either. Three regression tests cover both timings and the correction case, and the fix was confirmed by reverting it and watching them fail.
ExpressionNodeandExpressionFunctionName, the types behindSymbolExpression.ast, are now exported from the transform tier. They were reachable from the public API without a page in the API reference. Nothing else in the public API changed, and the bundles move by hundredths: base 76.52 to 76.46 KB, everything 215.04 to 214.99 KB Brotli.
2.3.0
2026-09-16
Added
- Symbol arithmetic.
openalgo-charts/transformgainsparseExpressionandevaluateExpression, so a host can chartNIFTY1!/NSE:RELIANCE,(A+B)/2,1/GOLD, or any expression over any number of legs.+ - * / ^with the usual precedence (^right associative), unary minus, parentheses, numeric constants, andabs sqrt ln log log10 exp min max pow. The keypad glyphs a search box prints are accepted too, so a pasted expression works. parseExpressionreports the symbols it needs before anything is fetched, which is what lets a host resolve and load exactly those legs.isPlainSymboltells an ordinary symbol from arithmetic, so one code path serves both.ExpressionErrorcarries the offending character’s index, so a search box can underline it.- Weighted legs, for options combinations.
2*CE25000 - CE25200is a ratio spread,CE + PEa straddle,75*(CE25000 - CE25200)the same spread scaled by lot size. A sold spread is a credit, so the combined premium is negative, and that is allowed rather than clamped. One consequence to know: a series that goes at or below zero cannot be drawn on a logarithmic price scale, so leave a spread pane on the regular scale. - A leg that did not print gaps the whole combination rather than pricing it from an earlier minute. For an illiquid strike that is the normal case, and a premium carried forward is exactly the number that gets someone hurt.
- The reference host wires it end to end: type
AAPL/MSFTinto the symbol field, or build an expression with the operator keypad beside it.
Notes
- Open and close are exact; the high and low are a bound. A bar records
where a market opened, closed and how far it travelled, but not when it was
at each price, so the true high of a ratio is not recoverable from two OHLC
bars.
ohlc: 'close'is the default and is exact.ohlc: 'interval'bounds the extremes by interval arithmetic, which is guaranteed to contain the truth and is usually wider, because it assumes each leg hit its extreme at the worst possible moment. An interval also cannot see that two mentions of one symbol move together, soA/Abounds rather than collapsing to 1. - A bar the other legs did not trade produces a gap, not a value carried forward: a ratio against another minute’s price was never true. A divisor reaching zero gaps rather than spiking.
- The result carries no
volume. The volume of a ratio is not a quantity anyone traded, and picking one leg’s would be arbitrary. - A ticker containing
-is written in quotes ('BRK-B'/SPY), because bare-is subtraction and no lookahead settlesA-Bin general. - Two size budgets were raised deliberately: the transform tier from 5 to 6 KB (it gained the parser and evaluator, 2.66 to 4.44 KB), and Everything from 215 to 218 KB, which had 0.08 KB of headroom left.
2.2.3
2026-09-16
Security
- Every interpolated attribute in the icon markup is escaped, not only
sizeandclassName.opts.strokeis typednumber, but a JavaScript host is not held to that, and it went into the<svg>raw, so a host forwarding a value from its own settings could close the attribute and open a tag. The sprite’s symbol ids, the<use>href, the path data and the registry’s own attributes are escaped too: one escaped value beside four unescaped ones is the shape a later edit gets wrong. - Three regular expressions rewritten to remove quadratic backtracking.
parseSessionSpecand the tworgb()colour parsers each placed a\s*beside something that also matches a space, so a long run of spaces on an input that ultimately fails split between them in quadratically many ways. Measured on the old patterns: 64k spaces took 1.96 s in the colour parser and over half a second at 32k in the session parser; both are now under a millisecond. The session spec is a string a user types into a settings field, where a half-typed value arrives on every keystroke. - The test suite’s fake DOM strips an unterminated trailing tag from
textContent, which/<[^>]*>/gleft behind.
These close every open CodeQL alert. The colour parsers and the session parser were verified to accept and capture exactly what they did before, over a corpus of valid and malformed inputs, and both fixes were confirmed by reverting them and watching the new tests fail.
Notes
- No public API changed, and no behaviour changed for any valid input.
2.2.2
2026-09-16
Security
- Documentation-site dependencies updated to clear every open advisory:
nextto 15.5.25,sharpto 0.35.4,js-yamlto 3.15.2, and@xmldom/xmldomto 0.9.12 through an npm override, becausespeech-rule-enginepins it exactly.postcssis overridden to 8.5.28 for the same reason: Next pins 8.4.31 and npm’s only offered route was a Next major.npm auditreports zero vulnerabilities.
The published package was never affected. openalgo-charts has no runtime
dependencies; every advisory was in the site’s own lockfile, which ships to
nobody. Upgrading from 2.2.0 or 2.2.1 is not a security action.
Notes
- No library code changed. The bundles differ from 2.2.1 only by the version string, which is why the measured sizes move by hundredths.
2.2.1
2026-09-16
Added
IndicatorInputgains an optionaltooltip, help text a settings UI renders as a hover mark beside the label. A label has to stay short enough for a dense panel, which left nowhere to say what a parameter does, so a ported study arrived with its explanation dropped. All six input variants carry it, as does the chart-settingscolorPairrow.IndicatorPlotgains an optionalpriceFormat, setting the axis and crosshair formatting of the scale the plot maps to. A newpercentvariant suffixes the value without scaling it, so a ratio study no longer has to choose between an axis that reads correctly and a value that does.PriceFormatis exported, and thepriceFormatoption onaddSeriesaccepts the samepercentvariant.
Improved
- Historical Volatility and Bollinger BandWidth label their axes as percentages and carry help text on the inputs whose meaning was previously left unstated.
- The chart-settings dialog explains Scale and Timezone: what Percent and Indexed to 100 rebase against, and that changing the zone moves VWAP and pivot values rather than only the labels.
Notes
- Both additions are optional fields, so a descriptor, a host implementing
IndicatorHost, and a saved chart state written by 2.2.0 all behave exactly as before. priceFormatis a property of the price scale, likestyle.precision, so it belongs to a plot that owns its pane.
2.2.0
2026-09-13
Added
- The drawing catalogue grows from 51 to 85 tools. New line tools include disjoint, flat-edge and regression channels, four pitchfork variants, Info Line, Trend Angle, a two-point Fibonacci extension, a full time/price speed-resistance fan and configurable vector stamps.
- Advanced geometry adds Trend Fib Time, Fibonacci circles, arcs, wedge and spiral, Gann Square, Dedekind Tessellation, and ordinary or golden-spaced sonic and supersonic wavefronts. Curve painting uses native circle/ellipse paths where appropriate, with bounded hit testing and recursive work.
- Eleven manual pattern tools cover XABCD, ABCD, Elliott impulse and correction, Head and Shoulders, Gartley, Bat, Butterfly, Crab, Shark and Cypher, with point labels, editable fills and measured harmonic ratios.
- A full-size drawing gallery provides editable samples of every tool with irregular simulated NIFTY prices near 23800. The website playground, widget rail and reference host expose the expanded catalogue on desktop and touch layouts.
ADVANCED_LINE_TOOLS,ADVANCED_GEOMETRY_TOOLSandPATTERN_DRAWING_TOOLSexpose the new descriptor families from the draw tier.
Improved
- Multi-point placement keeps chosen anchors and a dashed connecting guide visible until the final point is placed.
- Repeated label widths share measurements within one paint. Measurements are discarded afterward so newly available fonts cannot leave stale widths.
- Measurement volume reads seek directly to the selected history window and reread its live values on every paint. Dense stacks stop hit testing at the topmost exact hit while preserving nearest-hit and paint-order semantics.
- Table column layout and hit testing share actual font measurements, including bold headers. Long cells remain selectable without a fixed-width phantom area.
- Speed-resistance fan labels use compact, collision-checked edge placement. Dense geometry labels spread into separate rows; labels that cannot fit in a tiny plot are omitted instead of painting over one another. Numeric geometry, disabled levels, fill regions and DPR validity are covered by focused regression tests alongside real-browser catalogue sweeps.
Compatibility
- Drawing documents remain version 2. Existing tool IDs and stored anchor
meanings are preserved.
fib-fanis named Fib Fan; the full two-family construction has its ownfib-speed-resistance-fanID. Existingfib-extensionremains a three-anchor projection. - Drawings, temporary guides and handles remain visible in forward space and clipped inside the plot at price and time axes. The library keeps eight optional tiers and zero runtime dependencies.
2.1.9
2026-09-13
Added
- Charts display the OpenAlgo corner logo by default, with responsive sizing,
theme contrast and vector exports. Hosts can replace or disable the mark with
brandingandchart.setBranding. - An optional background watermark supports automatic symbol/interval text or
custom text. It is disabled by default and configurable through Appearance
settings,
watermarkandchart.setWatermarkOptions. - Watermark preferences persist independently of host branding and follow the current data context. The widget, reference demo and OpenAlgo chart settings share the same controls.
Fixed
- The basic profiles example maps volume buckets to renderer values, restoring its missing Volume Profile histogram bars. Its Market Profile and order-flow selections now reuse the full current demos, exposing TPO letters, themes, text coloring, lot display and the optional statistics table. The order-flow logo moves to the top-left while table rows are visible, keeping labels readable.
- Objects and managed-loading demos use irregular simulated OHLC candles and changing volume. Navigation and mobile controls have a dedicated example in the Mobile guide and are removed from the Examples page.
- Website and reference-host examples use the shared logo defaults, avoiding missing logos on widget/mobile charts and duplicate manual marks.
- PNG export skips hidden and empty pane buffers, including maximized panes.
- Logo gestures stay separate from drawing placement, including pinch release, missed mouse releases and secondary pen buttons.
The release build measures 76.16 KB base and 203.62 KB total Brotli. Budgets allow the default vector artwork, optional watermark and guarded gestures: 77 KB base, 85 KB base plus trade, 173 KB widget terminal and 205 KB total. The chart-only import measures 48.96 KiB against a 50 KiB ceiling.
2.1.8
2026-09-13
Navigation and scales
- Wheel and trackpad input now scales proportionally after pixel, line and page delta normalization. Horizontal input and Shift-wheel pan time. Ctrl-wheel and Meta-wheel pinch input zoom at the pointer.
- Wheel input over a visible left or right price axis scales that axis at the pointer price. Manual and fixed ranges remain authoritative, and plot drags retain two-axis panning by default.
animAutoscalesmooths an automatic price range while navigation reveals new extrema. It defaults toanimZoom; viewport replacement, primary data replacement, reset and destruction cancel pending navigation motion.
Mobile widget
WidgetOptions.mobileaccepts'auto','always'and'never'. Auto, the default, activates compact controls when the widget container is at most 640 CSS px wide or the primary pointer is coarse, retaining the controls on a phone in landscape.- A touch header, bottom navigation, drawing sheet and selected-drawing actions share
the desktop widget’s controller, object inventory, dialogs and overlay state.
rail.toolsfilters the drawing tools in both layouts. mountMobile,MobileMode,MobileOptionsandMobileHandleare public widget-tier exports for custom composition. The live example runs against the website’s current library bundles.- When reduced motion is preferred, the widget disables omitted
animZoomandanimAutoscaledefaults. Explicit values remain authoritative.
Reference host and fixes
The yfinance demo now has touch drawing, Undo, Redo, Magnet and zoom controls, a scrollable compact toolbar and reduced-motion defaults for both charts. Zoom buttons stop at scale limits without shifting the viewport, history controls follow keyboard actions, and replay controls remain clear of the touch bar on wide touchscreens.
Navigation callbacks respect viewport reset and destruction. Mobile drawing actions retain keyboard focus through refresh, symbol searches discard obsolete responses, and results remain reachable in short chart containers.
Migration and validation
No required migration is needed. Set animZoom: false for immediate wheel zoom and its
matching immediate autoscale default, set animAutoscale independently when desired,
or set mobile: 'never' to keep desktop chrome in a narrow widget.
Validation covers normalized wheel modes, horizontal pan, both price axes, pinch modifiers, autoscale transitions and cancellation, mobile mode changes, allowed drawing tools, shared state, teardown, public declarations, skill references and the documentation website. Bundle budgets rise intentionally for the new gesture and mobile-control code: 75 KB base, 83 KB base plus trade, 42 KB widget, 170 KB widget terminal and 202 KB total, with a 46 KiB chart-only ceiling.
The final checks pass 4,376 unit tests, 228 demo tests and 127 browser
tests. One optional historical-render comparison is skipped without its baseline
bundle. All 847 skill coverage entries are present; TypeDoc has no warnings.
Website navigation, Objects, loading, depth, profile and orderflow checks pass.
The packed candidate passes the OpenAlgo production build and 18 /trading
browser workflows with synthetic protocols. Touch checks use browser emulation.
Measured Brotli: 74.00 KB base, 41.59 KB widget, 169.54 KB widget
terminal and 201.15 KB all tiers.
2.1.7
2026-09-11
Added
- Headless
ChartObjectsinventory for the primary source, indicator instances, drawings and explicitly registered profiles. Immutable snapshots and supported actions are shared by the packaged widget and custom broker terminals. - Searchable Objects panel,
widget.objects,widget.openObjects()and reusablemountObjectsPanel. Drawings support selection, visibility, locking, settings, focus and removal through the existing controller and undo history. The primary price source is protected from removal. - Interactive Objects website example with a compact host, profile capabilities and layout save/restore, plus public API and agent-skill guidance.
Fixed
- Dialogs fit the actual chart container, including 350px panes on wide pages and short chart hosts. Tabs adapt their orientation, fields avoid horizontal overflow, and action footers remain reachable while content scrolls.
- Hidden indicators stay hidden after JSON layout restoration and plot-type edits. Reference levels follow indicator visibility, alongside plots and other visuals.
- Direct indicator-handle removal releases its inventory entry and empty pane. A throwing external cleanup cannot prevent owned chart resources being removed.
- Inventory observers remain synchronized through drawing undo, primary-source replacement, provider subscription failures and reentrant notifications.
- Drawing focus supports anchors beyond the loaded bars and a primary price axis moved to the left, without changing drawing coordinates.
Integration and documentation
- The companion OpenAlgo integration provides a focused-pane Objects panel with existing settings editors, generation-scoped ownership and persisted indicator visibility. Profile actions reflect the operations the host actually supports.
- Eight tiers and zero runtime dependencies are retained. Intentional object-model and panel code raises the base, base-plus-trade, widget, widget-terminal and total budgets to 74, 82, 40, 168 and 200 KB Brotli. The chart-only tree-shaking ceiling remains 45 kB.
Validation: 4,337 unit tests across 193 files and 219 demo tests pass, alongside lint, TypeScript, build, declaration and tree-shaking checks. All 846 skill coverage entries are present. All 106 browser tests and nine additional compact-dialog combinations pass across Chromium, Firefox and WebKit. TypeDoc has no warnings; the 55-route website build and Objects, loading, navigation, depth and profile browser checks pass. Measured Brotli: 73.31 KB base, 38.72 KB widget, 165.97 KB widget terminal and 197.58 KB all tiers.
2.1.6
2026-09-11
Added
- Shared headless
DataLoadingControllerfor history, live bars, refresh, older pages, display suspension and typed loading state. Widgets use it automatically; custom broker terminals can bind the same controller to their own UI. HistoryRequestPoolcoalesces identical requests per feed, limits concurrency and provides independent consumer cancellation, priority and deadlines. The OpenAlgo REST adapter bounds fetch and JSON body decoding.- Optional
DataFeed.getBarsPageandgetCachedBarscapabilities preserve existing feed implementations. Pagination distinguishes empty windows, provider exhaustion and local retention limits; cache snapshots paint closed history before refresh. - Explicit chart data context and external-study lifecycle status, automatic context/range refresh, cancellation, unsupported data and retry. The widget shows accessible chart, history and study status with compact Retry controls.
Fixed
- Durable cache failures and invalid entries fall back to bounded memory without preventing usable history. New entries are versioned; forming bars remain excluded.
- Context switches, recovery retries and teardown reject obsolete results. Replay can hold its displayed prefix while the controller maintains current live data.
- Same-context widget reloads and older pages preserve the visible time anchor. Hidden topbar/statusline combinations retain a usable chart and Retry control.
- OpenAlgo depth frames retain their exchange event timestamp. Book quantities remain distinct from traded volume.
- Saved drawings, previews and handles are clipped to the pane plot, preventing price-axis spill while retaining drawings beyond the latest candle. Drawing rendering and hit testing also follow a primary price axis moved to the left.
Integration and documentation
- Interactive failure, retry, empty-history and paging simulation in the website; updated feed, cache, widget, indicator and host guidance and skill references.
- Permanent release process in
CLAUDE.mdcovers regression evidence, browser validation, OpenAlgo compatibility, docs, publication and downloaded-artifact checks. - Base bundle budgets account for the shared controller and resilient cache. The package retains eight tiers and zero runtime dependencies.
Validation: 4,303 unit tests across 190 files, 219 demo tests and 82 browser checks pass. Browser coverage includes Chromium, Firefox and WebKit, failed refresh/retry, replay isolation, saved/future drawings and axis clipping. TypeDoc reports no warnings; all 843 skill coverage entries are present. The OpenAlgo consumer passes 1,892 frontend tests and 13 browser workflows. Measured Brotli: 71.57 KB base, 25.90 KB draw, 36.83 KB widget, 162.35 KB widget terminal and 193.96 KB all tiers. The chart-only import remains within its existing 45 kB tree-shaking budget.
2.1.5
2026-09-11
Fixed
- Drawing previews stay visible when an endpoint moves beyond the latest candle or before the first loaded bar. Trend lines, rectangles and other drawing tools use the existing pixel-to-time conversion in empty chart space.
- Freehand tools can start and continue in empty time-axis space instead of silently discarding those samples.
- Crosshair candle time and OHLC remain null where there is no bar. Magnet snapping still requires an actual hovered candle; drawing state and feed APIs retain their existing formats.
Validation: 4,215 unit tests across 182 files, 219 demo tests and 47 browser checks, including future-space preview, commit, handle dragging, save/restore, new-bar updates and freehand rendering. The complete verification gate passes. Measured Brotli: 67.08 KB base, 25.84 KB draw, 156.29 KB widget terminal and 187.90 KB all tiers. Existing budgets are unchanged.
2.1.4
2026-09-11
Mouse and pen plot drags again pan both time and price by default, restoring
vertical movement for market-profile, orderflow and other charts. Choose
navigation.mousePan: 'horizontal' when time-only panning is preferred.
Saved navigation preferences are preserved. If an existing layout still pans
horizontally, select Axes > Mouse drag > Time and price or call
chart.setNavigationOptions({ mousePan: 'both' }). Other saved settings stay intact.
The time-axis direction correction, Reset view, default visible bars and touch panning introduced or retained in 2.1.3 remain unchanged. See navigation settings and issue #9.
Validation: 4,209 unit tests across 181 files and 219 demo tests across 14 files pass, together with the complete verification gate. TypeDoc reports no warnings.
Measured Brotli: 67.10 KB base, 36.01 KB widget, 156.28 KB widget terminal and 187.89 KB all tiers. Bundle budgets are unchanged.
2.1.3
2026-09-11
- Drag the time axis left to expand candle spacing, or right to compress it. The website gallery, documentation charts, widget and embedded demos use the same corrected engine.
- Mouse and pen chart drags now pan horizontally by default and preserve price autoscale. Axes settings can enable Time and price movement. Direct price-axis drags and touch gestures retain their existing controls.
- A Reset view button sits between the bottom zoom and pan controls.
- Time-axis drags notify linked charts and saved layouts through zoom events. Initial fitting waits for a measurable container, and Reset view restores price autoscale on both axes and overlay scales.
- Axes settings can save a default visible-bar count. Positive values show the latest requested bars initially and on reset; 0 fits all loaded bars. This changes zoom without discarding history or reducing the requested data. The widget retains the preference across symbol and interval changes.
- Custom hosts can configure
ChartOptions.navigationand usenavigationOptions()/setNavigationOptions(). ExplicitfitContent()still fits all loaded bars. See navigation settings.
The panning, reset and default-view controls address issue #9.
Companion OpenAlgo /trading fixes preserve replay during live history refreshes,
discard stale history responses and prevent work from restarting after a pane
closes. Concurrent custom-indicator registration now completes before restoration.
These require the OpenAlgo application update; the package alone does not change
host data loading. See OpenAlgo compatibility.
Validation: 4,208 unit tests, 219 demo tests, 44 browser checks and the full verification gate pass. The corrected OpenAlgo consumer passes its build, 408 trading tests and 13 browser workflows using synthetic broker traffic. Website navigation checks compare the shipped bundles and drive native axis drags on gallery, market-profile and orderflow charts.
Measured Brotli: 67.05 KB base, 36.01 KB widget, 156.24 KB widget terminal and 187.84 KB all tiers.
2.1.2
2026-09-10
This maintenance release fixes external-study races and strengthens history/live continuity and OpenAlgo integration.
- External indicators discard responses from old data settings, clear previous symbol values immediately, share pending history across style changes and preserve current live observations when history arrives.
- The widget seeds its live candle from history and refreshes authoritative
history after reconnect, buffering live bars during the fetch and merging
them into the response. Recovery bypasses cached snapshots, preserves the
viewport and keeps monitoring through failures or further reconnects.
reload()retries a failed repair. Volume merging avoids double counting snapshots. Whole-bar merging can retain seeded extrema corrected by history; it does not replay unseen trades. - Seeded cumulative-volume builders retain known bar volume when no initial day-volume baseline is available. The synthetic feed continues at the seed.
- The OpenAlgo adapter reads the server’s top-level symbol/exchange identity, preserves explicit timestamp offsets, maps interval aliases to D/W/M and surfaces backend history errors.
- Optional
BarSubscriptionOptionsexposes seeding andonResyncto custom hosts. Existing two-argument feed implementations continue to work. - Optional
styleNoncesupports nonce-authorized widget stylesheets, including empty SSR placeholders. See the widget CSP guide for the separate policy needed for style attributes. - CI and website deployment now run the existing profile, orderflow and depth checks. Current profile captures retain their verified source/image hashes.
The actual OpenAlgo /trading app is checked in an isolated checkout with
mocked HTTP and WebSocket traffic. The compatibility guide
records tested workflows and a pre-existing replay issue in the host’s periodic
history reconciliation. The library update does not modify OpenAlgo itself.
Validation: 4,194 unit tests, 219 demo tests, 41 browser tests and the full lint/type/build/declaration/size/tree-shaking gate pass. OpenAlgo’s unchanged consumer passes its production build, 389 trading tests and 13 browser workflow checks.
Measured Brotli: 66.65 KB base, 27.36 KB indicators, 36.00 KB widget (35,998 bytes), 155.82 KB widget terminal and 187.42 KB all tiers. The full-package budget is now 188 KB; individual tier budgets are unchanged.
2.1.1
2026-09-10
This update adds three footprint styles, five demo themes and independent text coloring. Try the orderflow demo and compare Profile, Cluster ladder and Heatmap with Midnight, Graphite, Classic neon, Ocean and Ivory. All examples use explicitly synthetic classified trades.
Added
- Volume-proportional bid/ask profiles, square cluster ladders, real OHLC side candles, outlined POC rows, volume value-area lines and labeled per-bar cards.
- Contrast, side, delta, same-price dominance, diagonal imbalance and volume-strength text methods with independent foreground palettes.
- Initial
ChartOptions.timeScaleconfiguration for wide footprint columns, pluscvdOffsetfor session CVD across a rolling data window. - Optional OHLC, effective row size and actual trade-count metadata on
FootprintBar, populated by batch and live aggregation. - An optional table aligned with each footprint bar, disabled by default.
Select Delta, Min Delta, Max Delta, Cumulative Delta, Total Ask Volume,
Total Bid Volume and Total Volume independently through
tableRows. The NIFTY simulation starts around 23,800 with 2-point price rows and includes a Table switch plus row checkboxes. - Intrabar Min/Max Delta follow the running trade sequence, including zero at the bar’s start. Legacy input without this metadata reports unknown.
- Quantity/Lots display with an editable lot size, initially 65 in the demo.
volumeDivisorscales the displayed volumes and delta metrics while raw data, percentages, trade counts and calculations remain unchanged.
Correctness fixes
- Detached live snapshots; adjacent-row and fractional-volume comparisons; independent buy/sell stacked imbalances; actual trade counts; OHLC candle direction; correct volume-mode normalization and fractional labels.
- Plot clipping, rows and columns that fit their slots, half-row autoscale padding and hover bounds that follow each visible row/card.
- Invalid data and out-of-order live ticks reject before state mutation. Tick/volume bars coalesce tied opening timestamps until time advances, so their counts can exceed the target. Legacy bars without trade counts show an em dash instead of an invented count.
Market-profile documentation
The market-profile guide now includes the actual
compressed six-session display and regenerated theme screenshots. Its opening
examples explicitly use compact rendering. Enlarged comparisons are labeled
separately, and fingerprinted image URLs refresh stale cached screenshots.
The old screenshot directory is removed; all current captures use the
market-profile-v2.1.1 directory.
Validation: 4,139 unit tests and 219 demo tests pass, along with lint, typecheck, builds, declaration checks, size budgets and tree shaking.
Measured 2.1.1 sizes: 66.49 KB base, 14.96 KB profile tier and 186.74 KB full package, Brotli. The added footprint styles and validation use a 15 KB profile budget and a 187 KB full-package budget.
2.1.0
2026-09-06
This release adds compact market profiles, per-day display controls and a website gallery. Try the demo and compare screenshots.
Added
blockDisplay: 'compact'keeps distinct uppercase/lowercase TPO letters and volume values visible in small rows. The pixel alphabet needs 5 physical pixels vertically and 3 horizontally; below that, marks remain and hover gives the exact letters. It works with Canvas 2D and SVG export.setSessionSplit(index, true | false | null)andisSessionSplit(index)control one session. A day’s choice survives updated data and prepended history. An explicit globalsetOptions({ split })resets all overrides.showSessionOpenadds lowercaseoto every session’s opening-price row;showLastPriceadds#only to the newest supplied session’s latest close. Both are opt-in library options and enabled in the demo.- Dark, Blue, Graphite, Emerald and Ivory demo palettes, with matching controls, tooltips and markers. The theme presets belong to the standalone demo and use the public chart/profile APIs.
- A website profile demo, full-resolution screenshots for every theme, packed/split close-ups and updated integration documentation.
Changed and fixed
- The standalone demo uses six synthetic sessions, separate controls for row height and price aggregation, and a right-click split/unsplit menu. Its single-print dashes are disabled without removing the underlying data.
- Compact text stays pixel-aligned at fractional display scaling and is clipped to the plot. Nominal 5-pixel rows tolerate floating-point rounding.
- Open markers have reserved space.
#remains visible on narrow or one-bar newest sessions, stays inside the plot width and clears optional TPO counts. - Theme and display changes preserve individual split choices. Website builds reuse the standalone demo and the current local bundles.
Measured sizes: 66.51 KB base, 11.95 KB profile tier and 183.74 KB full package, Brotli. Validation covers 4,026 unit tests, 219 demo tests and browser checks for compact rendering, per-day controls and the website examples.
Website follow-up
The website now includes a redesigned marketing homepage with an interactive BTC/USD chart, refined dark and light styling across the guides and API reference, and a reduced-motion-aware intro. The homepage uses real exchange candles refreshed every 15 seconds, with visible connection status and no simulated fallback. Profile theme images use readable close-ups. The depth-of-market demo adds simulated live updates and price grouping, and the drawing playground provides visible tools with selection, deletion and undo/redo. These examples use the existing 2.1.0 library APIs. The API reference is regenerated with each website deployment.
2.0.2
Added
doubleClickonChartOptions:'reset'(default, fits every loaded bar, which also wakes a history loader),'maximize'(toggles the pane under the pointer to the whole stack) or'none'. Thedblclickevent carriespaneIndex,x,yand ahandledflag a listener can set to keep the chart’s own action from running.
Measured on the 2.0.2 build: base engine 66.45 KB, base + trade 74.06 KB, widget terminal 155.09 KB, everything 182.39 KB; the other six tiers did not move.
2.0.1
Fixed
- A depth subscription asks for its book depth under the key the OpenAlgo websocket proxy
reads. The feed sent
depth_level; the proxy readsdepth, and never read the other, so every request above the default was served at five levels with no error (five is also the proxy’s fallback). The frame now carriesdepth, and keepsdepth_levelbeside it for any consumer that copied the old name from this library.
Measured on the 2.0.1 build: base engine 66.42 KB, base + trade 74.03 KB, widget terminal 155.06 KB, everything 182.37 KB; the other six tiers did not move.
2.0.0
The drawing model, rebuilt, and the chrome as a package: paint order, a text block,
per-level colours, multi-select, a per-tool settings schema and an upgrade path for every
1.9.x layout; a render backend port with a WebGL2 backend behind it; vector SVG export;
and openalgo-charts/widget, the toolbar, rail, dialogs and shortcuts in one call.
Coming from 1.9.x? Read
Migrating to 2.0:
every breaking change with the before and after, and the fact that stored 1.9.x drawings
load unchanged.
Breaking changes
DrawingStyleno longer carries text. The seven text keys moved intodrawing.text(aDrawingText), so a host can tell a label from a stroke colour without knowing the tool. Tools merge adefaultTextunder the caller’s text.style.levelsisFibLevel[]({ ratio, color?, enabled?, label? }), notnumber[], withlevelColor(ratio)as the one statement of the conventional colour per ratio.toJSON()returns{ version: 2, drawings }, and so doeschart.getState().drawings;fromJSONstill accepts a 1.9.x array.DRAWING_CLIPBOARD_VERSIONis 2; version 1 bodies are upgraded.drawings()is paint order, andDrawing.zIndexis required on the type (add()fills in 0).select(id | ids | null, additive)takes a selection;selected()is the primary id andselection()the whole list. A host that calledselect(id)and readselected()sees no change.chart.rendererischart.rendererKind; the old name stays as the same value.
Added
openalgo-charts/widget: the chart with its chrome in onecreateWidgetcall. Top bar, drawing rail, status line, the settings and indicator dialogs, drawing properties, a level editor, in-place text editing, a right-click menu with order entry, a keymap with a?panel, theme tokens and optional persistence. The eighth tier and the only one that ships DOM; the engine underneath still ships none. 35.56 KB Brotli; a widget terminal (base + draw + indicators + widget) is 155.03 KB.- A render backend port with
renderer: 'canvas2d' | 'webgl2' | 'auto'andchart.rendererKind, and a newopenalgo-charts/webgltier (6.38 KB Brotli) that draws every standard chart type on the GPU in one draw call per frame into the pane’s own canvas, so screenshots and the SVG export are unchanged. A lost context falls back to the 2D path for the session and emitsrenderer:fallback. chart.exportSVG(options?): the chart as a standalone SVG string with the labels as text and no crosshair or hover state, at the live size or one of the caller’s choosing. The serialisingSvgContextbehind it is exported for hosts.zIndexwithsetZIndex,bringToFront,sendToBack,sendBehindSeriesandbringAboveSeries. The default paints exactly where 1.9.2 painted.- Multi-select:
selection(),updateMany,removeMany,duplicate,nudge; shift, ctrl or meta click is additive; a body drag moves the whole selection as one undo entry. New eventsdrawing:selectanddrawing:change. drawingSettingsSchema(toolId)withreadDrawingSettings,applyDrawingSettingsandcomposeSettings: a tool declares only fields its renderer reads.migrateDrawings(input), the pure 1.9.x to 2.0 upgrade, exported for hosts.keyToDrawingAction(e, ctx): undo, redo, copy, cut, paste, duplicate, delete and arrow-key nudge as a pure mapping; withplacing: true, Escape, Enter and Backspace map tocancel,finishandpopAnchor.- Drawing feel: hover handles (
hovered(),drawing:hover), Shift angle lock on the line family (DrawingTool.angleLock),magnet: 'off' | 'weak' | 'strong'with a ring where the next click lands,cancel()andpopAnchor(), touch-sized grab targets, and an under-series drawing lifted to the top layer for the length of a drag. style.showStatson the line family: a midpoint readout of change, percent, bars and angle.- Freehand strokes ink every coalesced pointer sample, thin on release and paint as a spline;
a pen’s
DrawingPoint.pressurecan drive the width (style.pressure).rdpSimplify,catmullRomandpressureWidthare exported. - Position tools that place a trade:
long-positionandshort-positiontake the entry click and the target click, the second click sets the direction, and the stop lands opposite the entry at 1:2, sized on screen.DrawingTool.constrain(points, handle)holds the stop and the target on opposite sides of the entry;ExpandContextgained optionaltoPixel/fromPixelso anexpanddefault can be sized in pixels. - Icons as markup: a chrome set (
CHROME_ICONSand friends),iconSvg,chromeIconSvg,iconSprite,iconUseandtoolCursor, all derived from one registry. - Pointer payloads:
crosshair:move,click,draganddrag:endreportmodifiers,pointerTypeandpressure;dragcarriespointand coalescedsamples, and so doescrosshair:movewhile pressed. No existing field changed. - An eased wheel zoom (
ZoomGlide, in log space, no input latency), on by default and switched off withanimZoom: false, andzoomAnchor: 'cursor' | 'right'to pin the latest bar. - Named descriptors for every built-in tool,
boundsOf,cloneDrawing.
Changed
- A wheel zoom eases over a few frames by default. The bar spacing it lands on is
unchanged and still moves on the event itself;
animZoom: falserestores the 1.9.2 single-frame step. - A position box is two clicks, not one: the entry and the target, with the stop derived. A saved 1.9.x box loads unchanged.
- The yfinance demo is a native-ESM host on the 2.0 drawing tier: thirty modules that
import
/distby URL, a rail and properties bar generated from the tier’s sprite and settings schema, toasts, an overlay stack, a light theme, a typed feed and a versioned layout document. Its own vitest config runs insidenpm run verify.
Fixed
- Fib retracement and extension stroke their anchor leg and tint bands per level;
extendLeftis honoured.price-rangeanddate-rangehonourshowLabels. Flag mark and arrow markers honourfill: false. Circle, triangle and rotated rectangle carry a shape label.
Performance
- Candles skip the body fill once the wick already covers it: one
fillRectper candle instead of two at tight zoom, zero differing pixels. - Hovering a drawing costs the overlay tier only; dragging an under-series drawing lifts it to the top layer so the base tier is not repainted per frame.
- The WebGL2 backend batches a pane’s series into one draw call and never opens more than one GL context however many panes are on the page.
Internal
- ESLint with a tier ACL that fails the build on a base-to-tier import, a pixel-level
render-parity harness against a baseline build, a WebGL parity spec that asserts a real
GPU draw call, a widget e2e spec, and
check-dts/check-shakeguards that keep the widget out of a chart-only bundle.
Measured with npm run size (Brotli) on the 2.0.0 build: base engine 66.39 KB against
67 KB, base + trade 74.00 KB against 75 KB, draw tier 25.82 KB against 26 KB, webgl
6.38 KB against 7 KB, widget 35.56 KB against 36 KB, everything 182.34 KB against 183 KB.
3999 tests across 170 files.
1.9.2
Eight annotation tools, and the icon set that was missing for all of them.
Added
-
Annotations:
note,balloon,comment,signpost,price-noteandtable, plusarrow-leftandarrow-right. 43 tools to 51.They share plate-and-tail machinery, so what separates them is where the tail leaves the plate: a note pins a bar and sits its text up-right, a balloon floats above with the tail down, a comment is the quiet square version, and a signpost stands a post on the bar so its plate clears the price action. The signpost is anchored to time rather than to a level, which is what an event needs.
price-notereads its price off the anchor rather than storing one.tableencodes its cells instyle.text, a newline per row and a pipe between columns, so a whole table is one editable string. -
An icon for every drawing tool, as path data:
DRAWING_TOOL_ICONS,drawingToolIcon(id),ICON_ATTRS.The engine still ships no DOM: these are strings, and the host still builds its own rail. What it no longer does is draw fifty-one glyphs first, which every adopter had to do, each set drifting on weight and grid until it read as fifty-one icons rather than one.
One grid, held mechanically by
tests/draw-icons.test.ts: 24 viewBox, live area 2 to 22, whole-unit coordinates, one stroke weight, round caps, a complexity ceiling and a minimum span. Those checks caught two faults on their first run that reading the paths had not.Render at 24px or a multiple. A 2-unit stroke on whole coordinates covers exactly two device pixels at 1:1; at 18px the 0.75 scale puts it on 1.5 and every edge blurs across two rows.
Changed
- Draw tier budget 14 to 16 kB, measured at 15.39 kB.
1.9.1
The legend and the axis name the same number, so they now spell it the same way. Released as 1.9.1 rather than 1.9.0 at the maintainer’s request.
Fixed
-
An indicator legend disagreed with the axis beside it. Seen on a live chart: a Supertrend legend read
1034.0next to a price axis reading1029.20, and a Williams VIX Fix legend read0.618next to its own axis reading0.62. The two sit inches apart and describe the same quantity.The legend worked its format out from the pane’s tick size, which is wrong in both directions: a study pane carries no tick at all since 1.8.9, so the legend fell through to a magnitude ladder written for volume columns; and a price pane’s tick alone knows nothing about the two-decimal floor or a host’s own price formatter, so a volume study printed seven digits where its axis said
1.20M.The axis already answers all of that, so the legend asks it.
IndicatorHostgains an optionalformatPrice(paneIndex, value), answered byChartfrom that pane’s price scale: one source of truth instead of two derivations that agreed by luck.
1.8.9
Indicator precision, reported as one indicator reading 0.6 where it should read 0.61,
and found to be wrong for a whole class of them.
Fixed
-
A study pane was formatted in the instrument’s tick size.
Chart.setPriceScaleOptionsdocuments'primary'scope as “each pane’s right scale only”, and that is literally what it did: every pane’s right scale, not just pane 0’s. A host pushing the instrument’s tick down chart-wide set the decimals on every study pane with it.A tick size belongs to the instrument. An RSI is a dimensionless 0..100 band and a Williams VIX Fix is a percentage; neither trades in it. With a 0.10 tick the RSI axis read
70.0 / 50.0 / 30.0and the VIX Fix read0.6.minMoveis now the one field in the chart-wide block withheld from a pane that does not quote the instrument. Pane 0 is one from birth, so a caller who configures nothing sees byte-identical behaviour there. -
Those panes now carry a two-decimal floor. The span alone is too coarse for a bounded oscillator, which would be labelled in whole points and round 62.24 to
62. The floor lifts above five integer digits, so a cumulative study keeps its integer form, and a study inside a tenth of a point still gets a third decimal.So an overlay study prints at the instrument’s tick and a study on its own pane at two decimals or finer. Custom descriptors get this with nothing to declare: the rule is keyed on the pane, not on the descriptor.
-
A click on a primitive fired twice. The missed-release recovery in
_onPointerMoveends the gesture, and the realpointerupthat followed fell through to the plain click path and fired the same click again. EverysubscribeClickcontrol doubled: a legend’s hide, maximize and move-pane, a comparison row’s remove, an order pill’s cancel.
Demo
A volume on/off control, and real tooltips on the icon-only controls.
1.8.8
Documentation and package metadata. No engine change: every number this library draws is identical to 1.8.7.
Added
-
A CDN guide, and the two package fields that make the short URL work.
Every release has been on unpkg and jsDelivr since it was first published, because both sit in front of npm rather than being places you upload to. But nothing in the docs said so, and
https://unpkg.com/openalgo-chartsreturned a 404: with nomain(this package is ESM-first, throughexports) the bare URL had nothing to resolve to.unpkgandjsdelivrnow point at the standalone build.Use from a CDN leads with the module form, which is the one to reach for: import each tier straight from a URL and a chart carrying all 102 indicators is a single HTML file with no build step, no bundler and no install.
It also writes down the two things the usual CDN-publishing advice gets wrong here. There is no stylesheet, because the engine ships no DOM, so the
<link rel="stylesheet">such guides recommend would 404. And there is no CDN deployment step, because unpkg and jsDelivr mirror npm.The standalone script is documented for what it is: base tier only, with no built-in indicators or drawing tools on the global, for a page that cannot load modules.
1.8.7
Market replay, reported broken from a live terminal and rebuilt around the question it exists to ask: from here, what happens next?
Fixed
-
Entering replay drew an empty chart. The data was there and the price axis was measured correctly; the viewport sat about 280 bars to the left of every bar. An indicator’s plots are series in the same data layer, so
dataLayer.baseIndexcounts them, and 1.8.5 deferred indicator recompute to the frame. After replay truncated the price series to a prefix, the indicator’s own series stayed at full length for the rest of the turn and held the base index up with it, so a host that truncates and then positions the viewport in the same turn aimed at a right edge hundreds of bars past the end of its data.A wholesale
setDatanow recomputes before the base index is read. The tick path stays deferred, so a burst of ticks between two frames still costs one recompute.
Added
-
Intra-bar replay.
ReplayControllertakessubBars, the finer session the displayed bars are built from, and steps a sub-bar at a time so the newest bar forms in front of the user. One 5-minute bar over 1-minute data takes five steps. A bucket closes on the displayed bar verbatim rather than on the aggregate, so two feeds that disagree mid-bar still agree on every close.ReplayStategainssubIndexandsubSteps, and itsbaris the partial one while a bar forms. -
TextWatermark: a word stamped faintly across the plot to say what mode the chart is in. A chart replaying August looks exactly like a chart showing today. -
ReplayShade: dims every bar after an index and rules a line at the cut, so a replay start is picked on what was known at the time rather than on the shape of what followed.
The yfinance demo shows the whole flow, plus a snapshot menu that saves or copies the chart as a PNG.
Base engine 60.57 kB Brotli against a budget raised from 60 to 62 kB, base + trade 68.18 kB against 70, and 121.90 kB for every tier at once. A host that never replays is unaffected: the tree-shaken chart-only import is 38.25 kB. 2466 tests across 137 files.
1.8.6
An indicator that has computed a number should say what that number is, at the precision the instrument trades in. Both items are that. No indicator maths changed, so every number is the same as 1.8.5.
Added
-
Every plotted series now carries its current value as a tag on the price axis. A study drew its line to the right edge and then said nothing about where it actually sat, so reading a Supertrend stop off the chart meant tracing the line back by eye against the ladder. The pane collected one tag and stopped, which meant the instrument claimed the only slot and every overlay, indicator plot and comparison series was skipped.
A tag is drawn in its own plot’s colour, formatted by the same scale as the ticks beside it, for any series on the pane’s readout scale: an overlay on the price pane, a study on a pane of its own, a volume histogram on its own pane. A plot whose current value is
nadraws nothing rather than showing the last number it happened to have, so a flipped Supertrend tags one half and not both.lastValueVisible: falseon a series style opts out, the same flag that has always controlled the instrument’s own tag.Tags are resolved against each other and against the ladder by the existing priority table, with a new
seriesValuerank betweenpreviousCloseandtick: two studies a rupee apart do not print over one another, a tag suppresses the plain tick it would otherwise sit on, and the last-price tag outranks all of them.
Fixed
- The legend rounded a four-figure price to whole numbers. A Supertrend at 1339.70 read
1340in the legend while the price axis two inches away read 1339.70. The legend formatter was a magnitude ladder written for volume columns, and one of its rungs rounds anything at or above 1000 to no decimals: right for 12.35M of turnover, wrong for a price, and 0.30 out for anyone reading a stop off it. The legend now formats to the precision the pane’s tick implies, which is the precision the axis prints. Panes with no tick, volume and open interest among them, keep the compacting ladder, and aminMoveof 0 still means “infer” rather than “no decimals”.
Base engine 59.68 kB Brotli, indicator tier 27.27 kB against 30 kB, and 121 kB for every tier at once. The tree-shaken chart-only budget went from 38.00 to 39.00 kB: the tags cost 0.31 kB there, measured against a 37.89 kB baseline. 2436 tests across 133 files.
1.8.5
Three axis defects, all reported off a live chart placed side by side with a professional terminal. No indicator maths changed, so every number is the same as 1.8.4.
Fixed
-
The price ladder printed six labels whatever the pane measured. A 700 px pane read one price every 120 px where the reference read one every 30, so a trader taking a level off the axis was interpolating between rungs hundreds of points apart. The count now follows the pane height at a fixed 32 px spacing, which puts about twenty prices on that pane instead of five. The knobs are internal, like the axis renderer they serve;
PriceScale.ticks(maxTicks)stays public and unchanged.One visible consequence, and it matches the reference terminal: the last-price tag now always suppresses the label beside it, because rungs sit about a tag-height apart. Suppression was always the rule; there was simply rarely anything close enough to suppress.
-
A session that had just opened carried no date. Time-axis labels were only placed on the regular grid, so when a new day had fewer bars than the grid stride, no tick landed inside it and the date was never drawn: today’s bars sat under yesterday’s date with nothing marking the change. The first bar of a new day is now always a candidate and outranks the grid tick beside it, which is why a terminal prints “Sep” between two hourly labels rather than on the hour.
-
Bars painted into the price axis. Nothing clipped the plot, and a bar is positioned by its centre and drawn outward, so the newest one against the right edge put half a body and a wick into the axis strip, behind the labels. The plot is now clipped for series and primitives, and released before the ladder, the last-price tag and the trading pills, which live in that strip on purpose.
A note for hosts drawing their own last-price line
If you add your own PriceLine at the last price, you now have two tags on the same pixel
row: the engine already draws a dashed line and a filled axis tag there. Drop yours. The
engine’s reserves its band before the ladder is drawn, so the prices either side yield to it
instead of being painted over, and it carries the countdown to the bar close.
Sizes
Base engine 59.38 kB Brotli, indicator tier 27.27 kB against 30 kB, and 120.7 kB for every tier at once. The tree-shaken chart-only budget is now close: 37.89 kB against 38.00 kB.
1.8.4
A performance release. No indicator maths changed and no public surface moved, so every number this engine draws is identical to 1.8.3. If you upgrade from 1.8.3 your charts will look exactly the same, and behave better under a fast feed.
Changed
-
Indicator recompute is now scheduled with the frame rather than driven from the data update. It used to run synchronously inside the bar update, so a burst of live ticks spent a full pass over every bar, for every indicator, on every tick, and threw all but the last away unseen. Rendering was already coalesced into an animation frame; the maths was not.
Measured on a 1875-bar chart with 50 ticks arriving between two frames:
recomputes per indicator cost of the burst before 50 175 ms with five indicators, 643 ms with ten after 1 7 ms with five, 21 ms with ten That is 24x and 31x. 643 ms of blocked main thread is roughly 38 dropped frames from one burst, which is what a trader sees as jank at the open.
Deferring the maths does not defer the answer:
chart.indicators()and an indicator’svalues()both flush any pending recompute before returning, so a caller that updates a bar and reads the value back in the same turn still gets the fresh number. The flush hook onIndicatorHostis optional, so a host that implements the interface itself needs no change.One honest limit. This bounds recompute by the display refresh instead of by the tick rate; it does not make a single recompute cheaper. A host that loads deep history still pays for that history once per frame, so capping the bars fed to a live chart remains the other half of the answer.
Added
npm run bench, an indicator performance benchmark, now running in CI. The test suite proves the numbers an indicator produces and never the cost of producing them, so an algorithmic regression shipped silently before this.npm run soak, a teardown and long-session memory harness, for the two questions a unit suite cannot answer. Measured: 1.30 kB retained per chart over 300 create and destroy cycles, and a heap flat across 20,000 ticks (+9.4 bytes per tick, about 0.80 MB over a full trading session). It is deliberately not in the CI gate, because heap thresholds on a shared runner are flaky and a memory test that cries wolf teaches people to ignore memory tests.
Sizes
Indicator tier 27.27 kB Brotli against a 30 kB budget, and 120.53 kB for every tier at once. The base engine is 59.2 kB.
1.8.3
An accuracy release for the indicator tier. Every built-in was measured value by value against its standard definition over a 600-bar fixture, which turned up eight studies that were missing, seven that disagreed, and ten defaults that had been chosen here rather than read off the definition. Three more trend overlays landed on top of that sweep, so the catalogue ends the release at 102. Nothing in the descriptor contract changed, so a custom indicator is untouched.
Added
-
Eight new built-in indicators, taking the catalogue from 91 to 99. Volatility gains five: Standard Error Bands (
standard-error-bands), a regression channel whose bands sit on the endpoint of the fitted line and are only then smoothed, each leg independently; Moving Average Channel (ma-channel), a mean of the highs and a mean of the lows, each with its own length and its own plot-time displacement, rather than one average of the close with a spread around it; Chaikin Volatility (chaikin-volatility); Standard Deviation (standard-deviation); and Standard Error (standard-error). Trend gains Linear Regression Slope (linreg-slope), the least-squares gradient in price per bar, and Smoothed Moving Average (smma), Wilder’s smoother with alpha1 / length. Volume gains Net Volume (net-volume), the bar’s own volume signed by the direction its close took. See the built-in catalog. -
Three of those carry a decision worth stating, because each is a place two implementations of the “same” study routinely disagree:
- Standard Deviation is the population figure (divide by n), not the sample one. That is settled by measurement rather than by reading: at bar 4 of the fixture the standard definition reads 1.5514, and the sample formula reads 1.7346.
- Standard Error divides by
length - 2, since the fitted slope and intercept each consume a degree of freedom. That is what separates it from a standard deviation, and it is why the length input cannot go below 3. - Net Volume has no warmup gap. Bar 0 has no previous close, so neither the up nor
the down comparison can hold and the bar reads 0, not a blank. It is drawn as a
histogram on base 0 rather than as a line: a signed quantity that flips every few bars
is unreadable as a line, and the sibling
volumedescriptor is already a histogram on base 0. The numbers are identical either way.
-
Three more trend overlays, taking the catalogue from 99 to 102. All three are
onchartand all three are Trend:- T3 Average (
t3), a smoothing filter built by nesting exponential averages and recombining them with a weighting factor, so it turns faster than a triple EMA without the overshoot a shorter length would buy. Defaultslength=5,factor=0.7. The nesting is what sets the warmup: each layer costs2 * (length - 1)bars, so at the default length the first value prints at bar 24. Settingfactor=0collapses it to a plain chained average, which prints earlier.highlightMovementscolours the line per bar as it rises and falls. - Hull Suite (
hull-suite), a hull average plotted against its own value two bars back, with the gap between them shaded so the turn is visible before the line has finished making it. Three variations off onemodeinput:Hma,Ehma(the outer pass is exponential rather than weighted) andThma(which runs on half the length, so it prints well before the other two). Defaultlength=55.visualSwitchhides the band, andcandleColhands the price candles the same trend colour. - Consolidation and Breakout (
consolidation-breakout), an inside-bar range tracker. A bar that stays inside the previous range extends it; the range high and low are drawn as rails, the inside bars are tinted, and the bar that finally closes outside is marked with a triangle. A break only counts once the range has held for more than one bar, so the follow-through bar right after a break is not itself a second signal.
- T3 Average (
Changed
-
Ten defaults now match the standard definition rather than a house preference:
Indicator Setting Was Now sma,ema,wmalength20 9 stochastickSmoothing3 1 ccimaLength14 20 obvmaLength14 9 ma-crosslongLength21 26 alligatorjawLength/teethLength/lipsLength13 / 8 / 5 21 / 13 / 8 This is visible on upgrade: an EMA added with no settings is now a 9-EMA. A chart saved with
saveStatestores the settings each instance actually had, so a restored layout keeps its old lengths and only newly added indicators pick up the new numbers. -
Warmups moved, so several studies now start later and start correct. EMA’s first print is at
length - 1instead of bar 0; MACD and ADX moved when their legs became SMA-seeded; Special K needs 725 bars before it prints, which is what its 12-term table actually requires. A plot that starts later is not a regression: the earlier bars were values the definition does not produce.
Fixed
- EMA drew a seed as though it were a reading.
addIndicator('ema')called the base bundle’sema(), which seeds from the first value and emits from bar 0. At length 14 that is 155 wrong bars against the standard definition, worst error 6.9e-4 on the seed bar itself. The descriptor now uses the SMA-seeded EMA the rest of the tier already used, and is exact. The rawema()export is deliberately unchanged: it matchesopenalgo.taand is documented API. - Parabolic SAR flipped trend on its own seed bar, and clamped the stop into the
previous two bars’ range before testing for a reversal, so a stop pulled back below the
bar could no longer be breached and reversals the definition fires were dropped. The
order is now propagate, reverse, accelerate, clamp. That is 39 wrong bars in 600 gone,
on top of this release’s earlier correction to the reversal stop (
max(ep, this bar's high)). - ADX went blank for the rest of the chart after one flat bar. A bar whose true range
is exactly 0, which is what an instrument locked at one price by a circuit freeze or a
halt produces, gapped
+DI,-DIandDX. Since ADX is a Wilder average over DX, that single gap poisoned the recursion from there on. The last finite pair is now carried across a zero or absent smoothed true range. ADX’s true-range seeding was corrected earlier in the same release. - HMA sat 0.4 slopes high at every odd length. The Hull average halves its length and hands that to a weighted average. We floored the half, so a length of 9 asked for a 4-bar window where the definition asks for a 4.5-bar one, five bars weighted 4.5 down to 0.5. That shortened the fast leg’s lag from 1.2 bars to 1, and the three passes then cancelled to zero lag instead of the correct 0.4. Even lengths were always exact, which is why it survived: the default of 9 is odd and the error is a smooth 4.6e-3, never a visible break. The half is now carried at full precision; the outer period still floors its square root, which is a different question and was always right.
- MACD’s EMA legs are SMA-seeded, Fisher Transform no longer divides by a flat window, Chop Zone takes its range off the high series, and Special K uses the full 12-term table.
Sizes
Indicator tier 27.27 kB Brotli against a 30 kB budget, and 120.38 kB for every tier at once. The base engine is unchanged at 59.06 kB: all eleven new studies land in the lazy indicator tier, so a page that does not import it pays nothing for them.
1.8.2
Added
ctx.tickSize, the instrument tick size on the calculation context, taken from the pane’s price scaleminMove. An indicator that sizes a range in ticks no longer has to ask the user for a number the chart already holds.undefinedwhen the host has not set one, because the scale’s 0 means “infer from the range” rather than a real tick. See the calculation context.
1.8.1
The indicator descriptor learns what the chart is doing, not only what the bars say: where the last bar stands, when a condition it declared came true, and how to state a regime as shading or as a colour on the price candles themselves. Around that, four smaller things a study kept having to hand-roll.
Added
- A calculation context.
calctakes an optional fourth argument (andcalcTaila sixth) carryingbarState(isNew,isConfirmed,isRealtime,lastIndex),symbol,interval,timezoneandnow(), so a study can act once per bar rather than once per tick. Optional and trailing, so every existing descriptor keeps its exact signature and behaviour. See The calculation context. alerts. A descriptor declares the conditions it wants watched, and a trigger arrives asindicator:alerton the chart’s bus. They fire only on a live tail change, so an indicator dropped onto two years of bars announces nothing. See Alerts and Indicator alerts.background, per-bar shading behind the indicator’s own pane, for a regime study whose answer is a property of the whole bar rather than of a price. Contributes nothing to autoscale, coalesces same-colour runs, culls off-screen bars.IndicatorBackgroundis exported as a plain primitive. See Shading the pane by regime.barColors, recolouring the main price candles from a study’s verdict, one publisher at a time. The engine clones only the bars whose colour changes, so it never writes into the array the host handedsetData. See Recolouring the price candles.IndicatorPlot.ohlc, fourcalccolumns in the same result so one plot draws as candles or OHLC bars: a smoothed overlay, a higher-timeframe candle, a synthetic spread. See A plot drawn as candles.- Session windows you state rather than infer:
parseSessionSpec,inSessionAtandsessionFlagsread'0915-1015'or'0930-1600:23456', half-open, wrapping past midnight, with the day filter naming the day the window opens on. See Stating a window as a string. - Interval introspection:
intervalParts,isIntradayInterval,isDailyInterval,isSecondsIntervalandisTickIntervalread the answer off the bucketing rule rather than the code’s spelling, so'120m'and'2h'agree and a host’s own registered code answers too. See Reading a code back. withAlphaandfromGradienton the public surface, so a per-bar colour rule stops needing a private hex parser. See Colour helpers.chart.beginPick(kind, cb), interactive capture: the next plot click answers with a price or a bar time. A'time'pick snaps to the bar clicked, and panning is deliberately left alone so you can scroll to the bar you mean first. See Picking a price or a bar.
Changed
IndicatorHostgained two optional members,setBarColorsandemit. Both optional, so a host implementing the interface itself needs no change.
1.7.1
The indicator descriptor gains the parts a ported study needs and could not express, and per-bar colour reaches every renderer that draws a bar rather than the two that happened to read it.
Added
draws: lines, boxes, labels and polylines anchored to{ time, price }, returned by the descriptor after everycalc. A plot is one value per bar and a level is a horizontal line, so a pivot-to-pivot trendline or a supply zone had nowhere to live. Anchors are times rather than logical indices, because paging history in at the left edge shifts every index; a ray extends along its own slope; the layer contributes nothing to autoscale, so a projection cannot squash the study it annotates. See Drawings.- Levels derived from the data.
levelsnow runs after everycalcand is handed the bars and computed values as well as the settings, so a level can be yesterday’s high rather than only a number the user typed. The context spreads the settings keys onto itself, so descriptors written againstlevels(settings)are untouched.IndicatorLevelgainedlineWidthandlineStyle. See Reference levels. IndicatorPlot.overlay, one plot of a pane indicator drawn on the price pane, so an oscillator with a trailing stop is not two indicators the user configures twice.- A wider attach context:
symbol,interval,now,paneIndex,addPrimitiveandremovePrimitive.symbolandintervalanswerundefinedunderchart.addIndicator, since the core is handed bars and never an instrument. See the attach context. Bar.coloron every Family-A renderer. Candles and OHLC bars take it on body, border and wick together, so a recoloured candle cannot keep a wick arguing the other way; line, step, area and the HLC-area close line split their stroke into runs at the bars where it changes. See per-bar colour.- Multi-line marker text, gradient and per-point colour on
IndicatorFill,PriceLineOptions.lineStyle, and 21 calculation helpers exported fromopenalgo-charts/indicatorsso a custom descriptor can port a study instead of re-deriving its maths. See Calculation helpers.
Fixed
calcTailcould splice a tail onto a history that no longer existed. The incremental path was gated on the bar count beingnorn + 1, which a symbol change or one older bar paged in at the left edge can satisfy while the earlier bars are no longer the same ones, leaving the plot silently wrong until the next fullcalc. The guard now reads the first and last bar times instead.- A column plot ignored the
style.colorits generated Colour control writes, and an area series drew a solid outline when itslineStylewas dashed or dotted.
Sizes
Base 56 to 60 kB (measured 57.31) and base + trade 64 to 68 kB (measured 64.92). The trade tier itself did not grow, so raising the combined budget by the base’s increase keeps its allowance exactly where it was. 1949 tests across 106 files.
1.6.0
Broker-readiness hardening across the trade and feed layers. An independent reassessment scored the result 68/100, up from 61.
Fixed, and the reason to upgrade
modifyorderzeroed the price field the caller did not touch. Dragging a stop-limit order’s line wiped its limit price to zero at the broker, on a live working order. OpenAlgo’s modifyorder takes the whole order rather than a delta, andmodify()took the two price fields from the patch alone with?? 0. Defaulting an unmentioned field to zero does not leave it alone. A stop-limit carries both a limit and a trigger, and dragging its line sends only the trigger, so that path sentprice: 0. Verified against the published 1.5.0 package, which answersprice: 0. Upgrade if your terminal has draggable stop lines.
Breaking, despite the minor version
mapOrderreturnsDecodedOrder, notOrder.statuswidens toOrderStatus | 'unknown'. Previously an unrecognised broker status silently becameworking, a missing action silently became BUY, and an unreadable number became 0. UsegetOrderBook(), which returns{ orders, quarantined }, so a row that cannot be decoded is surfaced rather than rendered as live state.
Added
- Feed-level quantity and idempotency guards. Both existed in
OrderEngineand were unreachable for anyone calling the feed directly. A guarantee you can bypass by calling one layer down is not a guarantee. Quantity is checked on every order with no configuration; the newconstraintshook adds freeze and lot limits, including on market orders. A repeatedclientTokenis refused pre-flight, and a failure after the request leaves is marked ambiguous rather than released. - Chart-anchored primitives.
addPrimitive(p, { anchor: 'chart-bottom' })makes a primitive chart furniture rather than pane furniture, re-homed as panes come and go. This also fixes maximize, which hides the other panes and used to take a pane-0 watermark with them. See Primitives and plugins. paneAdded, the counterpartpaneRemovednever had.- Analyzer mode is a guard, not cargo.
place()verifies the server’s mode instead of sending one the server would ignore. - WebSocket handshake gating, liveness probe, jittered backoff, topic-derived identity. Reference-counted subscriptions, so one consumer leaving no longer cuts the stream for the others.
Security
- Polynomial ReDoS in
scaleFont(shipped code): 813ms to 1ms on 40,000 digits, identical output on real font strings. Development advisories 12 to 0. Actions pinned by commit SHA, least-privilege CI, SBOM, CodeQL, and a release workflow using npm OIDC trusted publishing so no long-lived token need exist.
1.5.0
Fixed
- Removing a sub-plot indicator could throw
Cannot read properties of undefined (reading 'removeSeries'), leaving the teardown half-done: the legend went, the plot stayed. A series captured the pane slot it was created in, and slots are not stable: removing an emptied indicator pane splices the array so everything below shifts up one, and moving a pane swaps two entries. Three sub-plots, remove the first, then the last, and the index points past the end. Clearing them bottom-up shifts nothing, which is why it looked intermittent. The quieter half had no symptom at all: a stale index that still lands on a live pane strips the wrong one. Series now hold their pane by identity.
Added
SeriesStyle.bodyVisible.falsedrops the candle body fill and leaves the outline and the wick. Distinct fromhollow, which empties only the up candles. It exists so the Body row in Settings can carry a real switch: the row went without one until now, because nothing inSeriesStylebacked it and a checkbox that writes nowhere is worse than an absent one.
1.4.0
Breaking, in a minor release by explicit decision. Both changes only affect codes that were already producing the wrong answer.
intervalToSecondsthrowsUnknownIntervalErrorinstead of quietly answering 60 seconds for anything it did not recognise, which drew minute bars under whatever label the caller thought it had asked for. UsetryResolveInterval/isKnownIntervalto ask without throwing.- Upper-case
Mno longer resolves. The token regex folded case, soMread as minutes and anything gating on “has the next bar closed yet” believed a month closed every sixty seconds. A host wanting months registers a calendar interval deliberately.
Added
- Chart linking.
createLinkGroup({ crosshair, viewport, symbol })drives a grid as one workspace. Nothing crosses a chart boundary as a logical index: every value converts to a time on the sender and back to an index on the receiver, against that chart’s own bars. A follower resolves an instant to the last bar that had opened by then, not the nearest timestamp, because a bar is stamped at its open and “nearest” would show tomorrow’s candle for half of every day. See Chart Linking. - A drawing clipboard.
copy,cutandpaste, across charts and tabs through the OS clipboard, under one namespaced versioned key so foreign text pastes nothing.cutdeletes only after the write lands. - Warm-load bar caching.
withBarCache(feed, options)wraps anyDataFeed. The forming bar is never stored, so a warm load is short by at most the bar currently building and is never wrong about one it returns. See Bar Cache. - Custom intervals.
registerIntervaldescribes how to bucket, not how many seconds a bar lasts, because a month is not 30 days and a tick bar is not time-based at all. See Custom Intervals.
Fixed
- The kinetic glide after a flick emitted no viewport event, so anything downstream stayed on the window where the pointer lifted while the chart coasted on. Drag velocity never decayed either, so a drag that paused before releasing still flung the chart.
- The cache carried a second, weaker interval parser that could not see registered codes.
1.3.0
Added
- Market replay.
ReplayControllerwalks a historical session forward one bar at a time so a setup can be practised on it. Headless, in the same senseDrawingControlleris: it owns the playhead and ships no DOM, and the host renders its transport bar fromstate()and thereplay:*events. See Market Replay. - Multi-symbol comparison.
addComparison(chart, { symbol, bars })puts a second instrument on the primary one’s pane. Comparability comes from the scale rather than from the data, so the legend and crosshair still speak in real prices while equal percentage moves land on equal pixels. See Symbol Comparison. - Chart linking.
createLinkGroup({ crosshair, viewport, symbol })drives a grid of charts as one workspace. Nothing crosses a chart boundary as a logical index: every value is converted to a time on the sender and back to an index on the receiver, against that chart’s own bars, so a daily chart and an hourly one stay on the same instant and the same wall-clock window. A follower refuses an instant outside its coverage, and inside it snaps to the nearest bar in time or draws nothing underwhenMissing: 'hide'. See Chart Linking. - A drawing clipboard.
copy,cutandpasteonDrawingController, over a payload namespaced under one JSON key, so foreign text pastes nothing instead of throwing, and every field is validated before it can reach the model. A refused OS clipboard degrades to an in-page one rather than losing the copy, and a cut deletes only after the write succeeds. See Drawing Clipboard. - Warm-load bar caching.
withBarCache(feed)wraps anyDataFeed, keyedsymbol | exchange | intervalwith the range left out so a pan still hits. The forming bar is never stored, and freshness is gated on the feed’s own bar grid rather than on UTC midnight. See Bar Cache. - An interval registry. An interval code resolves to a bucketing rule, not a duration: fixed seconds, a calendar period, a tick count, or traded volume. Months, quarters and years open at local midnight in a named zone, so February is 29 days and a New York month is not a Mumbai month. See Custom Intervals.
- A chart settings schema.
chartSettingsSchema(chart)describes a dialog in the same control vocabulary the indicator form already uses, withreadChartSettingsandapplyChartSettingsas its round trip. Five tabs of our own (Price, Readout, Axes, Appearance, Trading), no Alerts and no Events, because the first is not built and the second has no data source. See Settings & Menus. - A paired colour control,
colorPair. A bullish and a bearish colour are one labelled row carrying both swatches and, where a flag exists behind it, its switch. Its value keys are ordinary flat keys, so nothing about the patch protocol, JSON-safety or state restore had to change to carry it. - The chart timezone is a control,
time.timezoneon the Axes tab, so a generated dialog gets a zone picker instead of the host bolting one on. See Timezones. - Reference price levels as one family.
PriceLevelsis a primitive over ten kinds (previous close, session high and low, last price, four extended-hours levels, bid and ask). Each kind is one options group carrying independentlineandlabelflags, so a level’s line and its axis tag cannot drift apart. Levels with no data arenull, never0, andavailable(kind)is what lets a host grey the control with its state still visible. See Price Levels & Axis Chrome. - Axis chrome: a corner session clock and a bar-close countdown. Both off unless asked for. The clock reads in the chart’s zone with its UTC offset underneath; the countdown is a second row in the last-price tag, its cadence read back from the bars as a median so a backfilled duplicate cannot halve it, rolling into the next bar rather than stalling at zero when a feed is late.
- A
contextmenuevent carrying the pane, the price, the time and a classified target, which is the part a canvas cannot work out for itself. A price-scale hit now says which strip was hit and which of the pane’s scales it draws. - A price-axis menu the host can build without guessing.
chart.priceAxisState(pane, scaleId)returns everything such a menu renders, andsetPriceAxisOptions,setPriceAxisAutoFit,setPriceAxisLockRatioandmovePriceAxisact on one axis at a time. See Scales & Panes. - Price-scale
percentageandindexed-to-100modes, andcolorByPreviousCloseplusprecisiononSeriesStyle. - The Canvas option block: grid, crosshair, scale text and lines, and plot margins, with the resolvers exported so a host previews with the code that paints. See Theming & Chart Options.
- Status-line options on the pane legend, switchable field by field per legend or chart-wide.
Changed
- An unknown interval code throws instead of meaning one minute.
resolveIntervalandintervalToSecondsraiseUnknownIntervalError, andsubscribeBarstherefore fails at subscribe time rather than drawing mislabelled minute bars for the life of a subscription.tryResolveIntervalandisKnownIntervalare the non-throwing probes for a host validating a picker. Note that the built-in token grammar is case-insensitive, so a bareMis a minute: register a calendar interval if it should be a month. panandzoomnow fire for programmatic viewport changes too, not only for gestures:setVisibleLogicalRange,fitContent,resetScaleand the keyboard pan and zoom commands all announce themselves, and only when the window actually moved. A linked grid follows an arrow key and a restored zoom; afitContenton a fresh chart now broadcasts, so suspend the viewport channel while loading a follower.- Destruction is announced rather than inferred.
chart.isDestroyed, adestroyevent, an idempotentdestroy(), and every listener dropped afterwards, so anything holding a chart it did not create can let go of it instead of guessing from an empty pane list. - Price-axis labels no longer draw underneath the last-price tag. The tag reserves its band before the ladder is drawn, so the tick it would have painted over is dropped instead of the two becoming illegible. Turning the countdown on makes the tag taller and can cost one more tick, which is the honest trade for the second row.
- The price scale owns its tick ladder, because a nice price is an ugly percentage in the rebasing modes: the values have to be chosen in label space.
- One settings key moved.
scales.lastValueVisibleis nowsymbol.lastValueVisible, so it names theSeriesStylefield it patches and sits beside the price line it contradicts when the two disagree. Every other dotted key stayed put. setGridOptionstakes the whole grid block, not just the two visibility flags.- Session windows are wall clock in a zone they name, never a fixed offset.
Fixed
- Half the chart ignored a price axis that had moved. Moving a pane’s prices to the left strip relabelled the axis and left the crosshair tag, the last-price line and tag, the coordinate API and the axis drag all reading the right scale, and the left strip could not be dragged at all. Each now follows the scale the pane’s values actually sit on.
- A chart could measure itself before its container had a size, leaving every price
scale on its
0..1placeholder. The chart now re-applies the container size one frame after construction, and only when the container reports a real positive box, so a host-applied size and a still-hidden container are both left alone. - The zone stopped at the axis. Four wiring gaps: the pane pre-baked its own labeller
instead of handing the zone to the time axis, nothing put the chart’s zone on an
indicator’s settings,
colorByPreviousClosewas copied into the candle style and nowhere else, and the demo’s readings carried nofieldtag so three status-line switches reached nothing. - Hollow candles ignored two of their own colour options: the up outline used
upColorrather thanborderUpColor, andborderVisiblewas never consulted.
1.2.0
Added
- Tables over the chart.
ChartTableis a new screen-space primitive: a grid pinned to a pane corner that stays put while the chart pans underneath. Columns size from a fixed width, a per-column array, or a percentage of the plot; rows size from a fixed height, a percentage, or per-row weights. IndicatorDescriptor.table, an optional hook besidemarkers, returning rows of cells and the options to draw them with. It runs aftercalc, so it reads the values it just produced.- Five new built-in indicators, taking the catalogue from 86 to 91: Seasonality (a monthly return heatmap, and the first indicator whose entire output is a table), CPR with Floor Pivot (Daily, Weekly and Monthly frames with an Auto mode), AlphaTrend, Range Analysis, and WaveTrend Pro.
sessionStartIndices,sessionStartFlagsandcalendarPeriodFlagson the package root, which read an exchange’s trading day back out of the bar timestamps.
Changed
- VWAP, TWAP and CPR read the trading session from the bars. They anchored to an IST calendar day, which is 18:30 UTC and therefore the middle of a New York session: VWAP restarted every afternoon, and CPR built each daily frame out of one session’s tail plus the next session’s head. Behaviour on NSE data is unchanged, since its session date and IST date always agree.
maximizePanehides the other panes rather than collapsing them to a sliver, and the bottom visible pane owns the time axis. Stored weights are no longer rewritten, so un-maximizing restores the stack exactly.
Fixed
- A pane kept the price range of a departed indicator, so a table-only indicator inherited a price ladder it had no prices for. A scale now forgets its range when it loses its last series, and the axis is drawn only for a scale that has been measured.
1.1.0
Added
- 66 new built-in indicators, taking the catalogue from 20 to 86. Moving averages and overlays (ALMA, DEMA, TEMA, HMA, KAMA, LSMA, VWMA, McGinley Dynamic, Median, MA Cross, MA Ribbon, TWAP, Envelope, Donchian, Keltner, Chande Kroll Stop, Chandelier Exit, Alligator), momentum and strength (Aroon, Awesome Oscillator, Balance of Power, Chande MO, Coppock, DPO, Fisher, Connors RSI, Momentum, ROC, PPO, TRIX, TSI, SMI and both Ergodic variants, KST), ranges (Stochastic RSI, Williams %R, Ultimate Oscillator, RVGI, RVI, Woodies CCI, Special K), volatility (Bollinger %b, BandWidth, BBTrend, Choppiness, Historical Volatility, ADR, Chop Zone, Mass Index, Ulcer), volume and flow (Chaikin Money Flow and Oscillator, Ease of Movement, Elder Force Index, Klinger, NVI, PVI, PVT, PVO), and signals (Vortex, Volatility Stop, Trend Strength Index, Williams Fractals, RSI Divergence).
- Shaded fill regions on 22 descriptors, 28 fills in all, including the
overbought and oversold background bands on RSI, Stochastic, Stochastic RSI,
MFI, CCI, Connors RSI, Choppiness, Williams %R, RVI, SMI and Bollinger %b.
IndicatorFillSpec.betweenresolves againstcalcoutput columns rather than declared plots, so a band between two levels is a fill between two constant columns that are never drawn. - A smoothing block on CCI and OBV: a selectable moving average over the indicator’s own output, with optional Bollinger Bands around it.
Changed
- VWAP gained standard-deviation bands (three pairs at multipliers 1, 2 and
3, only the first shown by default), a Standard Deviation or Percentage
calcMode, six anchor periods, anoffset, and a fill per band pair. The cumulative maths is unchanged. Note the bands collapse onto the line on daily bars with the session anchor, because each bar is then its own session. - Supertrend draws its shaded band between the stop and the candle body midpoint, recolouring at each flip.
Fixed
- The
columnrenderer ignored per-bar colour.drawColumnsalways used the series style’s up/down pair and discarded thecolorset on each point, even thoughcolorByis documented as supported by both the histogram and column renderers. Chop Zone, Awesome Oscillator and BBTrend were each painting one flat colour across the whole series.
1.0.29
Added
- HalfTrend (
halftrend) joins the indicator tier, taking the built-in count to 20. A trend level that holds flat through noise: a flip needs both a mean crossing and a close beyond the previous bar’s extreme, and the new level starts from where the other side ended, so the line steps rather than chasing price. Ships with half-ATR channel bands, per-side ribbons, and Buy/Sell label plates, each independently toggleable. IndicatorDescriptor.markers: an optional hook returning bar-anchored markers, run after everycalc. A plot is a column of prices; a signal is a discrete event with a name, and no plot expresses that. The layer is created lazily, so an indicator without the hook costs nothing.labelUp/labelDownmarker shapes: text plates with a tail pointing at the anchor price.drawLabelis exported for custom primitives.
Fixed
- The exported
VERSIONhad drifted four patches behindpackage.json, so anything displaying the library version showed the wrong one. A test now pins them together. - Pane legends no longer print
true true true: the parameter summary included boolean inputs, whose bare values name nothing.
Documentation
- A pass over claims that no longer matched the source: the stated Footprint gaps
were all stale (the renderer is theme-driven, has
setOptions, three display modes, and draws stacked imbalances), overlay price scales are implemented (priceScaleId: ''),DEFAULT_THEMEislightThemerather than the documenteddarkTheme, and theclick/drag:endpayloads,TradeFeedshape,OrderEngine.placeOrder,DrawingController.add,IPrimitive.zOrder, Market Profile colour modes, and arrow-key pan distance were all documented wrong. See the changelog for the itemised list.
1.0.28
Added
- Drawing tools carry a keyboard
shortcut:Alt+Ttrend line,Alt+Hhorizontal line,Alt+Jhorizontal ray,Alt+Vvertical line,Alt+Ccross line, and whatever a custom tool declares. matchDrawingShortcut(event)resolves a key event to a tool id anddrawingShortcuts()returns theid -> shortcutmap. The library binds no listener, and modifiers must match exactly so a tool cannot shadow a host chord.
1.0.27
Added
PriceLine.setOptions()restyles a line in place (colour, width, dash, labels) and repaints. Previously only the price and labels could change, so a last-price line could not follow the tick direction.idis excluded: it is the hit-test handle.
1.0.26
Fixed
- Price-scale margins are fractions of the pane height, as documented. They
padded the data span instead, so a volume overlay asking for
marginTop: 0.82to sit in the bottom 18% of the pane drew across 55% of it. The data band now occupies the1 - marginTop - marginBottomleft between the margins. Charts on the0.1default shift by a few percent; only large margins move much.
1.0.25
Added
LogoWatermarkplate padding is settable viapadding: a number for both axes, or{ x, y }.heightsizes the mark andpaddingsizes the plate, so a 40px mark withpadding: 2.5sits in a 45x45 square.
Fixed
- The plate now measures its requested size at every DPR; the padding was rounded and doubled, so a 45px plate came out 46 on a non-retina display.
- The hover target follows the padding rather than sitting fixed at 4px.
1.0.24
Added
-
LogoWatermarkcan be a hover-revealed, clickable brand lockup.labelshows the mark alone at rest and unrolls the wording to its right on hover. The mark and label share one colour, so the pair cannot render in two unrelated shades. A roundedbackgroundplate keeps the wording readable over busy candles.hrefmarks it clickable: the hit reports a pointer cursor andhref()returns the URL with UTM attribution naming the embedding page. A canvas cannot hold an anchor, so the host navigates.
Fixed
- Tinting threw where the drawing context had no document to borrow; it now falls back to an untinted mark.
1.0.23
Fixed
smawas poisoned permanently by a single non-finite value. A running sum that absorbsNaNstaysNaN, and subtracting it back out when it leaves the window cannot restore it, so any indicator chained onto one with a warmup gap produced nothing for the entire series. It now sums finite values and counts the rest, recovering as soon as the gap leaves the window.
Added
- Per-bar plot colour:
IndicatorPlot.colorByreturns a colour per bar, and histogram / column renderers honour acoloron the data point. - MACD’s histogram is four states: above/below zero for side, rising/falling for whether momentum is building or fading. All four are settings.
William VIX FIX(williams-vix-fix): lime whenwvfpierces its Bollinger upper band or the top percentile of its range, gray otherwise. Thehp/sdtoggles hide the plots only; the colour rule keeps working.- A hairline between stacked panes, themed as
paneSeparator.
1.0.22
Fixed
-
A maximized indicator pane drew its legend through the host’s overlay.
legendOffsetwas pinned to one pane index. Maximizing a lower pane parks the others at a placeholder weight, so the maximized pane moves into the corner the host has covered. Not being pane 0, it kept the default corner and drew straight through the host’s symbol / OHLC line.The offset now follows whichever pane actually renders at the chart’s top, re-evaluated on every relayout.
legendOffset.paneIndexis gone: it was a fixed answer to a question whose answer moves.
1.0.21
Fixed
-
legendOffsetshifted every pane, not just the overlaid one. Offsetting the price pane clear of a host’s OHLC readout also pushed each lower indicator pane’s legend down by the same amount. A lower pane is short, so its row went off the pane, taking the settings, close and move-pane buttons with it: an RSI pane could not be configured, moved or removed from its own legend.The offset now applies to one pane,
paneIndex(default 0).
1.0.20
Added
-
A repeated indicator gets its own colours. A second EMA took the descriptor’s one default blue, so three EMAs were indistinguishable on the chart and in the legend alike. The 2nd and later instances of the same
indicatorIdnow rotate through a palette.Only unset colour keys are filled, so an explicit colour always wins; the first instance keeps the descriptor’s own colours; and the count is per indicator id, so two EMAs do not shift the first RSI. Multi-plot indicators stride by their plot count, so MACD’s three lines shift as a block.
1.0.19
Fixed
BuySellButtonspainted its label outside the button at anyscalebelow 1. The two text baselines were fixed pixel offsets tuned for the 42px box: exact at scale 1, and increasingly wrong below it (at 0.72 the label sat 3px past the bottom edge). They are fractions of the button height now.
1.0.18
Added
- A plot’s chart type is now a setting.
indicatorStyleInputsgenerates a “Plot style” select per plot (<plot>:type), so the same column of numbers can be drawn as a line, step, area, histogram or columns: a descriptor cannot know which reads best. Defaults to the declared type;INDICATOR_PLOT_STYLESis the option list. Switching rebuilds that plot’s series, because the chart type belongs to the series rather than the style bag.
1.0.17
Added
-
Indicator fills: the Ichimoku cloud. A descriptor can declare
fills, shading the band between two of its plots:fills: [{ between: ['spanA', 'spanB'], colorUpKey: 'cloudUpColor', colorDownKey: 'cloudDownColor' }]Two lines are not the same picture as a filled region: the shading is what makes “price is above the cloud” and “the cloud flipped” readable, and which span leads is itself the signal, hence two colours. Ichimoku ships one, and
IndicatorFillis exported for shading your own pair.Runs split at the exact crossing rather than the nearest bar, and a gap in either plot breaks the band instead of bridging it.
-
measurereports what a measurement should: price and time arrows plus a chip with the change, percentage, bar count, calendar span and the volume over the span.
Changed
- Base tier budget 35 to 36 KB, base+trade 41.5 to 42.5 KB, full 72 to 73 KB: the
fill primitive sits in the base bundle because
IndicatorInstancedoes.
1.0.16
Added
-
BuySellButtonstakes ascale, default 1, clamped 0.6-1.5:new BuySellButtons({ id: 'trade', scale: 0.75 })The panel was a fixed 190x42, which crowds the pane’s legend rows in a dense trading layout. Box, gaps, corner radius and type scale together, and so do the hit rects.
1.0.15
Added
-
legendOffsetchart option sets where indicator legend rows start inside a pane, in media px:createChart(el, { legendOffset: { top: 40 } });A host drawing its own overlay in the top-left corner had no way to push the canvas legend rows clear of it, so adding an indicator put its legend underneath the host’s own text: unreadable, with its settings and close buttons invisible and unclickable. Defaults to
{ top: 6, left: 8 }.
1.0.14
Fixed
-
The lazy tiers could not be used from TypeScript at all. Passing the chart from
createChart()intonew DrawingController(chart)failed with “Types have separate declarations of a private property_container”, and there was no way to fix it from outside the package.openalgo-charts/draw,/tradeand/profilewere all affected.Each tier is bundled into its own
.d.ts. A tier that imported a shared type through a relative path had that declaration inlined, soChart,TimeScale,PriceScaleandDataLayereach existed twice. Those classes carry private members, which makes them nominal rather than structural, so the second copy was a genuinely different type. JavaScript consumers never saw it, which is why it survived this long.Tiers now import shared types from the package entry, which tier builds already leave external. One declaration, one identity. The tier declarations shrank as a side effect (draw 47 KB -> 17 KB).
-
DrawingControllernow takes a structuralDrawingChartHost(the members it actually uses) instead of the wholeChartclass, so the real chart satisfies it with nothing to cast.
Added
DataLayer,IndexedBarandSeriesIdare exported types.chart.dataLayerwas public while its type was not nameable.npm run check:dts(wired intoverify) fails the build if a tier re-inlines a shared declaration.
1.0.13
Added: Drawing tools
- Nine more tools, taking the built-in set from 34 to 43 (draw tier 8.3 KB ->
11.3 KB Brotli). Shapes:
rotated-rectangle,double-curve. Cycles:cyclic-lines,time-cycles,sine-line. Text and notes:price-label,callout,flag-mark. Brushes:brush. See Drawing Tools. rotated-rectanglelays out one edge from anchors 0->1 and takes its depth perpendicular from anchor 2, so it can follow a trend channel an axis-aligned rectangle cannot.double-curvemirrors its control about the chord’s midpoint, so the second half bends back into an S.price-labelreads its value off the anchor, so dragging it re-reads rather than going stale the way a typed-intextwould.DrawingTool.freehand: sample the cursor while the pointer is held and commit on release, so one press-drag-release is one stroke. Only the two end anchors get grab handles.DrawingTool.expand: turn the anchors actually clicked into the tool’s full anchor set, so it can place a complete, editable default from fewer clicks. ReceivesbarSecondsandvisibleBars.- Long/Short Position place from a single click at 1:1, sized to about 8% of the visible range so the box is grabbable at any zoom, with all three anchors still draggable. They previously needed three clicks and drew nothing until the third.
- Position and Forecast readouts are chips. Target and Stop each carry their move, percentage and cash amount and sit outside their own line, so the layout reads the same for a long and a short; Qty and risk/reward sit at the entry. Forecast shows its anchor price/date, the projected move with duration and landing price/date, and a SUCCESS/MISSED verdict once the window has elapsed.
PrimitiveRenderContext.bars(): the pane’s primary price series, lazily, for a primitive that needs what price actually did rather than just the scales. See Primitives.
Fixed
- Brush and Highlighter behaved as polylines: a vertex per click, with no
way to end the shape. Both declare
points: 0, which the controller read as “collect anchors until told to stop”, the contractpolylinewants. They are freehand now. - A selected brush showed a grab handle on every sampled point, burying the ink under dozens of circles and leaving no way to grab the stroke itself. A freehand drawing handles only its two ends while keeping every sample.
pathandpolylinecould never be finished. Nothing completed apoints: 0tool. Double-click reset the view instead, so they collected vertices forever. Double-click now finishes the shape while a tool is armed, andcontroller.finish()is public for binding a key. A fixed-anchor tool ignores it, so a stray double-click cannot commit a degenerate shape.pathis a click-per-vertex shape again with an arrowhead on its last leg, what separates it frompolyline. The freehand brush moved to its ownbrushid, so the two are no longer one tool wearing two names.
Demo
- 5m / 15m / 30m drew nothing while 1h and 1d worked. Yahoo caps intraday history (~60 days for 5m-90m, ~730 for 1h) and answers an over-long request with an empty frame rather than an error, so the default 1y range silently produced no bars. The range is clamped to what the interval can serve, the range menu only offers those, and the status line says when it clamped.
- Chart-only full screen: a toolbar button full-screens the stage (rail + chart + legend) rather than the page, with a matching exit chip inside it.
- The toolbar destroyed the element it depended on.
#statusis moved into the shellbar on the first render, and every later render wiped the bar before reading it, so the second re-render threw, and from then on changing symbol, timeframe or range silently did nothing. - The dev server sends
Cache-Control: no-store. The browser keeps ES modules in its own module map, so rebuilding the library and reloading still ran the previous bundle with no sign anything was stale.
1.0.12
Fixed
-
The forming candle could render as two overlapping candles of opposite colour (a red body with a green one painted over it, and a wick spanning both) while live ticks came in.
setDatasorted its input by time but never de-duplicated it, while the shared time axis collapses times through aSet. Two bars at the same time therefore resolved to the same logical index, so the renderer was handed both and drew them at the same x, the second over the first. A live feed produces that pair whenever its candle builder starts unseeded: it opens a fresh bar for the bucket the fetched history already ends in, and the host appends it alongside the historical one. Reconnecting mid-bar does it again.setDatanow collapses repeated times, keeping the last occurrence: the newer value when a live bar arrives alongside the historical bar it supersedes.prependDataandupdatealready de-duplicated.Seed your builder from the last historical bar (
builder.seed(bars[bars.length - 1])) so the live bar continues it: an unseeded builder still opens at the first tick price it sees rather than the bucket’s true open. See Live Data. -
VERSION/version()reported1.0.8. The constant is hand-maintained and was missed by the 1.0.9, 1.0.10 and 1.0.11 bumps. It now matches the package version again.
1.0.11
Added: Drawing tools
- 16 more tools, taking the built-in set from 18 to 34 for 1.7 KB (draw tier
6.6 KB -> 8.3 KB Brotli). Shapes:
circle,triangle. Paths:polyline,arc,curve. Channels:fib-channel. Fibonacci:fib-time-zone,fib-fan. Gann:gann-fan,gann-box. Forecasting:forecast. Measurers:price-range,date-range. Arrows:arrow-up,arrow-down. Brushes:highlighter. See Drawing Tools. circlemeasures its radius in pixels, so it stays round on screen rather than becoming the ellipse that differing axis scales would otherwise produce.arcpasses through its middle anchor whilecurvetreats that anchor as a control handle. The measurers take their bar count from logical indices, so it matches the gapless axis rather than raw elapsed time.pane.primitives(), matching the existingpane.series().
Fixed
-
A large blank region could appear under the chart, and persist across reloads. Three faults compounded.
removeIndicatordid not prune the pane it had just emptied: that logic sat in the pane legend’s close handler, so the on-chart X cleaned up but a host removing the same indicator from its own UI left an empty pane behind. An empty pane still claims its weight and still draws a default0..100price axis, which is both the blank region and the second set of axis labels under the price ticks. The pruning now lives inremoveIndicator.getStatethen persisted that orphan andrestoreStatefaithfully rebuilt it, so once it happened it came back on every load.restoreStatenow drops panes that end up with no series.maximizePaneparks the other panes at a0.001placeholder and snapshots the real weights by index, butremovePanenever spliced that snapshot, so un-maximizing restored weights against a shifted array and could strand panes at the placeholder.removePanenow keeps it aligned.An already-saved layout still holds the orphan until it is cleared or overwritten.
Demo
- Rail flyout menus: a group opens a sectioned list of its tools instead of cycling them on repeat clicks, which was undiscoverable past two and unusable at 34. A plain click re-activates the last tool picked; the caret opens the list. Icons were redrawn on a 24x24 grid with a thinner stroke and outlined endpoint handles.
- Right-click drawing actions: “Delete Drawing” and “Remove All Drawings (n)”, both hidden when there is nothing to act on. Removal goes through the controller, so it is a single undo step.
- Fixed the demo rendering a white chart inside dark chrome: it never passed a theme, and the library default is the light palette.
1.0.10
Added: Market Profile
- Controllable TPO / footprint row height. Row height is now
tickSize * rowTicksinstead of being pinned to the instrument tick. The multiplier is the one a trader already thinks in: Nifty trades in 0.1 and you want 2-point rows, sorowTicksis2 / 0.1 = 20(rowTicksFor(2, 0.1)does the division). Keeping the two separate matters: the tick is what imbalance and single-print logic count on, so widening rows must not mean lying about it. The same multiplier reaches order flow:computeFootprint(t, trades, 0.1, 20)andnew FootprintAggregator(tf, 0.1, 20). See Market Profile. - Letters degrade to bricks automatically. A TPO row is only as tall as the
price scale makes it, so at some zoom a letter stops fitting.
blockDisplay: 'auto'(the new default) crossfades: the block is always drawn and the letter fades in overletterFadepx aboveminLetterHeight, so zooming through the threshold reads as one continuous change instead of a jump. The footprint fades its cell numbers the same way viatextFade. - Analytics: per-period detail, the developing POC / value-area track, day
type, open type, range extension, buying / selling tails, volume POC, and
nakedLevels()for prior POC / VAH / VAL no later session traded through. - Session windows:
windowdrops bars outside a trading session and anchors periodAto the window’s open. Built-ins inTRADING_HOURS:all-hours,india,asia,london,new-york,us-regular.compositeSessionsmerges N sessions into a rolling composite. - Renderer options:
colorModegainsperiod(one hue per TPO period, now the default); plussplitperiod columns,showTpoCounts,showTails,showPoorHighLow,showNakedLevels,showDevelopingPoc/showDevelopingVa,showDayType/showOpenType,volumeProfileSide,showVolumeValues, andhitTest/hoverAtfor a host-drawn tooltip.
Added: Interaction
- Time navigator adds hover-revealed zoom / step controls just above the time
axis:
-+to zoom,‹›to step one bar. Invisible until the pointer nears the bottom of the chart, then faded in. The buttons run the same commands the keyboard does, so the two cannot drift apart, and each tooltip reads its combo from the live keymap. On by default;timeNavigator: falsedrops it. See Interactions. - New commands
panLeftBar/panRightBar(one bar, unbound by default), and a publicpane.primitives()accessor.
Fixed
- The docs Market Profile example rendered a histogram, not a market profile.
It used
computeTpo+HorizontalProfile(a volume-profile-shaped bar chart with no letters) even though the letter renderer already existed. - Periods anchored to the first bar rather than the session open, so a session whose first bar arrived late shifted every letter.
Breaking
MarketProfile’sshowLettersboolean is replaced byblockDisplay(showLetters: falsebecomesblockDisplay: 'blocks').
1.0.9
Added: Order flow
- Footprint rewritten. Cells fill proportionally to volume instead of being
outlined, so a column reads as a heat ladder; imbalanced cells fill saturated
rather than gaining a border, and runs of consecutive same-side imbalances get
a bracket. New
displayMode(bidask/delta/volume), a per-barstatsRowstable (volume / delta / delta % / CVD / trades) tinted by strength, plusstackedImbalances,showPoc,showCandle,widthFactor,radiusandminTextHeight.setOptions()restyles live;hitTest()/hoverAt()map a pointer back to the bar and price row. See Profiles & Order Flow. - Live order-flow example: synthetic classified ticks stream into a
FootprintAggregatorwith the forming bar updating in place, the same path a live WebSocket trade feed takes.
Added: Drawing
- Shape text:
DrawingStylegainsfontColor,textVAlignandtextPosition; rectangles, ellipses and parallel channels render astyle.textlabel. One shape, two colours:colorstrokes the outline,fontColorpaints the label. See Drawing Tools.
Fixed
- Drawing a rectangle by dragging placed nothing and scrolled the chart.
Press-drag-release is how every charting UI lays down a two-point shape, but
the chart only emitted a
clickwhen the pointer had not moved, so the gesture produced no anchors while the pan path consumed it. Newchart.setPlacementMode(active): a press no longer pans, and the gesture is reported as twoclickevents, the release taggedviaDrag. Every two-point tool gained drag-to-draw with no API change; single-anchor tools ignore the release half. - The price axis produced about half the tick labels it was asked for.
niceTicksrounded the span up to a nice number and then divided, rounding twice: a 10.5-point range became 20, giving three labels where six were requested. The step now comes from the raw span, clamped up the 1 / 2 / 2.5 / 5 / 10 ladder until it fits.
Changed
Footprint.hoverAt(x, y, rc?):rcis optional and defaults to the last paint’s context, so a crosshair handler can just callhoverAt(p.x, p.y).- Docs demos follow the site theme (the library default is the light palette, so every example had been rendering a white panel into a dark page).
1.0.8
Added: Indicators
- 18 built-in indicators in the new lazy
openalgo-charts/indicatorstier (4.5 KB Brotli): SMA, EMA, WMA, VWAP, Bollinger Bands, Supertrend, Parabolic SAR, Ichimoku Cloud, RSI, MACD, Stochastic, ADX/DMI, CCI, MFI, ATR, Volume, OBV, and A/D. See Indicators. - An indicator registry: the sibling of the chart-type registry. A
descriptor is data, and each plot names a registered chart type, so indicators
ride the existing renderers and add no drawing code.
chart.addIndicator(),chart.indicators(),chart.removeIndicator(). - A Tier-2 contract (
createTier2Indicator) for indicators whose data is not derived from the chart’s OHLCV: open interest, CVD, any external feed.
Added: Panes & legends
- Pane legends with per-plot readings in each plot’s colour, tracking the
crosshair, and inline controls revealed on hover: show/hide, settings, move
pane, maximize, delete.
PaneLegendis public, so a host can add its own rows (a symbol/OHLC header) and indicator rows stack beneath. - Draggable pane dividers plus
setPaneWeight,movePane,maximizePane, andremovePane. See Scales & Panes.
Added: State
chart.getState()/chart.restoreState()for saved layouts, with an opaquedrawingsslot. See Chart State.
Fixed: Packaging
- Lazy tiers could not register into the base bundle’s registries: each tier
inlined its own private copy, so
import 'openalgo-charts/transform'followed byaddSeries('point-figure')threw even though the tier was loaded. Tiers now import the package entry, which is external for tier builds.
Fixed: Interaction
- A
setPointerCapturethrow (Chrome raisesNotFoundErrorfor an inactive pointer id) aborted the rest ofpointerdown, silently losing the pane-divider grab, the axis-drag arm, and the order-line drag arm. - Pane hit-testing could be offset from what was drawn: panes were laid out with a flex ratio while their canvases were sized from the chart’s own height, so any drift between the two shifted every hit-test off the pixels.
Fixed: Point & Figure
- No more phantom first column. When the first move after the anchor bar was
down, the direction was still
0, so the reversal branch fired while the column’s top and bottom boxes were equal, emitting a zero-height column that drew as a blank slot at the start of every down-opening chart. - Columns are built from the bar range, not just the close. The new
methodoption defaults to'hl': a bar’s high extends an X column and its low extends an O column. A bar that swung through several boxes intrabar but closed flat used to produce no boxes at all.method: 'close'restores the previous behaviour. - The renderer walks integer box indices instead of accumulating
level += boxSize: thirty steps of0.05land on101.49999999999991, which duplicated the top glyph of tall columns. Off-screen glyph rows are now culled.
Added: Point & Figure
- Box-size modes:
mode: 'fixed' | 'percent' | 'atr', the latter two re-resolved each time a column opens so the grid tracks price level and volatility. PointFigureColumncarriesboxSizeandboxes, and the renderer reads the box size from the column, sostyle: { boxSize }is no longer needed, and the transform and style can no longer disagree. See Transforms: Point & Figure.
1.0.7
Fixed
- A right-click on the chart no longer replays the previous left-click. Only the
primary button starts a gesture, so a right-click’s
pointerdownis ignored, but the matchingpointerupwas unguarded and fell through to the click path, re-hit-testing at the stale position from the last left-click and re-firingsubscribeClick. WithBuySellButtonsthat meant the first right-click after buying/selling silently placed a second, duplicate order.pointerupnow applies the same primary-button guard aspointerdown(touch/pen unaffected; the missed-pointerupdrag recovery is preserved).
1.0.6
Added
BuySellButtons: an inline trade panel drawn on the chart (SELL· qty ·BUY, docked to a corner and fixed while the chart moves). Clicks hit-test to${id}:sell/${id}:buy/${id}:qty(routed throughchart.subscribeClick);setPrices/setMarkupdate prices per tick,setQty/setColorsrestyle at runtime. Add withchart.addPrimitive(new BuySellButtons({ ... })).
1.0.5
Fixed
- Native right-click “Save image as…” now saves the visible chart (the clicked
pane) instead of a blank overlay layer: the pane is composited into the
captured canvas just before the menu opens, and overlay repaints pause while
it is open.
downloadScreenshot()remains the full multi-pane export.
1.0.4
Trading-UI beautification: the order-placement surfaces get a modern, theme-aware visual pass plus real interaction feedback.
Added
- Hover / dragging states for interactive price lines (thicker line, brighter
pill, solid cancel button, soft drag halo) and chart-applied cursor hints
(
ns-resizeover draggable lines,pointerover cancel and ladder rows). - New
hoverevent:chart.on('hover', ({ id }) => ...)fires on primitive enter/leave. - Drag ghost:
PriceLine.setDragGhost(price | null)draws a dimmed reference line at the pre-drag price while modifying (automatic viachart.trading). - Broker-style segmented pill groups on order and position lines:
[badge][qty][label][x]with newbadge/qtyprice-line options and auto-contrast text; bracket chips with prices (SL 2,850.00) and theme-derived risk/reward zones; fill progress (3/10) and dimmed pending state on working-order lines; fully theme-aware DOM ladder with a hovered-row outline. PrimitiveRenderContext.hoverId/dragIdfor custom primitives.- Real-time order updates:
OpenAlgoWsFeed.subscribeOrders()/onOrderUpdate()(OpenAlgosubscribe_ordersstream) with reconnect replay;mapOrderStatus. chart.downloadScreenshot(filename?): PNG export of the full composited chart.
Fixed
- Right-click no longer leaves the chart “sticky-dragging”; missed
pointerupis recovered on the next move. - Crosshair hidden while dragging an order line (no phantom line at the grab point).
- Last-price line draws beneath order/position pills instead of striking through.
1.0.3
Cosmetic parity: the last visual gaps for a migration from another canvas charting library.
Added
- Per-series
priceLineVisible/lastValueVisible(toggle the last-price line and axis tag) andtitle. The last-price line/tag now follows the main (first right-scale) series. - Crosshair styling in the theme:
crosshairStyle,crosshairWidth,crosshairLabelBackground,crosshairLabelVisible. timeFormattergets an optionaltickMarkTypeboundary hint for adaptive axis labels; new exported typeTickMarkType.
1.0.2
Drop-in parity work so an app can back all its charts with this engine.
Added
- Left / right / overlay price scales.
addSeries(type, { priceScaleId: 'right' | 'left' | '' }).'left'and'right'draw independent, independently-autoscaled axes;''is a hidden overlay scale (volume-in-price-pane).series.priceScale()returns the scale (.setOptions({ marginTop, marginBottom })pins a volume histogram to the bottom). - Mutable series.
series.applyOptions(partialStyle),series.remove(), andstyle.visible: restyle, hide (also drops from autoscale), and remove at runtime. timeScale.setVisibleLogicalRange()/getVisibleLogicalRange()andchart.fitContent(): preserve the user’s zoom across a full-history reload.- Per-series
priceFormat(price/volume/custom), applied to that series’ scale;compactVolumehelper (1.2K / 3.4M / 5.6B). - Dashed / dotted line series via
style.lineStyle. - Runtime
chart.applyOptions()/chart.setTheme(): swap theme, grid, and formatters without recreating the chart. New theme fieldsaxisFontSize,gridStyle, and transparentbackground.
Fixed
RenderLoopno longer skips frames after the first when driven by a synchronous scheduler.
Quality
- 314 unit tests (40 files); base engine ~24.7 KB Brotli.
1.0.1
Full package ~38 KB Brotli (all tiers), base engine ~24 KB, zero runtime dependencies.
Added
- Unified event bus:
chart.on/off/once(crosshair:move,click,pan,zoom,resize,lazy-load,ready), withtrading:*mirrored through it. - Data-driven trading visualization (
chart.trading): position/order pills, TP/SL brackets, and fill markers (chevron / bubble / count). - Custom
priceFormatterandtimeFormatter(with runtime setters); per-panepriceScaleoptions; the time axis is no longer IST-only. setDataacceptsBar | LinePoint | Whitespace(normalized viatoBar);series.getData()reads the current bars.- WebSocket auto-reconnect (backoff + re-auth + resubscribe);
OpenAlgoLiveDataFeedbareD/Wintervals, day-delta volume, and symbol+exchange tick filtering. - Docs: an interactive example gallery (chart type, themes, tooltips, event markers, live streaming) plus framework-integration, mobile, data-loading, events, types, constants, and glossary pages.
Fixed
- Multi-series
DataLayer.updatetime-axis ordering; the package now shipsNOTICE; the accessible summary refreshes on live updates;visibleBarsuses binary search.
Quality
- 297 unit tests (39 files) + the Playwright smoke suite; CI adds a docs-site build and a
NOTICEpack check.
1.0.0
First public release. Full package ~30 KB Brotli (all tiers), zero runtime dependencies, Apache-2.0.
Added
- Indicators: RSI, ATR, Supertrend (Wilder semantics, matching
openalgo.ta), alongside EMA. - Sub-minute (second) and tick/volume timeframes are first-class: the time axis and crosshair show HH:MM:SS automatically on sub-minute bars.
- Interaction: vertical price pan,
resetScale()+ double-click/Fit,priceToCoordinate/coordinateToPrice, andsubscribeCrosshairMovefor OHLC legends/tooltips. - Touch: pinch-to-zoom and two-finger pan. Accessibility: focusable container with
role/aria-label, a polite live summary, and keyboard navigation. takeScreenshot()(composites all panes/layers) and runtime grid toggles.- Footprint primitive: volume-graded bid×ask cells, diagonal-imbalance boxes, POC marker, per-bar delta/volume footer.
- Live feed: composed REST + WebSocket + candle-builder data feed speaking the documented OpenAlgo protocol, with connection/control callbacks.
Quality
- 286 unit tests + a Playwright real-browser smoke suite; GitHub Actions CI runs typecheck, unit, build, size budgets, and the E2E smoke on every push/PR.
0.1.0
First end-to-end build of the engine - dependency-free, full package ~22 KB Brotli.
- Single-canvas render loop with per-pane invalidation; shared gapless time scale; linear price scale with autoscale and tick formatting.
- All standard chart types; live candle builder; markers, event badges, price lines; EMA.
- Transform tier (Heikin Ashi, Renko, Range, Line Break, Point & Figure, Kagi).
- Trade tier (order/position/bracket primitives, order state machine + validation, OCO, depth-of-market ladder).
- Profile tier (Volume Profile, Market Profile / TPO, Footprint, order flow).