From 57ba32f31e75e531d9a0e671bdb9b489a1ea942f Mon Sep 17 00:00:00 2001 From: cryptooda Date: Wed, 12 Nov 2025 18:11:39 +0700 Subject: [PATCH] Add bundle version number on the backtest name --- .../Controllers/BacktestController.cs | 14 ++-- .../Backtests/BacktestJobService.cs | 16 +++-- .../pages/backtestPage/BundleRequestModal.tsx | 69 ------------------- 3 files changed, 19 insertions(+), 80 deletions(-) diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index a9852c5b..c3b78e37 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -616,6 +616,11 @@ public class BacktestController : BaseController return BadRequest("Universal configuration is required"); } + if (request.UniversalConfig.Scenario == null) + { + return BadRequest("Scenario object must be provided in universal configuration for bundle backtest"); + } + if (request.DateTimeRanges == null || !request.DateTimeRanges.Any()) { return BadRequest("At least one DateTime range is required"); @@ -685,7 +690,7 @@ public class BacktestController : BaseController // Capture values for background task var bundleRequestId = bundleRequest.RequestId; var userId = user.Id; - + // Fire off background task to create jobs - don't await, return immediately _ = Task.Run(async () => { @@ -694,7 +699,7 @@ public class BacktestController : BaseController using var scope = _serviceScopeFactory.CreateScope(); var backtester = scope.ServiceProvider.GetRequiredService(); var userService = scope.ServiceProvider.GetRequiredService(); - + // Reload user and bundle request to ensure we have the latest data var reloadedUser = await userService.GetUserByIdAsync(userId); if (reloadedUser == null) @@ -704,8 +709,9 @@ public class BacktestController : BaseController userId, bundleRequestId); return; } - - var savedBundleRequest = backtester.GetBundleBacktestRequestByIdForUser(reloadedUser, bundleRequestId); + + var savedBundleRequest = + backtester.GetBundleBacktestRequestByIdForUser(reloadedUser, bundleRequestId); if (savedBundleRequest != null) { await backtester.CreateJobsForBundleRequestAsync(savedBundleRequest); diff --git a/src/Managing.Application/Backtests/BacktestJobService.cs b/src/Managing.Application/Backtests/BacktestJobService.cs index 54d4f5d8..10214f20 100644 --- a/src/Managing.Application/Backtests/BacktestJobService.cs +++ b/src/Managing.Application/Backtests/BacktestJobService.cs @@ -195,7 +195,7 @@ public class JobService MaxPositionTimeHours = backtestRequest.Config.MaxPositionTimeHours, FlipOnlyWhenInProfit = backtestRequest.Config.FlipOnlyWhenInProfit, FlipPosition = backtestRequest.Config.FlipPosition, - Name = $"{bundleRequest.Name} #{i + 1}", + Name = $"{bundleRequest.Name} v{bundleRequest.Version} #{i + 1}", CloseEarlyWhenProfitable = backtestRequest.Config.CloseEarlyWhenProfitable, UseSynthApi = backtestRequest.Config.UseSynthApi, UseForPositionSizing = backtestRequest.Config.UseForPositionSizing, @@ -233,7 +233,8 @@ public class JobService { try { - var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, bundleRequest.User); + var refundSuccess = + await _kaigenService.RefundUserCreditsAsync(creditRequestId, bundleRequest.User); if (refundSuccess) { _logger.LogInformation( @@ -243,7 +244,8 @@ public class JobService } catch (Exception refundEx) { - _logger.LogError(refundEx, "Error during refund attempt for user {UserName}", bundleRequest.User.Name); + _logger.LogError(refundEx, "Error during refund attempt for user {UserName}", + bundleRequest.User.Name); } } @@ -260,7 +262,7 @@ public class JobService public async Task RetryJobAsync(Guid jobId) { var job = await _jobRepository.GetByIdAsync(jobId); - + if (job == null) { throw new InvalidOperationException($"Job with ID {jobId} not found."); @@ -270,7 +272,8 @@ public class JobService // Running jobs should be handled by stale job recovery, not manual retry if (job.Status != JobStatus.Failed && job.Status != JobStatus.Cancelled) { - throw new InvalidOperationException($"Cannot retry job with status {job.Status}. Only Failed or Cancelled jobs can be retried."); + throw new InvalidOperationException( + $"Cannot retry job with status {job.Status}. Only Failed or Cancelled jobs can be retried."); } // Reset job to pending state @@ -303,5 +306,4 @@ public class JobService await _jobRepository.DeleteAsync(jobId); _logger.LogInformation("Deleted job {JobId}", jobId); } -} - +} \ No newline at end of file diff --git a/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx b/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx index fad38a75..c3f1d335 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/BundleRequestModal.tsx @@ -18,8 +18,6 @@ import { import useApiUrlStore from '../../app/store/apiStore'; import Toast from '../../components/mollecules/Toast/Toast'; import {useQuery} from '@tanstack/react-query'; -import * as signalR from '@microsoft/signalr'; -import AuthorizedApiBase from '../../generated/AuthorizedApiBase'; import BacktestTable from '../../components/organism/Backtest/backtestTable'; import FormInput from '../../components/mollecules/FormInput/FormInput'; import CustomScenario from '../../components/organism/CustomScenario/CustomScenario'; @@ -337,73 +335,6 @@ const BundleRequestModal: React.FC = ({ if (queryBacktests) setBacktests(queryBacktests); }, [queryBacktests]); - // SignalR live updates for existing bundles - useEffect(() => { - if (!open || !bundle) return; - if (bundle.status !== 'Pending' && bundle.status !== 'Running') return; - let connection: any = null; - let connectionId: string = ''; - let unsubscribed = false; - (async () => { - try { - connection = new signalR.HubConnectionBuilder() - .withUrl(`${apiUrl.replace(/\/$/, '')}/backtestHub`) - .withAutomaticReconnect() - .build(); - await connection.start(); - connectionId = connection.connectionId; - // Subscribe to bundle updates - const authBase = new AuthorizedApiBase({} as any); - let fetchOptions: any = { - method: 'POST', - headers: { 'X-SignalR-ConnectionId': connectionId }, - }; - fetchOptions = await authBase.transformOptions(fetchOptions); - await fetch(`${apiUrl}/backtest/Bundle/Subscribe?requestId=${bundle.requestId}`, fetchOptions); - connection.on('BundleBacktestUpdate', (result: LightBacktestResponse) => { - // Map enums if needed - if (result.config) { - if (typeof result.config.ticker === 'number') { - result.config.ticker = Ticker[result.config.ticker as keyof typeof Ticker]; - } else if (typeof result.config.ticker === 'string' && Ticker[result.config.ticker as keyof typeof Ticker]) { - result.config.ticker = Ticker[result.config.ticker as keyof typeof Ticker]; - } - if (typeof result.config.timeframe === 'number') { - result.config.timeframe = Timeframe[result.config.timeframe as keyof typeof Timeframe]; - } else if (typeof result.config.timeframe === 'string' && Timeframe[result.config.timeframe as keyof typeof Timeframe]) { - result.config.timeframe = Timeframe[result.config.timeframe as keyof typeof Timeframe]; - } - } - setBacktests((prev) => { - if (prev.some((b) => b.id === result.id)) return prev; - return [...prev, result]; - }); - }); - signalRRef.current = connection; - } catch (e: any) { - new Toast('Failed to subscribe to live updates', false); - } - })(); - return () => { - unsubscribed = true; - if (connection && connectionId) { - (async () => { - const authBase = new AuthorizedApiBase({} as any); - let fetchOptions: any = { - method: 'POST', - headers: { 'X-SignalR-ConnectionId': connectionId }, - }; - fetchOptions = await authBase.transformOptions(fetchOptions); - await fetch(`${apiUrl}/backtest/Bundle/Unsubscribe?requestId=${bundle.requestId}`, fetchOptions); - })(); - } - if (signalRRef.current) { - signalRRef.current.stop(); - signalRRef.current = null; - } - }; - }, [open, bundle, apiUrl]); - if (!open) return null; // If viewing an existing bundle