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 SuperTrendIndicatorBase : IndicatorBase { public List Signals { get; set; } public SuperTrendIndicatorBase(string name, int period, double multiplier) : base(name, IndicatorType.SuperTrend) { Signals = new List(); Period = period; Multiplier = multiplier; MinimumHistory = 100 + Period.Value; } public override List Run(HashSet candles) { if (candles.Count <= MinimumHistory) { return null; } try { var superTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value) .Where(s => s.SuperTrend.HasValue) .ToList(); if (superTrend.Count == 0) return null; ProcessSuperTrendSignals(superTrend, candles); return Signals.Where(s => s.Confidence != Confidence.None).ToList(); } catch (RuleException) { return null; } } /// /// Runs the indicator using pre-calculated SuperTrend values for performance optimization. /// public override List Run(HashSet candles, IndicatorsResultBase preCalculatedValues) { if (candles.Count <= MinimumHistory) { return null; } try { // Use pre-calculated SuperTrend values if available List superTrend = null; if (preCalculatedValues?.SuperTrend != null && preCalculatedValues.SuperTrend.Any()) { // Filter pre-calculated SuperTrend values to match the candles we're processing superTrend = preCalculatedValues.SuperTrend .Where(s => s.SuperTrend.HasValue && candles.Any(c => c.Date == s.Date)) .ToList(); } // If no pre-calculated values or they don't match, fall back to regular calculation if (superTrend == null || !superTrend.Any()) { return Run(candles); } ProcessSuperTrendSignals(superTrend, candles); return Signals.Where(s => s.Confidence != Confidence.None).ToList(); } catch (RuleException) { return null; } } /// /// Processes SuperTrend signals based on price position relative to SuperTrend line. /// This method is shared between the regular Run() and optimized Run() methods. /// /// List of SuperTrend calculation results /// Candles to process private void ProcessSuperTrendSignals(List superTrend, HashSet candles) { var superTrendCandle = MapSuperTrendToCandle(superTrend, candles.TakeLast(MinimumHistory)); if (superTrendCandle.Count == 0) return; var previousCandle = superTrendCandle[0]; foreach (var currentCandle in superTrendCandle.Skip(1)) { if (currentCandle.SuperTrend < currentCandle.Close) { AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium); } else if (currentCandle.SuperTrend > currentCandle.Close) { AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium); } previousCandle = currentCandle; } } public override IndicatorsResultBase GetIndicatorValues(HashSet candles) { return new IndicatorsResultBase() { SuperTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue) .ToList() }; } private List MapSuperTrendToCandle(IEnumerable superTrend, IEnumerable candles) { var superTrends = new List(); foreach (var candle in candles) { var currentSuperTrend = superTrend.Find(candle.Date); if (currentSuperTrend != null) { superTrends.Add(new CandleSuperTrend() { Close = candle.Close, Open = candle.Open, Date = candle.Date, Ticker = candle.Ticker, Exchange = candle.Exchange, SuperTrend = currentSuperTrend.SuperTrend.Value, LowerBand = currentSuperTrend.LowerBand, UpperBand = currentSuperTrend.UpperBand, }); } } return superTrends; } private void AddSignal(CandleSuperTrend 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 CandleSuperTrend : Candle { public decimal SuperTrend { get; internal set; } public decimal? LowerBand { get; internal set; } public decimal? UpperBand { get; internal set; } } }