Refactoring TradingBotBase.cs + clean architecture (#38)

* Refactoring TradingBotBase.cs + clean architecture

* Fix basic tests

* Fix tests

* Fix workers

* Fix open positions

* Fix closing position stucking the grain

* Fix comments

* Refactor candle handling to use IReadOnlyList for chronological order preservation across various components
This commit is contained in:
Oda
2025-12-01 19:32:06 +07:00
committed by GitHub
parent ab26260f6d
commit 9d536ea49e
74 changed files with 4525 additions and 2350 deletions

View File

@@ -5,6 +5,7 @@ using Managing.Domain.Bots;
using Managing.Domain.Indicators;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Backtests;
@@ -65,7 +66,7 @@ public class Backtest
Timeframe = Config.Timeframe,
IsForWatchingOnly = false, // Always start as active bot
BotTradingBalance = initialTradingBalance,
IsForBacktest = false, // Always false for live bots
TradingType = TradingType.Futures, // Always Futures for live bots
CooldownPeriod = Config.CooldownPeriod,
MaxLossStreak = Config.MaxLossStreak,
MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value
@@ -99,7 +100,7 @@ public class Backtest
Timeframe = Config.Timeframe,
IsForWatchingOnly = Config.IsForWatchingOnly,
BotTradingBalance = balance,
IsForBacktest = true,
TradingType = TradingType.BacktestFutures,
CooldownPeriod = Config.CooldownPeriod,
MaxLossStreak = Config.MaxLossStreak,
MaxPositionTimeHours = Config.MaxPositionTimeHours,

View File

@@ -21,7 +21,7 @@ public class TradingBotConfig
[Id(5)] [Required] public decimal BotTradingBalance { get; set; }
[Id(6)] [Required] public bool IsForBacktest { get; set; }
[Id(6)] [Required] public TradingType TradingType { get; set; }
[Id(7)] [Required] public int CooldownPeriod { get; set; }

View File

@@ -18,7 +18,7 @@ public abstract class BollingerBandsBase : IndicatorBase
StDev = stdev;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= Period)
{
@@ -45,7 +45,7 @@ public abstract class BollingerBandsBase : IndicatorBase
}
}
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= Period)
{
@@ -81,7 +81,7 @@ public abstract class BollingerBandsBase : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -93,7 +93,7 @@ public abstract class BollingerBandsBase : IndicatorBase
/// <summary>
/// Abstract method for processing Bollinger Bands signals - implemented by child classes
/// </summary>
protected abstract void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles);
protected abstract void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, IReadOnlyList<Candle> candles);
/// <summary>
/// Maps Bollinger Bands results to candle objects with all BollingerBandsResult properties

View File

@@ -20,7 +20,7 @@ public class BollingerBandsVolatilityProtection : BollingerBandsBase
/// </summary>
/// <param name="bbResults">List of Bollinger Bands calculation results</param>
/// <param name="candles">Candles to process</param>
protected override void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles)
protected override void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, IReadOnlyList<Candle> candles)
{
var bbCandles = MapBollingerBandsToCandle(bbResults, candles.TakeLast(Period.Value)).ToList();

View File

@@ -18,7 +18,7 @@ public class StDevContext : IndicatorBase
Period = period;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= Period)
{
@@ -44,7 +44,7 @@ public class StDevContext : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated StdDev values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= Period)
{
@@ -85,7 +85,7 @@ public class StDevContext : IndicatorBase
/// </summary>
/// <param name="stDev">List of StdDev calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessStDevSignals(List<StdDevResult> stDev, HashSet<Candle> candles)
private void ProcessStDevSignals(List<StdDevResult> stDev, IReadOnlyList<Candle> candles)
{
var stDevCandles = MapStDev(stDev, candles.TakeLast(Period.Value));
@@ -126,7 +126,7 @@ public class StDevContext : IndicatorBase
AddSignal(lastCandle, TradeDirection.None, confidence);
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
var test = new IndicatorsResultBase()
{

View File

@@ -21,17 +21,17 @@ namespace Managing.Domain.Strategies
double? KFactor { get; set; }
double? DFactor { get; set; }
List<LightSignal> Run(HashSet<Candle> candles);
List<LightSignal> Run(IReadOnlyList<Candle> candles);
/// <summary>
/// Runs the indicator using pre-calculated indicator values for performance optimization.
/// If pre-calculated values are not available or not applicable, falls back to regular Run().
/// </summary>
/// <param name="candles">The candles to process</param>
/// <param name="candles">The candles to process (must be ordered chronologically)</param>
/// <param name="preCalculatedValues">Pre-calculated indicator values (optional)</param>
/// <returns>List of signals generated by the indicator</returns>
List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues);
List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues);
IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles);
IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles);
}
}

