diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs
index d897815..d03de06 100644
--- a/src/Managing.Api/Controllers/BacktestController.cs
+++ b/src/Managing.Api/Controllers/BacktestController.cs
@@ -1,7 +1,6 @@
using System.Text.Json;
using Managing.Api.Models.Requests;
using Managing.Application.Abstractions;
-using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Domain.Backtests;
@@ -12,6 +11,7 @@ using Managing.Domain.Strategies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
+using MoneyManagementRequest = Managing.Domain.Backtests.MoneyManagementRequest;
namespace Managing.Api.Controllers;
@@ -33,7 +33,6 @@ public class BacktestController : BaseController
private readonly IAccountService _accountService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IGeneticService _geneticService;
- private readonly IBacktestRepository _backtestRepository;
///
/// Initializes a new instance of the class.
@@ -52,7 +51,6 @@ public class BacktestController : BaseController
IAccountService accountService,
IMoneyManagementService moneyManagementService,
IGeneticService geneticService,
- IBacktestRepository backtestRepository,
IUserService userService) : base(userService)
{
_hubContext = hubContext;
@@ -61,7 +59,6 @@ public class BacktestController : BaseController
_accountService = accountService;
_moneyManagementService = moneyManagementService;
_geneticService = geneticService;
- _backtestRepository = backtestRepository;
}
///
@@ -153,8 +150,8 @@ public class BacktestController : BaseController
[HttpGet]
[Route("ByRequestId/{requestId}/Paginated")]
public async Task> GetBacktestsByRequestIdPaginated(
- string requestId,
- int page = 1,
+ string requestId,
+ int page = 1,
int pageSize = 50,
string sortBy = "score",
string sortOrder = "desc")
@@ -179,10 +176,11 @@ public class BacktestController : BaseController
return BadRequest("Sort order must be 'asc' or 'desc'");
}
- var (backtests, totalCount) = _backtester.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder);
-
+ var (backtests, totalCount) =
+ _backtester.GetBacktestsByRequestIdPaginated(requestId, page, pageSize, sortBy, sortOrder);
+
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
-
+
var response = new PaginatedBacktestsResponse
{
Backtests = backtests.Select(b => new LightBacktestResponse
@@ -410,10 +408,12 @@ public class BacktestController : BaseController
/// This endpoint creates a request that will be processed by a background worker.
///
/// The list of backtest requests to execute.
+ /// Display name for the bundle (required).
/// The bundle backtest request with ID for tracking progress.
[HttpPost]
[Route("Bundle")]
- public async Task> RunBundle([FromBody] List requests)
+ public async Task> RunBundle([FromBody] List requests,
+ [FromQuery] string name)
{
if (requests == null || !requests.Any())
{
@@ -425,10 +425,15 @@ public class BacktestController : BaseController
return BadRequest("Maximum of 10 backtests allowed per bundle request");
}
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return BadRequest("Bundle name is required");
+ }
+
try
{
var user = await GetUser();
-
+
// Validate all requests before creating the bundle
foreach (var request in requests)
{
@@ -449,7 +454,8 @@ public class BacktestController : BaseController
if (string.IsNullOrEmpty(request.Config.MoneyManagementName) && request.Config.MoneyManagement == null)
{
- return BadRequest("Invalid request: Either money management name or money management object is required");
+ return BadRequest(
+ "Invalid request: Either money management name or money management object is required");
}
}
@@ -461,10 +467,11 @@ public class BacktestController : BaseController
TotalBacktests = requests.Count,
CompletedBacktests = 0,
FailedBacktests = 0,
- Status = BundleBacktestRequestStatus.Pending
+ Status = BundleBacktestRequestStatus.Pending,
+ Name = name
};
- _backtestRepository.InsertBundleBacktestRequestForUser(user, bundleRequest);
+ _backtester.InsertBundleBacktestRequestForUser(user, bundleRequest);
return Ok(bundleRequest);
}
@@ -483,7 +490,7 @@ public class BacktestController : BaseController
public async Task>> GetBundleBacktestRequests()
{
var user = await GetUser();
- var bundleRequests = _backtestRepository.GetBundleBacktestRequestsByUser(user);
+ var bundleRequests = _backtester.GetBundleBacktestRequestsByUser(user);
return Ok(bundleRequests);
}
@@ -497,7 +504,7 @@ public class BacktestController : BaseController
public async Task> GetBundleBacktestRequest(string id)
{
var user = await GetUser();
- var bundleRequest = _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
+ var bundleRequest = _backtester.GetBundleBacktestRequestByIdForUser(user, id);
if (bundleRequest == null)
{
@@ -518,16 +525,17 @@ public class BacktestController : BaseController
public async Task DeleteBundleBacktestRequest(string id)
{
var user = await GetUser();
-
+
// First, delete the bundle request
- _backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
-
+ _backtester.DeleteBundleBacktestRequestByIdForUser(user, id);
+
// Then, delete all related backtests
var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id);
-
- return Ok(new {
- BundleRequestDeleted = true,
- RelatedBacktestsDeleted = backtestsDeleted
+
+ return Ok(new
+ {
+ BundleRequestDeleted = true,
+ RelatedBacktestsDeleted = backtestsDeleted
});
}
@@ -641,21 +649,21 @@ public class BacktestController : BaseController
public async Task DeleteGeneticRequest(string id)
{
var user = await GetUser();
-
+
// First, delete the genetic request
_geneticService.DeleteGeneticRequestByIdForUser(user, id);
-
+
// Then, delete all related backtests
var backtestsDeleted = _backtester.DeleteBacktestsByRequestId(id);
-
- return Ok(new {
- GeneticRequestDeleted = true,
- RelatedBacktestsDeleted = backtestsDeleted
+
+ return Ok(new
+ {
+ GeneticRequestDeleted = true,
+ RelatedBacktestsDeleted = backtestsDeleted
});
}
-
///
/// Notifies subscribers about the backtesting results via SignalR.
///
diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs
index d3cabe1..506f55a 100644
--- a/src/Managing.Api/Controllers/BotController.cs
+++ b/src/Managing.Api/Controllers/BotController.cs
@@ -5,6 +5,7 @@ using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands;
using Managing.Common;
+using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs
index a58bd5b..50fc803 100644
--- a/src/Managing.Api/Controllers/DataController.cs
+++ b/src/Managing.Api/Controllers/DataController.cs
@@ -4,6 +4,7 @@ using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands;
+using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Scenarios;
diff --git a/src/Managing.Api/Models/Requests/GetCandlesWithIndicatorsRequest.cs b/src/Managing.Api/Models/Requests/GetCandlesWithIndicatorsRequest.cs
index 2ddd5d8..d56f546 100644
--- a/src/Managing.Api/Models/Requests/GetCandlesWithIndicatorsRequest.cs
+++ b/src/Managing.Api/Models/Requests/GetCandlesWithIndicatorsRequest.cs
@@ -1,3 +1,4 @@
+using Managing.Domain.Backtests;
using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests;
@@ -7,8 +8,6 @@ namespace Managing.Api.Models.Requests;
///
public class GetCandlesWithIndicatorsRequest
{
-
-
///
/// The ticker symbol.
///
@@ -33,4 +32,4 @@ public class GetCandlesWithIndicatorsRequest
/// Optional scenario for calculating indicators.
///
public ScenarioRequest Scenario { get; set; }
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Managing.Api/Models/Requests/StartBotRequest.cs b/src/Managing.Api/Models/Requests/StartBotRequest.cs
index 42bedc8..5bd7b30 100644
--- a/src/Managing.Api/Models/Requests/StartBotRequest.cs
+++ b/src/Managing.Api/Models/Requests/StartBotRequest.cs
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
+using Managing.Domain.Bots;
namespace Managing.Api.Models.Requests
{
diff --git a/src/Managing.Api/Models/Requests/UpdateBotConfigRequest.cs b/src/Managing.Api/Models/Requests/UpdateBotConfigRequest.cs
index 26bc075..76c817b 100644
--- a/src/Managing.Api/Models/Requests/UpdateBotConfigRequest.cs
+++ b/src/Managing.Api/Models/Requests/UpdateBotConfigRequest.cs
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
+using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
namespace Managing.Api.Models.Requests;
diff --git a/src/Managing.Application.Abstractions/Services/IBacktester.cs b/src/Managing.Application.Abstractions/Services/IBacktester.cs
index 9192d77..316fb86 100644
--- a/src/Managing.Application.Abstractions/Services/IBacktester.cs
+++ b/src/Managing.Application.Abstractions/Services/IBacktester.cs
@@ -62,6 +62,14 @@ namespace Managing.Application.Abstractions.Services
bool DeleteBacktestsByRequestId(string requestId);
(IEnumerable Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
+ // Bundle backtest methods
+ void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
+ IEnumerable GetBundleBacktestRequestsByUser(User user);
+ BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
+ void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
+ void DeleteBundleBacktestRequestByIdForUser(User user, string id);
+ IEnumerable GetPendingBundleBacktestRequests();
+
}
}
\ No newline at end of file
diff --git a/src/Managing.Application.Workers/BundleBacktestWorker.cs b/src/Managing.Application.Workers/BundleBacktestWorker.cs
index 09f44fa..5306c26 100644
--- a/src/Managing.Application.Workers/BundleBacktestWorker.cs
+++ b/src/Managing.Application.Workers/BundleBacktestWorker.cs
@@ -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;
///
public class BundleBacktestWorker : BaseWorker
{
- 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 logger,
IWorkerService workerService) : base(
@@ -27,7 +29,6 @@ public class BundleBacktestWorker : BaseWorker
TimeSpan.FromMinutes(1),
workerService)
{
- _backtestRepository = backtestRepository;
_backtester = backtester;
}
@@ -36,7 +37,7 @@ public class BundleBacktestWorker : BaseWorker
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
// 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>(bundleRequest.BacktestRequestsJson);
+ // Deserialize the backtest requests as strongly-typed objects
+ var backtestRequests =
+ JsonSerializer.Deserialize>(
+ bundleRequest.BacktestRequestsJson);
if (backtestRequests == null)
{
throw new InvalidOperationException("Failed to deserialize backtest requests");
@@ -78,28 +81,27 @@ public class BundleBacktestWorker : BaseWorker
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
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);
}
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs
index 94ec5f6..daaf256 100644
--- a/src/Managing.Application/Backtesting/Backtester.cs
+++ b/src/Managing.Application/Backtesting/Backtester.cs
@@ -573,5 +573,36 @@ namespace Managing.Application.Backtesting
_backtestRepository.GetBacktestsByUserPaginated(user, page, pageSize, sortBy, sortOrder);
return (backtests, totalCount);
}
+
+ // Bundle backtest methods
+ public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
+ {
+ _backtestRepository.InsertBundleBacktestRequestForUser(user, bundleRequest);
+ }
+
+ public IEnumerable GetBundleBacktestRequestsByUser(User user)
+ {
+ return _backtestRepository.GetBundleBacktestRequestsByUser(user);
+ }
+
+ public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
+ {
+ return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
+ }
+
+ public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
+ {
+ _backtestRepository.UpdateBundleBacktestRequest(bundleRequest);
+ }
+
+ public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
+ {
+ _backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
+ }
+
+ public IEnumerable GetPendingBundleBacktestRequests()
+ {
+ return _backtestRepository.GetPendingBundleBacktestRequests();
+ }
}
}
\ No newline at end of file
diff --git a/src/Managing.Domain/Backtests/BundleBacktestRequest.cs b/src/Managing.Domain/Backtests/BundleBacktestRequest.cs
index dc8fc18..3a55d89 100644
--- a/src/Managing.Domain/Backtests/BundleBacktestRequest.cs
+++ b/src/Managing.Domain/Backtests/BundleBacktestRequest.cs
@@ -59,6 +59,12 @@ public class BundleBacktestRequest
[Required]
public BundleBacktestRequestStatus Status { get; set; }
+ ///
+ /// Display name for the bundle backtest request
+ ///
+ [Required]
+ public string Name { get; set; }
+
///
/// The list of backtest requests to execute (serialized as JSON)
///
@@ -143,4 +149,4 @@ public enum BundleBacktestRequestStatus
/// Request was cancelled
///
Cancelled
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Managing.Api/Models/Requests/IndicatorRequest.cs b/src/Managing.Domain/Backtests/IndicatorRequest.cs
similarity index 97%
rename from src/Managing.Api/Models/Requests/IndicatorRequest.cs
rename to src/Managing.Domain/Backtests/IndicatorRequest.cs
index 1a11600..e64779a 100644
--- a/src/Managing.Api/Models/Requests/IndicatorRequest.cs
+++ b/src/Managing.Domain/Backtests/IndicatorRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using static Managing.Common.Enums;
-namespace Managing.Api.Models.Requests;
+namespace Managing.Domain.Backtests;
///
/// Request model for indicator configuration without user information
diff --git a/src/Managing.Api/Models/Requests/MoneyManagementRequest.cs b/src/Managing.Domain/Backtests/MoneyManagementRequest.cs
similarity index 90%
rename from src/Managing.Api/Models/Requests/MoneyManagementRequest.cs
rename to src/Managing.Domain/Backtests/MoneyManagementRequest.cs
index eef3162..e401f03 100644
--- a/src/Managing.Api/Models/Requests/MoneyManagementRequest.cs
+++ b/src/Managing.Domain/Backtests/MoneyManagementRequest.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using Managing.Common;
-namespace Managing.Api.Models.Requests;
+namespace Managing.Domain.Backtests;
public class MoneyManagementRequest
{
diff --git a/src/Managing.Api/Models/Requests/RunBacktestRequest.cs b/src/Managing.Domain/Backtests/RunBacktestRequest.cs
similarity index 79%
rename from src/Managing.Api/Models/Requests/RunBacktestRequest.cs
rename to src/Managing.Domain/Backtests/RunBacktestRequest.cs
index 80892d5..7bd130c 100644
--- a/src/Managing.Api/Models/Requests/RunBacktestRequest.cs
+++ b/src/Managing.Domain/Backtests/RunBacktestRequest.cs
@@ -1,6 +1,7 @@
-using Managing.Domain.MoneyManagements;
+using Managing.Domain.Bots;
+using Managing.Domain.MoneyManagements;
-namespace Managing.Api.Models.Requests;
+namespace Managing.Domain.Backtests;
///
/// Request model for running a backtest
@@ -37,6 +38,12 @@ public class RunBacktestRequest
///
public bool Save { get; set; } = false;
+ ///
+ /// Whether to include candles and indicators values in the response.
+ /// Set to false to reduce response size dramatically.
+ ///
+ public bool WithCandles { get; set; } = false;
+
///
/// The name of the money management to use (optional if MoneyManagement is provided)
///
@@ -46,4 +53,4 @@ public class RunBacktestRequest
/// The money management details (optional if MoneyManagementName is provided)
///
public MoneyManagement? MoneyManagement { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/Managing.Api/Models/Requests/ScenarioRequest.cs b/src/Managing.Domain/Backtests/ScenarioRequest.cs
similarity index 93%
rename from src/Managing.Api/Models/Requests/ScenarioRequest.cs
rename to src/Managing.Domain/Backtests/ScenarioRequest.cs
index 40d3fc3..547d8fe 100644
--- a/src/Managing.Api/Models/Requests/ScenarioRequest.cs
+++ b/src/Managing.Domain/Backtests/ScenarioRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace Managing.Api.Models.Requests;
+namespace Managing.Domain.Backtests;
///
/// Request model for scenario configuration without user information
diff --git a/src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs b/src/Managing.Domain/Bots/TradingBotConfigRequest.cs
similarity index 98%
rename from src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs
rename to src/Managing.Domain/Bots/TradingBotConfigRequest.cs
index 95e98f8..a75c1d5 100644
--- a/src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs
+++ b/src/Managing.Domain/Bots/TradingBotConfigRequest.cs
@@ -1,7 +1,8 @@
using System.ComponentModel.DataAnnotations;
+using Managing.Domain.Backtests;
using static Managing.Common.Enums;
-namespace Managing.Api.Models.Requests;
+namespace Managing.Domain.Bots;
///
/// Simplified trading bot configuration request with only primary properties
diff --git a/src/Managing.Infrastructure.Database/BacktestRepository.cs b/src/Managing.Infrastructure.Database/BacktestRepository.cs
index 7a5f2c7..5c5ac2d 100644
--- a/src/Managing.Infrastructure.Database/BacktestRepository.cs
+++ b/src/Managing.Infrastructure.Database/BacktestRepository.cs
@@ -314,7 +314,7 @@ public class BacktestRepository : IBacktestRepository
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
{
bundleRequest.User = user;
- var dto = MapToDto(bundleRequest);
+ var dto = MongoMappers.Map(bundleRequest);
_bundleBacktestRepository.InsertOne(dto);
}
@@ -324,7 +324,7 @@ public class BacktestRepository : IBacktestRepository
.Where(b => b.User.Name == user.Name)
.ToList();
- return bundleRequests.Select(MapToDomain);
+ return bundleRequests.Select(MongoMappers.Map);
}
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
@@ -333,7 +333,7 @@ public class BacktestRepository : IBacktestRepository
if (bundleRequest != null && bundleRequest.User.Name == user.Name)
{
- return MapToDomain(bundleRequest);
+ return MongoMappers.Map(bundleRequest);
}
return null;
@@ -341,7 +341,7 @@ public class BacktestRepository : IBacktestRepository
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{
- var dto = MapToDto(bundleRequest);
+ var dto = MongoMappers.Map(bundleRequest);
_bundleBacktestRepository.ReplaceOne(dto);
}
@@ -361,55 +361,6 @@ public class BacktestRepository : IBacktestRepository
.Where(b => b.Status == BundleBacktestRequestStatus.Pending)
.ToList();
- return pendingRequests.Select(MapToDomain);
- }
-
- ///
- /// Maps a domain model to DTO
- ///
- /// The domain model
- /// The DTO
- private static BundleBacktestRequestDto MapToDto(BundleBacktestRequest domain)
- {
- return new BundleBacktestRequestDto
- {
- RequestId = domain.RequestId,
- User = MongoMappers.Map(domain.User),
- CompletedAt = domain.CompletedAt,
- Status = domain.Status,
- BacktestRequestsJson = domain.BacktestRequestsJson,
- TotalBacktests = domain.TotalBacktests,
- CompletedBacktests = domain.CompletedBacktests,
- FailedBacktests = domain.FailedBacktests,
- ErrorMessage = domain.ErrorMessage,
- ProgressInfo = domain.ProgressInfo,
- CurrentBacktest = domain.CurrentBacktest,
- EstimatedTimeRemainingSeconds = domain.EstimatedTimeRemainingSeconds
- };
- }
-
- ///
- /// Maps a DTO to domain model
- ///
- /// The DTO
- /// The domain model
- private static BundleBacktestRequest MapToDomain(BundleBacktestRequestDto dto)
- {
- return new BundleBacktestRequest
- {
- RequestId = dto.RequestId,
- User = MongoMappers.Map(dto.User),
- CreatedAt = dto.CreatedAt,
- CompletedAt = dto.CompletedAt,
- Status = dto.Status,
- BacktestRequestsJson = dto.BacktestRequestsJson,
- TotalBacktests = dto.TotalBacktests,
- CompletedBacktests = dto.CompletedBacktests,
- FailedBacktests = dto.FailedBacktests,
- ErrorMessage = dto.ErrorMessage,
- ProgressInfo = dto.ProgressInfo,
- CurrentBacktest = dto.CurrentBacktest,
- EstimatedTimeRemainingSeconds = dto.EstimatedTimeRemainingSeconds
- };
+ return pendingRequests.Select(MongoMappers.Map);
}
}
\ No newline at end of file
diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs
index f9c7c2f..dc0e2d1 100644
--- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs
+++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BundleBacktestRequestDto.cs
@@ -19,4 +19,5 @@ public class BundleBacktestRequestDto : Document
public string? ProgressInfo { get; set; }
public string? CurrentBacktest { get; set; }
public int? EstimatedTimeRemainingSeconds { get; set; }
-}
\ No newline at end of file
+ public string Name { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs
index 9ad7eab..de3a818 100644
--- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs
+++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs
@@ -1108,4 +1108,51 @@ public static class MongoMappers
}
#endregion
+
+ #region BundleBacktestRequests
+
+ public static BundleBacktestRequestDto Map(BundleBacktestRequest domain)
+ {
+ if (domain == null) return null;
+ return new BundleBacktestRequestDto
+ {
+ RequestId = domain.RequestId,
+ User = Map(domain.User),
+ CompletedAt = domain.CompletedAt,
+ Status = domain.Status,
+ BacktestRequestsJson = domain.BacktestRequestsJson,
+ TotalBacktests = domain.TotalBacktests,
+ CompletedBacktests = domain.CompletedBacktests,
+ FailedBacktests = domain.FailedBacktests,
+ ErrorMessage = domain.ErrorMessage,
+ ProgressInfo = domain.ProgressInfo,
+ CurrentBacktest = domain.CurrentBacktest,
+ EstimatedTimeRemainingSeconds = domain.EstimatedTimeRemainingSeconds,
+ Name = domain.Name
+ };
+ }
+
+ public static BundleBacktestRequest Map(BundleBacktestRequestDto dto)
+ {
+ if (dto == null) return null;
+ return new BundleBacktestRequest
+ {
+ RequestId = dto.RequestId,
+ User = Map(dto.User),
+ CreatedAt = dto.CreatedAt,
+ CompletedAt = dto.CompletedAt,
+ Status = dto.Status,
+ BacktestRequestsJson = dto.BacktestRequestsJson,
+ TotalBacktests = dto.TotalBacktests,
+ CompletedBacktests = dto.CompletedBacktests,
+ FailedBacktests = dto.FailedBacktests,
+ ErrorMessage = dto.ErrorMessage,
+ ProgressInfo = dto.ProgressInfo,
+ CurrentBacktest = dto.CurrentBacktest,
+ EstimatedTimeRemainingSeconds = dto.EstimatedTimeRemainingSeconds,
+ Name = dto.Name
+ };
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts
index 66afb40..f685c1b 100644
--- a/src/Managing.WebApp/src/generated/ManagingApi.ts
+++ b/src/Managing.WebApp/src/generated/ManagingApi.ts
@@ -716,6 +716,163 @@ export class BacktestClient extends AuthorizedApiBase {
return Promise.resolve(null as any);
}
+ backtest_RunBundle(name: string | null | undefined, requests: RunBacktestRequest[]): Promise {
+ let url_ = this.baseUrl + "/Backtest/Bundle?";
+ if (name !== undefined && name !== null)
+ url_ += "name=" + encodeURIComponent("" + name) + "&";
+ url_ = url_.replace(/[?&]$/, "");
+
+ const content_ = JSON.stringify(requests);
+
+ let options_: RequestInit = {
+ body: content_,
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Accept": "application/json"
+ }
+ };
+
+ return this.transformOptions(options_).then(transformedOptions_ => {
+ return this.http.fetch(url_, transformedOptions_);
+ }).then((_response: Response) => {
+ return this.processBacktest_RunBundle(_response);
+ });
+ }
+
+ protected processBacktest_RunBundle(response: Response): Promise {
+ const status = response.status;
+ let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
+ if (status === 200) {
+ return response.text().then((_responseText) => {
+ let result200: any = null;
+ result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequest;
+ return result200;
+ });
+ } else if (status !== 200 && status !== 204) {
+ return response.text().then((_responseText) => {
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
+ });
+ }
+ return Promise.resolve(null as any);
+ }
+
+ backtest_GetBundleBacktestRequests(): Promise {
+ let url_ = this.baseUrl + "/Backtest/Bundle";
+ url_ = url_.replace(/[?&]$/, "");
+
+ let options_: RequestInit = {
+ method: "GET",
+ headers: {
+ "Accept": "application/json"
+ }
+ };
+
+ return this.transformOptions(options_).then(transformedOptions_ => {
+ return this.http.fetch(url_, transformedOptions_);
+ }).then((_response: Response) => {
+ return this.processBacktest_GetBundleBacktestRequests(_response);
+ });
+ }
+
+ protected processBacktest_GetBundleBacktestRequests(response: Response): Promise {
+ const status = response.status;
+ let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
+ if (status === 200) {
+ return response.text().then((_responseText) => {
+ let result200: any = null;
+ result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequest[];
+ return result200;
+ });
+ } else if (status !== 200 && status !== 204) {
+ return response.text().then((_responseText) => {
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
+ });
+ }
+ return Promise.resolve(null as any);
+ }
+
+ backtest_GetBundleBacktestRequest(id: string): Promise {
+ let url_ = this.baseUrl + "/Backtest/Bundle/{id}";
+ if (id === undefined || id === null)
+ throw new Error("The parameter 'id' must be defined.");
+ url_ = url_.replace("{id}", encodeURIComponent("" + id));
+ url_ = url_.replace(/[?&]$/, "");
+
+ let options_: RequestInit = {
+ method: "GET",
+ headers: {
+ "Accept": "application/json"
+ }
+ };
+
+ return this.transformOptions(options_).then(transformedOptions_ => {
+ return this.http.fetch(url_, transformedOptions_);
+ }).then((_response: Response) => {
+ return this.processBacktest_GetBundleBacktestRequest(_response);
+ });
+ }
+
+ protected processBacktest_GetBundleBacktestRequest(response: Response): Promise {
+ const status = response.status;
+ let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
+ if (status === 200) {
+ return response.text().then((_responseText) => {
+ let result200: any = null;
+ result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BundleBacktestRequest;
+ return result200;
+ });
+ } else if (status !== 200 && status !== 204) {
+ return response.text().then((_responseText) => {
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
+ });
+ }
+ return Promise.resolve(null as any);
+ }
+
+ backtest_DeleteBundleBacktestRequest(id: string): Promise {
+ let url_ = this.baseUrl + "/Backtest/Bundle/{id}";
+ if (id === undefined || id === null)
+ throw new Error("The parameter 'id' must be defined.");
+ url_ = url_.replace("{id}", encodeURIComponent("" + id));
+ url_ = url_.replace(/[?&]$/, "");
+
+ let options_: RequestInit = {
+ method: "DELETE",
+ headers: {
+ "Accept": "application/octet-stream"
+ }
+ };
+
+ return this.transformOptions(options_).then(transformedOptions_ => {
+ return this.http.fetch(url_, transformedOptions_);
+ }).then((_response: Response) => {
+ return this.processBacktest_DeleteBundleBacktestRequest(_response);
+ });
+ }
+
+ protected processBacktest_DeleteBundleBacktestRequest(response: Response): Promise {
+ const status = response.status;
+ let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
+ if (status === 200 || status === 206) {
+ const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
+ let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
+ let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
+ if (fileName) {
+ fileName = decodeURIComponent(fileName);
+ } else {
+ fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
+ fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
+ }
+ return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
+ } else if (status !== 200 && status !== 204) {
+ return response.text().then((_responseText) => {
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
+ });
+ }
+ return Promise.resolve(null as any);
+ }
+
backtest_RunGenetic(request: RunGeneticRequest): Promise {
let url_ = this.baseUrl + "/Backtest/Genetic";
url_ = url_.replace(/[?&]$/, "");
@@ -3880,6 +4037,33 @@ export interface MoneyManagementRequest {
leverage: number;
}
+export interface BundleBacktestRequest {
+ requestId: string;
+ user: User;
+ createdAt: Date;
+ completedAt?: Date | null;
+ status: BundleBacktestRequestStatus;
+ name: string;
+ backtestRequestsJson: string;
+ results?: Backtest[] | null;
+ totalBacktests: number;
+ completedBacktests: number;
+ failedBacktests: number;
+ progressPercentage?: number;
+ errorMessage?: string | null;
+ progressInfo?: string | null;
+ currentBacktest?: string | null;
+ estimatedTimeRemainingSeconds?: number | null;
+}
+
+export enum BundleBacktestRequestStatus {
+ Pending = "Pending",
+ Running = "Running",
+ Completed = "Completed",
+ Failed = "Failed",
+ Cancelled = "Cancelled",
+}
+
export interface GeneticRequest {
requestId: string;
user: User;
diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts
index c0c772c..e01647a 100644
--- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts
+++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts
@@ -686,6 +686,33 @@ export interface MoneyManagementRequest {
leverage: number;
}
+export interface BundleBacktestRequest {
+ requestId: string;
+ user: User;
+ createdAt: Date;
+ completedAt?: Date | null;
+ status: BundleBacktestRequestStatus;
+ name: string;
+ backtestRequestsJson: string;
+ results?: Backtest[] | null;
+ totalBacktests: number;
+ completedBacktests: number;
+ failedBacktests: number;
+ progressPercentage?: number;
+ errorMessage?: string | null;
+ progressInfo?: string | null;
+ currentBacktest?: string | null;
+ estimatedTimeRemainingSeconds?: number | null;
+}
+
+export enum BundleBacktestRequestStatus {
+ Pending = "Pending",
+ Running = "Running",
+ Completed = "Completed",
+ Failed = "Failed",
+ Cancelled = "Cancelled",
+}
+
export interface GeneticRequest {
requestId: string;
user: User;