Add bundle backtest

This commit is contained in:
2025-07-21 17:03:27 +07:00
parent 0870edee61
commit 6f49f2659f
20 changed files with 492 additions and 132 deletions

View File

@@ -1,8 +1,11 @@
using System.Text.Json;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -13,12 +16,11 @@ namespace Managing.Application.Workers;
/// </summary>
public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
{
private readonly IBacktestRepository _backtestRepository;
// Removed direct repository usage for bundle requests
private readonly IBacktester _backtester;
private static readonly WorkerType _workerType = WorkerType.BundleBacktest;
public BundleBacktestWorker(
IBacktestRepository backtestRepository,
IBacktester backtester,
ILogger<BundleBacktestWorker> logger,
IWorkerService workerService) : base(
@@ -27,7 +29,6 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
TimeSpan.FromMinutes(1),
workerService)
{
_backtestRepository = backtestRepository;
_backtester = backtester;
}
@@ -36,7 +37,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
try
{
// Get pending bundle backtest requests
var pendingRequests = _backtestRepository.GetPendingBundleBacktestRequests();
var pendingRequests = _backtester.GetPendingBundleBacktestRequests();
foreach (var bundleRequest in pendingRequests)
{
@@ -61,10 +62,12 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
// Update status to running
bundleRequest.Status = BundleBacktestRequestStatus.Running;
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
_backtester.UpdateBundleBacktestRequest(bundleRequest);
// Deserialize the backtest requests as dynamic objects
var backtestRequests = JsonSerializer.Deserialize<List<JsonElement>>(bundleRequest.BacktestRequestsJson);
// Deserialize the backtest requests as strongly-typed objects
var backtestRequests =
JsonSerializer.Deserialize<List<RunBacktestRequest>>(
bundleRequest.BacktestRequestsJson);
if (backtestRequests == null)
{
throw new InvalidOperationException("Failed to deserialize backtest requests");
@@ -78,28 +81,27 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
try
{
var requestElement = backtestRequests[i];
var runBacktestRequest = backtestRequests[i];
// Update current backtest being processed
bundleRequest.CurrentBacktest = $"Backtest {i + 1} of {backtestRequests.Count}";
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
_backtester.UpdateBundleBacktestRequest(bundleRequest);
// Convert JSON element to domain model and run backtest
await RunSingleBacktest(requestElement, bundleRequest.RequestId, cancellationToken);
// Run the backtest directly with the strongly-typed request
await RunSingleBacktest(runBacktestRequest, bundleRequest, i, cancellationToken);
// Update progress
bundleRequest.CompletedBacktests++;
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
_backtester.UpdateBundleBacktestRequest(bundleRequest);
_logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
_logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
i + 1, bundleRequest.RequestId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
_logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
i + 1, bundleRequest.RequestId);
bundleRequest.FailedBacktests++;
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
_backtester.UpdateBundleBacktestRequest(bundleRequest);
}
}
@@ -121,36 +123,120 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
bundleRequest.CompletedAt = DateTime.UtcNow;
bundleRequest.CurrentBacktest = null;
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
_backtester.UpdateBundleBacktestRequest(bundleRequest);
_logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
_logger.LogInformation("Completed processing bundle backtest request {RequestId} with status {Status}",
bundleRequest.RequestId, bundleRequest.Status);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing bundle backtest request {RequestId}", bundleRequest.RequestId);
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
bundleRequest.ErrorMessage = ex.Message;
bundleRequest.CompletedAt = DateTime.UtcNow;
_backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
_backtester.UpdateBundleBacktestRequest(bundleRequest);
}
}
private async Task RunSingleBacktest(JsonElement requestElement, string bundleRequestId, CancellationToken cancellationToken)
// Change RunSingleBacktest to accept RunBacktestRequest directly
private async Task RunSingleBacktest(RunBacktestRequest runBacktestRequest, BundleBacktestRequest bundleRequest,
int index, CancellationToken cancellationToken)
{
// For now, we'll use a simplified approach that simulates backtest execution
// In a real implementation, you would parse the JSON and convert to domain models
// Simulate backtest processing time
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
_logger.LogInformation("Processed backtest for bundle request {RequestId}", bundleRequestId);
// TODO: Implement actual backtest execution by:
// 1. Parsing the JSON request element
// 2. Converting to TradingBotConfig domain model
// 3. Calling _backtester.RunTradingBotBacktest with proper parameters
// 4. Handling the results and saving to database if needed
if (runBacktestRequest == null || runBacktestRequest.Config == null)
{
_logger.LogError("Invalid RunBacktestRequest in bundle (null config)");
return;
}
// Map MoneyManagement
MoneyManagement moneyManagement = null;
if (!string.IsNullOrEmpty(runBacktestRequest.Config.MoneyManagementName))
{
// In worker context, we cannot resolve by name (no user/db), so skip or set null
// Optionally, log a warning
_logger.LogWarning("MoneyManagementName provided but cannot resolve in worker context: {Name}",
(string)runBacktestRequest.Config.MoneyManagementName);
}
else if (runBacktestRequest.Config.MoneyManagement != null)
{
var mmReq = runBacktestRequest.Config.MoneyManagement;
moneyManagement = new MoneyManagement
{
Name = mmReq.Name,
Timeframe = mmReq.Timeframe,
StopLoss = mmReq.StopLoss,
TakeProfit = mmReq.TakeProfit,
Leverage = mmReq.Leverage
};
moneyManagement.FormatPercentage();
}
// Map Scenario
Scenario scenario = null;
if (runBacktestRequest.Config.Scenario != null)
{
var sReq = runBacktestRequest.Config.Scenario;
scenario = new Scenario(sReq.Name, sReq.LoopbackPeriod)
{
User = null // No user context in worker
};
foreach (var indicatorRequest in sReq.Indicators)
{
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
{
SignalType = indicatorRequest.SignalType,
MinimumHistory = indicatorRequest.MinimumHistory,
Period = indicatorRequest.Period,
FastPeriods = indicatorRequest.FastPeriods,
SlowPeriods = indicatorRequest.SlowPeriods,
SignalPeriods = indicatorRequest.SignalPeriods,
Multiplier = indicatorRequest.Multiplier,
SmoothPeriods = indicatorRequest.SmoothPeriods,
StochPeriods = indicatorRequest.StochPeriods,
CyclePeriods = indicatorRequest.CyclePeriods,
User = null // No user context in worker
};
scenario.AddIndicator(indicator);
}
}
// Map TradingBotConfig
var backtestConfig = new TradingBotConfig
{
AccountName = runBacktestRequest.Config.AccountName,
MoneyManagement = moneyManagement,
Ticker = runBacktestRequest.Config.Ticker,
ScenarioName = runBacktestRequest.Config.ScenarioName,
Scenario = scenario,
Timeframe = runBacktestRequest.Config.Timeframe,
IsForWatchingOnly = runBacktestRequest.Config.IsForWatchingOnly,
BotTradingBalance = runBacktestRequest.Config.BotTradingBalance,
IsForBacktest = true,
CooldownPeriod = runBacktestRequest.Config.CooldownPeriod,
MaxLossStreak = runBacktestRequest.Config.MaxLossStreak,
MaxPositionTimeHours = runBacktestRequest.Config.MaxPositionTimeHours,
FlipOnlyWhenInProfit = runBacktestRequest.Config.FlipOnlyWhenInProfit,
FlipPosition = runBacktestRequest.Config.FlipPosition,
Name = $"{bundleRequest.Name} #{index + 1}",
CloseEarlyWhenProfitable = runBacktestRequest.Config.CloseEarlyWhenProfitable,
UseSynthApi = runBacktestRequest.Config.UseSynthApi,
UseForPositionSizing = runBacktestRequest.Config.UseForPositionSizing,
UseForSignalFiltering = runBacktestRequest.Config.UseForSignalFiltering,
UseForDynamicStopLoss = runBacktestRequest.Config.UseForDynamicStopLoss
};
// Run the backtest (no user context)
var result = await _backtester.RunTradingBotBacktest(
backtestConfig,
runBacktestRequest.StartDate,
runBacktestRequest.EndDate,
null, // No user context in worker
runBacktestRequest.Save,
runBacktestRequest.WithCandles,
bundleRequest.RequestId // Use bundleRequestId as requestId for traceability
);
_logger.LogInformation("Processed backtest for bundle request {RequestId}", bundleRequest.RequestId);
}
}
}