Add StochasticCross indicator support in GeneticService and related classes
- Introduced StochasticCross indicator type in GeneticService with configuration settings for stochPeriods, signalPeriods, smoothPeriods, kFactor, and dFactor. - Updated IIndicator interface and IndicatorBase class to include KFactor and DFactor properties. - Enhanced LightIndicator class to support new properties and ensure proper conversion back to full Indicator. - Modified ScenarioHelpers to handle StochasticCross indicator creation and validation, ensuring default values and error handling for kFactor and dFactor.
This commit is contained in:
@@ -84,6 +84,14 @@ public class GeneticService : IGeneticService
|
|||||||
["signalPeriods"] = 9.0,
|
["signalPeriods"] = 9.0,
|
||||||
["smoothPeriods"] = 3.0
|
["smoothPeriods"] = 3.0
|
||||||
},
|
},
|
||||||
|
[IndicatorType.StochasticCross] = new()
|
||||||
|
{
|
||||||
|
["stochPeriods"] = 14.0,
|
||||||
|
["signalPeriods"] = 3.0,
|
||||||
|
["smoothPeriods"] = 3.0,
|
||||||
|
["kFactor"] = 3.0,
|
||||||
|
["dFactor"] = 2.0
|
||||||
|
},
|
||||||
[IndicatorType.Stc] = new()
|
[IndicatorType.Stc] = new()
|
||||||
{
|
{
|
||||||
["cyclePeriods"] = 10.0,
|
["cyclePeriods"] = 10.0,
|
||||||
@@ -159,6 +167,14 @@ public class GeneticService : IGeneticService
|
|||||||
["signalPeriods"] = (3.0, 15.0),
|
["signalPeriods"] = (3.0, 15.0),
|
||||||
["smoothPeriods"] = (1.0, 10.0)
|
["smoothPeriods"] = (1.0, 10.0)
|
||||||
},
|
},
|
||||||
|
[IndicatorType.StochasticCross] = new()
|
||||||
|
{
|
||||||
|
["stochPeriods"] = (5.0, 30.0),
|
||||||
|
["signalPeriods"] = (3.0, 15.0),
|
||||||
|
["smoothPeriods"] = (1.0, 10.0),
|
||||||
|
["kFactor"] = (0.1, 10.0),
|
||||||
|
["dFactor"] = (0.1, 10.0)
|
||||||
|
},
|
||||||
[IndicatorType.Stc] = new()
|
[IndicatorType.Stc] = new()
|
||||||
{
|
{
|
||||||
["cyclePeriods"] = (5.0, 50.0),
|
["cyclePeriods"] = (5.0, 50.0),
|
||||||
@@ -188,6 +204,7 @@ public class GeneticService : IGeneticService
|
|||||||
[IndicatorType.SuperTrendCrossEma] = ["period", "multiplier"],
|
[IndicatorType.SuperTrendCrossEma] = ["period", "multiplier"],
|
||||||
[IndicatorType.ChandelierExit] = ["period", "multiplier"],
|
[IndicatorType.ChandelierExit] = ["period", "multiplier"],
|
||||||
[IndicatorType.StochRsiTrend] = ["period", "stochPeriods", "signalPeriods", "smoothPeriods"],
|
[IndicatorType.StochRsiTrend] = ["period", "stochPeriods", "signalPeriods", "smoothPeriods"],
|
||||||
|
[IndicatorType.StochasticCross] = ["stochPeriods", "signalPeriods", "smoothPeriods", "kFactor", "dFactor"],
|
||||||
[IndicatorType.Stc] = ["cyclePeriods", "fastPeriods", "slowPeriods"],
|
[IndicatorType.Stc] = ["cyclePeriods", "fastPeriods", "slowPeriods"],
|
||||||
[IndicatorType.LaggingStc] = ["cyclePeriods", "fastPeriods", "slowPeriods"]
|
[IndicatorType.LaggingStc] = ["cyclePeriods", "fastPeriods", "slowPeriods"]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public static class Enums
|
|||||||
EmaTrend,
|
EmaTrend,
|
||||||
Composite,
|
Composite,
|
||||||
StochRsiTrend,
|
StochRsiTrend,
|
||||||
|
StochasticCross,
|
||||||
Stc,
|
Stc,
|
||||||
StDev,
|
StDev,
|
||||||
LaggingStc,
|
LaggingStc,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ namespace Managing.Domain.Strategies
|
|||||||
int? StochPeriods { get; set; }
|
int? StochPeriods { get; set; }
|
||||||
int? SmoothPeriods { get; set; }
|
int? SmoothPeriods { get; set; }
|
||||||
int? CyclePeriods { get; set; }
|
int? CyclePeriods { get; set; }
|
||||||
|
double? KFactor { get; set; }
|
||||||
|
double? DFactor { get; set; }
|
||||||
|
|
||||||
List<LightSignal> Run(HashSet<Candle> candles);
|
List<LightSignal> Run(HashSet<Candle> candles);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ namespace Managing.Domain.Strategies
|
|||||||
|
|
||||||
public int? CyclePeriods { get; set; }
|
public int? CyclePeriods { get; set; }
|
||||||
|
|
||||||
|
public double? KFactor { get; set; }
|
||||||
|
|
||||||
|
public double? DFactor { get; set; }
|
||||||
|
|
||||||
public User User { get; set; }
|
public User User { get; set; }
|
||||||
|
|
||||||
public virtual List<LightSignal> Run(HashSet<Candle> candles)
|
public virtual List<LightSignal> Run(HashSet<Candle> candles)
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ public class LightIndicator
|
|||||||
|
|
||||||
[Id(11)] public int? CyclePeriods { get; set; }
|
[Id(11)] public int? CyclePeriods { get; set; }
|
||||||
|
|
||||||
|
[Id(12)] public double? KFactor { get; set; }
|
||||||
|
|
||||||
|
[Id(13)] public double? DFactor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a LightIndicator back to a full Indicator
|
/// Converts a LightIndicator back to a full Indicator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -64,7 +68,9 @@ public class LightIndicator
|
|||||||
Multiplier = Multiplier,
|
Multiplier = Multiplier,
|
||||||
SmoothPeriods = SmoothPeriods,
|
SmoothPeriods = SmoothPeriods,
|
||||||
StochPeriods = StochPeriods,
|
StochPeriods = StochPeriods,
|
||||||
CyclePeriods = CyclePeriods
|
CyclePeriods = CyclePeriods,
|
||||||
|
KFactor = KFactor,
|
||||||
|
DFactor = DFactor
|
||||||
};
|
};
|
||||||
|
|
||||||
return baseIndicator;
|
return baseIndicator;
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
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 StochasticCrossIndicator : IndicatorBase
|
||||||
|
{
|
||||||
|
public List<LightSignal> Signals { get; set; }
|
||||||
|
|
||||||
|
public StochasticCrossIndicator(
|
||||||
|
string name,
|
||||||
|
int stochPeriods,
|
||||||
|
int signalPeriods,
|
||||||
|
int smoothPeriods,
|
||||||
|
double kFactor = 3.0,
|
||||||
|
double dFactor = 2.0) : base(name, IndicatorType.StochasticCross)
|
||||||
|
{
|
||||||
|
Signals = new List<LightSignal>();
|
||||||
|
StochPeriods = stochPeriods;
|
||||||
|
SignalPeriods = signalPeriods;
|
||||||
|
SmoothPeriods = smoothPeriods;
|
||||||
|
KFactor = kFactor;
|
||||||
|
DFactor = dFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 10 * StochPeriods.Value + 50)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stochResults = candles
|
||||||
|
.GetStoch(StochPeriods.Value, SmoothPeriods.Value, SignalPeriods.Value)
|
||||||
|
.RemoveWarmupPeriods()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (stochResults.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ProcessStochasticSignals(stochResults, candles);
|
||||||
|
|
||||||
|
return Signals.ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
|
||||||
|
{
|
||||||
|
if (candles.Count <= 10 * StochPeriods.Value + 50)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use pre-calculated Stoch values if available
|
||||||
|
List<StochResult> stochResults = null;
|
||||||
|
if (preCalculatedValues?.Stoch != null && preCalculatedValues.Stoch.Any())
|
||||||
|
{
|
||||||
|
// Filter pre-calculated Stoch values to match the candles we're processing
|
||||||
|
stochResults = preCalculatedValues.Stoch
|
||||||
|
.Where(s => s.K.HasValue && s.D.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 (stochResults == null || !stochResults.Any())
|
||||||
|
{
|
||||||
|
return Run(candles);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStochasticSignals(stochResults, candles);
|
||||||
|
|
||||||
|
return Signals.ToList();
|
||||||
|
}
|
||||||
|
catch (RuleException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes Stochastic signals based on %K/%D crossovers filtered by extreme zones.
|
||||||
|
/// Long signals: %K crosses above %D when both lines are below 20 (oversold)
|
||||||
|
/// Short signals: %K crosses below %D when both lines are above 80 (overbought)
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessStochasticSignals(List<StochResult> stochResults, HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
var stochCandles = MapStochToCandle(stochResults, candles.TakeLast(StochPeriods.Value));
|
||||||
|
|
||||||
|
if (stochCandles.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var previousCandle = stochCandles[0];
|
||||||
|
foreach (var currentCandle in stochCandles.Skip(1))
|
||||||
|
{
|
||||||
|
// Check for bullish crossover in oversold zone (both %K and %D < 20)
|
||||||
|
if (previousCandle.PercentK < 20 && previousCandle.PercentD < 20 &&
|
||||||
|
currentCandle.PercentK >= 20 && currentCandle.PercentD < 20)
|
||||||
|
{
|
||||||
|
// %K crossed above %D in oversold zone
|
||||||
|
if (previousCandle.PercentK < previousCandle.PercentD &&
|
||||||
|
currentCandle.PercentK >= currentCandle.PercentD)
|
||||||
|
{
|
||||||
|
AddSignal(currentCandle, TradeDirection.Long, Confidence.Medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for bearish crossover in overbought zone (both %K and %D > 80)
|
||||||
|
else if (previousCandle.PercentK > 80 && previousCandle.PercentD > 80 &&
|
||||||
|
currentCandle.PercentK <= 80 && currentCandle.PercentD > 80)
|
||||||
|
{
|
||||||
|
// %K crossed below %D in overbought zone
|
||||||
|
if (previousCandle.PercentK > previousCandle.PercentD &&
|
||||||
|
currentCandle.PercentK <= currentCandle.PercentD)
|
||||||
|
{
|
||||||
|
AddSignal(currentCandle, TradeDirection.Short, Confidence.Medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousCandle = currentCandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
|
||||||
|
{
|
||||||
|
return new IndicatorsResultBase()
|
||||||
|
{
|
||||||
|
Stoch = candles.GetStoch(StochPeriods.Value, SmoothPeriods.Value, SignalPeriods.Value)
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CandleStoch> MapStochToCandle(IEnumerable<StochResult> stochResults, IEnumerable<Candle> candles)
|
||||||
|
{
|
||||||
|
var stochCandles = new List<CandleStoch>();
|
||||||
|
foreach (var candle in candles)
|
||||||
|
{
|
||||||
|
var currentStoch = stochResults.Find(candle.Date);
|
||||||
|
if (currentStoch != null && currentStoch.K.HasValue && currentStoch.D.HasValue)
|
||||||
|
{
|
||||||
|
stochCandles.Add(new CandleStoch()
|
||||||
|
{
|
||||||
|
Close = candle.Close,
|
||||||
|
Open = candle.Open,
|
||||||
|
Date = candle.Date,
|
||||||
|
Ticker = candle.Ticker,
|
||||||
|
Exchange = candle.Exchange,
|
||||||
|
PercentK = currentStoch.K.Value,
|
||||||
|
PercentD = currentStoch.D.Value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stochCandles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSignal(CandleStoch 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 CandleStoch : Candle
|
||||||
|
{
|
||||||
|
public double PercentK { get; internal set; }
|
||||||
|
public double PercentD { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,6 +91,9 @@ public static class ScenarioHelpers
|
|||||||
IndicatorType.StochRsiTrend => new StochRsiTrendIndicatorBase(indicator.Name,
|
IndicatorType.StochRsiTrend => new StochRsiTrendIndicatorBase(indicator.Name,
|
||||||
indicator.Period.Value, indicator.StochPeriods.Value, indicator.SignalPeriods.Value,
|
indicator.Period.Value, indicator.StochPeriods.Value, indicator.SignalPeriods.Value,
|
||||||
indicator.SmoothPeriods.Value),
|
indicator.SmoothPeriods.Value),
|
||||||
|
IndicatorType.StochasticCross => new StochasticCrossIndicator(indicator.Name,
|
||||||
|
indicator.StochPeriods.Value, indicator.SignalPeriods.Value, indicator.SmoothPeriods.Value,
|
||||||
|
indicator.KFactor ?? 3.0, indicator.DFactor ?? 2.0),
|
||||||
IndicatorType.Stc => new StcIndicatorBase(indicator.Name, indicator.CyclePeriods.Value,
|
IndicatorType.Stc => new StcIndicatorBase(indicator.Name, indicator.CyclePeriods.Value,
|
||||||
indicator.FastPeriods.Value, indicator.SlowPeriods.Value),
|
indicator.FastPeriods.Value, indicator.SlowPeriods.Value),
|
||||||
IndicatorType.LaggingStc => new LaggingSTC(indicator.Name, indicator.CyclePeriods.Value,
|
IndicatorType.LaggingStc => new LaggingSTC(indicator.Name, indicator.CyclePeriods.Value,
|
||||||
@@ -133,7 +136,9 @@ public static class ScenarioHelpers
|
|||||||
double? multiplier = null,
|
double? multiplier = null,
|
||||||
int? stochPeriods = null,
|
int? stochPeriods = null,
|
||||||
int? smoothPeriods = null,
|
int? smoothPeriods = null,
|
||||||
int? cyclePeriods = null)
|
int? cyclePeriods = null,
|
||||||
|
double? kFactor = null,
|
||||||
|
double? dFactor = null)
|
||||||
{
|
{
|
||||||
IIndicator indicator = new IndicatorBase(name, type);
|
IIndicator indicator = new IndicatorBase(name, type);
|
||||||
|
|
||||||
@@ -214,6 +219,33 @@ public static class ScenarioHelpers
|
|||||||
indicator.SmoothPeriods = smoothPeriods;
|
indicator.SmoothPeriods = smoothPeriods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case IndicatorType.StochasticCross:
|
||||||
|
if (!stochPeriods.HasValue || !signalPeriods.HasValue || !smoothPeriods.HasValue)
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
$"Missing stochPeriods, signalPeriods, smoothPeriods for {indicator.Type} strategy type");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indicator.StochPeriods = stochPeriods;
|
||||||
|
indicator.SignalPeriods = signalPeriods;
|
||||||
|
indicator.SmoothPeriods = smoothPeriods;
|
||||||
|
// Set default values for optional parameters
|
||||||
|
indicator.KFactor = kFactor ?? 3.0;
|
||||||
|
indicator.DFactor = dFactor ?? 2.0;
|
||||||
|
|
||||||
|
// Validate kFactor and dFactor are greater than 0
|
||||||
|
if (indicator.KFactor <= 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"kFactor must be greater than 0 for {indicator.Type} strategy type");
|
||||||
|
}
|
||||||
|
if (indicator.DFactor <= 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"dFactor must be greater than 0 for {indicator.Type} strategy type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case IndicatorType.Stc:
|
case IndicatorType.Stc:
|
||||||
case IndicatorType.LaggingStc:
|
case IndicatorType.LaggingStc:
|
||||||
@@ -252,6 +284,7 @@ public static class ScenarioHelpers
|
|||||||
IndicatorType.EmaTrend => SignalType.Trend,
|
IndicatorType.EmaTrend => SignalType.Trend,
|
||||||
IndicatorType.Composite => SignalType.Signal,
|
IndicatorType.Composite => SignalType.Signal,
|
||||||
IndicatorType.StochRsiTrend => SignalType.Trend,
|
IndicatorType.StochRsiTrend => SignalType.Trend,
|
||||||
|
IndicatorType.StochasticCross => SignalType.Signal,
|
||||||
IndicatorType.Stc => SignalType.Signal,
|
IndicatorType.Stc => SignalType.Signal,
|
||||||
IndicatorType.StDev => SignalType.Context,
|
IndicatorType.StDev => SignalType.Context,
|
||||||
IndicatorType.LaggingStc => SignalType.Signal,
|
IndicatorType.LaggingStc => SignalType.Signal,
|
||||||
|
|||||||
Reference in New Issue
Block a user