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',
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');