Add bundle version number on the backtest name
This commit is contained in:
@@ -616,6 +616,11 @@ public class BacktestController : BaseController
|
|||||||
return BadRequest("Universal configuration is required");
|
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())
|
if (request.DateTimeRanges == null || !request.DateTimeRanges.Any())
|
||||||
{
|
{
|
||||||
return BadRequest("At least one DateTime range is required");
|
return BadRequest("At least one DateTime range is required");
|
||||||
@@ -705,7 +710,8 @@ public class BacktestController : BaseController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var savedBundleRequest = backtester.GetBundleBacktestRequestByIdForUser(reloadedUser, bundleRequestId);
|
var savedBundleRequest =
|
||||||
|
backtester.GetBundleBacktestRequestByIdForUser(reloadedUser, bundleRequestId);
|
||||||
if (savedBundleRequest != null)
|
if (savedBundleRequest != null)
|
||||||
{
|
{
|
||||||
await backtester.CreateJobsForBundleRequestAsync(savedBundleRequest);
|
await backtester.CreateJobsForBundleRequestAsync(savedBundleRequest);
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ public class JobService
|
|||||||
MaxPositionTimeHours = backtestRequest.Config.MaxPositionTimeHours,
|
MaxPositionTimeHours = backtestRequest.Config.MaxPositionTimeHours,
|
||||||
FlipOnlyWhenInProfit = backtestRequest.Config.FlipOnlyWhenInProfit,
|
FlipOnlyWhenInProfit = backtestRequest.Config.FlipOnlyWhenInProfit,
|
||||||
FlipPosition = backtestRequest.Config.FlipPosition,
|
FlipPosition = backtestRequest.Config.FlipPosition,
|
||||||
Name = $"{bundleRequest.Name} #{i + 1}",
|
Name = $"{bundleRequest.Name} v{bundleRequest.Version} #{i + 1}",
|
||||||
CloseEarlyWhenProfitable = backtestRequest.Config.CloseEarlyWhenProfitable,
|
CloseEarlyWhenProfitable = backtestRequest.Config.CloseEarlyWhenProfitable,
|
||||||
UseSynthApi = backtestRequest.Config.UseSynthApi,
|
UseSynthApi = backtestRequest.Config.UseSynthApi,
|
||||||
UseForPositionSizing = backtestRequest.Config.UseForPositionSizing,
|
UseForPositionSizing = backtestRequest.Config.UseForPositionSizing,
|
||||||
@@ -233,7 +233,8 @@ public class JobService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var refundSuccess = await _kaigenService.RefundUserCreditsAsync(creditRequestId, bundleRequest.User);
|
var refundSuccess =
|
||||||
|
await _kaigenService.RefundUserCreditsAsync(creditRequestId, bundleRequest.User);
|
||||||
if (refundSuccess)
|
if (refundSuccess)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
@@ -243,7 +244,8 @@ public class JobService
|
|||||||
}
|
}
|
||||||
catch (Exception refundEx)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +272,8 @@ public class JobService
|
|||||||
// Running jobs should be handled by stale job recovery, not manual retry
|
// Running jobs should be handled by stale job recovery, not manual retry
|
||||||
if (job.Status != JobStatus.Failed && job.Status != JobStatus.Cancelled)
|
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
|
// Reset job to pending state
|
||||||
@@ -304,4 +307,3 @@ public class JobService
|
|||||||
_logger.LogInformation("Deleted job {JobId}", jobId);
|
_logger.LogInformation("Deleted job {JobId}", jobId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import {
|
|||||||
import useApiUrlStore from '../../app/store/apiStore';
|
import useApiUrlStore from '../../app/store/apiStore';
|
||||||
import Toast from '../../components/mollecules/Toast/Toast';
|
import Toast from '../../components/mollecules/Toast/Toast';
|
||||||
import {useQuery} from '@tanstack/react-query';
|
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 BacktestTable from '../../components/organism/Backtest/backtestTable';
|
||||||
import FormInput from '../../components/mollecules/FormInput/FormInput';
|
import FormInput from '../../components/mollecules/FormInput/FormInput';
|
||||||
import CustomScenario from '../../components/organism/CustomScenario/CustomScenario';
|
import CustomScenario from '../../components/organism/CustomScenario/CustomScenario';
|
||||||
@@ -337,73 +335,6 @@ const BundleRequestModal: React.FC<BundleRequestModalProps> = ({
|
|||||||
if (queryBacktests) setBacktests(queryBacktests);
|
if (queryBacktests) setBacktests(queryBacktests);
|
||||||
}, [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 (!open) return null;
|
||||||
|
|
||||||
// If viewing an existing bundle
|
// If viewing an existing bundle
|
||||||
|
|||||||
Reference in New Issue
Block a user