169 lines
5.6 KiB
C#
169 lines
5.6 KiB
C#
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<LightSignal> Signals { get; set; }
|
|
|
|
public SuperTrendIndicatorBase(string name, int period, double multiplier) : base(name, IndicatorType.SuperTrend)
|
|
{
|
|
Signals = new List<LightSignal>();
|
|
Period = period;
|
|
Multiplier = multiplier;
|
|
MinimumHistory = 100 + Period.Value;
|
|
}
|
|
|
|
public override List<LightSignal> Run(HashSet<Candle> 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).OrderBy(s => s.Date).ToList();
|
|
}
|
|
catch (RuleException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs the indicator using pre-calculated SuperTrend values for performance optimization.
|
|
/// </summary>
|
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
|
{
|
|
if (candles.Count <= MinimumHistory)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Use pre-calculated SuperTrend values if available
|
|
List<SuperTrendResult> 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))
|
|
.OrderBy(s => 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).OrderBy(s => s.Date).ToList();
|
|
}
|
|
catch (RuleException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes SuperTrend signals based on price position relative to SuperTrend line.
|
|
/// This method is shared between the regular Run() and optimized Run() methods.
|
|
/// </summary>
|
|
/// <param name="superTrend">List of SuperTrend calculation results</param>
|
|
/// <param name="candles">Candles to process</param>
|
|
private void ProcessSuperTrendSignals(List<SuperTrendResult> superTrend, HashSet<Candle> 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<Candle> candles)
|
|
{
|
|
return new IndicatorsResultBase()
|
|
{
|
|
SuperTrend = candles.GetSuperTrend(Period.Value, Multiplier.Value).Where(s => s.SuperTrend.HasValue)
|
|
.ToList()
|
|
};
|
|
}
|
|
|
|
private List<CandleSuperTrend> MapSuperTrendToCandle(IEnumerable<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;
|
|
}
|
|
|
|
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; }
|
|
}
|
|
} |