358 lines
16 KiB
C#
358 lines
16 KiB
C#
using Managing.Domain.Strategies;
|
||
using Managing.Domain.Strategies.Context;
|
||
using Managing.Domain.Strategies.Signals;
|
||
using Managing.Domain.Strategies.Trends;
|
||
using Newtonsoft.Json;
|
||
using static Managing.Common.Enums;
|
||
|
||
namespace Managing.Domain.Scenarios;
|
||
|
||
public static class ScenarioHelpers
|
||
{
|
||
/// <summary>
|
||
/// Compares two lists of indicators and returns a list of changes (added, removed, modified).
|
||
/// </summary>
|
||
/// <param name="oldIndicators">The previous list of indicators</param>
|
||
/// <param name="newIndicators">The new list of indicators</param>
|
||
/// <returns>A list of change descriptions</returns>
|
||
public static List<string> CompareIndicators(List<LightIndicator> oldIndicators, List<LightIndicator> newIndicators)
|
||
{
|
||
var changes = new List<string>();
|
||
|
||
// Create dictionaries for easier comparison using Type as key
|
||
var oldIndicatorDict = oldIndicators.ToDictionary(i => i.Type, i => i);
|
||
var newIndicatorDict = newIndicators.ToDictionary(i => i.Type, i => i);
|
||
|
||
// Find removed indicators
|
||
var removedTypes = oldIndicatorDict.Keys.Except(newIndicatorDict.Keys);
|
||
foreach (var removedType in removedTypes)
|
||
{
|
||
var indicator = oldIndicatorDict[removedType];
|
||
changes.Add($"➖ **Removed Indicator:** {removedType} ({indicator.GetType().Name})");
|
||
}
|
||
|
||
// Find added indicators
|
||
var addedTypes = newIndicatorDict.Keys.Except(oldIndicatorDict.Keys);
|
||
foreach (var addedType in addedTypes)
|
||
{
|
||
var indicator = newIndicatorDict[addedType];
|
||
changes.Add($"➕ **Added Indicator:** {addedType} ({indicator.GetType().Name})");
|
||
}
|
||
|
||
// Find modified indicators (same type but potentially different configuration)
|
||
var commonTypes = oldIndicatorDict.Keys.Intersect(newIndicatorDict.Keys);
|
||
foreach (var commonType in commonTypes)
|
||
{
|
||
var oldIndicator = oldIndicatorDict[commonType];
|
||
var newIndicator = newIndicatorDict[commonType];
|
||
|
||
// Compare indicators by serializing them (simple way to detect configuration changes)
|
||
var oldSerialized = JsonConvert.SerializeObject(oldIndicator, Formatting.None);
|
||
var newSerialized = JsonConvert.SerializeObject(newIndicator, Formatting.None);
|
||
|
||
if (oldSerialized != newSerialized)
|
||
{
|
||
changes.Add($"🔄 **Modified Indicator:** {commonType} ({newIndicator.GetType().Name})");
|
||
}
|
||
}
|
||
|
||
// Add summary if there are changes
|
||
if (changes.Any())
|
||
{
|
||
var summary =
|
||
$"📊 **Indicator Changes:** {addedTypes.Count()} added, {removedTypes.Count()} removed, {commonTypes.Count(c => JsonConvert.SerializeObject(oldIndicatorDict[c]) != JsonConvert.SerializeObject(newIndicatorDict[c]))} modified";
|
||
changes.Insert(0, summary);
|
||
}
|
||
|
||
return changes;
|
||
}
|
||
|
||
public static IIndicator BuildIndicator(LightIndicator indicator)
|
||
{
|
||
IIndicator result = indicator.Type switch
|
||
{
|
||
IndicatorType.StDev => new StDevContext(indicator.Name, indicator.Period ?? 14),
|
||
IndicatorType.RsiDivergence => new RsiDivergenceIndicatorBase(indicator.Name,
|
||
indicator.Period ?? 14),
|
||
IndicatorType.RsiDivergenceConfirm => new RsiDivergenceConfirmIndicatorBase(indicator.Name,
|
||
indicator.Period ?? 14),
|
||
IndicatorType.MacdCross => new MacdCrossIndicatorBase(indicator.Name,
|
||
indicator.FastPeriods ?? 12, indicator.SlowPeriods ?? 26, indicator.SignalPeriods ?? 9),
|
||
IndicatorType.EmaCross => new EmaCrossIndicatorBase(indicator.Name, indicator.Period ?? 14),
|
||
IndicatorType.DualEmaCross => new DualEmaCrossIndicatorBase(indicator.Name,
|
||
indicator.FastPeriods ?? 12, indicator.SlowPeriods ?? 26),
|
||
IndicatorType.ThreeWhiteSoldiers => new ThreeWhiteSoldiersIndicatorBase(indicator.Name,
|
||
indicator.Period ?? 14),
|
||
IndicatorType.SuperTrend => new SuperTrendIndicatorBase(indicator.Name,
|
||
indicator.Period ?? 14, indicator.Multiplier ?? 3.0),
|
||
IndicatorType.ChandelierExit => new ChandelierExitIndicatorBase(indicator.Name,
|
||
indicator.Period ?? 14, indicator.Multiplier ?? 3.0),
|
||
IndicatorType.EmaTrend => new EmaTrendIndicatorBase(indicator.Name, indicator.Period ?? 14),
|
||
IndicatorType.StochRsiTrend => new StochRsiTrendIndicatorBase(indicator.Name,
|
||
indicator.Period ?? 14, indicator.StochPeriods ?? 14, indicator.SignalPeriods ?? 9,
|
||
indicator.SmoothPeriods ?? 3),
|
||
IndicatorType.StochasticCross => new StochasticCrossIndicator(indicator.Name,
|
||
indicator.StochPeriods ?? 14, indicator.SignalPeriods ?? 3, indicator.SmoothPeriods ?? 3,
|
||
indicator.KFactor ?? 3.0, indicator.DFactor ?? 2.0),
|
||
IndicatorType.Stc => new StcIndicatorBase(indicator.Name, indicator.CyclePeriods ?? 10,
|
||
indicator.FastPeriods ?? 12, indicator.SlowPeriods ?? 26),
|
||
IndicatorType.LaggingStc => new LaggingSTC(indicator.Name, indicator.CyclePeriods ?? 10,
|
||
indicator.FastPeriods ?? 12, indicator.SlowPeriods ?? 26),
|
||
IndicatorType.SuperTrendCrossEma => new SuperTrendCrossEma(indicator.Name,
|
||
indicator.Period ?? 14, indicator.Multiplier ?? 3.0),
|
||
IndicatorType.BollingerBandsPercentBMomentumBreakout => new BollingerBandsPercentBMomentumBreakout(
|
||
indicator.Name,
|
||
indicator.Period ?? 20, indicator.StDev ?? 2.0),
|
||
IndicatorType.BollingerBandsVolatilityProtection => new BollingerBandsVolatilityProtection(indicator.Name,
|
||
indicator.Period ?? 20, indicator.StDev ?? 2.0),
|
||
IndicatorType.IchimokuKumoTrend => new IchimokuKumoTrend(indicator.Name,
|
||
indicator.TenkanPeriods ?? 9,
|
||
indicator.KijunPeriods ?? 26,
|
||
indicator.SenkouBPeriods ?? 52,
|
||
indicator.OffsetPeriods ?? 26,
|
||
indicator.SenkouOffset,
|
||
indicator.ChikouOffset),
|
||
_ => throw new NotImplementedException(),
|
||
};
|
||
|
||
result.SignalType = GetSignalType(indicator.Type);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts a full Indicator to a LightIndicator
|
||
/// </summary>
|
||
public static LightIndicator BaseToLight(IndicatorBase indicatorBase)
|
||
{
|
||
return new LightIndicator(indicatorBase.Name, indicatorBase.Type)
|
||
{
|
||
MinimumHistory = indicatorBase.MinimumHistory,
|
||
Period = indicatorBase.Period,
|
||
FastPeriods = indicatorBase.FastPeriods,
|
||
SlowPeriods = indicatorBase.SlowPeriods,
|
||
SignalPeriods = indicatorBase.SignalPeriods,
|
||
Multiplier = indicatorBase.Multiplier,
|
||
StDev = indicatorBase.StDev,
|
||
SmoothPeriods = indicatorBase.SmoothPeriods,
|
||
StochPeriods = indicatorBase.StochPeriods,
|
||
CyclePeriods = indicatorBase.CyclePeriods,
|
||
KFactor = indicatorBase.KFactor,
|
||
DFactor = indicatorBase.DFactor,
|
||
TenkanPeriods = indicatorBase.TenkanPeriods,
|
||
KijunPeriods = indicatorBase.KijunPeriods,
|
||
SenkouBPeriods = indicatorBase.SenkouBPeriods,
|
||
OffsetPeriods = indicatorBase.OffsetPeriods,
|
||
SenkouOffset = indicatorBase.SenkouOffset,
|
||
ChikouOffset = indicatorBase.ChikouOffset
|
||
};
|
||
}
|
||
|
||
public static IIndicator BuildIndicator(
|
||
IndicatorType type,
|
||
string name,
|
||
int? period = null,
|
||
int? fastPeriods = null,
|
||
int? slowPeriods = null,
|
||
int? signalPeriods = null,
|
||
double? multiplier = null,
|
||
double? stdev = null,
|
||
int? stochPeriods = null,
|
||
int? smoothPeriods = null,
|
||
int? cyclePeriods = null,
|
||
double? kFactor = null,
|
||
double? dFactor = null,
|
||
int? tenkanPeriods = null,
|
||
int? kijunPeriods = null,
|
||
int? senkouBPeriods = null,
|
||
int? offsetPeriods = null,
|
||
int? senkouOffset = null,
|
||
int? chikouOffset = null)
|
||
{
|
||
IIndicator indicator = new IndicatorBase(name, type);
|
||
|
||
switch (type)
|
||
{
|
||
case IndicatorType.RsiDivergence:
|
||
case IndicatorType.RsiDivergenceConfirm:
|
||
case IndicatorType.EmaTrend:
|
||
case IndicatorType.EmaCross:
|
||
case IndicatorType.StDev:
|
||
if (!period.HasValue)
|
||
{
|
||
throw new Exception($"Missing period for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
indicator.Period = period.Value;
|
||
}
|
||
|
||
break;
|
||
case IndicatorType.MacdCross:
|
||
if (!fastPeriods.HasValue || !slowPeriods.HasValue || !signalPeriods.HasValue)
|
||
{
|
||
throw new Exception(
|
||
$"Missing fastPeriods or slowPeriods or signalPeriods, for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
indicator.FastPeriods = fastPeriods;
|
||
indicator.SlowPeriods = slowPeriods;
|
||
indicator.SignalPeriods = signalPeriods;
|
||
}
|
||
|
||
break;
|
||
case IndicatorType.DualEmaCross:
|
||
if (!fastPeriods.HasValue || !slowPeriods.HasValue)
|
||
{
|
||
throw new Exception(
|
||
$"Missing fastPeriods or slowPeriods for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
indicator.FastPeriods = fastPeriods;
|
||
indicator.SlowPeriods = slowPeriods;
|
||
}
|
||
|
||
break;
|
||
case IndicatorType.ThreeWhiteSoldiers:
|
||
break;
|
||
case IndicatorType.SuperTrend:
|
||
case IndicatorType.SuperTrendCrossEma:
|
||
case IndicatorType.ChandelierExit:
|
||
if (!period.HasValue || !multiplier.HasValue)
|
||
{
|
||
throw new Exception($"Missing period or multiplier, for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
indicator.Period = period;
|
||
indicator.Multiplier = multiplier;
|
||
}
|
||
|
||
break;
|
||
case IndicatorType.StochRsiTrend:
|
||
if (!period.HasValue
|
||
|| !stochPeriods.HasValue
|
||
|| !signalPeriods.HasValue
|
||
|| !smoothPeriods.HasValue)
|
||
{
|
||
throw new Exception(
|
||
$"Missing period, stochPeriods, signalPeriods, smoothPeriods for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
indicator.Period = period;
|
||
indicator.StochPeriods = stochPeriods;
|
||
indicator.SignalPeriods = signalPeriods;
|
||
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;
|
||
case IndicatorType.BollingerBandsPercentBMomentumBreakout:
|
||
case IndicatorType.BollingerBandsVolatilityProtection:
|
||
if (!period.HasValue || !stdev.HasValue)
|
||
{
|
||
throw new Exception($"Missing period or stdev for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
((IndicatorBase)indicator).Period = period;
|
||
((IndicatorBase)indicator).StDev = stdev;
|
||
}
|
||
|
||
break;
|
||
case IndicatorType.IchimokuKumoTrend:
|
||
if (!tenkanPeriods.HasValue || !kijunPeriods.HasValue || !senkouBPeriods.HasValue ||
|
||
!offsetPeriods.HasValue)
|
||
{
|
||
throw new Exception($"Missing Ichimoku parameters for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
((IndicatorBase)indicator).TenkanPeriods = tenkanPeriods;
|
||
((IndicatorBase)indicator).KijunPeriods = kijunPeriods;
|
||
((IndicatorBase)indicator).SenkouBPeriods = senkouBPeriods;
|
||
((IndicatorBase)indicator).OffsetPeriods = offsetPeriods;
|
||
((IndicatorBase)indicator).SenkouOffset = senkouOffset;
|
||
((IndicatorBase)indicator).ChikouOffset = chikouOffset;
|
||
}
|
||
|
||
break;
|
||
case IndicatorType.Stc:
|
||
case IndicatorType.LaggingStc:
|
||
if (!fastPeriods.HasValue || !slowPeriods.HasValue || !cyclePeriods.HasValue)
|
||
{
|
||
throw new Exception(
|
||
$"Missing fastPeriods or slowPeriods or cyclePeriods, for {indicator.Type} strategy type");
|
||
}
|
||
else
|
||
{
|
||
indicator.FastPeriods = fastPeriods;
|
||
indicator.SlowPeriods = slowPeriods;
|
||
indicator.CyclePeriods = cyclePeriods;
|
||
}
|
||
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return indicator;
|
||
}
|
||
|
||
public static SignalType GetSignalType(IndicatorType type)
|
||
{
|
||
return type switch
|
||
{
|
||
IndicatorType.RsiDivergence => SignalType.Signal,
|
||
IndicatorType.RsiDivergenceConfirm => SignalType.Signal,
|
||
IndicatorType.MacdCross => SignalType.Signal,
|
||
IndicatorType.EmaCross => SignalType.Signal,
|
||
IndicatorType.DualEmaCross => SignalType.Signal,
|
||
IndicatorType.ThreeWhiteSoldiers => SignalType.Signal,
|
||
IndicatorType.SuperTrend => SignalType.Trend,
|
||
IndicatorType.ChandelierExit => SignalType.Signal,
|
||
IndicatorType.EmaTrend => SignalType.Trend,
|
||
IndicatorType.Composite => SignalType.Signal,
|
||
IndicatorType.StochRsiTrend => SignalType.Trend,
|
||
IndicatorType.StochasticCross => SignalType.Signal,
|
||
IndicatorType.Stc => SignalType.Signal,
|
||
IndicatorType.StDev => SignalType.Context,
|
||
IndicatorType.LaggingStc => SignalType.Signal,
|
||
IndicatorType.SuperTrendCrossEma => SignalType.Signal,
|
||
IndicatorType.BollingerBandsPercentBMomentumBreakout => SignalType.Signal,
|
||
IndicatorType.BollingerBandsVolatilityProtection => SignalType.Context,
|
||
IndicatorType.IchimokuKumoTrend => SignalType.Trend,
|
||
_ => throw new NotImplementedException(),
|
||
};
|
||
}
|
||
} |