import { IndicatorRegistry } from '../indicators/index.js'; export function calculateSignalMarkers(candles) { const activeIndicators = window.getActiveIndicators?.() || []; const markers = []; if (!candles || candles.length < 2) { return markers; } for (const indicator of activeIndicators) { if (indicator.params.showMarkers === false || indicator.params.showMarkers === 'false') { continue; } console.log('[SignalMarkers] Processing indicator:', indicator.type, 'showMarkers:', indicator.params.showMarkers); // Use cache if available let results = indicator.cachedResults; if (!results || !Array.isArray(results) || results.length !== candles.length) { const IndicatorClass = IndicatorRegistry[indicator.type]; if (!IndicatorClass) { continue; } const instance = new IndicatorClass(indicator); results = instance.calculate(candles); } if (!results || !Array.isArray(results) || results.length === 0) { continue; } const indicatorMarkers = findCrossoverMarkers(indicator, candles, results); markers.push(...indicatorMarkers); } markers.sort((a, b) => a.time - b.time); return markers; } function findCrossoverMarkers(indicator, candles, results) { const markers = []; const overbought = indicator.params?.overbought || 70; const oversold = indicator.params?.oversold || 30; const indicatorType = indicator.type; const buyColor = indicator.params?.markerBuyColor || '#26a69a'; const sellColor = indicator.params?.markerSellColor || '#ef5350'; const buyShape = indicator.params?.markerBuyShape || 'arrowUp'; const sellShape = indicator.params?.markerSellShape || 'arrowDown'; const buyCustom = indicator.params?.markerBuyCustom || '◭'; const sellCustom = indicator.params?.markerSellCustom || '▼'; for (let i = 1; i < results.length; i++) { const candle = candles[i]; const prevCandle = candles[i - 1]; const result = results[i]; const prevResult = results[i - 1]; if (!result || !prevResult) continue; if (indicatorType === 'rsi' || indicatorType === 'stoch') { const rsi = result.rsi ?? result; const prevRsi = prevResult.rsi ?? prevResult; if (rsi === undefined || prevRsi === undefined) continue; if (prevRsi > overbought && rsi <= overbought) { markers.push({ time: candle.time, position: 'aboveBar', color: sellColor, shape: sellShape === 'custom' ? '' : sellShape, text: sellShape === 'custom' ? sellCustom : '' }); } if (prevRsi < oversold && rsi >= oversold) { markers.push({ time: candle.time, position: 'belowBar', color: buyColor, shape: buyShape === 'custom' ? '' : buyShape, text: buyShape === 'custom' ? buyCustom : '' }); } } else if (indicatorType === 'macd') { const macd = result.macd ?? result.MACD; const signal = result.signal ?? result.signalLine; const prevMacd = prevResult.macd ?? prevResult.MACD; const prevSignal = prevResult.signal ?? prevResult.signalLine; if (macd === undefined || signal === undefined || prevMacd === undefined || prevSignal === undefined) continue; const macdAbovePrev = prevMacd > prevSignal; const macdAboveNow = macd > signal; if (macdAbovePrev && !macdAboveNow) { markers.push({ time: candle.time, position: 'aboveBar', color: sellColor, shape: sellShape === 'custom' ? '' : sellShape, text: sellShape === 'custom' ? sellCustom : '' }); } if (!macdAbovePrev && macdAboveNow) { markers.push({ time: candle.time, position: 'belowBar', color: buyColor, shape: buyShape === 'custom' ? '' : buyShape, text: buyShape === 'custom' ? buyCustom : '' }); } } else if (indicatorType === 'bb') { const upper = result.upper ?? result.upperBand; const lower = result.lower ?? result.lowerBand; if (upper === undefined || lower === undefined) continue; const priceAboveUpperPrev = prevCandle.close > (prevResult.upper ?? prevResult.upperBand); const priceAboveUpperNow = candle.close > upper; if (priceAboveUpperPrev && !priceAboveUpperNow) { markers.push({ time: candle.time, position: 'aboveBar', color: sellColor, shape: sellShape === 'custom' ? '' : sellShape, text: sellShape === 'custom' ? sellCustom : '' }); } if (!priceAboveUpperPrev && priceAboveUpperNow) { markers.push({ time: candle.time, position: 'belowBar', color: buyColor, shape: buyShape === 'custom' ? '' : buyShape, text: buyShape === 'custom' ? buyCustom : '' }); } const priceBelowLowerPrev = prevCandle.close < (prevResult.lower ?? prevResult.lowerBand); const priceBelowLowerNow = candle.close < lower; if (priceBelowLowerPrev && !priceBelowLowerNow) { markers.push({ time: candle.time, position: 'belowBar', color: buyColor, shape: buyShape === 'custom' ? '' : buyShape, text: buyShape === 'custom' ? buyCustom : '' }); } if (!priceBelowLowerPrev && priceBelowLowerNow) { markers.push({ time: candle.time, position: 'aboveBar', color: sellColor, shape: sellShape === 'custom' ? '' : sellShape, text: sellShape === 'custom' ? sellCustom : '' }); } } else if (indicatorType === 'hurst') { const upper = result.upper; const lower = result.lower; const prevUpper = prevResult?.upper; const prevLower = prevResult?.lower; if (upper === undefined || lower === undefined || prevUpper === undefined || prevLower === undefined) continue; // BUY: price crosses down below lower band (was above, now below) if (prevCandle.close > prevLower && candle.close < lower) { markers.push({ time: candle.time, position: 'belowBar', color: buyColor, shape: buyShape === 'custom' ? '' : buyShape, text: buyShape === 'custom' ? buyCustom : '' }); } // SELL: price crosses down below upper band (was above, now below) if (prevCandle.close > prevUpper && candle.close < upper) { markers.push({ time: candle.time, position: 'aboveBar', color: sellColor, shape: sellShape === 'custom' ? '' : sellShape, text: sellShape === 'custom' ? sellCustom : '' }); } } else { const ma = result.ma ?? result; const prevMa = prevResult.ma ?? prevResult; if (ma === undefined || prevMa === undefined) continue; const priceAbovePrev = prevCandle.close > prevMa; const priceAboveNow = candle.close > ma; if (priceAbovePrev && !priceAboveNow) { markers.push({ time: candle.time, position: 'aboveBar', color: sellColor, shape: sellShape === 'custom' ? '' : sellShape, text: sellShape === 'custom' ? sellCustom : '' }); } if (!priceAbovePrev && priceAboveNow) { markers.push({ time: candle.time, position: 'belowBar', color: buyColor, shape: buyShape === 'custom' ? '' : buyShape, text: buyShape === 'custom' ? buyCustom : '' }); } } } return markers; }