Skip to content

AXIS v2.25.0

Choose a tag to compare

@github-actions github-actions released this 20 Oct 00:43
· 270 commits to main since this release
7fe4de9

[2.25.0] — 2026-10-20

Added

  • AXIS Settings → Data can flush the dataset manager. A new “Dataset manager” section (next to the exchange keys) wipes every cached OHLCV dataset — bars and metadata — from the memory mirror, the local IndexedDB cache, and the remote dataset stores (git / cloud), behind a confirm step and with a result line (Dataset manager flushed · N datasets removed). Chart bars stay until the symbol is reloaded.

Changed

  • The Rolling window is now the chart window, and it slides instead of growing. Settings → Rolling window used to bound only what the engine was asked to evaluate: the chart itself kept every bar a live tick appended (up to the 100k safety rail) and only the next dataset repaint re-capped it. The window is now one number (chartWindowLimit) shared by the load paths, the live append path, the background catch-up and the engine input, and a new bar at the cap drops the head instead of extending the series. The status-bar inline edit and the Settings page both re-slice the chart through the same helper, so the setting and the painted window can no longer disagree. ROLLING_WINDOW_MAX is now HISTORY_BARS_MAX (100k) — the setting cannot promise a window the chart would refuse to paint.
  • A live tick is applied through one audited path. store.appendBar returns a verdict (ignored / updated / appended / slid) and chart/live-append.ts is the only place a live bar reaches the chart. A rejected tick no longer moves the price card, no longer evaluates alerts, and no longer overwrites a dataset bar.
  • The chart data session follows the chart window. The engine session was provisioned with a fixed 5 000 bars while the chart window is 10 000, so the engine's store could only serve half the configured window. The depth is part of the session identity now, and changing the Rolling window re-provisions the session.
  • A dataset write no longer re-repairs the whole series. The stored series is already repaired, so a page write merges the page in one linear pass instead of rebuilding a map, re-sorting and re-sanitizing every bar.

