Add bundle backtest refact + fix whitelist

This commit is contained in:
2025-10-12 14:40:20 +07:00
parent 4543246871
commit 5acc77650f
21 changed files with 2961 additions and 628 deletions

View File

@@ -419,21 +419,30 @@ public class BacktestController : BaseController
/// Creates a bundle backtest request with the specified configurations.
/// This endpoint creates a request that will be processed by a background worker.
/// </summary>
/// <param name="requests">The list of backtest requests to execute.</param>
/// <param name="name">Display name for the bundle (required).</param>
/// <param name="request">The bundle backtest request with variant lists.</param>
/// <returns>The bundle backtest request with ID for tracking progress.</returns>
[HttpPost]
[Route("BacktestBundle")]
public async Task<ActionResult<BundleBacktestRequest>> RunBundle([FromBody] RunBundleBacktestRequest request)
{
if (request?.Requests == null || !request.Requests.Any())
if (request?.UniversalConfig == null)
{
return BadRequest("At least one backtest request is required");
return BadRequest("Universal configuration is required");
}
if (request.Requests.Count > 10)
if (request.DateTimeRanges == null || !request.DateTimeRanges.Any())
{
return BadRequest("Maximum of 10 backtests allowed per bundle request");
return BadRequest("At least one DateTime range is required");
}
if (request.MoneyManagementVariants == null || !request.MoneyManagementVariants.Any())
{
return BadRequest("At least one money management variant is required");
}
if (request.TickerVariants == null || !request.TickerVariants.Any())
{
return BadRequest("At least one ticker variant is required");
}
if (string.IsNullOrWhiteSpace(request.Name))
@@ -441,32 +450,35 @@ public class BacktestController : BaseController
return BadRequest("Bundle name is required");
}
// Calculate total number of backtests
var totalBacktests = request.DateTimeRanges.Count * request.MoneyManagementVariants.Count * request.TickerVariants.Count;
if (totalBacktests > 100)
{
return BadRequest("Maximum of 100 backtests allowed per bundle request");
}
try
{
var user = await GetUser();
// Validate all requests before creating the bundle
foreach (var req in request.Requests)
// Validate universal configuration
if (string.IsNullOrEmpty(request.UniversalConfig.AccountName))
{
if (req?.Config == null)
{
return BadRequest("Invalid request: Configuration is required");
}
return BadRequest("Account name is required in universal configuration");
}
if (string.IsNullOrEmpty(req.Config.AccountName))
{
return BadRequest("Invalid request: Account name is required");
}
if (string.IsNullOrEmpty(request.UniversalConfig.ScenarioName) && request.UniversalConfig.Scenario == null)
{
return BadRequest("Either scenario name or scenario object is required in universal configuration");
}
if (string.IsNullOrEmpty(req.Config.ScenarioName) && req.Config.Scenario == null)
// Validate all money management variants
foreach (var mmVariant in request.MoneyManagementVariants)
{
if (mmVariant.MoneyManagement == null)
{
return BadRequest("Invalid request: Either scenario name or scenario object is required");
}
if (string.IsNullOrEmpty(req.Config.MoneyManagementName) && req.Config.MoneyManagement == null)
{
return BadRequest(
"Invalid request: Either money management name or money management object is required");
return BadRequest("Each money management variant must have a money management object");
}
}
@@ -474,8 +486,11 @@ public class BacktestController : BaseController
var bundleRequest = new BundleBacktestRequest
{
User = user,
BacktestRequestsJson = JsonSerializer.Serialize(request.Requests),
TotalBacktests = request.Requests.Count,
UniversalConfigJson = JsonSerializer.Serialize(request.UniversalConfig),
DateTimeRangesJson = JsonSerializer.Serialize(request.DateTimeRanges),
MoneyManagementVariantsJson = JsonSerializer.Serialize(request.MoneyManagementVariants),
TickerVariantsJson = JsonSerializer.Serialize(request.TickerVariants),
TotalBacktests = totalBacktests,
CompletedBacktests = 0,
FailedBacktests = 0,
Status = BundleBacktestRequestStatus.Pending,
@@ -491,6 +506,72 @@ public class BacktestController : BaseController
}
}
/// <summary>
/// Generates individual backtest requests from variant configuration
/// </summary>
/// <param name="request">The bundle backtest request</param>
/// <returns>List of individual backtest requests</returns>
private List<RunBacktestRequest> GenerateBacktestRequests(RunBundleBacktestRequest request)
{
var backtestRequests = new List<RunBacktestRequest>();
foreach (var dateRange in request.DateTimeRanges)
{
foreach (var mmVariant in request.MoneyManagementVariants)
{
foreach (var ticker in request.TickerVariants)
{
var config = new TradingBotConfigRequest
{
AccountName = request.UniversalConfig.AccountName,
Ticker = ticker,
Timeframe = request.UniversalConfig.Timeframe,
IsForWatchingOnly = request.UniversalConfig.IsForWatchingOnly,
BotTradingBalance = request.UniversalConfig.BotTradingBalance,
Name = $"{request.UniversalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}",
FlipPosition = request.UniversalConfig.FlipPosition,
CooldownPeriod = request.UniversalConfig.CooldownPeriod,
MaxLossStreak = request.UniversalConfig.MaxLossStreak,
Scenario = request.UniversalConfig.Scenario,
ScenarioName = request.UniversalConfig.ScenarioName,
MoneyManagement = mmVariant.MoneyManagement,
MaxPositionTimeHours = request.UniversalConfig.MaxPositionTimeHours,
CloseEarlyWhenProfitable = request.UniversalConfig.CloseEarlyWhenProfitable,
FlipOnlyWhenInProfit = request.UniversalConfig.FlipOnlyWhenInProfit,
UseSynthApi = request.UniversalConfig.UseSynthApi,
UseForPositionSizing = request.UniversalConfig.UseForPositionSizing,
UseForSignalFiltering = request.UniversalConfig.UseForSignalFiltering,
UseForDynamicStopLoss = request.UniversalConfig.UseForDynamicStopLoss
};
var backtestRequest = new RunBacktestRequest
{
Config = config,
StartDate = dateRange.StartDate,
EndDate = dateRange.EndDate,
Balance = request.UniversalConfig.BotTradingBalance,
WatchOnly = request.UniversalConfig.WatchOnly,
Save = request.UniversalConfig.Save,
WithCandles = request.UniversalConfig.WithCandles,
MoneyManagement = mmVariant.MoneyManagement != null ?
new MoneyManagement
{
Name = mmVariant.MoneyManagement.Name,
Timeframe = mmVariant.MoneyManagement.Timeframe,
StopLoss = mmVariant.MoneyManagement.StopLoss,
TakeProfit = mmVariant.MoneyManagement.TakeProfit,
Leverage = mmVariant.MoneyManagement.Leverage
} : null
};
backtestRequests.Add(backtestRequest);
}
}
}
return backtestRequests;
}
/// <summary>
/// Retrieves all bundle backtest requests for the authenticated user.
/// </summary>
@@ -741,35 +822,3 @@ public class BacktestController : BaseController
};
}
}
/// <summary>
/// Request model for running a backtest
/// </summary>
public class RunBacktestRequest
{
/// <summary>
/// The trading bot configuration request to use for the backtest
/// </summary>
public TradingBotConfigRequest Config { get; set; }
/// <summary>
/// The start date for the backtest
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// The end date for the backtest
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// Whether to save the backtest results
/// </summary>
public bool Save { get; set; } = false;
/// <summary>
/// Whether to include candles and indicators values in the response.
/// Set to false to reduce response size dramatically.
/// </summary>
public bool WithCandles { get; set; } = false;
}

