Repository navigation
Keep built-in indicators on their candles while history loads - #294
Merged
Merged
Conversation
The renderer paints an indicator's points index-aligned from the bar its computation started on (the model's anchorTime), bar 0 when absent. Script indicators stamp that anchor and hold their first run until the history completes; native indicators did neither. A native computed over the first part of a progressive load was painted from the chart's first bar once older history landed: the whole plot slid back in time, and a deep prepend pushed it off-screen entirely. A depth trim shifted it the other way, and progressive snapshots never re-ran natives at all. A live tick recomputed them, which hid the shift on busy markets; on a quiet one it stayed until the load completed. - Every native emit is anchored to the chart's first bar at the call, so an output stays on its candles across prepends and trims until its next compute. - New optional waitsForHistory descriptor flag: such a native starts only once the history is complete (a loading legend row meanwhile, ready and indicator:added deferred, inspect() skipping it) and holds onBars() during a later backfill. The classic studies opt in; volume and VPVR do not. - Progressive snapshots poke consumers with 'backfill', like the chunked backfill does. - A price-style transform change (Heikin Ashi) re-runs started natives over the new candles instead of waiting for the next tick. - Blanked models drop their anchor, so a market switch no longer warns that the old anchor matches no bar on the new axis. Co-authored-by: Cursor <cursoragent@cursor.com>
alexgrover
marked this pull request as ready for review
October 16, 2026 20:24
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: 08a29fd4-7337-4a8e-a284-8955bba1ae72) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
On production, a built-in EMA on AAPL 15m could vanish when zooming out, or sit shifted left of its candles and stop well before the last one. Both come from history arriving in stages: the data server answers a cold symbol with growing snapshots while it completes the history, and the chart paints each one.
The renderer paints an indicator's points index-aligned from the bar its computation started on (the model's
anchorTime), bar 0 when absent. Script indicators follow two rules that keep them correct across a staged load; native indicators followed neither:anchorTime. Natives did not, so the renderer pinned their first point to whatever the chart's first bar was at paint time. A native computed over the first snapshot was painted from the first bar of the next, deeper one: the whole plot slid back in time by the number of bars that landed in front. A first paint can be as small as 20 bars, so a deep history pushed the plot off-screen entirely: the "disappeared" report. A depth trim shifted natives the other way, because that path's completion fires while the switch still gates every poke.Summary
buildNativeModel), so an output stays on its candles across prepends and trims until its next compute. This covers every native, built-in or plugin, with no change on their side.waitsForHistory. Such a native starts only once the history is complete: it gets a loading legend row meanwhile (the same placeholder path script indicators use, soready/indicator:addedwait andinspect()skips it), then one compute over every candle. A later backfill (a deeper depth) holdsonBars(), live ticks included, while the previous output stays aligned through its anchor, and re-runs at completion. A depth trim during an in-flight load releases held natives. The classic studies opt in; volume and VPVR do not. Absent flag = today's behavior.'backfill', the same as a chunked backfill chunk. Script engines ignore it; the footprint engine and the volume layer treat it as a no-op on a live chart.ctx.bars()is the view, so the synthetic candles are the studies' source.docs/contributing/plugin-sdk.mddescribes staged history and the new flag.CHANGELOG.mdhas two fixes and one addition under[Unreleased].Public API: one additive, optional field on
NativeIndicatorDescriptor(@luxalgo/vela/plugin). Non-breaking. Contract comments onemitandonBarsnow state the alignment rule and the price-style poke.End-to-end verification
Exercised in the Vela playground (Chromium) against the real renderer. A stand-in progressive provider registered from the console served real BTCUSDT 1h bars from Binance, released snapshots on command, and streamed no live ticks (a quiet market). A probe read the renderer's scene: each indicator's points, its anchor offset, and the bar each point is painted on.
dev): after a 1,500-bar first paint and a 2,000-bar snapshot, all 1,500 EMA and Bollinger points sat on the wrong candles and the newest candle had no value. In the recent view the studies were gone; zoomed out they ran 500 bars early, matching the production screenshot.test/native-history-load.test.ts, 10 tests: progressive load, the hold and its loading row, volume not held, ticks during the hold, a default-policy native following snapshots, depth trim, trim during an in-flight load, a deeper depth held then recomputed (sampled at each chunk), market-switch blanking, and Heikin Ashi both ways. All 10 fail ondevand pass here.typecheck,lint,test(2,192 passed) andbuildall pass. One timing-based replay test (play()pace, 100 ms wall-clock sleep) failed once under full-suite load, then passed on the rerun and three times alone.Checklist
npm run typecheck,npm run lint,npm test, andnpm run buildall passdocs/has been added / updated (for public interface or feature changes)CHANGELOG.mdhas an entry under[Unreleased](for user-visible changes)Future work
waitsForHistoryonce this ships.history:progressthe way the chunked backfill does.Note
Medium Risk
Touches core engine history/backfill and native indicator lifecycle paths that affect every on-chart study; behavior change is intentional but broad across progressive loads, depth changes, and price styles.
Overview
Fixes native indicators sliding or vanishing while history loads in chunks, and aligns built-in studies with script-indicator behavior.
Native emits are now anchored via
anchorTimeon each model (first chart bar at emit time), so plots stay on the correct candles when older bars prepend or depth trims until the next compute. OptionalwaitsForHistoryonNativeIndicatorDescriptordefers start until history is complete (loading legend row,readywaits); during backfill or deeper loads,onBars()is held and prior output stays anchored. Classic built-in studies opt in; volume/VPVR still paint from the first candles.Orchestrator changes: progressive snapshot repaints notify sessions with
'backfill';pokeNative/releaseHeldNativesimplement the hold policy; Heikin Ashi (and other bar transforms) immediately re-run started natives overctx.bars(); blanked models dropanchorTimeon market switch. Plugin SDK and CHANGELOG document staged history and the flag. Newtest/native-history-load.test.tscovers progressive load, depth changes, and price-style switches.Reviewed by Cursor Bugbot for commit 25fd936. Bugbot is set up for automated code reviews on this repo. Configure here.