diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index 45ea08fc..4e21f404 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -9,6 +9,7 @@ using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.Scenarios; +using Managing.Domain.Shared.Helpers; using Managing.Domain.Statistics; using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; @@ -346,7 +347,7 @@ public class DataController : ControllerBase { // Map ScenarioRequest to domain Scenario object var domainScenario = MapScenarioRequestToScenario(request.Scenario); - indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles); + indicatorsValues = TradingBox.CalculateIndicatorsValues(domainScenario, candles); } return Ok(new CandlesWithIndicatorsResponse diff --git a/src/Managing.Application.Abstractions/Services/ITradingService.cs b/src/Managing.Application.Abstractions/Services/ITradingService.cs index 5958b5a0..39e96deb 100644 --- a/src/Managing.Application.Abstractions/Services/ITradingService.cs +++ b/src/Managing.Application.Abstractions/Services/ITradingService.cs @@ -1,11 +1,9 @@ using Managing.Domain.Accounts; using Managing.Domain.Bots; -using Managing.Domain.Candles; using Managing.Domain.Indicators; using Managing.Domain.Scenarios; using Managing.Domain.Statistics; using Managing.Domain.Strategies; -using Managing.Domain.Strategies.Base; using Managing.Domain.Synth.Models; using Managing.Domain.Trades; using Managing.Domain.Users; @@ -52,15 +50,6 @@ public interface ITradingService Task MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice, decimal liquidationPrice, Guid positionIdentifier, TradingBotConfig botConfig); - /// - /// Calculates indicators values for a given scenario and candles. - /// - /// The scenario containing indicators. - /// The candles to calculate indicators for. - /// A dictionary of indicator types to their calculated values. - Task> CalculateIndicatorsValuesAsync( - Scenario scenario, - HashSet candles); Task GetIndicatorByNameUserAsync(string name, User user); Task GetScenarioByNameUserAsync(string scenarioName, User user); diff --git a/src/Managing.Application/Backtests/BacktestExecutor.cs b/src/Managing.Application/Backtests/BacktestExecutor.cs index ee7bf5ba..bc2c42cb 100644 --- a/src/Managing.Application/Backtests/BacktestExecutor.cs +++ b/src/Managing.Application/Backtests/BacktestExecutor.cs @@ -4,7 +4,6 @@ using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Bots; using Managing.Common; -using Managing.Core; using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.Candles; @@ -186,21 +185,11 @@ public class BacktestExecutor _logger.LogInformation("⚡ Pre-calculating indicator values for {IndicatorCount} indicators", config.Scenario.Indicators?.Count ?? 0); - // Convert LightScenario to Scenario for CalculateIndicatorsValuesAsync + // Convert LightScenario to Scenario for CalculateIndicatorsValues var scenario = config.Scenario.ToScenario(); // Calculate all indicator values once with all candles - preCalculatedIndicatorValues = await ServiceScopeHelpers - .WithScopedService>( - _scopeFactory, - async tradingService => - { - return await tradingService.CalculateIndicatorsValuesAsync(scenario, candles); - }); - - // Store pre-calculated values in trading bot for use during signal generation - tradingBot.PreCalculatedIndicatorValues = preCalculatedIndicatorValues; - + preCalculatedIndicatorValues = TradingBox.CalculateIndicatorsValues(scenario, candles); telemetry.IndicatorPreCalculationTime = Stopwatch.GetElapsedTime(indicatorCalcStart); _logger.LogInformation( "✅ Successfully pre-calculated indicator values for {IndicatorCount} indicator types in {Duration:F2}ms", @@ -264,7 +253,7 @@ public class BacktestExecutor // Run with optimized backtest path (minimize async calls) var backtestStepStart = Stopwatch.GetTimestamp(); - await tradingBot.UpdateSignals(fixedCandles); + await tradingBot.UpdateSignals(fixedCandles, preCalculatedIndicatorValues); await tradingBot.Run(); backtestStepTotalTime += Stopwatch.GetElapsedTime(backtestStepStart); diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index e103bc27..666bb1b5 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -45,13 +45,6 @@ public class TradingBotBase : ITradingBot public Candle LastCandle { get; set; } public DateTime? LastPositionClosingTime { get; set; } - /// - /// Pre-calculated indicator values for backtesting optimization. - /// Key is IndicatorType, Value is the calculated indicator result. - /// - public Dictionary PreCalculatedIndicatorValues { get; set; } - - public TradingBotBase( ILogger logger, IServiceScopeFactory scopeFactory, @@ -65,7 +58,6 @@ public class TradingBotBase : ITradingBot Positions = new Dictionary(); WalletBalances = new Dictionary(); PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe); - PreCalculatedIndicatorValues = new Dictionary(); } public async Task Start(BotStatus previousStatus) @@ -264,7 +256,7 @@ public class TradingBotBase : ITradingBot } public async Task UpdateSignals(HashSet candles, - Dictionary preCalculatedSignals = null) + Dictionary preCalculatedIndicatorValues = null) { // Skip indicator checking if flipping is disabled and there's an open position // This prevents unnecessary indicator calculations when we can't act on signals anyway @@ -285,7 +277,7 @@ public class TradingBotBase : ITradingBot if (Config.IsForBacktest) { var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod, - PreCalculatedIndicatorValues); + preCalculatedIndicatorValues); if (backtestSignal == null) return; await AddSignal(backtestSignal); } diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs index 27ea2208..afa74cda 100644 --- a/src/Managing.Application/Trading/TradingService.cs +++ b/src/Managing.Application/Trading/TradingService.cs @@ -2,13 +2,11 @@ using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; using Managing.Domain.Bots; -using Managing.Domain.Candles; using Managing.Domain.Indicators; using Managing.Domain.Scenarios; using Managing.Domain.Shared.Helpers; using Managing.Domain.Statistics; using Managing.Domain.Strategies; -using Managing.Domain.Strategies.Base; using Managing.Domain.Synth.Models; using Managing.Domain.Trades; using Managing.Domain.Users; @@ -429,44 +427,6 @@ public class TradingService : ITradingService positionIdentifier, botConfig); } - /// - /// Calculates indicators values for a given scenario and candles. - /// - /// The scenario containing indicators. - /// The candles to calculate indicators for. - /// A dictionary of indicator types to their calculated values. - public async Task> CalculateIndicatorsValuesAsync( - Scenario scenario, - HashSet candles) - { - // Offload CPU-bound indicator calculations to thread pool - return await Task.Run(() => - { - var indicatorsValues = new Dictionary(); - - if (scenario?.Indicators == null || scenario.Indicators.Count == 0) - { - return indicatorsValues; - } - - // Build indicators from scenario - foreach (var indicator in scenario.Indicators) - { - try - { - var buildedIndicator = ScenarioHelpers.BuildIndicator(ScenarioHelpers.BaseToLight(indicator)); - indicatorsValues[indicator.Type] = buildedIndicator.GetIndicatorValues(candles); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error calculating indicator {IndicatorName}: {ErrorMessage}", - indicator.Name, ex.Message); - } - } - - return indicatorsValues; - }); - } public async Task GetIndicatorByNameUserAsync(string name, User user) { diff --git a/src/Managing.Domain/Indicators/Signals/RsiDivergenceIndicatorBase.cs b/src/Managing.Domain/Indicators/Signals/RsiDivergenceIndicatorBase.cs index 0c2d7c3c..2bacbfe6 100644 --- a/src/Managing.Domain/Indicators/Signals/RsiDivergenceIndicatorBase.cs +++ b/src/Managing.Domain/Indicators/Signals/RsiDivergenceIndicatorBase.cs @@ -65,10 +65,9 @@ public class RsiDivergenceIndicatorBase : IndicatorBase if (preCalculatedValues?.Rsi != null && preCalculatedValues.Rsi.Any()) { // Filter pre-calculated RSI values to match the candles we're processing - var relevantCandles = candles.TakeLast(10 * Period.Value); + var lastCandle = candles.Last(); rsiResult = preCalculatedValues.Rsi - .Where(r => relevantCandles.Any(c => c.Date == r.Date)) - .OrderBy(r => r.Date) + .Where(r => r.Date <= lastCandle.Date) .ToList(); } diff --git a/src/Managing.Domain/Shared/Helpers/TradingBox.cs b/src/Managing.Domain/Shared/Helpers/TradingBox.cs index ec2cfcc8..9d992c69 100644 --- a/src/Managing.Domain/Shared/Helpers/TradingBox.cs +++ b/src/Managing.Domain/Shared/Helpers/TradingBox.cs @@ -788,4 +788,39 @@ public static class TradingBox return (wins, losses); } + + /// + /// Calculates indicators values for a given scenario and candles. + /// + /// The scenario containing indicators. + /// The candles to calculate indicators for. + /// A dictionary of indicator types to their calculated values. + public static Dictionary CalculateIndicatorsValues( + Scenario scenario, + HashSet candles) + { + var indicatorsValues = new Dictionary(); + + if (scenario?.Indicators == null || scenario.Indicators.Count == 0) + { + return indicatorsValues; + } + + // Build indicators from scenario + foreach (var indicator in scenario.Indicators) + { + try + { + var buildedIndicator = ScenarioHelpers.BuildIndicator(ScenarioHelpers.BaseToLight(indicator)); + indicatorsValues[indicator.Type] = buildedIndicator.GetIndicatorValues(candles); + } + catch (Exception ex) + { + // Removed logging for performance in static method + // Consider adding logging back if error handling is needed + } + } + + return indicatorsValues; + } } \ No newline at end of file