View File

@@ -55,7 +55,7 @@ namespace Managing.Domain.Strategies
public User User { get; set; }
public virtual List<LightSignal> Run(HashSet<Candle> candles)
public virtual List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
throw new NotImplementedException();
}
@@ -64,14 +64,16 @@ namespace Managing.Domain.Strategies
/// Runs the indicator using pre-calculated values if available, otherwise falls back to regular Run().
/// Default implementation falls back to regular Run() - override in derived classes to use pre-calculated values.
/// </summary>
public virtual List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
/// <param name="candles">The candles to process (must be ordered chronologically)</param>
/// <param name="preCalculatedValues">Pre-calculated indicator values (optional)</param>
public virtual List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
// Default implementation: ignore pre-calculated values and use regular Run()
// Derived classes should override this to use pre-calculated values for performance
return Run(candles);
}
public virtual IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public virtual IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
throw new NotImplementedException();
}

View File

@@ -19,7 +19,7 @@ public class BollingerBandsPercentBMomentumBreakout : BollingerBandsBase
/// Long signals: %B crosses above 0.8 after being below (strong upward momentum)
/// Short signals: %B crosses below 0.2 after being above (strong downward momentum)
/// </summary>
protected override void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, HashSet<Candle> candles)
protected override void ProcessBollingerBandsSignals(List<BollingerBandsResult> bbResults, IReadOnlyList<Candle> candles)
{
var bbCandles = MapBollingerBandsToCandle(bbResults, candles.TakeLast(Period.Value)).ToList();

View File

@@ -21,7 +21,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
MinimumHistory = 1 + Period.Value;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= MinimumHistory)
{
@@ -43,7 +43,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated Chandelier values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= MinimumHistory)
{
@@ -88,7 +88,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
/// This method is shared between the regular Run() and optimized Run() methods.
/// </summary>
/// <param name="candles">Candles to process</param>
private void ProcessChandelierSignals(HashSet<Candle> candles)
private void ProcessChandelierSignals(IReadOnlyList<Candle> candles)
{
GetSignals(ChandelierType.Long, candles);
GetSignals(ChandelierType.Short, candles);
@@ -103,13 +103,13 @@ public class ChandelierExitIndicatorBase : IndicatorBase
private void ProcessChandelierSignalsWithPreCalculated(
List<ChandelierResult> chandelierLong,
List<ChandelierResult> chandelierShort,
HashSet<Candle> candles)
IReadOnlyList<Candle> candles)
{
GetSignalsWithPreCalculated(ChandelierType.Long, chandelierLong, candles);
GetSignalsWithPreCalculated(ChandelierType.Short, chandelierShort, candles);
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -118,7 +118,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
};
}
private void GetSignals(ChandelierType chandelierType, HashSet<Candle> candles)
private void GetSignals(ChandelierType chandelierType, IReadOnlyList<Candle> candles)
{
var chandelier = candles.GetChandelier(Period.Value, Multiplier.Value, chandelierType)
.Where(s => s.ChandelierExit.HasValue).ToList();
@@ -126,7 +126,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
}
private void GetSignalsWithPreCalculated(ChandelierType chandelierType, List<ChandelierResult> chandelier,
HashSet<Candle> candles)
IReadOnlyList<Candle> candles)
{
ProcessChandelierSignalsForType(chandelier, chandelierType, candles);
}
@@ -139,7 +139,7 @@ public class ChandelierExitIndicatorBase : IndicatorBase
/// <param name="chandelierType">Type of Chandelier (Long or Short)</param>
/// <param name="candles">Candles to process</param>
private void ProcessChandelierSignalsForType(List<ChandelierResult> chandelier, ChandelierType chandelierType,
HashSet<Candle> candles)
IReadOnlyList<Candle> candles)
{
var chandelierCandle = MapChandelierToCandle(chandelier, candles.TakeLast(MinimumHistory));
if (chandelierCandle.Count == 0)

View File

@@ -21,7 +21,7 @@ public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
MinimumHistory = Math.Max(fastPeriod, slowPeriod) * 2;
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -30,7 +30,7 @@ public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
};
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= MinimumHistory)
{
@@ -58,7 +58,7 @@ public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated EMA values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= MinimumHistory)
{
@@ -105,7 +105,7 @@ public class DualEmaCrossIndicatorBase : EmaBaseIndicatorBase
/// <param name="fastEma">List of Fast EMA calculation results</param>
/// <param name="slowEma">List of Slow EMA calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessDualEmaCrossSignals(List<EmaResult> fastEma, List<EmaResult> slowEma, HashSet<Candle> candles)
private void ProcessDualEmaCrossSignals(List<EmaResult> fastEma, List<EmaResult> slowEma, IReadOnlyList<Candle> candles)
{
var dualEmaCandles = MapDualEmaToCandle(fastEma, slowEma, candles.TakeLast(MinimumHistory));

View File

@@ -18,7 +18,7 @@ public class EmaCrossIndicator : EmaBaseIndicatorBase
Period = period;
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -26,7 +26,7 @@ public class EmaCrossIndicator : EmaBaseIndicatorBase
};
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= Period)
{
@@ -52,7 +52,7 @@ public class EmaCrossIndicator : EmaBaseIndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated EMA values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= Period)
{
@@ -93,7 +93,7 @@ public class EmaCrossIndicator : EmaBaseIndicatorBase
/// </summary>
/// <param name="ema">List of EMA calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessEmaCrossSignals(List<EmaResult> ema, HashSet<Candle> candles)
private void ProcessEmaCrossSignals(List<EmaResult> ema, IReadOnlyList<Candle> candles)
{
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));

