All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
chart.setMarkers / addMarker / removeMarker /
clearMarkers / getMarkers. Markers pin by time (robust to history
loads and maxCandles eviction — no index bookkeeping), support
above/below/inline placement and arrowUp/arrowDown/circle/
square/flag glyphs with optional text. New Marker type,
MarkerRenderer, and markerY helper exported from core; React and Vue
ref surfaces expose the same methods. Drawn on the chart layer (panned
with data, included in toPNG).maxCandles memory cap. New ChartConfig.maxCandles evicts the
oldest candles (O(1) via CandleBuffer.evictHead, with in-place
compaction so capacity stays bounded) once live updates push past the
cap. The viewport and drawings shift in lockstep
(DrawingLayer.shiftIndices) so they stay anchored to the same candles.
Prepended history is never auto-evicted.hitTest(x, y, layout, viewport, tolerance) with geometry
matching its rendered shape (segments, full-width/height lines, rays,
rectangles, channels, fib levels). DrawingLayer gains hitTest,
selectAt, select, removeSelected, undo/redo (canUndo/
canRedo), and renders square handles on the selected drawing.
OHLCVChart exposes selectDrawingAt, selectDrawing,
getSelectedDrawingId, deleteSelectedDrawing, undoDrawing,
redoDrawing — input wiring stays in the consumer, consistent with the
render-only drawing-layer design.findGaps(buffer, intervalSeconds) reports
runs of missing candles (weekends, exchange close, dropped ticks) as
CandleGap[]; resolutionToSeconds(resolution) maps a resolution string
to its interval (null for calendar months). Detection only — consumers
decide whether to backfill or draw session breaks.Indicator.computeCached(buffer)
wraps compute() and caches the result keyed on the new
CandleBuffer.version revision counter. The render loop now uses
computeCached, so indicators recompute only when the data actually
changes instead of on every render frame (pan, zoom, crosshair move).CandleBuffer.version — monotonic revision counter bumped on every
mutation (append, appendBatch, updateLast, prepend, clear).@rekurt/openkline-core is now a peerDependency (not a regular
dependency) of @rekurt/openkline-react and @rekurt/openkline-vue, preventing
duplicate core copies / version mismatches in consumer bundles.ChartEngine.setCrosshair marks the UI layer (legend, price line, pill)
dirty only when the snapped candle actually changes; sub-candle mouse
moves repaint just the crosshair layer. Cuts per-move work on hover.ChartEngine
now dispatches indicator-compute failures through ErrorReporter with
where: 'indicator' instead of an empty catch {}, matching the
project’s “no silent catches” policy. New ChartEngine.setErrorReporter.setData({ preserveView: true }) no longer re-arms the load-more
guard. A preserveView refresh (React/Vue re-dispatching the same data
array on a prop change) is not a dataset swap, so it now leaves
_dataGeneration and the _loadingMore flag untouched. Previously it
cleared the guard (and orphaned the in-flight request’s completion via the
generation bump), letting a left-edge viewport change fire a duplicate
onLoadMoreHistory before the first settled.stopReplay() no longer trims maxCandles when no replay cap was
active. The post-stop eviction is now gated on a cap having been active
before stopping, so a defensive stopReplay() call from a replay UI can no
longer evict bars added via prependHistory (documented as never
auto-evicted).BinanceWsTransport and its types (BinanceWsTransportOptions,
IWebSocketLike). It was an unverified skeleton (lost ticks on
reconnect, no heartbeat, no history pagination); shipping it as a public
export implied production-readiness it did not have. Build exchange
adapters on the abstract WebSocketTransport base instead.IndicatorConfig + createIndicator
indicatorId:WMA, HMA, Keltner, Donchian, PivotPoints,
Ichimoku, Supertrend, ParabolicSAR, plus anchored VWAP.WilliamsR, OBV, ADX (+DI/-DI), CCI, MFI,
StochRSI, ROC.
All use O(n) or amortized-O(1) algorithms (rolling sums,
monotonic-deque window extrema, Wilder smoothing).Rectangle, Ray,
VerticalLine, FibRetracement, FibExtension, Channel,
Arrow alongside the original TrendLine / HorizontalLine.
All buffer-space anchored and snapshot-serializable; the shared
DrawingTool type drives OHLCVChart.startDrawing and both
wrappers.chartType:
'heikinashi') — rendered directly from the raw buffer with a
50-bar warmup lead-in, no manual data transform needed.Viewport.scalePriceRangeBy /
resetPriceScale).Two-finger touch gestures now pan and pinch-zoom simultaneously (sliding both fingers pans; spreading/pinching zooms).
placement: 'pane' (RSI, MACD, Stochastic, ATR, plus the new
WilliamsR / OBV / ADX / CCI) renders in its own auto-sized
vertical band with independent Y-axis, label, min/max bounds, and
a dashed zero-line for ranges that straddle zero.
computeLayout(width, height, paneCount) reserves
INDICATOR_PANE_HEIGHT (80 px) per pane and clamps so the main
candle area is never thinner than MIN_MAIN_AREA_HEIGHT (120 px).WilliamsR, OBV, ADX (with +DI/-DI),
CCI. Registered in the IndicatorConfig discriminated union
('williamsr', 'obv', 'adx', 'cci') and wired through
createIndicator, indicatorId, and the React/Vue wrappers’
reconciliation path.Rectangle, Ray, VerticalLine,
FibRetracement (8 canonical levels with inline labels). All
buffer-space anchored and snapshot-serializable. OHLCVChart.startDrawing
widened to accept the new tools; the shared DrawingTool type
is exported from core so React/Vue wrappers stay in sync.Σx, Σx²) and the
σ² = E[x²] − (E[x])² identity. ~20× faster at
n=100k, period=20 while preserving correctness on all 9
existing tests.@rekurt/openkline-core/indicators and
@rekurt/openkline-core/drawings are now importable directly,
enabling consumers to tree-shake unused subsystems.migrateState + migrations
registry + CURRENT_STATE_VERSION so future schema bumps are
forward-compatible with shipped clients without changes in
wrappers.CODE_OF_CONDUCT.md, SECURITY.md,
.github/ISSUE_TEMPLATE/, .github/pull_request_template.md,
.github/dependabot.yml. Expanded .gitignore to cover env
files, IDE configs, monorepo caches, and editor swap files.CandleBuffer.prepend is O(1) per candle after the first
growth instead of O(n) every time. The buffer now maintains a
logical _head offset into the raw arrays; prepends shift
_head left in-place when leftPad headroom is available, and
reserve max(incoming, currentLength) of leftPad on growth so
subsequent equal-sized history pages also hit the fast path.
Eliminates the 6× Float64Array allocation per prepend that
previously dominated TradingView-style infinite scroll.Viewport.scalePriceRangeBy(factor, anchorY) and
resetPriceScale() expose this programmatically.useOHLCVChart (React + Vue) now reacts to every option after
mount — previously the headless hook ignored post-mount changes
to symbol, resolution, theme, chartType, indicators,
idleCursor, and transport. Vue version accepts
MaybeRefOrGetter<T> for reactive options. Callbacks
(onHover, onCandleClick, onError, onVisibleRangeChange,
onLoadMoreHistory) flow through trampoline refs so identity
changes don’t recreate the chart.CandleBuffer.append / appendBatch / updateLast / prepend
now reject non-finite OHLCVT fields at the public API boundary
with a clear RangeError. Prevents silent NaN propagation into
priceToY and indicator computation.BinanceWsTransport so consumers do not copy-paste it into
production code.vitest.config.ts was silently skipping *.test.tsx files; the
React wrapper tests were never running in CI. Re-included.@rekurt/openkline-core to its built dist/, so typecheck
must follow the build in a fresh npm ci environment. (Both
Node 20 and 22 jobs were red on master since commit 2f322f4.)useOHLCVChart no longer fires a duplicate switchSymbol
on mount (avoids a redundant transport fetch + buffer reset).useOHLCVChart callback watchers are registered once at
composable scope instead of leaking a new set per transport
recreation.CandleBuffer.appendBatch / prepend validate the full incoming
range before mutating, so a thrown validation leaves the buffer
untouched (atomic ingestion).paneAreaBottom so
they render in the reserved axis strip, not the first sub-pane,
when pane indicators are active.DataFeed.disconnect() bumps the connect version so in-flight
history fetches can’t leak into a freshly-cleared buffer;
subscribe-time + per-tick errors are now reported via
ErrorReporter instead of silently rejecting.First public release under the @rekurt npm scope. This release
consolidates all prior iteration work into a shippable package set.
@ohlcv/* to
@rekurt/ohlcv-*. All package names, imports, dependencies,
and documentation updated. There is no upgrade path from 0.0.x
— treat this as a fresh install.@rekurt/openkline-react) reaches full API parity.
<OHLCVChart> exposes chartType, indicators (declarative
config array), idleCursor, onHover, onError, and
onLoadMoreHistory props. forwardRef / useImperativeHandle
surface includes goToLive, fitVisible, fitAll,
prependHistory, updateLastCandle, saveLayoutState,
saveFullState, loadState, startDrawing, getDrawings,
loadDrawings, clearDrawings, toPNG. useOHLCVChart hook
extended to 1:1 parity with the component. Indicator
reconciliation through diffIndicatorConfigs — reference-stable
arrays no longer thrash the chart.@rekurt/openkline-vue) reaches full API parity.
Reactive props match React, typed emits for all events, and
defineExpose mirrors the React ref surface 1:1. v-model:indicators
supported via the update:indicators emit for forward compatibility.
useOHLCVChart composable extended to match component.saveLayoutState / saveFullState / loadState) with versioned
JSON schema. LayoutState is URL-friendly (for share links),
FullState includes the data window (for workspace persistence).createIndicator factory
indicatorId stable hash + diffIndicatorConfigs pure function**
so wrapper-driven code never constructs indicator classes by hand
and reconciliation is zero-duplication between React and Vue.OHLCVChart.setIndicatorConfigs(configs) — declarative path
used by wrappers; stores configs so saveLayoutState can round-trip
them. The legacy setIndicators(Indicator[]) still works for custom
indicators but clears the internal config mirror.OHLCVChart constructs and owns
a default DrawingLayer. New facade methods: getDrawingLayer,
startDrawing('trendline'|'hline'), getDrawings, loadDrawings,
clearDrawings.examples/playground/) with framework
switcher (Vanilla TS / React / Vue), shared toolbar (theme,
chartType, indicators), and share URL feature via
base64-encoded LayoutState in ?state= query param. Vite config
aliases @rekurt/ohlcv-* at workspace src/ directly to prevent
the stale-dist pitfall that caused the preserveView regression.npm run lint + npm run lint:fix, --max-warnings 0 enforced.ci.yml runs lint + typecheck + tests + build
on PRs and pushes to master (Node 20 + 22 matrix). pages.yml
builds the playground + TypeDoc API reference and deploys them to
GitHub Pages./api/.examples/core|react|vue/)
kept as self-contained repro scaffolds for issue reports.setData preserveView option — commit 46f8eea. Previously the
framework wrappers re-dispatched setData on every [data] effect
without preserveView, causing the core else branch to call
scrollToEnd() and snap the viewport back to the live edge on
every unrelated re-render (hover, indicator toggle, theme swap).
Wrappers now always pass preserveView: true and core keeps
startIndex / candleWidth / autoFollow across the reload.@ohlcv/core/drawings). Buffer-space anchored drawings that stick to the underlying candles on pan/zoom.
Drawing abstract base with AnchorPoint = { index, price }, point accumulation up to requiredPoints, and toSnapshot / fromSnapshot JSON round-trip for persistence.TrendLine — two-point straight segment with chart-area clipping.HorizontalLine — single-point full-width dashed line with a price pill on the right axis.DrawingLayer — ordered collection with a single “active creation” slot (startDrawing / addPoint / updateActiveLastPoint / cancelActive / remove / clear), registry-based hydration from snapshots, and render() that draws completed drawings plus the in-progress one.Indicator base class:
MACD(fast=12, slow=26, signal=9) — three aligned series (macd, signal, histogram). Placement: 'pane'.Stochastic(kPeriod=14, dPeriod=3) — %K and %D in [0, 100]. Placement: 'pane'.ATR(period=14) — Wilder-smoothed true-range volatility. Placement: 'pane'.VWAP(anchor='session' | 'cumulative') — volume-weighted average price with optional UTC-day session reset. Placement: 'overlay'.@ohlcv/core/transforms). Pure-function transforms that consume Candle[] and return Candle[] so consumers can feed the result into OHLCVChart.setData() unchanged.
toHeikinAshi(candles) — smoothed candles via standard (o+h+l+c)/4 and (prev.o+prev.c)/2 recurrence. Includes advanceHeikinAshi(prevHA, rawCandle) helper for incremental live-tick updates.toRenko(candles, brickSize) — price-driven bricks with 2×brickSize reversal threshold. Timestamps carry over from the triggering source candle; volume is 0.ChartEngine.toPNG() — synchronous snapshot that composites the three canvas layers (chart / UI / crosshair) over the background color onto an offscreen canvas and returns a data:image/png;base64,... data URL. Suitable for download links, uploading to a backend, or <img src>. 2 new smoke tests.@ohlcv/core). New Pane and PaneLayout classes provide a foundation for multi-pane charts. Each pane has its own priceMin/priceMax, independent linear or log Y-axis, and can be positioned in the vertical stack. Main price pane is always present; sub-panes for indicators (RSI, MACD) can be added via PaneLayout.addPane(). (Integration into ChartEngine is planned for the next release.)@ohlcv/core/indicators). Base Indicator class with compute(buffer) → IndicatorSeries[] contract, placement: 'overlay' | 'pane' flag, and stable id for caching. First batch of implementations:
SMA(period) — simple moving averageEMA(period) — exponential moving average with Wilder seedingBollingerBands(period, stdDev) — three-series (upper/middle/lower)RSI(period) — 14-period Wilder-smoothed Relative Strength IndexLineRenderer (close-price line), AreaRenderer (line with gradient fill), OHLCBarRenderer (bar chart) as opt-in alternatives to CandleRenderer. All respect the existing layout + viewport contract and can be swapped in by consumers without touching ChartEngine.BinanceWsTransport extends WebSocketTransport with concrete support for Binance public kline streams + REST history fetching. Includes URL construction, resolution mapping (1H → 1h), message parsing, and automatic reconnection via ExponentialBackoff.ExponentialBackoff utility. Full-jitter exponential backoff policy with configurable baseDelay / maxDelay / injectable RNG. Used by BinanceWsTransport for reconnect and exposed publicly so consumers can build their own resilient transports.Pane + PaneLayout public API, new exports: Pane, PaneLayout, PaneKind, YScale.role="img" and an aria-label describing chart navigation keys so screen readers announce chart presence on focus.prefers-reduced-motion: reduce and skips the decay animation.theme: 'auto' option resolves to light or dark based on prefers-color-scheme.ErrorReporter and ChartConfig.onError callback for structured error dispatch. Every transport/fetch error now reaches the consumer with { where, error, fatal } instead of being silently swallowed.ValidationError, validateCandle, validateCandles utilities. Checks shape, finite numbers, positive time, h >= l, h >= max(o,c), l <= min(o,c), v >= 0.noUncheckedIndexedAccess, noImplicitOverride, noFallthroughCasesInSwitch enabled across the monorepo.ChartEngine.resize() applied ctx.scale(dpr, dpr) twice (once inside resizeHiDPICanvas, once explicitly after). Since getContext('2d') returns the same context object, scale compounded to dpr², making candles render at 4× on DPR=2 displays. Resize now relies on resizeHiDPICanvas alone.Viewport.isAtEnd() previously returned true for any startIndex past “edge minus padding”, causing live updates to snap the viewport back to the anchor every tick when the user had panned into the empty future zone. Now requires an exact match (abs(startIndex - anchor) < 0.5).PanZoomController._handleWheel ignored deltaX entirely; trackpad horizontal swipes fell through to the zoom branch (deltaY=0 → factor=1.1 always). Rewritten with axis priority and a bias (|dx|*2 >= |dy|) so real-world trackpad jitter is still classified as pan.Vue example’s live loop did data.value = [...data.value] every tick, triggering the wrapper’s watch(data) → setData() → scrollToEnd(). Rewritten to use the imperative chartComponentRef.value.chart.updateLastCandle() API, matching the React example.catch {} blocks in DataFeed and PollingTransport silently swallowed errors. All now dispatch through ErrorReporter.report(where, error, fatal); defaults to console.warn if no handler is configured.PollingTransport.defaultParser did unchecked data as { o: number[], ... } casts. Now validates array shape and per-element number types, skipping malformed rows.ThemeMode type now includes 'auto' in addition to 'dark' and 'light'.constants.ts as a single source of truth.@ohlcv/core, @ohlcv/react, @ohlcv/vue scaffolding with candlestick rendering, volume sub-panel, crosshair, pan/zoom, keyboard shortcuts, and examples/{core,react,vue} workspace apps.