feat: implement interactive Best Moving Averages panel based on 1D timeframe

This commit is contained in:
DiTus
2026-03-04 10:13:39 +01:00
parent 756b1dbd65
commit e41afcf005

View File

@ -23,10 +23,62 @@ constructor() {
this.lastCandleTimestamp = null;
this.simulationMarkers = [];
this.avgPriceSeries = null;
this.dailyMAData = new Map(); // timestamp -> { ma44, ma125, price }
this.currentMouseTime = null;
this.init();
}
async loadDailyMAData() {
try {
// Use 1d interval for this calculation
const interval = '1d';
let candles = this.allData.get(interval);
if (!candles || candles.length < 125) {
const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${interval}&limit=1000`);
const data = await response.json();
if (data.candles && data.candles.length > 0) {
candles = data.candles.reverse().map(c => ({
time: Math.floor(new Date(c.time).getTime() / 1000),
open: parseFloat(c.open),
high: parseFloat(c.high),
low: parseFloat(c.low),
close: parseFloat(c.close)
}));
this.allData.set(interval, candles);
}
}
if (candles && candles.length >= 44) {
const ma44 = this.calculateSimpleSMA(candles, 44);
const ma125 = this.calculateSimpleSMA(candles, 125);
this.dailyMAData.clear();
candles.forEach((c, i) => {
this.dailyMAData.set(c.time, {
price: c.close,
ma44: ma44[i],
ma125: ma125[i]
});
});
}
} catch (error) {
console.error('[DailyMA] Error:', error);
}
}
calculateSimpleSMA(candles, period) {
const results = new Array(candles.length).fill(null);
let sum = 0;
for (let i = 0; i < candles.length; i++) {
sum += candles[i].close;
if (i >= period) sum -= candles[i - period].close;
if (i >= period - 1) results[i] = sum / period;
}
return results;
}
setSimulationMarkers(markers) {
this.simulationMarkers = markers || [];
this.updateSignalMarkers();
@ -166,6 +218,17 @@ constructor() {
this.chart.timeScale().subscribeVisibleLogicalRangeChange(this.onVisibleRangeChange.bind(this));
// Subscribe to crosshair movement for Best Moving Averages updates
this.chart.subscribeCrosshairMove(param => {
if (param.time) {
this.currentMouseTime = param.time;
this.renderTA();
} else {
this.currentMouseTime = null;
this.renderTA();
}
});
window.addEventListener('resize', () => {
this.chart.applyOptions({
width: chartContainer.clientWidth,
@ -357,7 +420,8 @@ constructor() {
async loadInitialData() {
await Promise.all([
this.loadData(2000, true),
this.loadStats()
this.loadStats(),
this.loadDailyMAData()
]);
this.hasInitialLoad = true;
this.loadTA();
@ -465,6 +529,7 @@ async loadNewData() {
window.drawIndicatorsOnChart?.();
window.updateIndicatorCandles?.();
this.loadDailyMAData();
await this.loadSignals();
}
} catch (error) {
@ -719,9 +784,6 @@ async loadSignals() {
const trendClass = data.trend.direction.toLowerCase();
const signalClass = data.trend.signal.toLowerCase();
const ma44Change = data.moving_averages.price_vs_ma44;
const ma125Change = data.moving_averages.price_vs_ma125;
document.getElementById('taInterval').textContent = this.currentInterval.toUpperCase();
document.getElementById('taLastUpdate').textContent = new Date().toLocaleTimeString();
@ -752,6 +814,34 @@ async loadSignals() {
const summaryBadge = '';
// Best Moving Averages Logic (1D based)
let displayMA = { ma44: null, ma125: null, price: null, time: null };
if (this.currentMouseTime && this.dailyMAData.size > 0) {
// Find the 1D candle that includes this mouse time
const dayTimestamp = Math.floor(this.currentMouseTime / 86400) * 86400;
if (this.dailyMAData.has(dayTimestamp)) {
displayMA = { ...this.dailyMAData.get(dayTimestamp), time: dayTimestamp };
} else {
// Fallback to latest if specific day not found
const keys = Array.from(this.dailyMAData.keys()).sort((a, b) => b - a);
const latestKey = keys[0];
displayMA = { ...this.dailyMAData.get(latestKey), time: latestKey };
}
} else if (this.dailyMAData.size > 0) {
const keys = Array.from(this.dailyMAData.keys()).sort((a, b) => b - a);
const latestKey = keys[0];
displayMA = { ...this.dailyMAData.get(latestKey), time: latestKey };
}
const ma44Value = displayMA.ma44;
const ma125Value = displayMA.ma125;
const currentPrice = displayMA.price;
const ma44Change = (ma44Value && currentPrice) ? ((currentPrice - ma44Value) / ma44Value * 100) : null;
const ma125Change = (ma125Value && currentPrice) ? ((currentPrice - ma125Value) / ma125Value * 100) : null;
const maDateStr = displayMA.time ? TimezoneConfig.formatDate(displayMA.time * 1000).split(' ')[0] : 'Latest';
document.getElementById('taContent').innerHTML = `
<div class="ta-section">
<div class="ta-section-title">
@ -762,18 +852,21 @@ async loadSignals() {
</div>
<div class="ta-section">
<div class="ta-section-title">Moving Averages</div>
<div class="ta-section-title" style="display: flex; justify-content: space-between;">
<span>Best Moving Averages</span>
<span style="font-size: 10px; font-weight: normal; color: var(--tv-blue);">${maDateStr} (1D)</span>
</div>
<div class="ta-ma-row">
<span class="ta-ma-label">MA 44</span>
<span class="ta-ma-value">
${data.moving_averages.ma_44 ? data.moving_averages.ma_44.toFixed(2) : 'N/A'}
${ma44Value ? ma44Value.toFixed(2) : 'N/A'}
${ma44Change !== null ? `<span class="ta-ma-change ${ma44Change >= 0 ? 'positive' : 'negative'}">${ma44Change >= 0 ? '+' : ''}${ma44Change.toFixed(1)}%</span>` : ''}
</span>
</div>
<div class="ta-ma-row">
<span class="ta-ma-label">MA 125</span>
<span class="ta-ma-value">
${data.moving_averages.ma_125 ? data.moving_averages.ma_125.toFixed(2) : 'N/A'}
${ma125Value ? ma125Value.toFixed(2) : 'N/A'}
${ma125Change !== null ? `<span class="ta-ma-change ${ma125Change >= 0 ? 'positive' : 'negative'}">${ma125Change >= 0 ? '+' : ''}${ma125Change.toFixed(1)}%</span>` : ''}
</span>
</div>