using Managing.Core; using Managing.Domain.Candles; using Managing.Domain.Shared.Rules; using Managing.Domain.Strategies.Base; using Skender.Stock.Indicators; using static Managing.Common.Enums; namespace Managing.Domain.Strategies; public class SuperTrendCrossEma : Strategy { public List Signals { get; set; } public SuperTrendCrossEma(string name, int period, double multiplier) : base(name, StrategyType.SuperTrendCrossEma) { Signals = new List(); Period = period; Multiplier = multiplier; MinimumHistory = 100 + Period.Value; } public override List Run() { // Validate sufficient historical data for all indicators const int emaPeriod = 50; const int adxPeriod = 14; // Standard ADX period const int adxThreshold = 25; // Minimum ADX level to confirm a trend int minimumRequiredHistory = Math.Max(Math.Max(emaPeriod, adxPeriod), Period.Value * 2); // Ensure enough data if (Candles.Count < minimumRequiredHistory) { return null; } try { // 1. Calculate indicators var superTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value) .Where(s => s.SuperTrend.HasValue) .ToList(); var ema50 = Candles.GetEma(emaPeriod) .Where(e => e.Ema.HasValue) .ToList(); var adxResults = Candles.GetAdx(adxPeriod) .Where(a => a.Adx.HasValue && a.Pdi.HasValue && a.Mdi.HasValue) // Ensure all values exist .ToList(); // 2. Create merged dataset with price + indicators var superTrendCandles = MapSuperTrendToCandle(superTrend, Candles.TakeLast(minimumRequiredHistory)); if (superTrendCandles.Count == 0) return null; // 3. Add EMA50 and ADX values to the CandleSuperTrend objects foreach (var candle in superTrendCandles) { var emaValue = ema50.Find(e => e.Date == candle.Date)?.Ema; var adxValue = adxResults.Find(a => a.Date == candle.Date); if (emaValue.HasValue) candle.Ema50 = emaValue.Value; if (adxValue != null) { candle.Adx = (decimal)adxValue.Adx.Value; candle.Pdi = (decimal)adxValue.Pdi.Value; candle.Mdi = (decimal)adxValue.Mdi.Value; } } // 4. Signal detection logic with ADX filter for (int i = 1; i < superTrendCandles.Count; i++) { var current = superTrendCandles[i]; var previous = superTrendCandles[i - 1]; // Convert SuperTrend to double for comparison double currentSuperTrend = (double)current.SuperTrend; double previousSuperTrend = (double)previous.SuperTrend; // Ensure ADX data exists if (current.Adx < adxThreshold) // Only trade when ADX confirms trend strength continue; /* LONG SIGNAL CONDITIONS: * 1. SuperTrend crosses above EMA50 * 2. Price > SuperTrend and > EMA50 * 3. Previous state shows SuperTrend < EMA50 * 4. ADX > threshold and +DI > -DI (bullish momentum) */ bool longCross = currentSuperTrend > current.Ema50 && previousSuperTrend < previous.Ema50; bool longPricePosition = current.Close > (decimal)currentSuperTrend && current.Close > (decimal)current.Ema50; bool adxBullish = current.Pdi > current.Mdi; // Bullish momentum confirmation if (longCross && longPricePosition && adxBullish) { AddSignal(current, TradeDirection.Long, Confidence.Medium); } /* SHORT SIGNAL CONDITIONS: * 1. SuperTrend crosses below EMA50 * 2. Price < SuperTrend and < EMA50 * 3. Previous state shows SuperTrend > EMA50 * 4. ADX > threshold and -DI > +DI (bearish momentum) */ bool shortCross = currentSuperTrend < current.Ema50 && previousSuperTrend > previous.Ema50; bool shortPricePosition = current.Close < (decimal)currentSuperTrend && current.Close < (decimal)current.Ema50; bool adxBearish = current.Mdi > current.Pdi; // Bearish momentum confirmation if (shortCross && shortPricePosition && adxBearish) { AddSignal(current, TradeDirection.Short, Confidence.Medium); } } return Signals.Where(s => s.Confidence != Confidence.None) .OrderBy(s => s.Date) .ToList(); } catch (RuleException) { return null; } } private List MapSuperTrendToCandle(List 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; } public override StrategiesResultBase GetStrategyValues() { return new StrategiesResultBase() { SuperTrend = Candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue) .ToList() }; } private void AddSignal(CandleSuperTrend candleSignal, TradeDirection direction, Confidence confidence) { var signal = new Signal(MiscExtensions.ParseEnum(candleSignal.Ticker), direction, confidence, candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType); 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; } public double Ema50 { get; set; } public decimal Adx { get; set; } // ADX value public decimal Pdi { get; set; } // Positive Directional Indicator (+DI) public decimal Mdi { get; set; } // Negative Directional Indicator (-DI) } }