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 ChandelierExitIndicatorBase : IndicatorBase { public List Signals { get; set; } public ChandelierExitIndicatorBase(string name, int period, double multiplier) : base(name, IndicatorType.ChandelierExit) { Signals = new List(); Period = period; Multiplier = multiplier; MinimumHistory = 1 + Period.Value; } public override List Run(HashSet candles) { if (candles.Count <= MinimumHistory) { return null; } try { ProcessChandelierSignals(candles); return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); } catch (RuleException) { return null; } } /// /// Runs the indicator using pre-calculated Chandelier values for performance optimization. /// public override List Run(HashSet candles, IndicatorsResultBase preCalculatedValues) { if (candles.Count <= MinimumHistory) { return null; } try { // Use pre-calculated Chandelier values if available List chandelierLong = null; List chandelierShort = null; if (preCalculatedValues?.ChandelierLong != null && preCalculatedValues.ChandelierLong.Any() && preCalculatedValues?.ChandelierShort != null && preCalculatedValues.ChandelierShort.Any()) { // Filter pre-calculated Chandelier values to match the candles we're processing chandelierLong = preCalculatedValues.ChandelierLong .Where(c => c.ChandelierExit.HasValue && candles.Any(candle => candle.Date == c.Date)) .OrderBy(c => c.Date) .ToList(); chandelierShort = preCalculatedValues.ChandelierShort .Where(c => c.ChandelierExit.HasValue && candles.Any(candle => candle.Date == c.Date)) .OrderBy(c => c.Date) .ToList(); } // If no pre-calculated values or they don't match, fall back to regular calculation if (chandelierLong == null || !chandelierLong.Any() || chandelierShort == null || !chandelierShort.Any()) { return Run(candles); } ProcessChandelierSignalsWithPreCalculated(chandelierLong, chandelierShort, candles); return Signals.Where(s => s.Confidence != Confidence.None).OrderBy(s => s.Date).ToList(); } catch (RuleException) { return null; } } /// /// Processes Chandelier signals for both Long and Short types. /// This method is shared between the regular Run() and optimized Run() methods. /// /// Candles to process private void ProcessChandelierSignals(HashSet candles) { GetSignals(ChandelierType.Long, candles); GetSignals(ChandelierType.Short, candles); } /// /// Processes Chandelier signals using pre-calculated values. /// /// Pre-calculated Long Chandelier values /// Pre-calculated Short Chandelier values /// Candles to process private void ProcessChandelierSignalsWithPreCalculated( List chandelierLong, List chandelierShort, HashSet candles) { GetSignalsWithPreCalculated(ChandelierType.Long, chandelierLong, candles); GetSignalsWithPreCalculated(ChandelierType.Short, chandelierShort, candles); } public override IndicatorsResultBase GetIndicatorValues(HashSet candles) { return new IndicatorsResultBase() { ChandelierLong = candles.GetChandelier(Period.Value, Multiplier.Value, ChandelierType.Long).ToList(), ChandelierShort = candles.GetChandelier(Period.Value, Multiplier.Value, ChandelierType.Short).ToList() }; } private void GetSignals(ChandelierType chandelierType, HashSet candles) { var chandelier = candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType) .Where(s => s.ChandelierExit.HasValue).ToList(); ProcessChandelierSignalsForType(chandelier, chandelierType, candles); } private void GetSignalsWithPreCalculated(ChandelierType chandelierType, List chandelier, HashSet candles) { ProcessChandelierSignalsForType(chandelier, chandelierType, candles); } /// /// Processes Chandelier signals for a specific type (Long or Short). /// This method is shared between regular and optimized signal processing. /// /// Chandelier calculation results /// Type of Chandelier (Long or Short) /// Candles to process private void ProcessChandelierSignalsForType(List chandelier, ChandelierType chandelierType, HashSet candles) { var chandelierCandle = MapChandelierToCandle(chandelier, candles.TakeLast(MinimumHistory)); if (chandelierCandle.Count == 0) return; var previousCandle = chandelierCandle[0]; foreach (var currentCandle in chandelierCandle.Skip(1)) { // Short if (currentCandle.Close < previousCandle.ChandelierExit && previousCandle.Close > previousCandle.ChandelierExit && currentCandle.Close < previousCandle.Open && chandelierType == ChandelierType.Short) { AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium); } // Long if (currentCandle.Close > previousCandle.ChandelierExit && previousCandle.Close < previousCandle.ChandelierExit && currentCandle.Close > currentCandle.Open && chandelierType == ChandelierType.Long) { AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium); } previousCandle = currentCandle; } } private List MapChandelierToCandle(List superTrend, IEnumerable candles) { var superTrends = new List(); foreach (var candle in candles) { var currentChandelier = superTrend.Find(candle.Date); if (currentChandelier != null) { superTrends.Add(new CandleChandelier() { Close = candle.Close, Open = candle.Open, Date = candle.Date, Ticker = candle.Ticker, Exchange = candle.Exchange, ChandelierExit = (decimal)currentChandelier.ChandelierExit.Value, }); } } return superTrends; } private void AddSignal(CandleChandelier 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 CandleChandelier : Candle { public decimal ChandelierExit { get; internal set; } } }