View File

@@ -18,7 +18,7 @@ public class EmaCrossIndicatorBase : EmaBaseIndicatorBase
Period = period;
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -26,7 +26,7 @@ public class EmaCrossIndicatorBase : EmaBaseIndicatorBase
};
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= Period)
{
@@ -52,7 +52,7 @@ public class EmaCrossIndicatorBase : EmaBaseIndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated EMA values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= Period)
{
@@ -93,7 +93,7 @@ public class EmaCrossIndicatorBase : EmaBaseIndicatorBase
/// </summary>
/// <param name="ema">List of EMA calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessEmaCrossSignals(List<EmaResult> ema, HashSet<Candle> candles)
private void ProcessEmaCrossSignals(List<EmaResult> ema, IReadOnlyList<Candle> candles)
{
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value).ToHashSet());

View File

@@ -28,7 +28,7 @@ public class LaggingSTC : IndicatorBase
CyclePeriods = cyclePeriods;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
{
@@ -54,7 +54,7 @@ public class LaggingSTC : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated STC values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
{
@@ -95,7 +95,7 @@ public class LaggingSTC : IndicatorBase
/// </summary>
/// <param name="stc">List of STC calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessLaggingStcSignals(List<StcResult> stc, HashSet<Candle> candles)
private void ProcessLaggingStcSignals(List<StcResult> stc, IReadOnlyList<Candle> candles)
{
var stcCandles = MapStcToCandle(stc, candles.TakeLast(CyclePeriods.Value * 3));
@@ -142,7 +142,7 @@ public class LaggingSTC : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
var stc = candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
return new IndicatorsResultBase

View File

@@ -21,7 +21,7 @@ public class MacdCrossIndicatorBase : IndicatorBase
SignalPeriods = signalPeriods;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= 2 * (SlowPeriods + SignalPeriods))
{
@@ -47,7 +47,7 @@ public class MacdCrossIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated MACD values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= 2 * (SlowPeriods + SignalPeriods))
{
@@ -88,7 +88,7 @@ public class MacdCrossIndicatorBase : IndicatorBase
/// </summary>
/// <param name="macd">List of MACD calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessMacdSignals(List<MacdResult> macd, HashSet<Candle> candles)
private void ProcessMacdSignals(List<MacdResult> macd, IReadOnlyList<Candle> candles)
{
var macdCandle = MapMacdToCandle(macd, candles.TakeLast(SignalPeriods.Value));
@@ -114,7 +114,7 @@ public class MacdCrossIndicatorBase : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{

View File

@@ -22,7 +22,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
/// Get RSI signals
/// </summary>
/// <returns></returns>
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= Period)
{
@@ -48,7 +48,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated RSI values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= Period)
{
@@ -90,7 +90,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
/// </summary>
/// <param name="rsiResult">List of RSI calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessRsiDivergenceConfirmSignals(List<RsiResult> rsiResult, HashSet<Candle> candles)
private void ProcessRsiDivergenceConfirmSignals(List<RsiResult> rsiResult, IReadOnlyList<Candle> candles)
{
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
@@ -101,7 +101,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
GetShortSignals(candlesRsi, candles);
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -109,7 +109,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
};
}
private void GetLongSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
private void GetLongSignals(List<CandleRsi> candlesRsi, IReadOnlyList<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -182,7 +182,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
}
}
private void GetShortSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
private void GetShortSignals(List<CandleRsi> candlesRsi, IReadOnlyList<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -256,7 +256,7 @@ public class RsiDivergenceConfirmIndicatorBase : IndicatorBase
}
}
private void CheckIfConfimation(CandleRsi currentCandle, TradeDirection direction, HashSet<Candle> candles)
private void CheckIfConfimation(CandleRsi currentCandle, TradeDirection direction, IReadOnlyList<Candle> candles)
{
var lastCandleOnPeriod = candles.TakeLast(Period.Value).ToList();
var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date

View File

@@ -25,7 +25,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
/// Get RSI signals
/// </summary>
/// <returns></returns>
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (!Period.HasValue || candles.Count <= Period)
{
@@ -51,7 +51,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated RSI values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (!Period.HasValue || candles.Count <= Period)
{
@@ -93,7 +93,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
/// </summary>
/// <param name="rsiResult">List of RSI calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessRsiDivergenceSignals(List<RsiResult> rsiResult, HashSet<Candle> candles)
private void ProcessRsiDivergenceSignals(List<RsiResult> rsiResult, IReadOnlyList<Candle> candles)
{
var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));
@@ -104,7 +104,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
GetShortSignals(candlesRsi, candles);
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{
@@ -112,7 +112,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
};
}
private void GetLongSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
private void GetLongSignals(List<CandleRsi> candlesRsi, IReadOnlyList<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -183,7 +183,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
}
}
private void GetShortSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
private void GetShortSignals(List<CandleRsi> candlesRsi, IReadOnlyList<Candle> candles)
{
// Set the low and high for first candle
var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
@@ -255,7 +255,7 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
}
}
private void AddSignal(CandleRsi candleSignal, TradeDirection direction, HashSet<Candle> candles)
private void AddSignal(CandleRsi candleSignal, TradeDirection direction, IReadOnlyList<Candle> candles)
{
var signal = new LightSignal(candleSignal.Ticker, direction, Confidence.Low,
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);

View File

@@ -21,7 +21,7 @@ public class StcIndicatorBase : IndicatorBase
CyclePeriods = cyclePeriods;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
{
@@ -50,7 +50,7 @@ public class StcIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated STC values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= 2 * (SlowPeriods + CyclePeriods))
{
@@ -85,7 +85,7 @@ public class StcIndicatorBase : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
if (FastPeriods != null && SlowPeriods != null)
{
@@ -105,7 +105,7 @@ public class StcIndicatorBase : IndicatorBase
/// </summary>
/// <param name="stc">List of STC calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessStcSignals(List<StcResult> stc, HashSet<Candle> candles)
private void ProcessStcSignals(List<StcResult> stc, IReadOnlyList<Candle> candles)
{
if (CyclePeriods == null)
return;

View File

@@ -28,7 +28,7 @@ public class StochasticCrossIndicator : IndicatorBase
DFactor = dFactor;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= 10 * StochPeriods.Value + 50)
{
@@ -55,7 +55,7 @@ public class StochasticCrossIndicator : IndicatorBase
}
}
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= 10 * StochPeriods.Value + 50)
{
@@ -95,7 +95,7 @@ public class StochasticCrossIndicator : IndicatorBase
/// 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)
private void ProcessStochasticSignals(List<StochResult> stochResults, IReadOnlyList<Candle> candles)
{
var stochCandles = MapStochToCandle(stochResults, candles.TakeLast(StochPeriods.Value));
@@ -132,7 +132,7 @@ public class StochasticCrossIndicator : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{

View File

@@ -20,7 +20,7 @@ public class SuperTrendCrossEma : IndicatorBase
MinimumHistory = 100 + Period.Value;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
// Validate sufficient historical data for all indicators
const int emaPeriod = 50;
@@ -89,7 +89,7 @@ public class SuperTrendCrossEma : IndicatorBase
/// Runs the indicator using pre-calculated SuperTrend values for performance optimization.
/// Note: EMA50 and ADX are still calculated on-the-fly as they're not part of the standard indicator values.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
// Validate sufficient historical data for all indicators
const int emaPeriod = 50;
@@ -157,7 +157,7 @@ public class SuperTrendCrossEma : IndicatorBase
List<SuperTrendResult> superTrend,
List<EmaResult> ema50,
List<AdxResult> adxResults,
HashSet<Candle> candles,
IReadOnlyList<Candle> candles,
int minimumRequiredHistory,
int adxThreshold)
{
@@ -237,7 +237,7 @@ public class SuperTrendCrossEma : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{

View File

@@ -20,7 +20,7 @@ public class SuperTrendIndicatorBase : IndicatorBase
MinimumHistory = 100 + Period.Value;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= MinimumHistory)
{
@@ -48,7 +48,7 @@ public class SuperTrendIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated SuperTrend values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= MinimumHistory)
{
@@ -89,7 +89,7 @@ public class SuperTrendIndicatorBase : IndicatorBase
/// </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)
private void ProcessSuperTrendSignals(List<SuperTrendResult> superTrend, IReadOnlyList<Candle> candles)
{
var superTrendCandle = MapSuperTrendToCandle(superTrend, candles.TakeLast(MinimumHistory));
@@ -112,7 +112,7 @@ public class SuperTrendIndicatorBase : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{

View File

@@ -17,7 +17,7 @@ namespace Managing.Domain.Strategies.Signals
public TradeDirection Direction { get; }
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
var signals = new List<LightSignal>();
@@ -43,7 +43,7 @@ namespace Managing.Domain.Strategies.Signals
/// Note: ThreeWhiteSoldiers is a pattern-based indicator that doesn't use traditional indicator calculations,
/// so pre-calculated values are not applicable. This method falls back to regular Run().
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
// ThreeWhiteSoldiers doesn't use traditional indicators, so pre-calculated values don't apply
// Fall back to regular calculation
@@ -55,7 +55,7 @@ namespace Managing.Domain.Strategies.Signals
/// This method is shared between the regular Run() and optimized Run() methods.
/// </summary>
/// <param name="candles">Candles to process</param>
private void ProcessThreeWhiteSoldiersSignals(HashSet<Candle> candles)
private void ProcessThreeWhiteSoldiersSignals(IReadOnlyList<Candle> candles)
{
var lastFourCandles = candles.TakeLast(4);
Candle previousCandles = null;
@@ -75,7 +75,7 @@ namespace Managing.Domain.Strategies.Signals
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
throw new NotImplementedException();
}

View File

@@ -18,7 +18,7 @@ public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
Period = period;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= 2 * Period)
{
@@ -44,7 +44,7 @@ public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated EMA values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= 2 * Period)
{
@@ -85,7 +85,7 @@ public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
/// </summary>
/// <param name="ema">List of EMA calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessEmaTrendSignals(List<EmaResult> ema, HashSet<Candle> candles)
private void ProcessEmaTrendSignals(List<EmaResult> ema, IReadOnlyList<Candle> candles)
{
var emaCandles = MapEmaToCandle(ema, candles.TakeLast(Period.Value));
@@ -108,7 +108,7 @@ public class EmaTrendIndicatorBase : EmaBaseIndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{

View File

@@ -30,7 +30,7 @@ public class IchimokuKumoTrend : IndicatorBase
ChikouOffset = chikouOffset; // Separate offset for Chikou span
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
// Need at least the greater of tenkanPeriods, kijunPeriods, senkouBPeriods, and all offset periods
var maxOffset = Math.Max(Math.Max(OffsetPeriods.Value, SenkouOffset ?? OffsetPeriods.Value), ChikouOffset ?? OffsetPeriods.Value);
@@ -56,7 +56,7 @@ public class IchimokuKumoTrend : IndicatorBase
}
}
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
// Need at least the greater of tenkanPeriods, kijunPeriods, senkouBPeriods, and all offset periods
var maxOffset = Math.Max(Math.Max(OffsetPeriods.Value, SenkouOffset ?? OffsetPeriods.Value), ChikouOffset ?? OffsetPeriods.Value);
@@ -160,7 +160,7 @@ public class IchimokuKumoTrend : IndicatorBase
return candleIchimokuResults;
}
private void ProcessKumoTrendSignals(List<CandleIchimoku> ichimokuResults, HashSet<Candle> candles)
private void ProcessKumoTrendSignals(List<CandleIchimoku> ichimokuResults, IReadOnlyList<Candle> candles)
{
var mappedData = ichimokuResults;
@@ -193,7 +193,7 @@ public class IchimokuKumoTrend : IndicatorBase
}
}
private void ProcessKumoTrendSignalsFromResults(List<IchimokuResult> ichimokuResults, HashSet<Candle> candles)
private void ProcessKumoTrendSignalsFromResults(List<IchimokuResult> ichimokuResults, IReadOnlyList<Candle> candles)
{
if (ichimokuResults.Count == 0)
return;
@@ -229,7 +229,7 @@ public class IchimokuKumoTrend : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
IEnumerable<IchimokuResult> ichimokuResults;

View File

@@ -26,7 +26,7 @@ public class StochRsiTrendIndicatorBase : IndicatorBase
Period = period;
}
public override List<LightSignal> Run(HashSet<Candle> candles)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles)
{
if (candles.Count <= 10 * Period + 50)
{
@@ -55,7 +55,7 @@ public class StochRsiTrendIndicatorBase : IndicatorBase
/// <summary>
/// Runs the indicator using pre-calculated StochRsi values for performance optimization.
/// </summary>
public override List<LightSignal> Run(HashSet<Candle> candles, IndicatorsResultBase preCalculatedValues)
public override List<LightSignal> Run(IReadOnlyList<Candle> candles, IndicatorsResultBase preCalculatedValues)
{
if (candles.Count <= 10 * Period + 50)
{
@@ -96,7 +96,7 @@ public class StochRsiTrendIndicatorBase : IndicatorBase
/// </summary>
/// <param name="stochRsi">List of StochRsi calculation results</param>
/// <param name="candles">Candles to process</param>
private void ProcessStochRsiTrendSignals(List<StochRsiResult> stochRsi, HashSet<Candle> candles)
private void ProcessStochRsiTrendSignals(List<StochRsiResult> stochRsi, IReadOnlyList<Candle> candles)
{
var stochRsiCandles = MapStochRsiToCandle(stochRsi, candles.TakeLast(Period.Value));
@@ -119,7 +119,7 @@ public class StochRsiTrendIndicatorBase : IndicatorBase
}
}
public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
public override IndicatorsResultBase GetIndicatorValues(IReadOnlyList<Candle> candles)
{
return new IndicatorsResultBase()
{

View File

@@ -55,13 +55,13 @@ public static class TradingBox
{
private static readonly IndicatorComboConfig _defaultConfig = new();
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario scenario,
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod = 1)
{
return GetSignal(newCandles, scenario, previousSignal, _defaultConfig, loopbackPeriod, null);
}
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario scenario,
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario scenario,
Dictionary<string, LightSignal> previousSignal, int? loopbackPeriod,
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
{
@@ -69,13 +69,13 @@ public static class TradingBox
preCalculatedIndicatorValues);
}
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario lightScenario,
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
{
return GetSignal(newCandles, lightScenario, previousSignal, config, loopbackPeriod, null);
}
public static LightSignal GetSignal(HashSet<Candle> newCandles, LightScenario lightScenario,
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario lightScenario,
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod,
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
{
@@ -127,12 +127,11 @@ public static class TradingBox
continue;
}
var limitedCandles = newCandles.ToList();
// Optimized: limitedCandles is already ordered, no need to re-order
// newCandles is already a List and ordered chronologically
var loopback = loopbackPeriod.HasValue && loopbackPeriod > 1 ? loopbackPeriod.Value : 1;
var candleLoopback = limitedCandles.Count > loopback
? limitedCandles.Skip(limitedCandles.Count - loopback).ToList()
: limitedCandles;
var candleLoopback = newCandles.Count > loopback
? newCandles.Skip(newCandles.Count - loopback).ToList()
: newCandles.ToList();
if (!candleLoopback.Any())
{
@@ -920,7 +919,7 @@ public static class TradingBox
/// <returns>A dictionary of indicator types to their calculated values.</returns>
public static Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValues(
Scenario scenario,
HashSet<Candle> candles)
IReadOnlyList<Candle> candles)
{
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();

View File

@@ -84,6 +84,12 @@ namespace Managing.Domain.Trades
[Id(18)]
public bool RecoveryAttempted { get; set; }
/// <summary>
/// The trading type for this position (BacktestFutures or Futures)
/// </summary>
[Id(19)]
public TradingType TradingType { get; set; }
/// <summary>
/// Return true if position is finished even if the position was canceled or rejected
/// </summary>