View File

@@ -1,11 +1,41 @@
using System.ComponentModel.DataAnnotations;
using Managing.Api.Controllers;
using Managing.Domain.Backtests;
using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests;
/// <summary>
/// Request model for running bundle backtests with variant lists instead of individual requests
/// </summary>
public class RunBundleBacktestRequest
{
[Required] public string Name { get; set; } = string.Empty;
/// <summary>
/// Display name for the bundle backtest
/// </summary>
[Required]
public string Name { get; set; } = string.Empty;
[Required] public List<RunBacktestRequest> Requests { get; set; } = new();
/// <summary>
/// Universal configuration that applies to all backtests in the bundle
/// </summary>
[Required]
public BundleBacktestUniversalConfig UniversalConfig { get; set; } = new();
/// <summary>
/// List of DateTime ranges to test (each range will generate a separate backtest)
/// </summary>
[Required]
public List<DateTimeRange> DateTimeRanges { get; set; } = new();
/// <summary>
/// List of money management configurations to test (each will generate a separate backtest)
/// </summary>
[Required]
public List<MoneyManagementVariant> MoneyManagementVariants { get; set; } = new();
/// <summary>
/// List of tickers to test (each will generate a separate backtest)
/// </summary>
[Required]
public List<Ticker> TickerVariants { get; set; } = new();
}