diff --git a/manual_trading_hub/static/chart.js b/manual_trading_hub/static/chart.js index e25c13e..8466517 100644 --- a/manual_trading_hub/static/chart.js +++ b/manual_trading_hub/static/chart.js @@ -1868,6 +1868,16 @@ }); } + function buildVolumeBar(candle) { + const p = chartThemePalette(); + const up = Number(candle.close) >= Number(candle.open); + return { + time: candle.time, + value: Number(candle.volume) || 0, + color: up ? p.volUp : p.volDown, + }; + } + function ensureChart() { if (chart && candleSeries && volumeSeries) return true; if (!window.LightweightCharts) { @@ -2141,6 +2151,45 @@ return merged; } + /** 尾部静默刷新:仅 update 变更 K 线,不 setData,避免视口跳动 */ + function applyTailCandlePatch(incoming) { + if (!candleSeries || !volumeSeries || !incoming || !incoming.length) return false; + const aligned = alignCandlesToTick(incoming); + const prevLen = lastCandles.length; + const oldestTime = prevLen ? lastCandles[0].time : null; + const merged = mergeCandles(lastCandles, aligned, { prepend: false }); + if ( + prevLen > 0 && + merged.length > 0 && + merged[0].time !== oldestTime && + merged.length <= prevLen + ) { + return false; + } + aligned.forEach(function (bar) { + candleSeries.update(bar); + volumeSeries.update(buildVolumeBar(bar)); + }); + if (merged.length > prevLen) { + for (let i = prevLen; i < merged.length; i++) { + const bar = merged[i]; + candleSeries.update(bar); + volumeSeries.update(buildVolumeBar(bar)); + } + } + lastCandles = merged; + indexCandles(lastCandles); + readIndicatorState(); + if (indicatorState.ema || indicatorState.macd || indicatorState.rsi) { + try { + updateIndicators(); + } catch (indErr) {} + } + updateVisibleRangeMarkers(); + showLatestOhlcv(); + return true; + } + function applyCandlesToChart(candles, rangeShift, opts) { opts = opts || {}; let savedRange = null; @@ -2151,7 +2200,9 @@ indexCandles(lastCandles); candleSeries.setData(lastCandles); volumeSeries.setData(buildVolumeData(lastCandles)); - applyChartRightGap(); + if (!opts.skipRightGap) { + applyChartRightGap(); + } if (rangeShift && chart) { const range = chart.timeScale().getVisibleLogicalRange(); if (range) { @@ -2270,21 +2321,25 @@ applyChartPriceFormat(); } } - applyCandlesToChart( - mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }), - 0, - { + const incoming = alignCandlesToTick(data.candles); + if (!autoFollow && applyTailCandlePatch(incoming)) { + /* 手动模式:增量更新,不触碰时间轴 */ + } else { + const merged = mergeCandles(lastCandles, incoming, { prepend: false }); + applyCandlesToChart(merged, 0, { preserveRange: false, skipAutoScale: !autoFollow, + skipRightGap: !autoFollow, + }); + if (epochAtStart !== chartViewEpoch) return; + const n = lastCandles.length; + if (autoFollow) { + applyDefaultVisibleRange(); + } else if (savedRange) { + applyPreservedVisibleRange(savedRange, n); } - ); - if (epochAtStart !== chartViewEpoch) return; - const n = lastCandles.length; - if (autoFollow) { - applyDefaultVisibleRange(); - } else if (savedRange) { - applyPreservedVisibleRange(savedRange, n); } + if (epochAtStart !== chartViewEpoch) return; scheduleRangeUiUpdate(); if (posContext) { updateLivePosPnl(); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 7cf6efd..e64e46b 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -349,7 +349,7 @@
- +