diff --git a/src/api/dashboard/static/css/indicators-new.css b/src/api/dashboard/static/css/indicators-new.css index 4138129..d2f00e7 100644 --- a/src/api/dashboard/static/css/indicators-new.css +++ b/src/api/dashboard/static/css/indicators-new.css @@ -646,4 +646,54 @@ /* Collapsed sidebar adjustments */ .right-sidebar.collapsed .sidebar-tabs { display: none; +} + +/* Strategy Panel Styles */ +.indicator-checklist { + max-height: 120px; + overflow-y: auto; + background: var(--tv-bg); + border: 1px solid var(--tv-border); + border-radius: 4px; + padding: 4px; + margin-top: 4px; +} +.indicator-checklist::-webkit-scrollbar { + width: 4px; +} +.indicator-checklist::-webkit-scrollbar-thumb { + background: var(--tv-border); + border-radius: 2px; +} + +.checklist-item { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + font-size: 12px; + cursor: pointer; + border-radius: 3px; +} +.checklist-item:hover { + background: var(--tv-hover); +} +.checklist-item input { + cursor: pointer; +} + +.equity-chart-container { + width: 100%; + height: 150px; + margin-top: 12px; + border-radius: 4px; + overflow: hidden; + border: 1px solid var(--tv-border); + background: var(--tv-bg); +} + +.results-actions { + display: flex; + gap: 8px; + margin-top: 12px; } \ No newline at end of file diff --git a/src/api/dashboard/static/index.html b/src/api/dashboard/static/index.html index 35c9c79..db4d05b 100644 --- a/src/api/dashboard/static/index.html +++ b/src/api/dashboard/static/index.html @@ -1480,6 +1480,7 @@
@@ -1493,6 +1494,14 @@ + + + diff --git a/src/api/dashboard/static/js/app.js b/src/api/dashboard/static/js/app.js index 33a7ba3..839bb38 100644 --- a/src/api/dashboard/static/js/app.js +++ b/src/api/dashboard/static/js/app.js @@ -8,6 +8,7 @@ import { addIndicator, removeIndicatorById } from './ui/indicators-panel-new.js'; +import { initStrategyPanel } from './ui/strategy-panel.js'; import { IndicatorRegistry } from './indicators/index.js'; import { TimezoneConfig } from './config/timezone.js'; @@ -75,6 +76,7 @@ document.addEventListener('DOMContentLoaded', async () => { restoreSidebarTabState(); initSidebarTabs(); - // Initialize indicator panel + // Initialize panels window.initIndicatorPanel(); + initStrategyPanel(); }); diff --git a/src/api/dashboard/static/js/indicators/bb.js b/src/api/dashboard/static/js/indicators/bb.js index dceb879..ae1aa5c 100644 --- a/src/api/dashboard/static/js/indicators/bb.js +++ b/src/api/dashboard/static/js/indicators/bb.js @@ -32,35 +32,38 @@ class BaseIndicator { } // Signal calculation for Bollinger Bands -function calculateBollingerBandsSignal(indicator, lastCandle, prevCandle, values) { +function calculateBollingerBandsSignal(indicator, lastCandle, prevCandle, values, prevValues) { const close = lastCandle.close; + const prevClose = prevCandle?.close; const upper = values?.upper; const lower = values?.lower; - const middle = values?.middle; + const prevUpper = prevValues?.upper; + const prevLower = prevValues?.lower; - if (!upper || !lower || !middle) { + if (!upper || !lower || prevUpper === undefined || prevLower === undefined || prevClose === undefined) { return null; } - const bandwidth = (upper - lower) / middle * 100; - - if (close <= lower) { + // BUY: Price crosses DOWN through lower band (reversal/bounce play) + if (prevClose > prevLower && close <= lower) { return { type: SIGNAL_TYPES.BUY, - strength: Math.min(50 + (lower - close) / close * 1000, 100), + strength: 70, value: close, - reasoning: `Price (${close.toFixed(2)}) at or below lower band (${lower.toFixed(2)}), bandwidth: ${bandwidth.toFixed(1)}%` + reasoning: `Price crossed DOWN through lower Bollinger Band` }; - } else if (close >= upper) { + } + // SELL: Price crosses UP through upper band (overextended play) + else if (prevClose < prevUpper && close >= upper) { return { type: SIGNAL_TYPES.SELL, - strength: Math.min(50 + (close - upper) / close * 1000, 100), + strength: 70, value: close, - reasoning: `Price (${close.toFixed(2)}) at or above upper band (${upper.toFixed(2)}), bandwidth: ${bandwidth.toFixed(1)}%` + reasoning: `Price crossed UP through upper Bollinger Band` }; - } else { - return null; } + + return null; } // Bollinger Bands Indicator class diff --git a/src/api/dashboard/static/js/indicators/hts.js b/src/api/dashboard/static/js/indicators/hts.js index 79f12ef..df15521 100644 --- a/src/api/dashboard/static/js/indicators/hts.js +++ b/src/api/dashboard/static/js/indicators/hts.js @@ -126,35 +126,41 @@ function getMA(type, candles, period, source = 'close') { } // Signal calculation for HTS -function calculateHTSSignal(indicator, lastCandle, prevCandle, values) { - const fastHigh = values?.fastHigh; - const fastLow = values?.fastLow; - const slowHigh = values?.slowHigh; +function calculateHTSSignal(indicator, lastCandle, prevCandle, values, prevValues) { const slowLow = values?.slowLow; + const slowHigh = values?.slowHigh; + const prevSlowLow = prevValues?.slowLow; + const prevSlowHigh = prevValues?.slowHigh; - if (!fastHigh || !fastLow || !slowHigh || !slowLow) { + if (!slowLow || !slowHigh || !prevSlowLow || !prevSlowHigh) { return null; } const close = lastCandle.close; + const prevClose = prevCandle?.close; - if (close > slowLow) { + if (prevClose === undefined) return null; + + // BUY: Price crosses UP through slow low + if (prevClose <= prevSlowLow && close > slowLow) { return { type: SIGNAL_TYPES.BUY, - strength: Math.min(60 + (close - slowLow) / slowLow * 500, 100), + strength: 85, value: close, - reasoning: `Price (${close.toFixed(2)}) is above slow low (${slowLow.toFixed(2)})` + reasoning: `Price crossed UP through slow low` }; - } else if (close < slowHigh) { + } + // SELL: Price crosses DOWN through slow high + else if (prevClose >= prevSlowHigh && close < slowHigh) { return { type: SIGNAL_TYPES.SELL, - strength: Math.min(60 + (slowHigh - close) / close * 500, 100), + strength: 85, value: close, - reasoning: `Price (${close.toFixed(2)}) is below slow high (${slowHigh.toFixed(2)})` + reasoning: `Price crossed DOWN through slow high` }; - } else { - return null; } + + return null; } // HTS Indicator class diff --git a/src/api/dashboard/static/js/indicators/hurst.js b/src/api/dashboard/static/js/indicators/hurst.js index b8cb4dc..7fe7c9a 100644 --- a/src/api/dashboard/static/js/indicators/hurst.js +++ b/src/api/dashboard/static/js/indicators/hurst.js @@ -63,26 +63,30 @@ function calculateHurstSignal(indicator, lastCandle, prevCandle, values, prevVal const prevClose = prevCandle?.close; const upper = values?.upper; const lower = values?.lower; + const prevUpper = prevValues?.upper; + const prevLower = prevValues?.lower; - if (!upper || !lower || prevClose === undefined) { + if (close === undefined || prevClose === undefined || !upper || !lower || !prevUpper || !prevLower) { return null; } - if (prevClose > lower && close < lower) { + // BUY: Price crosses DOWN through lower Hurst Band + if (prevClose > prevLower && close <= lower) { return { type: 'buy', strength: 75, value: close, - reasoning: `Price crossed down below lower Hurst Band (${lower.toFixed(2)}), expect bounce` + reasoning: `Price crossed DOWN through lower Hurst Band` }; } - if (prevClose > upper && close < upper) { + // SELL: Price crosses DOWN through upper Hurst Band (reversal from top) + if (prevClose > prevUpper && close <= upper) { return { type: 'sell', strength: 75, value: close, - reasoning: `Price crossed down below upper Hurst Band (${upper.toFixed(2)}), expect reversal` + reasoning: `Price crossed DOWN through upper Hurst Band` }; } diff --git a/src/api/dashboard/static/js/indicators/macd.js b/src/api/dashboard/static/js/indicators/macd.js index aa80fbc..71ff6ab 100644 --- a/src/api/dashboard/static/js/indicators/macd.js +++ b/src/api/dashboard/static/js/indicators/macd.js @@ -50,32 +50,37 @@ function calculateEMAInline(data, period) { } // Signal calculation for MACD -function calculateMACDSignal(indicator, lastCandle, prevCandle, values) { +function calculateMACDSignal(indicator, lastCandle, prevCandle, values, prevValues) { const macd = values?.macd; const signal = values?.signal; - const histogram = values?.histogram; + const prevMacd = prevValues?.macd; + const prevSignal = prevValues?.signal; - if (!macd || macd === null || !signal || signal === null) { + if (macd === undefined || macd === null || signal === undefined || signal === null || + prevMacd === undefined || prevMacd === null || prevSignal === undefined || prevSignal === null) { return null; } - let signalType, strength, reasoning; - - const prevCandleHistogram = prevCandle ? values?.histogram : null; - - if (macd > signal) { - signalType = SIGNAL_TYPES.BUY; - strength = Math.min(50 + histogram * 500, 100); - reasoning = `MACD (${macd.toFixed(2)}) is above Signal (${signal.toFixed(2)})`; - } else if (macd < signal) { - signalType = SIGNAL_TYPES.SELL; - strength = Math.min(50 + Math.abs(histogram) * 500, 100); - reasoning = `MACD (${macd.toFixed(2)}) is below Signal (${signal.toFixed(2)})`; - } else { - return null; + // BUY: MACD crosses UP through Signal line + if (prevMacd <= prevSignal && macd > signal) { + return { + type: SIGNAL_TYPES.BUY, + strength: 80, + value: macd, + reasoning: `MACD crossed UP through Signal line` + }; + } + // SELL: MACD crosses DOWN through Signal line + else if (prevMacd >= prevSignal && macd < signal) { + return { + type: SIGNAL_TYPES.SELL, + strength: 80, + value: macd, + reasoning: `MACD crossed DOWN through Signal line` + }; } - return { type: signalType, strength, value: macd, reasoning }; + return null; } // MACD Indicator class diff --git a/src/api/dashboard/static/js/indicators/moving_average.js b/src/api/dashboard/static/js/indicators/moving_average.js index ea89324..624ce69 100644 --- a/src/api/dashboard/static/js/indicators/moving_average.js +++ b/src/api/dashboard/static/js/indicators/moving_average.js @@ -114,31 +114,35 @@ function calculateVWMA(candles, period, source = 'close') { } // Signal calculation for Moving Average -function calculateMASignal(indicator, lastCandle, prevCandle, values) { +function calculateMASignal(indicator, lastCandle, prevCandle, values, prevValues) { const close = lastCandle.close; + const prevClose = prevCandle?.close; const ma = values?.ma; + const prevMa = prevValues?.ma; - if (!ma && ma !== 0) { - return null; - } + if (!ma && ma !== 0) return null; + if (prevClose === undefined || prevMa === undefined || prevMa === null) return null; - if (close > ma) { + // BUY: Price crosses UP through MA + if (prevClose <= prevMa && close > ma) { return { type: SIGNAL_TYPES.BUY, - strength: Math.min(60 + ((close - ma) / ma) * 500, 100), + strength: 80, value: close, - reasoning: `Price (${close.toFixed(2)}) is above MA (${ma.toFixed(2)})` + reasoning: `Price crossed UP through MA` }; - } else if (close < ma) { + } + // SELL: Price crosses DOWN through MA + else if (prevClose >= prevMa && close < ma) { return { type: SIGNAL_TYPES.SELL, - strength: Math.min(60 + ((ma - close) / ma) * 500, 100), + strength: 80, value: close, - reasoning: `Price (${close.toFixed(2)}) is below MA (${ma.toFixed(2)})` + reasoning: `Price crossed DOWN through MA` }; - } else { - return null; } + + return null; } // MA Indicator class diff --git a/src/api/dashboard/static/js/indicators/rsi.js b/src/api/dashboard/static/js/indicators/rsi.js index 3f42337..a67a1d3 100644 --- a/src/api/dashboard/static/js/indicators/rsi.js +++ b/src/api/dashboard/static/js/indicators/rsi.js @@ -38,42 +38,30 @@ function calculateRSISignal(indicator, lastCandle, prevCandle, values, prevValue const overbought = indicator.params?.overbought || 70; const oversold = indicator.params?.oversold || 30; - if (!rsi || rsi === null) { + if (rsi === undefined || rsi === null || prevRsi === undefined || prevRsi === null) { return null; } - let signalType, strength, reasoning; - - // BUY when RSI crosses UP through oversold band (bottom band) - // RSI was below oversold, now above oversold - if (prevRsi !== undefined && prevRsi !== null && prevRsi < oversold && rsi >= oversold) { - signalType = SIGNAL_TYPES.BUY; - strength = Math.min(50 + (rsi - oversold) * 2, 100); - reasoning = `RSI (${rsi.toFixed(2)}) crossed up through oversold level (${oversold})`; + // BUY when RSI crosses UP through oversold level + if (prevRsi < oversold && rsi >= oversold) { + return { + type: SIGNAL_TYPES.BUY, + strength: 75, + value: rsi, + reasoning: `RSI crossed UP through oversold level (${oversold})` + }; } - // SELL when RSI crosses DOWN through overbought band (top band) - // RSI was above overbought, now below overbought - else if (prevRsi !== undefined && prevRsi !== null && prevRsi > overbought && rsi <= overbought) { - signalType = SIGNAL_TYPES.SELL; - strength = Math.min(50 + (overbought - rsi) * 2, 100); - reasoning = `RSI (${rsi.toFixed(2)}) crossed down through overbought level (${overbought})`; - } - // When RSI is in oversold territory but no crossover - strong BUY - else if (rsi < oversold) { - signalType = SIGNAL_TYPES.BUY; - strength = Math.min(40 + (oversold - rsi) * 1.5, 80); - reasoning = `RSI (${rsi.toFixed(2)}) is oversold (<${oversold})`; - } - // When RSI is in overbought territory but no crossover - strong SELL - else if (rsi > overbought) { - signalType = SIGNAL_TYPES.SELL; - strength = Math.min(40 + (rsi - overbought) * 1.5, 80); - reasoning = `RSI (${rsi.toFixed(2)}) is overbought (>${overbought})`; - } else { - return null; + // SELL when RSI crosses DOWN through overbought level + else if (prevRsi > overbought && rsi <= overbought) { + return { + type: SIGNAL_TYPES.SELL, + strength: 75, + value: rsi, + reasoning: `RSI crossed DOWN through overbought level (${overbought})` + }; } - return { type: signalType, strength, value: rsi, reasoning }; + return null; } // RSI Indicator class diff --git a/src/api/dashboard/static/js/indicators/stoch.js b/src/api/dashboard/static/js/indicators/stoch.js index ca15fe1..81ad0ef 100644 --- a/src/api/dashboard/static/js/indicators/stoch.js +++ b/src/api/dashboard/static/js/indicators/stoch.js @@ -32,33 +32,38 @@ class BaseIndicator { } // Signal calculation for Stochastic -function calculateStochSignal(indicator, lastCandle, prevCandle, values) { +function calculateStochSignal(indicator, lastCandle, prevCandle, values, prevValues) { const k = values?.k; const d = values?.d; + const prevK = prevValues?.k; + const prevD = prevValues?.d; const overbought = indicator.params?.overbought || 80; const oversold = indicator.params?.oversold || 20; - if (!k || !d) { + if (k === undefined || d === undefined || prevK === undefined || prevD === undefined) { return null; } - if (k < oversold && d < oversold) { + // BUY: %K crosses UP through %D while both are oversold + if (prevK <= prevD && k > d && k < oversold) { return { type: SIGNAL_TYPES.BUY, - strength: Math.min(50 + (oversold - k) * 2, 100), + strength: 80, value: k, - reasoning: `Stochastic %K (${k.toFixed(2)}) and %D (${d.toFixed(2)}) oversold (<${oversold})` + reasoning: `Stochastic %K crossed UP through %D in oversold zone` }; - } else if (k > overbought && d > overbought) { + } + // SELL: %K crosses DOWN through %D while both are overbought + else if (prevK >= prevD && k < d && k > overbought) { return { type: SIGNAL_TYPES.SELL, - strength: Math.min(50 + (k - overbought) * 2, 100), + strength: 80, value: k, - reasoning: `Stochastic %K (${k.toFixed(2)}) and %D (${d.toFixed(2)}) overbought (>${overbought})` + reasoning: `Stochastic %K crossed DOWN through %D in overbought zone` }; - } else { - return null; } + + return null; } // Stochastic Oscillator Indicator class diff --git a/src/api/dashboard/static/js/ui/chart.js b/src/api/dashboard/static/js/ui/chart.js index 1c3defd..2f395c8 100644 --- a/src/api/dashboard/static/js/ui/chart.js +++ b/src/api/dashboard/static/js/ui/chart.js @@ -21,9 +21,20 @@ constructor() { this.indicatorSignals = []; this.summarySignal = null; this.lastCandleTimestamp = null; + this.simulationMarkers = []; this.init(); } + + setSimulationMarkers(markers) { + this.simulationMarkers = markers || []; + this.updateSignalMarkers(); + } + + clearSimulationMarkers() { + this.simulationMarkers = []; + this.updateSignalMarkers(); + } init() { this.createTimeframeButtons(); @@ -584,11 +595,18 @@ async loadSignals() { } } -updateSignalMarkers() { + updateSignalMarkers() { const candles = this.allData.get(this.currentInterval); if (!candles || candles.length === 0) return; - const markers = calculateSignalMarkers(candles); + let markers = calculateSignalMarkers(candles); + + // Merge simulation markers if present + if (this.simulationMarkers && this.simulationMarkers.length > 0) { + markers = [...markers, ...this.simulationMarkers]; + // Re-sort combined markers by time + markers.sort((a, b) => a.time - b.time); + } // If we have a marker controller, update markers through it if (this.markerController) { diff --git a/src/api/dashboard/static/js/ui/sidebar.js b/src/api/dashboard/static/js/ui/sidebar.js index 54434ac..c5f2b7c 100644 --- a/src/api/dashboard/static/js/ui/sidebar.js +++ b/src/api/dashboard/static/js/ui/sidebar.js @@ -54,6 +54,12 @@ export function switchTab(tabId) { window.drawIndicatorsOnChart(); } }, 50); + } else if (tabId === 'strategy') { + setTimeout(() => { + if (window.renderStrategyPanel) { + window.renderStrategyPanel(); + } + }, 50); } } diff --git a/src/api/dashboard/static/js/ui/strategy-panel.js b/src/api/dashboard/static/js/ui/strategy-panel.js new file mode 100644 index 0000000..8c5a6d1 --- /dev/null +++ b/src/api/dashboard/static/js/ui/strategy-panel.js @@ -0,0 +1,422 @@ +import { IndicatorRegistry, getSignalFunction } from '../indicators/index.js'; + +let activeIndicators = []; +let simulationResults = null; +let equitySeries = null; +let equityChart = null; + +export function initStrategyPanel() { + window.renderStrategyPanel = renderStrategyPanel; + renderStrategyPanel(); + + // Listen for indicator changes to update the signal selection list + const originalAddIndicator = window.addIndicator; + window.addIndicator = function(...args) { + const res = originalAddIndicator.apply(this, args); + setTimeout(renderStrategyPanel, 100); + return res; + }; + + const originalRemoveIndicator = window.removeIndicatorById; + window.removeIndicatorById = function(...args) { + const res = originalRemoveIndicator.apply(this, args); + setTimeout(renderStrategyPanel, 100); + return res; + }; +} + +export function renderStrategyPanel() { + const container = document.getElementById('strategyPanel'); + if (!container) return; + + activeIndicators = window.getActiveIndicators?.() || []; + + container.innerHTML = ` + + + + `; + + document.getElementById('runSimulationBtn').addEventListener('click', runSimulation); +} + +function renderIndicatorChecklist(prefix) { + if (activeIndicators.length === 0) { + return '