using Managing.Core; using Managing.Domain.Candles; using Managing.Domain.Indicators; using Managing.Domain.Shared.Rules; using Managing.Domain.Strategies.Base; using Skender.Stock.Indicators; using static Managing.Common.Enums; namespace Managing.Domain.Strategies.Signals; public class MacdCrossIndicatorBase : IndicatorBase { public List Signals { get; set; } public MacdCrossIndicatorBase(string name, int fastPeriods, int slowPeriods, int signalPeriods) : base(name, IndicatorType.MacdCross) { Signals = new List(); FastPeriods = fastPeriods; SlowPeriods = slowPeriods; SignalPeriods = signalPeriods; } public override List Run(IReadOnlyList candles) { if (candles.Count <= 2 * (SlowPeriods + SignalPeriods)) { return null; } try { var macd = candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList(); if (macd.Count == 0) return null; ProcessMacdSignals(macd, candles); return Signals.Where(s => s.Confidence != Confidence.None).ToList(); } catch (RuleException) { return null; } } /// /// Runs the indicator using pre-calculated MACD values for performance optimization. /// public override List Run(IReadOnlyList candles, IndicatorsResultBase preCalculatedValues) { if (candles.Count <= 2 * (SlowPeriods + SignalPeriods)) { return null; } try { // Use pre-calculated MACD values if available List macd = null; if (preCalculatedValues?.Macd != null && preCalculatedValues.Macd.Any()) { // Filter pre-calculated MACD values to match the candles we're processing macd = preCalculatedValues.Macd .Where(m => candles.Any(c => c.Date == m.Date)) .ToList(); } // If no pre-calculated values or they don't match, fall back to regular calculation if (macd == null || !macd.Any()) { return Run(candles); } ProcessMacdSignals(macd, candles); return Signals.Where(s => s.Confidence != Confidence.None).ToList(); } catch (RuleException) { return null; } } /// /// Processes MACD signals based on MACD line crossing the Signal line. /// This method is shared between the regular Run() and optimized Run() methods. /// /// List of MACD calculation results /// Candles to process private void ProcessMacdSignals(List macd, IReadOnlyList candles) { var macdCandle = MapMacdToCandle(macd, candles.TakeLast(SignalPeriods.Value)); if (macdCandle.Count == 0) return; var previousCandle = macdCandle[0]; foreach (var currentCandle in macdCandle.Skip(1)) { // Check for MACD line crossing below Signal line (bearish cross) if (previousCandle.Macd > previousCandle.Signal && currentCandle.Macd < currentCandle.Signal) { AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium); } // Check for MACD line crossing above Signal line (bullish cross) if (previousCandle.Macd < previousCandle.Signal && currentCandle.Macd > currentCandle.Signal) { AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium); } previousCandle = currentCandle; } } public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList candles) { return new IndicatorsResultBase() { Macd = candles.GetMacd(FastPeriods.Value, SlowPeriods.Value, SignalPeriods.Value).ToList() }; } private List MapMacdToCandle(List macd, IEnumerable candles) { var macdList = new List(); foreach (var candle in candles) { var currentMacd = macd.Find(candle.Date); if (currentMacd != null) { macdList.Add(new CandleMacd() { Close = candle.Close, Open = candle.Open, Date = candle.Date, Ticker = candle.Ticker, Exchange = candle.Exchange, Macd = currentMacd.Macd.Value, Histogram = currentMacd.Histogram.Value, Signal = currentMacd.Signal.Value }); } } return macdList; } private void AddSignal(CandleMacd candleSignal, TradeDirection direction, Confidence confidence) { var signal = new LightSignal(candleSignal.Ticker, direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name); if (!Signals.Any(s => s.Identifier == signal.Identifier)) { Signals.AddItem(signal); } } private class CandleMacd : Candle { public double Macd { get; set; } public double Signal { get; set; } public double Histogram { get; set; } } }