diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index e67e7f7c..14bf30d6 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -153,14 +153,17 @@ public class BacktestController : BaseController { return BadRequest("scoreMin must be between 0 and 100"); } + if (scoreMax.HasValue && (scoreMax < 0 || scoreMax > 100)) { return BadRequest("scoreMax must be between 0 and 100"); } + if (winrateMin.HasValue && (winrateMin < 0 || winrateMin > 100)) { return BadRequest("winrateMin must be between 0 and 100"); } + if (winrateMax.HasValue && (winrateMax < 0 || winrateMax > 100)) { return BadRequest("winrateMax must be between 0 and 100"); @@ -170,10 +173,12 @@ public class BacktestController : BaseController { return BadRequest("scoreMin must be less than or equal to scoreMax"); } + if (winrateMin.HasValue && winrateMax.HasValue && winrateMin > winrateMax) { return BadRequest("winrateMin must be less than or equal to winrateMax"); } + if (maxDrawdownMax.HasValue && maxDrawdownMax < 0) { return BadRequest("maxDrawdownMax must be greater than or equal to 0"); @@ -186,7 +191,7 @@ public class BacktestController : BaseController var indicatorList = string.IsNullOrWhiteSpace(indicators) ? Array.Empty() : indicators.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - + var filter = new BacktestsFilter { NameContains = string.IsNullOrWhiteSpace(name) ? null : name.Trim(), @@ -364,14 +369,17 @@ public class BacktestController : BaseController { return BadRequest("scoreMin must be between 0 and 100"); } + if (scoreMax.HasValue && (scoreMax < 0 || scoreMax > 100)) { return BadRequest("scoreMax must be between 0 and 100"); } + if (winrateMin.HasValue && (winrateMin < 0 || winrateMin > 100)) { return BadRequest("winrateMin must be between 0 and 100"); } + if (winrateMax.HasValue && (winrateMax < 0 || winrateMax > 100)) { return BadRequest("winrateMax must be between 0 and 100"); @@ -381,10 +389,12 @@ public class BacktestController : BaseController { return BadRequest("scoreMin must be less than or equal to scoreMax"); } + if (winrateMin.HasValue && winrateMax.HasValue && winrateMin > winrateMax) { return BadRequest("winrateMin must be less than or equal to winrateMax"); } + if (maxDrawdownMax.HasValue && maxDrawdownMax < 0) { return BadRequest("maxDrawdownMax must be greater than or equal to 0"); @@ -619,8 +629,9 @@ public class BacktestController : BaseController } // Calculate total number of backtests - var totalBacktests = request.DateTimeRanges.Count * request.MoneyManagementVariants.Count * request.TickerVariants.Count; - + var totalBacktests = request.DateTimeRanges.Count * request.MoneyManagementVariants.Count * + request.TickerVariants.Count; + try { var user = await GetUser(); @@ -651,7 +662,9 @@ public class BacktestController : BaseController TotalBacktests = totalBacktests, CompletedBacktests = 0, FailedBacktests = 0, - Status = BundleBacktestRequestStatus.Pending, + Status = request.SaveAsTemplate + ? BundleBacktestRequestStatus.Saved + : BundleBacktestRequestStatus.Pending, Name = request.Name }; @@ -687,7 +700,8 @@ public class BacktestController : BaseController Timeframe = request.UniversalConfig.Timeframe, IsForWatchingOnly = request.UniversalConfig.IsForWatchingOnly, BotTradingBalance = request.UniversalConfig.BotTradingBalance, - Name = $"{request.UniversalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}", + Name = + $"{request.UniversalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}", FlipPosition = request.UniversalConfig.FlipPosition, CooldownPeriod = request.UniversalConfig.CooldownPeriod, MaxLossStreak = request.UniversalConfig.MaxLossStreak, @@ -712,15 +726,16 @@ public class BacktestController : BaseController WatchOnly = request.UniversalConfig.WatchOnly, Save = request.UniversalConfig.Save, WithCandles = request.UniversalConfig.WithCandles, - MoneyManagement = mmVariant.MoneyManagement != null ? - new MoneyManagement + 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 + } + : null }; backtestRequests.Add(backtestRequest); @@ -980,4 +995,4 @@ public class BacktestController : BaseController Timeframe = moneyManagementRequest.Timeframe }; } -} +} \ No newline at end of file diff --git a/src/Managing.Domain/Backtests/BundleBacktestRequest.cs b/src/Managing.Domain/Backtests/BundleBacktestRequest.cs index 17c8f6cd..fe02a02d 100644 --- a/src/Managing.Domain/Backtests/BundleBacktestRequest.cs +++ b/src/Managing.Domain/Backtests/BundleBacktestRequest.cs @@ -143,7 +143,6 @@ public class BundleBacktestRequest /// Estimated time remaining in seconds /// public int? EstimatedTimeRemainingSeconds { get; set; } - } /// @@ -174,5 +173,6 @@ public enum BundleBacktestRequestStatus /// /// Request was cancelled /// - Cancelled + Cancelled, + Saved } \ 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 180455ee..ed6792ab 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -4493,6 +4493,7 @@ export enum BundleBacktestRequestStatus { Completed = "Completed", Failed = "Failed", Cancelled = "Cancelled", + Saved = "Saved", } export interface RunBundleBacktestRequest { diff --git a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts index 9a59198c..65eedafc 100644 --- a/src/Managing.WebApp/src/generated/ManagingApiTypes.ts +++ b/src/Managing.WebApp/src/generated/ManagingApiTypes.ts @@ -668,6 +668,7 @@ export enum BundleBacktestRequestStatus { Completed = "Completed", Failed = "Failed", Cancelled = "Cancelled", + Saved = "Saved", } export interface RunBundleBacktestRequest { diff --git a/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx b/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx index f7536528..999fdf79 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx @@ -201,7 +201,7 @@ const BundleRequestModal: React.FC = ({ }; // Create bundle backtest request - const handleCreateBundle = async () => { + const handleCreateBundle = async (asTemplate: boolean = false) => { if (!strategyName || selectedTickers.length === 0) { new Toast('Please fill in all required fields', false); return; @@ -254,15 +254,22 @@ const BundleRequestModal: React.FC = ({ universalConfig, dateTimeRanges, moneyManagementVariants, - tickerVariants: selectedTickers + tickerVariants: selectedTickers, + saveAsTemplate: asTemplate }; try { await onCreateBundle?.(request); - new Toast('Bundle backtest request created successfully!', true); + const successMessage = asTemplate + ? 'Template saved successfully!' + : 'Bundle backtest request created successfully!'; + new Toast(successMessage, true); onClose(); } catch (error) { - new Toast('Failed to create bundle backtest request', false); + const errorMessage = asTemplate + ? 'Failed to save template' + : 'Failed to create bundle backtest request'; + new Toast(errorMessage, false); } }; @@ -809,18 +816,22 @@ const BundleRequestModal: React.FC = ({
-