Update precalculated indicators values

This commit is contained in:
2025-11-12 23:26:12 +07:00
parent a8f55c80a9
commit 3b176c290c
7 changed files with 44 additions and 79 deletions

View File

@@ -9,6 +9,7 @@ using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base; using Managing.Domain.Strategies.Base;
@@ -346,7 +347,7 @@ public class DataController : ControllerBase
{ {
// Map ScenarioRequest to domain Scenario object // Map ScenarioRequest to domain Scenario object
var domainScenario = MapScenarioRequestToScenario(request.Scenario); var domainScenario = MapScenarioRequestToScenario(request.Scenario);
indicatorsValues = await _tradingService.CalculateIndicatorsValuesAsync(domainScenario, candles); indicatorsValues = TradingBox.CalculateIndicatorsValues(domainScenario, candles);
} }
return Ok(new CandlesWithIndicatorsResponse return Ok(new CandlesWithIndicatorsResponse

View File

@@ -1,11 +1,9 @@
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators; using Managing.Domain.Indicators;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Synth.Models; using Managing.Domain.Synth.Models;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users; using Managing.Domain.Users;
@@ -52,15 +50,6 @@ public interface ITradingService
Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice, Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
decimal liquidationPrice, Guid positionIdentifier, TradingBotConfig botConfig); decimal liquidationPrice, Guid positionIdentifier, TradingBotConfig botConfig);
/// <summary>
/// Calculates indicators values for a given scenario and candles.
/// </summary>
/// <param name="scenario">The scenario containing indicators.</param>
/// <param name="candles">The candles to calculate indicators for.</param>
/// <returns>A dictionary of indicator types to their calculated values.</returns>
Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
Scenario scenario,
HashSet<Candle> candles);
Task<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user); Task<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user);
Task<Scenario?> GetScenarioByNameUserAsync(string scenarioName, User user); Task<Scenario?> GetScenarioByNameUserAsync(string scenarioName, User user);

View File

