From c3cf0578f5dc81ae2540a69bfaf8b976f516abfa Mon Sep 17 00:00:00 2001 From: BTC Bot Date: Wed, 18 Feb 2026 12:16:57 +0100 Subject: [PATCH] Improve chart UX: dynamic price line color, smooth historical data prefetch - Add custom price line (green/red based on candle direction) - Disable default orange price line visibility - Chart scrolls to recent data on timeframe switch - Implement dynamic buffer for historical data prefetch (2x visible bars) - Silently refills buffer when below 80% threshold --- src/api/dashboard/static/js/ui/chart.js | 88 +++++++++---------------- 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/src/api/dashboard/static/js/ui/chart.js b/src/api/dashboard/static/js/ui/chart.js index a403885..3c6982d 100644 --- a/src/api/dashboard/static/js/ui/chart.js +++ b/src/api/dashboard/static/js/ui/chart.js @@ -91,6 +91,17 @@ export class TradingDashboard { borderDownColor: '#ff9800', wickUpColor: '#ff9800', wickDownColor: '#ff9800', + lastValueVisible: false, + priceLineVisible: false, + }); + + this.currentPriceLine = this.candleSeries.createPriceLine({ + price: 0, + color: '#26a69a', + lineWidth: 1, + lineStyle: LightweightCharts.LineStyle.Dotted, + axisLabelVisible: true, + title: '', }); this.initPriceScaleControls(); @@ -240,7 +251,7 @@ export class TradingDashboard { this.candleSeries.setData(mergedData); if (fitToContent) { - this.chart.timeScale().fitContent(); + this.chart.timeScale().scrollToRealTime(); } else if (visibleRange) { this.chart.timeScale().setVisibleLogicalRange(visibleRange); } @@ -308,73 +319,44 @@ export class TradingDashboard { onVisibleRangeChange() { if (!this.hasInitialLoad || this.isLoading) { - console.log('Skipping range change:', { hasInitialLoad: this.hasInitialLoad, isLoading: this.isLoading }); return; } const visibleRange = this.chart.timeScale().getVisibleLogicalRange(); if (!visibleRange) { - console.log('No visible range'); return; } const data = this.candleSeries.data(); if (!data || data.length === 0) { - console.log('No data available'); return; } - const barsFromLeft = visibleRange.from; - const totalBars = data.length; + const visibleBars = Math.ceil(visibleRange.to - visibleRange.from); + const bufferSize = visibleBars * 2; + const refillThreshold = bufferSize * 0.8; + const barsFromLeft = Math.floor(visibleRange.from); - console.log('Visible range:', { from: visibleRange.from, to: visibleRange.to, barsFromLeft, totalBars }); - - if (barsFromLeft < 50) { - console.log('Near left edge (within 50 bars), loading historical data...'); + if (barsFromLeft < refillThreshold) { + console.log(`Buffer low (${barsFromLeft} < ${refillThreshold.toFixed(0)}), silently prefetching ${bufferSize} candles...`); const oldestCandle = data[0]; if (oldestCandle) { - this.loadHistoricalData(oldestCandle.time); + this.loadHistoricalData(oldestCandle.time, bufferSize); } } } - async loadHistoricalData(beforeTime) { + async loadHistoricalData(beforeTime, limit = 1000) { if (this.isLoading) { - console.log('Already loading, skipping...'); return; } this.isLoading = true; - console.log(`Loading historical data for ${this.currentInterval} before ${beforeTime}`); - - const currentData = this.candleSeries.data(); - const visibleRange = this.chart.timeScale().getVisibleLogicalRange(); - let leftmostTime = null; - - let savedPriceRange = null; - const isAutoScale = this.priceScaleState?.autoScale !== false; - if (!isAutoScale) { - try { - savedPriceRange = this.candleSeries.priceScale().getVisiblePriceRange(); - console.log('Saving price range:', savedPriceRange); - } catch (e) { - console.log('Could not save price range'); - } - } - - if (visibleRange && currentData.length > 0) { - const leftmostIndex = Math.floor(visibleRange.from); - if (leftmostIndex >= 0 && leftmostIndex < currentData.length) { - leftmostTime = currentData[leftmostIndex].time; - } - } try { const endTime = new Date((beforeTime - 1) * 1000); - console.log(`Loading historical data before ${new Date((beforeTime - 1) * 1000).toISOString()}`); - const response = await fetch( - `/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&end=${endTime.toISOString()}&limit=500` + `/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&end=${endTime.toISOString()}&limit=${limit}` ); if (!response.ok) { @@ -398,25 +380,7 @@ export class TradingDashboard { this.candleSeries.setData(mergedData); - if (leftmostTime) { - const idx = mergedData.findIndex(c => c.time === leftmostTime); - if (idx >= 0) { - this.chart.timeScale().setVisibleLogicalRange({ from: idx, to: idx + 50 }); - } - } - - if (!isAutoScale && savedPriceRange) { - setTimeout(() => { - try { - this.candleSeries.priceScale().setVisiblePriceRange(savedPriceRange); - console.log('Restored price range:', savedPriceRange); - } catch (e) { - console.log('Could not restore price range'); - } - }, 50); - } - - console.log(`Loaded ${chartData.length} historical candles`); + console.log(`Prefetched ${chartData.length} candles, total: ${mergedData.length}`); } else { console.log('No more historical data available'); } @@ -504,6 +468,14 @@ export class TradingDashboard { updateStats(candle) { const price = candle.close; const change = ((price - candle.open) / candle.open * 100); + const isUp = candle.close >= candle.open; + + if (this.currentPriceLine) { + this.currentPriceLine.applyOptions({ + price: price, + color: isUp ? '#26a69a' : '#ef5350', + }); + } document.getElementById('currentPrice').textContent = price.toFixed(2); document.getElementById('currentPrice').className = 'stat-value ' + (change >= 0 ? 'positive' : 'negative');