diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index 63fc8f0..53042e7 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -652,6 +652,7 @@ def api_chart_ohlcv( symbol: str = "", timeframe: str = "1d", refresh: str = "", + tail: str = "", limit: int = 0, before_ms: str = "", ): @@ -665,6 +666,7 @@ def api_chart_ohlcv( raise HTTPException(status_code=400, detail="请输入币种") ex_key = str(ex.get("key") or "").strip().lower() force = (refresh or "").strip().lower() in ("1", "true", "yes", "on") + tail_refresh = (tail or "").strip().lower() in ("1", "true", "yes", "on") lim = int(limit) if int(limit or 0) > 0 else None bms_raw = (before_ms or "").strip() bms = None @@ -690,6 +692,7 @@ def api_chart_ohlcv( timeframe, remote_fetch, force_refresh=force, + tail_refresh=tail_refresh, limit=lim, before_ms=bms, ) diff --git a/manual_trading_hub/static/chart.js b/manual_trading_hub/static/chart.js index bbe943a..bc8cbbe 100644 --- a/manual_trading_hub/static/chart.js +++ b/manual_trading_hub/static/chart.js @@ -178,7 +178,7 @@ let chartRangeUserLocked = false; let chartRangeLockTimer = null; let suppressRangeUserLock = false; - let pendingViewportEpoch = -1; + const CHART_TAIL_REFRESH_LIMIT = 30; let priceTagTimer = null; let tfDigitBuf = ""; let tfDigitTimer = null; @@ -2033,7 +2033,6 @@ } function markChartRangeUserAdjusted() { - pendingViewportEpoch = -1; chartRangeUserLocked = true; if (chartRangeLockTimer) clearTimeout(chartRangeLockTimer); chartRangeLockTimer = setTimeout(function () { @@ -2168,6 +2167,7 @@ }); if (params.before_ms) qs.set("before_ms", String(params.before_ms)); if (params.refresh) qs.set("refresh", "1"); + if (params.tail) qs.set("tail", "1"); const r = await fetch("/api/chart/ohlcv?" + qs.toString(), { credentials: "same-origin" }); const data = await r.json(); if (!r.ok) { @@ -2197,8 +2197,10 @@ if (data.exhausted) exhaustedLeft = true; const incoming = alignCandlesToTick(data.candles || []); if (!incoming.length) return; - const shift = incoming.length; - applyCandlesToChart(mergeCandles(lastCandles, incoming, { prepend: true }), shift); + const prevLen = lastCandles.length; + const merged = mergeCandles(lastCandles, incoming, { prepend: true }); + const shift = merged.length - prevLen; + applyCandlesToChart(merged, shift); if (elStatus && !elStatus.classList.contains("err")) { elStatus.textContent = "已加载 " + @@ -2230,12 +2232,15 @@ const candleCountBefore = lastCandles.length; let savedRange = null; if (chart) savedRange = chart.timeScale().getVisibleLogicalRange(); + const wasViewingTail = + !savedRange || isViewingChartTail(savedRange, candleCountBefore); try { const data = await fetchChartChunk({ exchange_key: exKey, symbol: sym, timeframe: tf, - limit: chartChunkLimit(tf), + limit: CHART_TAIL_REFRESH_LIMIT, + tail: true, }); if (myToken !== loadToken) return; if (vKey !== lastViewKey) return; @@ -2250,25 +2255,28 @@ applyChartPriceFormat(); } } - const keepViewport = - pendingViewportEpoch !== chartViewEpoch && + const shouldPreserve = savedRange && - isVisibleRangeValidForCandles(savedRange, candleCountBefore); + isVisibleRangeValidForCandles(savedRange, candleCountBefore) && + (chartRangeUserLocked || !wasViewingTail); applyCandlesToChart( mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }), 0, { - preserveRange: !!keepViewport, + preserveRange: !!shouldPreserve, skipAutoScale: chartRangeUserLocked, } ); if (epochAtStart !== chartViewEpoch) return; const n = lastCandles.length; - if (pendingViewportEpoch === chartViewEpoch) { - applyDefaultVisibleRange(); - } else if (savedRange && isVisibleRangeValidForCandles(savedRange, n)) { - restoreVisibleLogicalRange(savedRange, n); - } else if (!chartRangeUserLocked) { + const minorTailUpdate = Math.abs(n - candleCountBefore) <= CHART_TAIL_REFRESH_LIMIT + 5; + if (wasViewingTail && !chartRangeUserLocked) { + restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n); + } else if (shouldPreserve && minorTailUpdate) { + if (!restoreVisibleLogicalRange(savedRange, n) && !chartRangeUserLocked) { + restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n); + } + } else if (!restoreVisibleLogicalRange(savedRange, n) && !chartRangeUserLocked) { const curRange = chart && chart.timeScale().getVisibleLogicalRange(); if (!curRange || !isVisibleRangeValidForCandles(curRange, n)) { restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n); @@ -2525,7 +2533,6 @@ chartDataLoading = true; if (resetView) { chartViewEpoch += 1; - pendingViewportEpoch = chartViewEpoch; chartRangeUserLocked = false; if (chartRangeLockTimer) { clearTimeout(chartRangeLockTimer);