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:
@ -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');
|
||||
|
||||
Reference in New Issue
Block a user