// Signal Calculator - orchestrates signal calculation using indicator-specific functions // Signal calculation logic is now in each indicator file import { IndicatorRegistry, getSignalFunction } from '../indicators/index.js'; /** * Calculate signal for a single indicator using its signal function * @param {Object} indicator - Indicator object with type, params, etc. * @param {Array} candles - Recent candle data * @param {Object} indicatorValues - Computed indicator values for last candle * @returns {Object} Signal object with type, strength, value, reasoning */ function calculateIndicatorSignal(indicator, candles, indicatorValues) { const signalFunction = getSignalFunction(indicator.type); if (!signalFunction) { console.warn('[Signals] No signal function for indicator type:', indicator.type); return null; } const lastCandle = candles[candles.length - 1]; const prevCandle = candles[candles.length - 2]; return signalFunction(indicator, lastCandle, prevCandle, indicatorValues); } /** * Calculate aggregate summary signal from all indicators */ export function calculateSummarySignal(signals) { console.log('[calculateSummarySignal] Input signals:', signals?.length); if (!signals || signals.length === 0) { return { signal: 'hold', strength: 0, reasoning: 'No active indicators', buyCount: 0, sellCount: 0, holdCount: 0 }; } const buySignals = signals.filter(s => s.signal === 'buy'); const sellSignals = signals.filter(s => s.signal === 'sell'); const holdSignals = signals.filter(s => s.signal === 'hold'); const buyCount = buySignals.length; const sellCount = sellSignals.length; const holdCount = holdSignals.length; const total = signals.length; console.log('[calculateSummarySignal] BUY:', buyCount, 'SELL:', sellCount, 'HOLD:', holdCount); const buyWeight = buySignals.reduce((sum, s) => sum + (s.strength || 0), 0); const sellWeight = sellSignals.reduce((sum, s) => sum + (s.strength || 0), 0); let summarySignal, strength, reasoning; if (buyCount > sellCount && buyCount > holdCount) { summarySignal = 'buy'; const avgBuyStrength = buyWeight / buyCount; strength = Math.round(avgBuyStrength * (buyCount / total)); reasoning = `${buyCount} buy signals, ${sellCount} sell, ${holdCount} hold`; } else if (sellCount > buyCount && sellCount > holdCount) { summarySignal = 'sell'; const avgSellStrength = sellWeight / sellCount; strength = Math.round(avgSellStrength * (sellCount / total)); reasoning = `${sellCount} sell signals, ${buyCount} buy, ${holdCount} hold`; } else { summarySignal = 'hold'; strength = 30; reasoning = `Mixed signals: ${buyCount} buy, ${sellCount} sell, ${holdCount} hold`; } const result = { signal: summarySignal, strength: Math.min(Math.max(strength, 0), 100), reasoning, buyCount, sellCount, holdCount, color: summarySignal === 'buy' ? '#26a69a' : summarySignal === 'sell' ? '#ef5350' : '#787b86' }; console.log('[calculateSummarySignal] Result:', result); return result; } /** * Calculate signals for all active indicators * @returns {Array} Array of indicator signals */ export function calculateAllIndicatorSignals() { const activeIndicators = window.getActiveIndicators?.() || []; const candles = window.dashboard?.allData?.get(window.dashboard?.currentInterval); console.log('[Signals] ========== calculateAllIndicatorSignals START =========='); console.log('[Signals] Active indicators:', activeIndicators.length, 'Candles:', candles?.length || 0); if (!candles || candles.length < 2) { console.log('[Signals] Insufficient candles available:', candles?.length || 0); return []; } if (!activeIndicators || activeIndicators.length === 0) { console.log('[Signals] No active indicators'); return []; } const signals = []; for (const indicator of activeIndicators) { const IndicatorClass = IndicatorRegistry[indicator.type]; if (!IndicatorClass) { console.log('[Signals] No class for indicator type:', indicator.type); continue; } // Use cached results if available, otherwise calculate let results = indicator.cachedResults; let meta = indicator.cachedMeta; if (!results || !meta || results.length !== candles.length) { const instance = new IndicatorClass(indicator); meta = instance.getMetadata(); results = instance.calculate(candles); indicator.cachedResults = results; indicator.cachedMeta = meta; } if (!results || results.length === 0) { console.log('[Signals] No results for indicator:', indicator.type); continue; } const lastResult = results[results.length - 1]; if (lastResult === null || lastResult === undefined) { console.log('[Signals] No valid last result for indicator:', indicator.type); continue; } let values; if (typeof lastResult === 'object' && lastResult !== null && !Array.isArray(lastResult)) { values = lastResult; } else if (typeof lastResult === 'number') { values = { ma: lastResult }; } else { console.log('[Signals] Unexpected result type for', indicator.type, ':', typeof lastResult); continue; } const signal = calculateIndicatorSignal(indicator, candles, values); let currentSignal = signal; let lastSignalDate = indicator.lastSignalTimestamp || null; let lastSignalType = indicator.lastSignalType || null; if (!currentSignal || !currentSignal.type) { console.log('[Signals] No valid signal for', indicator.type, '- Using last signal if available'); if (lastSignalType && lastSignalDate) { currentSignal = { type: lastSignalType, strength: 50, value: candles[candles.length - 1]?.close, reasoning: `No crossover (price equals MA)` }; } else { console.log('[Signals] No previous signal available - Skipping'); continue; } } else { const currentCandleTimestamp = candles[candles.length - 1].time; if (currentSignal.type !== lastSignalType || !lastSignalType) { console.log('[Signals] Signal changed for', indicator.type, ':', lastSignalType, '->', currentSignal.type); lastSignalDate = currentCandleTimestamp; lastSignalType = currentSignal.type; indicator.lastSignalTimestamp = lastSignalDate; indicator.lastSignalType = lastSignalType; } } signals.push({ id: indicator.id, name: meta?.name || indicator.type, label: indicator.type?.toUpperCase(), params: indicator.params && typeof indicator.params === 'object' ? Object.entries(indicator.params) .filter(([k, v]) => !k.startsWith('_') && v !== undefined && v !== null) .map(([k, v]) => `${k}=${v}`) .join(', ') : null, type: indicator.type, signal: currentSignal.type, strength: Math.round(currentSignal.strength), value: currentSignal.value, reasoning: currentSignal.reasoning, color: currentSignal.type === 'buy' ? '#26a69a' : currentSignal.type === 'sell' ? '#ef5350' : '#787b86', lastSignalDate: lastSignalDate }); } console.log('[Signals] ========== calculateAllIndicatorSignals END =========='); console.log('[Signals] Total signals calculated:', signals.length); return signals; }