From e41afcf005ec215fac36abab63b3857a6a8b0310 Mon Sep 17 00:00:00 2001 From: DiTus Date: Wed, 4 Mar 2026 10:13:39 +0100 Subject: [PATCH] feat: implement interactive Best Moving Averages panel based on 1D timeframe --- src/api/dashboard/static/js/ui/chart.js | 107 ++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/src/api/dashboard/static/js/ui/chart.js b/src/api/dashboard/static/js/ui/chart.js index 60b920d..5e5e14a 100644 --- a/src/api/dashboard/static/js/ui/chart.js +++ b/src/api/dashboard/static/js/ui/chart.js @@ -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 = `
@@ -762,18 +852,21 @@ async loadSignals() {
-
Moving Averages
+
+ Best Moving Averages + ${maDateStr} (1D) +
MA 44 - ${data.moving_averages.ma_44 ? data.moving_averages.ma_44.toFixed(2) : 'N/A'} + ${ma44Value ? ma44Value.toFixed(2) : 'N/A'} ${ma44Change !== null ? `${ma44Change >= 0 ? '+' : ''}${ma44Change.toFixed(1)}%` : ''}
MA 125 - ${data.moving_averages.ma_125 ? data.moving_averages.ma_125.toFixed(2) : 'N/A'} + ${ma125Value ? ma125Value.toFixed(2) : 'N/A'} ${ma125Change !== null ? `${ma125Change >= 0 ? '+' : ''}${ma125Change.toFixed(1)}%` : ''}