Coalesce forming 1m ticks to one candle update per frame.
Schwab can print many times a frame; only the last OHLC is visible.
This commit is contained in:
parent
fe4e6b1ae8
commit
f7fbee1187
3 changed files with 57 additions and 0 deletions
|
|
@ -1398,3 +1398,6 @@ HTF line, and the socket still attached 180 `future_times` (twice) plus a
|
||||||
A forming tick of an already-seen minute is now `{type, tf, bar}` only. HTF
|
A forming tick of an already-seen minute is now `{type, tf, bar}` only. HTF
|
||||||
geometry and future calendars go out when that timeframe's timestamp advances.
|
geometry and future calendars go out when that timeframe's timestamp advances.
|
||||||
The header quote paints the DOM directly; Vue's `price` flushes at 1 Hz.
|
The header quote paints the DOM directly; Vue's `price` flushes at 1 Hz.
|
||||||
|
|
||||||
|
Schwab still prints many forming ticks per frame. Those now coalesce to one
|
||||||
|
`candles.update` on the next animation frame. A new minute flushes immediately.
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,8 @@ class ConfluenceChart {
|
||||||
this.animateCurrentPrice = true;
|
this.animateCurrentPrice = true;
|
||||||
this.autoScrollLivePrice = true;
|
this.autoScrollLivePrice = true;
|
||||||
this.lastCurrentPrice = null;
|
this.lastCurrentPrice = null;
|
||||||
|
this.pendingBar = null;
|
||||||
|
this.pendingBarFrame = null;
|
||||||
this.previewLine = null;
|
this.previewLine = null;
|
||||||
this.lineBridgeLayer = null;
|
this.lineBridgeLayer = null;
|
||||||
this.bars = [];
|
this.bars = [];
|
||||||
|
|
@ -805,6 +807,30 @@ class ConfluenceChart {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBar(bar) {
|
updateBar(bar) {
|
||||||
|
const last = this.bars[this.bars.length - 1];
|
||||||
|
if (last && bar.t < last.t) return;
|
||||||
|
if (!last || bar.t > last.t) {
|
||||||
|
this.flushPendingBar();
|
||||||
|
this.applyBar(bar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pendingBar = bar;
|
||||||
|
if (this.pendingBarFrame == null) {
|
||||||
|
this.pendingBarFrame = requestAnimationFrame(() => this.flushPendingBar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flushPendingBar() {
|
||||||
|
if (this.pendingBarFrame != null) {
|
||||||
|
cancelAnimationFrame(this.pendingBarFrame);
|
||||||
|
this.pendingBarFrame = null;
|
||||||
|
}
|
||||||
|
const bar = this.pendingBar;
|
||||||
|
this.pendingBar = null;
|
||||||
|
if (bar) this.applyBar(bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBar(bar) {
|
||||||
// update() throws on anything older than the series' last point, which
|
// update() throws on anything older than the series' last point, which
|
||||||
// takes the whole app down rather than dropping one stale bar. Ticks made
|
// 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
|
// this reachable often enough to matter, so refuse it here as well as at
|
||||||
|
|
@ -893,6 +919,10 @@ class ConfluenceChart {
|
||||||
|
|
||||||
updateCurrentPrice(price) {
|
updateCurrentPrice(price) {
|
||||||
if (price == null) return;
|
if (price == null) return;
|
||||||
|
if (this.currentPriceLine && price === this.lastCurrentPrice) {
|
||||||
|
this.syncCurrentPricePulse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const options = {
|
const options = {
|
||||||
price,
|
price,
|
||||||
color: '#b7771d',
|
color: '#b7771d',
|
||||||
|
|
|
||||||
|
|
@ -498,6 +498,30 @@ test('repeating the same future times does not rebuild the time scale',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('forming ticks coalesce to one candle update per frame',
|
||||||
|
{ timeout: 180000 }, async () => {
|
||||||
|
await withChart(async page => {
|
||||||
|
const result = await page.evaluate(async () => {
|
||||||
|
const c = window.__chart;
|
||||||
|
const last = c.bars.at(-1);
|
||||||
|
let updates = 0;
|
||||||
|
const original = c.candles.update.bind(c.candles);
|
||||||
|
c.candles.update = data => {
|
||||||
|
updates += 1;
|
||||||
|
return original(data);
|
||||||
|
};
|
||||||
|
for (let index = 0; index < 12; index += 1) {
|
||||||
|
c.updateBar({ ...last, c: last.c + index * 0.25, closed: false });
|
||||||
|
}
|
||||||
|
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||||
|
return { updates };
|
||||||
|
});
|
||||||
|
assert.ok(result.updates <= 2,
|
||||||
|
`twelve forming ticks painted ${result.updates} candle updates`);
|
||||||
|
assertNoPageErrors(page, assert);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('painted canvas pixels keep the same slope through the live edge',
|
test('painted canvas pixels keep the same slope through the live edge',
|
||||||
{ timeout: 180000 }, async () => {
|
{ timeout: 180000 }, async () => {
|
||||||
await withChart(async page => {
|
await withChart(async page => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue