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:
@@ -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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user