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
This commit is contained in:
BTC Bot
2026-02-18 12:16:57 +01:00
parent 30bedcbb67
commit c3cf0578f5

View File

@ -91,6 +91,17 @@ export class TradingDashboard {
borderDownColor: '#ff9800', borderDownColor: '#ff9800',
wickUpColor: '#ff9800', wickUpColor: '#ff9800',
wickDownColor: '#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(); this.initPriceScaleControls();
@ -240,7 +251,7 @@ export class TradingDashboard {
this.candleSeries.setData(mergedData); this.candleSeries.setData(mergedData);
if (fitToContent) { if (fitToContent) {
this.chart.timeScale().fitContent(); this.chart.timeScale().scrollToRealTime();
} else if (visibleRange) { } else if (visibleRange) {
this.chart.timeScale().setVisibleLogicalRange(visibleRange); this.chart.timeScale().setVisibleLogicalRange(visibleRange);
} }
@ -308,73 +319,44 @@ export class TradingDashboard {
onVisibleRangeChange() { onVisibleRangeChange() {
if (!this.hasInitialLoad || this.isLoading) { if (!this.hasInitialLoad || this.isLoading) {
console.log('Skipping range change:', { hasInitialLoad: this.hasInitialLoad, isLoading: this.isLoading });
return; return;
} }
const visibleRange = this.chart.timeScale().getVisibleLogicalRange(); const visibleRange = this.chart.timeScale().getVisibleLogicalRange();
if (!visibleRange) { if (!visibleRange) {
console.log('No visible range');
return; return;
} }
const data = this.candleSeries.data(); const data = this.candleSeries.data();
if (!data || data.length === 0) { if (!data || data.length === 0) {
console.log('No data available');
return; return;
} }
const barsFromLeft = visibleRange.from; const visibleBars = Math.ceil(visibleRange.to - visibleRange.from);
const totalBars = data.length; 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 < refillThreshold) {
console.log(`Buffer low (${barsFromLeft} < ${refillThreshold.toFixed(0)}), silently prefetching ${bufferSize} candles...`);
if (barsFromLeft < 50) {
console.log('Near left edge (within 50 bars), loading historical data...');
const oldestCandle = data[0]; const oldestCandle = data[0];
if (oldestCandle) { if (oldestCandle) {
this.loadHistoricalData(oldestCandle.time); this.loadHistoricalData(oldestCandle.time, bufferSize);
} }
} }
} }
async loadHistoricalData(beforeTime) { async loadHistoricalData(beforeTime, limit = 1000) {
if (this.isLoading) { if (this.isLoading) {
console.log('Already loading, skipping...');
return; return;
} }
this.isLoading = true; 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 { try {
const endTime = new Date((beforeTime - 1) * 1000); const endTime = new Date((beforeTime - 1) * 1000);
console.log(`Loading historical data before ${new Date((beforeTime - 1) * 1000).toISOString()}`);
const response = await fetch( 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) { if (!response.ok) {
@ -398,25 +380,7 @@ export class TradingDashboard {
this.candleSeries.setData(mergedData); this.candleSeries.setData(mergedData);
if (leftmostTime) { console.log(`Prefetched ${chartData.length} candles, total: ${mergedData.length}`);
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`);
} else { } else {
console.log('No more historical data available'); console.log('No more historical data available');
} }
@ -504,6 +468,14 @@ export class TradingDashboard {
updateStats(candle) { updateStats(candle) {
const price = candle.close; const price = candle.close;
const change = ((price - candle.open) / candle.open * 100); 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').textContent = price.toFixed(2);
document.getElementById('currentPrice').className = 'stat-value ' + (change >= 0 ? 'positive' : 'negative'); document.getElementById('currentPrice').className = 'stat-value ' + (change >= 0 ? 'positive' : 'negative');