Fixed

  • The dataset repair no longer deletes venue bars (the phantom-gap loop). The phase-aware snap moved an off-phase bar onto the nearest grid point even when that slot already held a bar; the dedupe then dropped one of the two — losing a real venue bar, shifting another bar's OHLCV onto the wrong candle, and leaving a hole the gap detector reported as fillable. The DSM chased that hole on every round and the job always ended with “Partial: N gaps remain”. The snap is now collision-safe (a bar only moves onto a free slot, and keeps its own timestamp when both neighbours are taken), never snaps onto a non-positive time, and the repair is idempotent.
  • The backfill walk stays on one grid. The walk advanced its cursor by one second, so a venue that answers “the newest bar with open time ≤ endTime” returned a bar one second off its own grid — a one-second hole in the dataset, which is a phantom gap the job then chased. The cursor now advances by one step (one duplicated bar per page, dropped by the merge).
  • A widened backfill window is fetched by the running walk. A second startBackfill for the same series widens the job's window while the walk runs; the walk now re-reads the bound on every page instead of keeping the one it was launched with.
  • A zero Rolling window falls back to the default. 0 used to mean "no cap" for the chart as well, which could grow the series without limit; the engine keeps its "no cap" escape hatch, the chart stays bounded.
  • Monthly intervals count calendar months. The expected-bar count used a fixed 30-day step, so the density floor was unreachable for most windows and a 1M job could never report "complete".
  • A session venue's Friday closure is no longer chased as a gap. The weekend classifier excused only gaps whose every missing slot was a Saturday or Sunday, so a venue closed from Friday's close to Monday's open produced a fillable gap: every job burned all six gap rounds on a bar the venue does not have. A missing Friday bar now belongs to that closure; a missing Monday bar is still chased.
  • The dataset job no longer re-walks a complete dataset. The pre-walk completeness check validated the series without the venue calendar class, so a session venue's weekend closure counted as a hole and every job re-walked the whole range.
  • A failed dataset read can no longer truncate the dataset. A sink that could not be read looked exactly like an empty dataset, so the merge base was empty and the last backfill page replaced the whole series. The sinks now distinguish “absent” from “broken”, and a write against an unreadable sink is refused instead of applied.
  • A dataset write is atomic and serialized. The sink replace was a clear-then-write (a window where the dataset did not exist at all, and a concurrent write could be clobbered); it is now a single record put. Writes are serialized per key, so two concurrent writers (a backfill page and the debounced live-bar write) can no longer drop each other's bars. The memory mirror evicts LRU instead of FIFO, so the hottest series is no longer the first to be dropped.
  • A closed candle is immutable in the dataset. A stale REST page carrying the partial (forming) values of a candle the venue had already closed could overwrite the final bar; a closed bar now wins against a forming one.
  • The live tail survives a dataset repaint. The dataset store only learns about a live bar when the Data Manager source is active, so a background backfill slice landing in between repainted the chart from a series that was one bar behind and the forming candle vanished. The repaint now merges the chart's live tail back in and re-caps the result to the window.
  • A late tick cannot rewrite history. A tick for a bar behind the tip was appended behind it, which put the series out of order and broke every index-based zip. Such a tick is now dropped, and a closed tip is never reopened by a forming tick.
  • The forming tip is replaced, not merged. Solid's store setter merges an object into the existing node, so a tick without a volume kept the previous volume and the chart drew a stale histogram. The tip payload is normalised before the path-set.
  • A slid window no longer drifts the indicator pane. When the window slides, the candles move forward by one bar while the script series do not (the engine has not re-run yet); the prune left them one row short, which shifts the pane's logical range against the price pane. The pruned series are padded back to one row per candle with Pine na whitespace rows.
  • A Pine na is a gap, not a zero. A non-finite plot value was coerced to 0, which draws a line at the axis; it now becomes a whitespace row (an omitted value). null is still coerced to 0 because the library rejects it.
  • Strategy fills follow the bar, not a stale index. The engine stamps bar_index relative to the window it evaluated, and the chart window slides (it drops its head on every new candle) — so an index pointed at a different bar in the current series: every entry/exit marker landed one bar early and the PnL was computed from the wrong bars. The absolute bar_time is now the authority (the index is the fallback), and the report is built from the run's bars rather than the live store.
  • plotshape() markers survive the apply that paints them. The owner-scoped series cleanup also cleared the script's shape and trade markers — and the apply path calls it on the price pane right after painting them, so plotshape() on every non-overlay script (RSI/MACD sub-panes) never rendered, and "Add Indicator" lost its markers before they could be re-homed. The series cleanup is now series-only; the detach path still clears the markers.
  • Two scripts that plot the same series name no longer collide. The marker id was plot-scoped (<plot>_<index>), so the price-pane merge kept only one owner's markers and the rest vanished. The id is owner-scoped now.
  • The legacy plots[] fallback is length-checked. A plots[] array of a different length than the chart window was index-zipped, mis-plotting every value with no error and no visual cue. It now goes through the same alignment as the named series.
  • A re-owned indicator's bgcolor band stays on its own pane. The re-own path left the band on the price pane, where the removal loop (which only inspects the target pane) never removed it — a permanent duplicate band and a leaked series.
  • A destroyed pane releases its drawing layer. The script DrawingLayer (SVG overlay, DOM listeners, a reference to the removed series) outlived the pane; a recreated pane with the same id got the stale layer back and projected drawings against a disposed scale.
  • A bar replay cannot leak the future. The bgcolor bands were built from the full series and had no clamp, so they extended the shared time scale past the replayed candles — the user could scroll into the colours of bars the replay had not reached. The bands are clamped to the painted range now.
  • A bar-colour repaint updates the painted range. The overlay clamp used a stale range after the candles were re-tinted, so it either dropped the newest overlay points or kept rows that no longer had candles.
  • A hidden script no longer fires its alerts. The Pine alert notification ran before the "hide / detach after the engine returned" check.
  • A live alert sees the bar it fired on. The alert context was built from the store, which is appended after the alert hook — so a condition on bar.open / bar.high / bar.low saw the previous candle. The tick's own bar is part of the context now.
  • plotshape() follows Pine's defaults. The marker position is no longer inferred from the arrow direction (Pine's default is location.abovebar whatever the style), and the plot title is only used as the marker text for the label styles (shape.labelup and friends) — in Pine the title is the plot's name.
  • A fill band is only drawn where both edges exist. Two edges of different lengths were zipped positionally, which drew half a band (one edge present, the other missing) instead of a clean gap.
  • A hidden plot keeps its palette slot. The colour index only advanced for the plots that were painted, so hiding one plot changed the colour of every later plot of the same script.
  • A cross-indicator input.source series is re-joined by time. The cached series was zipped against the consumer's window by index, but the chart window slides — so every value of a plot:<script>:<plot> source was off by the slide. The cache now carries the bar times it was written for and the consumer re-joins by time.
  • A session run without an engine bar axis says so. The series is tail-pinned onto the chart (the only anchor left), which is only correct when the session's window is the chart's window. The apply now logs a warning instead of mis-plotting silently.
  • A bar replay cannot leak the future through the compare overlay. The overlay was built from the full series, so the compared symbol's future extended the shared time scale past the replayed candles.
  • The compare overlay notices a mid-history correction. The memo compared the refs, lengths and endpoints, so a gap fill (same endpoints, changed middle) left the compare line stale.
  • A line-break primitive is keyed by pane. The same owner and plot name on two panes shared one entry, so one pane detached the other's primitive.
  • The Pine drawings fallback calls the layer once. Without a sub-pane layer everything went to the price pane in a second call, which replaced the first call's list and overwrote the kept count.
  • Scrolling to a time before the history falls back to the ±14-day window instead of snapping to the oldest bar.
  • The paint dedup is per chart slot and samples the whole window. The signature was a module global (the second slot's paint could be skipped, leaving it blank) and sampled one middle row, so a correction anywhere else in the window left the stale candle on screen. It now samples up to nine evenly spaced rows per slot.
  • The dataset job cannot be resurrected after a cancel or a dismiss. The final validation flipped a cancelled job back to complete and painted a chart the user had just cancelled, the cache-only job had no abort check at all, and a late status sync re-inserted a dismissed job into the list.
  • A paused job can be resumed. The pause request only reached the runner at the next page boundary, so a resume in that window was dropped and the job stayed paused until the user pressed resume again.
  • The gap counter counts the right store. The gap-fill progress was measured against the bars cache while the job writes to the dataset store, so “gaps filled” stayed at 0 in session persistence mode.
  • Adjacent gaps are filled in one walk. The gap-fill phase now merges adjacent holes before walking back, so a fragmented hole costs one request instead of one per fragment.
  • The background catch-up runs once. The in-flight gate was armed after the first await, so focus and visibilitychange — which fire together when the user tabs back — both started a repair (double network traffic, two DSM jobs).
  • The internal gap fill no longer skips single-bar holes, and the closed tail / filled holes it fetches are persisted instead of being thrown away on the next load.
  • The interval parser is case-insensitive for the fixed units (1H is an hour, not a day) and warns on an unknown interval instead of silently assuming a 24× wrong step. Monthly intervals count calendar months, so their density floor is reachable.
  • A DEX pool symbol keeps its case in the dataset key. Uppercasing a base58 / 0x… address made two distinct pools share one cache key — the store served the wrong series and merged one pool's bars into the other's. The provider lock (market / authMode) is part of the key for a non-spot or keyed series.
  • A persistence-mode switch migrates every dataset. Only the memory mirror was copied, so a dataset that lived solely in the old sink (evicted from the mirror, or written before it was warmed) was invisible in the new mode.
  • The chart window slide copies the window once, not twice. The head drop and the tail append were two full copies of the series; the tail is now pushed into the sliced array. The 500k-tick firehose bench (50k window) halved from 15.7s to 8.0s.
  • The backfill is an order of magnitude cheaper per page. A page write rebuilt a Map, re-sorted the whole series and re-sanitized every bar; the stored series is already repaired, so the page is now merged in one linear pass. The completeness pre-check and the validation round no longer re-repair a dataset that is already sorted and unique.- The chart window slide no longer rebuilds the overlay axis per bar. The bar-time cache recognizes a slid window (the head moved, the length did not) and shifts the axis instead of reading every bar through the store proxy.
  • An engine series that covers a different window is aligned by time. A same-length series from a session that slid independently of the chart was index-zipped onto the chart axis, which mis-plotted every value; the engine's own axis now decides when the windows disagree.
  • Deleting a dataset in the Dataset manager removes the full set, metadata included. The delete only cleared the local bars cache, so the dataset-store memory mirror kept the series and its metadata row survived — the dataset came back in the list on the next refresh. The delete now sweeps the mirror and every sink (session / local / git / worker), including the |market|authMode cache-key variants, and waits for in-flight persistence-mode copies so a background copy can no longer resurrect a dataset that was just deleted.

Desktop installers (Tauri 2). CLI tarball, worker bundle, and checksums attach from the Release workflow.