192
src/Managing.Domain/Strategies/SuperTrendCrossEma.cs
Normal file
192
src/Managing.Domain/Strategies/SuperTrendCrossEma.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
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<Signal> Signals { get; set; }
|
||||
|
||||
public SuperTrendCrossEma(string name, int period, double multiplier) : base(name, StrategyType.SuperTrendCrossEma)
|
||||
{
|
||||
Signals = new List<Signal>();
|
||||
Period = period;
|
||||
Multiplier = multiplier;
|
||||
MinimumHistory = 100 + Period.Value;
|
||||
}
|
||||
|
||||
public override List<Signal> 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<CandleSuperTrend> MapSuperTrendToCandle(List<SuperTrendResult> superTrend, IEnumerable<Candle> candles)
|
||||
{
|
||||
var superTrends = new List<CandleSuperTrend>();
|
||||
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<Ticker>(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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user