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