@@ -4,7 +4,6 @@ using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Bots; using Managing.Application.Bots;
using Managing.Common; using Managing.Common;
using Managing.Core;
using Managing.Domain.Backtests; using Managing.Domain.Backtests;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
@@ -186,21 +185,11 @@ public class BacktestExecutor
_logger.LogInformation("⚡ Pre-calculating indicator values for {IndicatorCount} indicators", _logger.LogInformation("⚡ Pre-calculating indicator values for {IndicatorCount} indicators",
config.Scenario.Indicators?.Count ?? 0); config.Scenario.Indicators?.Count ?? 0);
// Convert LightScenario to Scenario for CalculateIndicatorsValuesAsync // Convert LightScenario to Scenario for CalculateIndicatorsValues
var scenario = config.Scenario.ToScenario(); var scenario = config.Scenario.ToScenario();
// Calculate all indicator values once with all candles // Calculate all indicator values once with all candles
preCalculatedIndicatorValues = await ServiceScopeHelpers preCalculatedIndicatorValues = TradingBox.CalculateIndicatorsValues(scenario, candles);
.WithScopedService<ITradingService, Dictionary<IndicatorType, IndicatorsResultBase>>(
_scopeFactory,
async tradingService =>
{
return await tradingService.CalculateIndicatorsValuesAsync(scenario, candles);
});
// Store pre-calculated values in trading bot for use during signal generation
tradingBot.PreCalculatedIndicatorValues = preCalculatedIndicatorValues;
telemetry.IndicatorPreCalculationTime = Stopwatch.GetElapsedTime(indicatorCalcStart); telemetry.IndicatorPreCalculationTime = Stopwatch.GetElapsedTime(indicatorCalcStart);
_logger.LogInformation( _logger.LogInformation(
"✅ Successfully pre-calculated indicator values for {IndicatorCount} indicator types in {Duration:F2}ms", "✅ 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) // Run with optimized backtest path (minimize async calls)
var backtestStepStart = Stopwatch.GetTimestamp(); var backtestStepStart = Stopwatch.GetTimestamp();
await tradingBot.UpdateSignals(fixedCandles); await tradingBot.UpdateSignals(fixedCandles, preCalculatedIndicatorValues);
await tradingBot.Run(); await tradingBot.Run();
backtestStepTotalTime += Stopwatch.GetElapsedTime(backtestStepStart); backtestStepTotalTime += Stopwatch.GetElapsedTime(backtestStepStart);

View File

@@ -45,13 +45,6 @@ public class TradingBotBase : ITradingBot
public Candle LastCandle { get; set; } public Candle LastCandle { get; set; }
public DateTime? LastPositionClosingTime { get; set; } public DateTime? LastPositionClosingTime { get; set; }
/// <summary>
/// Pre-calculated indicator values for backtesting optimization.
/// Key is IndicatorType, Value is the calculated indicator result.
/// </summary>
public Dictionary<IndicatorType, IndicatorsResultBase> PreCalculatedIndicatorValues { get; set; }
public TradingBotBase( public TradingBotBase(
ILogger<TradingBotBase> logger, ILogger<TradingBotBase> logger,
IServiceScopeFactory scopeFactory, IServiceScopeFactory scopeFactory,
@@ -65,7 +58,6 @@ public class TradingBotBase : ITradingBot
Positions = new Dictionary<Guid, Position>(); Positions = new Dictionary<Guid, Position>();
WalletBalances = new Dictionary<DateTime, decimal>(); WalletBalances = new Dictionary<DateTime, decimal>();
PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe); PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe);
PreCalculatedIndicatorValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
} }
public async Task Start(BotStatus previousStatus) public async Task Start(BotStatus previousStatus)
@@ -264,7 +256,7 @@ public class TradingBotBase : ITradingBot
} }
public async Task UpdateSignals(HashSet<Candle> candles, public async Task UpdateSignals(HashSet<Candle> candles,
Dictionary<DateTime, LightSignal> preCalculatedSignals = null) Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues = null)
{ {
// Skip indicator checking if flipping is disabled and there's an open position // 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 // This prevents unnecessary indicator calculations when we can't act on signals anyway
@@ -285,7 +277,7 @@ public class TradingBotBase : ITradingBot
if (Config.IsForBacktest) if (Config.IsForBacktest)
{ {
var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod, var backtestSignal = TradingBox.GetSignal(candles, Config.Scenario, Signals, Config.Scenario.LoopbackPeriod,
PreCalculatedIndicatorValues); preCalculatedIndicatorValues);
if (backtestSignal == null) return; if (backtestSignal == null) return;
await AddSignal(backtestSignal); await AddSignal(backtestSignal);
} }

View File

@@ -2,13 +2,11 @@
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Indicators; using Managing.Domain.Indicators;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Synth.Models; using Managing.Domain.Synth.Models;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users; using Managing.Domain.Users;
@@ -429,44 +427,6 @@ public class TradingService : ITradingService
positionIdentifier, botConfig); positionIdentifier, botConfig);
} }
/// <summary>
/// Calculates indicators values for a given scenario and candles.
/// </summary>
/// <param name="scenario">The scenario containing indicators.</param>
/// <param name="candles">The candles to calculate indicators for.</param>
/// <returns>A dictionary of indicator types to their calculated values.</returns>
public async Task<Dictionary<IndicatorType, IndicatorsResultBase>> CalculateIndicatorsValuesAsync(
Scenario scenario,
HashSet<Candle> candles)
{
// Offload CPU-bound indicator calculations to thread pool
return await Task.Run(() =>
{
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
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<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user) public async Task<IndicatorBase?> GetIndicatorByNameUserAsync(string name, User user)
{ {

View File

@@ -65,10 +65,9 @@ public class RsiDivergenceIndicatorBase : IndicatorBase
if (preCalculatedValues?.Rsi != null && preCalculatedValues.Rsi.Any()) if (preCalculatedValues?.Rsi != null && preCalculatedValues.Rsi.Any())
{ {
// Filter pre-calculated RSI values to match the candles we're processing // 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 rsiResult = preCalculatedValues.Rsi
.Where(r => relevantCandles.Any(c => c.Date == r.Date)) .Where(r => r.Date <= lastCandle.Date)
.OrderBy(r => r.Date)
.ToList(); .ToList();
} }

View File

@@ -788,4 +788,39 @@ public static class TradingBox
return (wins, losses); return (wins, losses);
} }
/// <summary>
/// Calculates indicators values for a given scenario and candles.
/// </summary>
/// <param name="scenario">The scenario containing indicators.</param>
/// <param name="candles">The candles to calculate indicators for.</param>
/// <returns>A dictionary of indicator types to their calculated values.</returns>
public static Dictionary<IndicatorType, IndicatorsResultBase> CalculateIndicatorsValues(
Scenario scenario,
HashSet<Candle> candles)
{
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
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;
}
} }