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.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

View File

@@ -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<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
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<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.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<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;
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);

View File

@@ -45,13 +45,6 @@ public class TradingBotBase : ITradingBot
public Candle LastCandle { 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(
ILogger<TradingBotBase> logger,
IServiceScopeFactory scopeFactory,
@@ -65,7 +58,6 @@ public class TradingBotBase : ITradingBot
Positions = new Dictionary<Guid, Position>();
WalletBalances = new Dictionary<DateTime, decimal>();
PreloadSince = CandleHelpers.GetBotPreloadSinceFromTimeframe(config.Timeframe);
PreCalculatedIndicatorValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
}
public async Task Start(BotStatus previousStatus)
@@ -264,7 +256,7 @@ public class TradingBotBase : ITradingBot
}
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
// 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);
}

View File

@@ -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);
}
/// <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)
{

View File

@@ -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();
}

View File

@@ -788,4 +788,39 @@ public static class TradingBox
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;
}
}