Drop stale bar events instead of letting the chart throw
Cannot update oldest data appeared in the console once ticks were live. Switching timeframe races: the server answers subscribe with a fresh snapshot from one coroutine while another is still draining bar events for the timeframe just left, so a 1m bar can arrive after the 1h snapshot. Against the 1h series it is older than every point in it, and Lightweight Charts throws rather than ignoring it, which takes the app down instead of dropping one bar. The race predates the tick feed. Level 1 made bar events about fifteen times more frequent, which is what surfaced it. Guarded at both ends. app.js honours the tf each event already carries and drops anything for a timeframe no longer selected. chart.js refuses a bar older than the series' last point whatever its origin, since a bar behind the last one has nothing to contribute. Verified: 36 rapid timeframe switches under a live tick feed produce zero console errors, and calling candles.update() directly with a stale bar still throws while the guarded updateBar() does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
52e657fb1e
commit
a13a54bc3f
3 changed files with 29 additions and 0 deletions
|
|
@ -1322,3 +1322,20 @@ cooldowns, so a deploy can re-alert whatever price is sitting on.
|
||||||
"warm" means to every consumer of `/api/status`, so it wants its own thought.
|
"warm" means to every consumer of `/api/status`, so it wants its own thought.
|
||||||
|
|
||||||
Recommended order: 1, then 3, then 2. 4 only if the window still bites after 1.
|
Recommended order: 1, then 3, then 2. 4 only if the window still bites after 1.
|
||||||
|
|
||||||
|
**Stale bar events across a timeframe switch.** `Cannot update oldest data`
|
||||||
|
appeared in the console once ticks were live. Switching timeframe races: the
|
||||||
|
server answers `subscribe` with a fresh snapshot from one coroutine while
|
||||||
|
another is still draining bar events for the timeframe just left, so a 1m bar
|
||||||
|
can land after the 1h snapshot. Applied to the 1h series it is older than every
|
||||||
|
point in it, and Lightweight Charts throws rather than ignoring it — taking the
|
||||||
|
app down instead of dropping one bar. The race predates the tick feed; Level 1
|
||||||
|
made bar events ~15x more frequent, which is what surfaced it.
|
||||||
|
|
||||||
|
Guarded at both ends. `app.js` honours the `tf` the event already carries and
|
||||||
|
drops anything for a timeframe that is no longer selected. `chart.js` refuses a
|
||||||
|
bar older than the series' last point regardless of where it came from — a bar
|
||||||
|
behind the last one has nothing to contribute. Verified: 36 rapid timeframe
|
||||||
|
switches under a live tick feed produce zero errors, and calling
|
||||||
|
`candles.update()` directly with a stale bar still throws while the guarded
|
||||||
|
`updateBar()` does not.
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,12 @@ createApp({
|
||||||
syncVisibleLevels();
|
syncVisibleLevels();
|
||||||
price.value = message.price;
|
price.value = message.price;
|
||||||
} else if (message.type === 'bar') {
|
} else if (message.type === 'bar') {
|
||||||
|
// Switching timeframe races: the server answers `subscribe` with a
|
||||||
|
// fresh snapshot from one coroutine while another is still draining
|
||||||
|
// bar events for the timeframe just left. A 1m bar applied over a 1h
|
||||||
|
// series is older than everything in it, which the chart rejects
|
||||||
|
// outright. The event carries its timeframe, so honour it.
|
||||||
|
if (message.tf && message.tf !== timeframe.value) return;
|
||||||
chartApi.updateBar(message.bar);
|
chartApi.updateBar(message.bar);
|
||||||
price.value = message.bar.c;
|
price.value = message.bar.c;
|
||||||
status.value.last_bar_t = message.bar.t;
|
status.value.last_bar_t = message.bar.t;
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,12 @@ class ConfluenceChart {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBar(bar) {
|
updateBar(bar) {
|
||||||
|
// update() throws on anything older than the series' last point, which
|
||||||
|
// takes the whole app down rather than dropping one stale bar. Ticks made
|
||||||
|
// this reachable often enough to matter, so refuse it here as well as at
|
||||||
|
// the source: a bar behind the last one has nothing to contribute.
|
||||||
|
const last = this.bars[this.bars.length - 1];
|
||||||
|
if (last && bar.t < last.t) return;
|
||||||
this.candles.update(this.toCandle(bar));
|
this.candles.update(this.toCandle(bar));
|
||||||
if (this.bars.length && this.bars[this.bars.length - 1].t === bar.t) this.bars[this.bars.length - 1] = bar;
|
if (this.bars.length && this.bars[this.bars.length - 1].t === bar.t) this.bars[this.bars.length - 1] = bar;
|
||||||
else this.bars.push(bar);
|
else this.bars.push(bar);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue