// 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 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; }