Bundle from worker to grain
This commit is contained in:
@@ -131,7 +131,12 @@ public class BacktestController : BaseController
|
|||||||
return BadRequest("Request ID is required");
|
return BadRequest("Request ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
var backtests = await _backtester.GetBacktestsByRequestIdAsync(requestId);
|
if (!Guid.TryParse(requestId, out var requestGuid))
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid request ID format. Must be a valid GUID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var backtests = await _backtester.GetBacktestsByRequestIdAsync(requestGuid);
|
||||||
return Ok(backtests);
|
return Ok(backtests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +164,11 @@ public class BacktestController : BaseController
|
|||||||
return BadRequest("Request ID is required");
|
return BadRequest("Request ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Guid.TryParse(requestId, out var requestGuid))
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid request ID format. Must be a valid GUID.");
|
||||||
|
}
|
||||||
|
|
||||||
if (page < 1)
|
if (page < 1)
|
||||||
{
|
{
|
||||||
return BadRequest("Page must be greater than 0");
|
return BadRequest("Page must be greater than 0");
|
||||||
@@ -175,7 +185,7 @@ public class BacktestController : BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (backtests, totalCount) =
|
var (backtests, totalCount) =
|
||||||
await _backtester.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy, sortOrder);
|
await _backtester.GetBacktestsByRequestIdPaginatedAsync(requestGuid, page, pageSize, sortBy, sortOrder);
|
||||||
|
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||||
|
|
||||||
@@ -503,8 +513,13 @@ public class BacktestController : BaseController
|
|||||||
[Route("Bundle/{id}")]
|
[Route("Bundle/{id}")]
|
||||||
public async Task<ActionResult<BundleBacktestRequest>> GetBundleBacktestRequest(string id)
|
public async Task<ActionResult<BundleBacktestRequest>> GetBundleBacktestRequest(string id)
|
||||||
{
|
{
|
||||||
|
if (!Guid.TryParse(id, out var requestId))
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid bundle request ID format. Must be a valid GUID.");
|
||||||
|
}
|
||||||
|
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
var bundleRequest = _backtester.GetBundleBacktestRequestByIdForUser(user, id);
|
var bundleRequest = _backtester.GetBundleBacktestRequestByIdForUser(user, requestId);
|
||||||
|
|
||||||
if (bundleRequest == null)
|
if (bundleRequest == null)
|
||||||
{
|
{
|
||||||
@@ -524,13 +539,18 @@ public class BacktestController : BaseController
|
|||||||
[Route("Bundle/{id}")]
|
[Route("Bundle/{id}")]
|
||||||
public async Task<ActionResult> DeleteBundleBacktestRequest(string id)
|
public async Task<ActionResult> DeleteBundleBacktestRequest(string id)
|
||||||
{
|
{
|
||||||
|
if (!Guid.TryParse(id, out var requestId))
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid bundle request ID format. Must be a valid GUID.");
|
||||||
|
}
|
||||||
|
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
|
||||||
// First, delete the bundle request
|
// First, delete the bundle request
|
||||||
_backtester.DeleteBundleBacktestRequestByIdForUser(user, id);
|
_backtester.DeleteBundleBacktestRequestByIdForUser(user, requestId);
|
||||||
|
|
||||||
// Then, delete all related backtests
|
// Then, delete all related backtests
|
||||||
var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id);
|
var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(requestId);
|
||||||
|
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
@@ -695,7 +715,11 @@ public class BacktestController : BaseController
|
|||||||
_geneticService.DeleteGeneticRequestByIdForUser(user, id);
|
_geneticService.DeleteGeneticRequestByIdForUser(user, id);
|
||||||
|
|
||||||
// Then, delete all related backtests
|
// Then, delete all related backtests
|
||||||
var backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(id);
|
var backtestsDeleted = false;
|
||||||
|
if (Guid.TryParse(id, out var requestGuid))
|
||||||
|
{
|
||||||
|
backtestsDeleted = await _backtester.DeleteBacktestsByRequestIdAsync(requestGuid);
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Orleans;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain interface for Bundle Backtest operations.
|
||||||
|
/// This is a stateless worker grain that processes bundle backtest requests.
|
||||||
|
/// Uses the bundle request ID as the primary key (Guid).
|
||||||
|
/// The grain processes a single bundle request identified by its primary key.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBundleBacktestGrain : IGrainWithGuidKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the bundle backtest request for this grain's RequestId
|
||||||
|
/// The RequestId is determined by the grain's primary key
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Task representing the async operation</returns>
|
||||||
|
Task ProcessBundleRequestAsync();
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ public interface IBacktestRepository
|
|||||||
void InsertBacktestForUser(User user, Backtest result);
|
void InsertBacktestForUser(User user, Backtest result);
|
||||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||||
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
|
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
|
||||||
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
IEnumerable<Backtest> GetBacktestsByRequestId(Guid requestId);
|
||||||
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId);
|
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(Guid requestId);
|
||||||
|
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page,
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(Guid requestId, int page,
|
||||||
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId,
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(Guid requestId,
|
||||||
int page,
|
int page,
|
||||||
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
@@ -28,18 +28,19 @@ public interface IBacktestRepository
|
|||||||
Task DeleteBacktestByIdForUserAsync(User user, string id);
|
Task DeleteBacktestByIdForUserAsync(User user, string id);
|
||||||
Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
|
Task DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
|
||||||
void DeleteAllBacktestsForUser(User user);
|
void DeleteAllBacktestsForUser(User user);
|
||||||
Task DeleteBacktestsByRequestIdAsync(string requestId);
|
Task DeleteBacktestsByRequestIdAsync(Guid requestId);
|
||||||
|
|
||||||
// Bundle backtest methods
|
// Bundle backtest methods
|
||||||
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
||||||
|
Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
||||||
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
|
||||||
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
|
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, Guid id);
|
||||||
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
|
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, Guid id);
|
||||||
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
||||||
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
|
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
|
||||||
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
|
void DeleteBundleBacktestRequestByIdForUser(User user, Guid id);
|
||||||
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
|
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, Guid id);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
||||||
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
|
||||||
}
|
}
|
||||||
@@ -56,28 +56,29 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
bool DeleteBacktests();
|
bool DeleteBacktests();
|
||||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||||
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
|
Task<IEnumerable<Backtest>> GetBacktestsByUserAsync(User user);
|
||||||
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
IEnumerable<Backtest> GetBacktestsByRequestId(Guid requestId);
|
||||||
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId);
|
Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(Guid requestId);
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(Guid requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(Guid requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
|
Task<Backtest> GetBacktestByIdForUserAsync(User user, string id);
|
||||||
Task<bool> DeleteBacktestByUserAsync(User user, string id);
|
Task<bool> DeleteBacktestByUserAsync(User user, string id);
|
||||||
Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
|
Task<bool> DeleteBacktestsByIdsForUserAsync(User user, IEnumerable<string> ids);
|
||||||
bool DeleteBacktestsByUser(User user);
|
bool DeleteBacktestsByUser(User user);
|
||||||
Task<bool> DeleteBacktestsByRequestIdAsync(string requestId);
|
Task<bool> DeleteBacktestsByRequestIdAsync(Guid requestId);
|
||||||
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByUserPaginated(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByUserPaginatedAsync(User user, int page, int pageSize, string sortBy = "score", string sortOrder = "desc");
|
||||||
|
|
||||||
// Bundle backtest methods
|
// Bundle backtest methods
|
||||||
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
|
||||||
|
Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
|
||||||
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByUserAsync(User user);
|
||||||
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
|
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, Guid id);
|
||||||
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id);
|
Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, Guid id);
|
||||||
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
|
||||||
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
|
Task UpdateBundleBacktestRequestAsync(BundleBacktestRequest bundleRequest);
|
||||||
void DeleteBundleBacktestRequestByIdForUser(User user, string id);
|
void DeleteBundleBacktestRequestByIdForUser(User user, Guid id);
|
||||||
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id);
|
Task DeleteBundleBacktestRequestByIdForUserAsync(User user, Guid id);
|
||||||
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
|
||||||
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
|
Task<IEnumerable<BundleBacktestRequest>> GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus status);
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ namespace Managing.Application.Backtests
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
var candles = await GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
||||||
return await RunBacktestWithCandles(config, candles, user, save, withCandles, requestId, metadata);
|
return await RunBacktestWithCandles(config, candles, user, save, withCandles, requestId, metadata);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -197,11 +197,11 @@ namespace Managing.Application.Backtests
|
|||||||
return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
|
return await _accountService.GetAccountByAccountName(config.AccountName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashSet<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
|
private async Task<HashSet<Candle>> GetCandles(Ticker ticker, Timeframe timeframe,
|
||||||
DateTime startDate, DateTime endDate)
|
DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
|
var candles = await _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
|
||||||
startDate, timeframe, endDate).Result;
|
startDate, timeframe, endDate);
|
||||||
|
|
||||||
if (candles == null || candles.Count == 0)
|
if (candles == null || candles.Count == 0)
|
||||||
throw new Exception($"No candles for {ticker} on {timeframe} timeframe");
|
throw new Exception($"No candles for {ticker} on {timeframe} timeframe");
|
||||||
@@ -277,19 +277,19 @@ namespace Managing.Application.Backtests
|
|||||||
return backtests;
|
return backtests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
public IEnumerable<Backtest> GetBacktestsByRequestId(Guid requestId)
|
||||||
{
|
{
|
||||||
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
|
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
|
||||||
return backtests;
|
return backtests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
|
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(Guid requestId)
|
||||||
{
|
{
|
||||||
var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId);
|
var backtests = await _backtestRepository.GetBacktestsByRequestIdAsync(requestId);
|
||||||
return backtests;
|
return backtests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
|
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(Guid requestId,
|
||||||
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
{
|
{
|
||||||
var (backtests, totalCount) =
|
var (backtests, totalCount) =
|
||||||
@@ -298,7 +298,7 @@ namespace Managing.Application.Backtests
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
|
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
|
||||||
string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
Guid requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
{
|
{
|
||||||
var (backtests, totalCount) =
|
var (backtests, totalCount) =
|
||||||
await _backtestRepository.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy,
|
await _backtestRepository.GetBacktestsByRequestIdPaginatedAsync(requestId, page, pageSize, sortBy,
|
||||||
@@ -384,7 +384,7 @@ namespace Managing.Application.Backtests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteBacktestsByRequestIdAsync(string requestId)
|
public async Task<bool> DeleteBacktestsByRequestIdAsync(Guid requestId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -418,6 +418,17 @@ namespace Managing.Application.Backtests
|
|||||||
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
|
public void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest)
|
||||||
{
|
{
|
||||||
_backtestRepository.InsertBundleBacktestRequestForUser(user, bundleRequest);
|
_backtestRepository.InsertBundleBacktestRequestForUser(user, bundleRequest);
|
||||||
|
|
||||||
|
// Trigger the BundleBacktestGrain to process this request
|
||||||
|
TriggerBundleBacktestGrain(bundleRequest.RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertBundleBacktestRequestForUserAsync(User user, BundleBacktestRequest bundleRequest)
|
||||||
|
{
|
||||||
|
await _backtestRepository.InsertBundleBacktestRequestForUserAsync(user, bundleRequest);
|
||||||
|
|
||||||
|
// Trigger the BundleBacktestGrain to process this request
|
||||||
|
await TriggerBundleBacktestGrainAsync(bundleRequest.RequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
|
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
|
||||||
@@ -430,12 +441,12 @@ namespace Managing.Application.Backtests
|
|||||||
return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
|
return await _backtestRepository.GetBundleBacktestRequestsByUserAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
|
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, Guid id)
|
||||||
{
|
{
|
||||||
return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
|
return _backtestRepository.GetBundleBacktestRequestByIdForUser(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
|
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, Guid id)
|
||||||
{
|
{
|
||||||
return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id);
|
return await _backtestRepository.GetBundleBacktestRequestByIdForUserAsync(user, id);
|
||||||
}
|
}
|
||||||
@@ -450,12 +461,12 @@ namespace Managing.Application.Backtests
|
|||||||
await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
|
await _backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
|
public void DeleteBundleBacktestRequestByIdForUser(User user, Guid id)
|
||||||
{
|
{
|
||||||
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
|
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
|
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, Guid id)
|
||||||
{
|
{
|
||||||
await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
|
await _backtestRepository.DeleteBundleBacktestRequestByIdForUserAsync(user, id);
|
||||||
}
|
}
|
||||||
@@ -480,5 +491,65 @@ namespace Managing.Application.Backtests
|
|||||||
if (string.IsNullOrWhiteSpace(requestId) || response == null) return;
|
if (string.IsNullOrWhiteSpace(requestId) || response == null) return;
|
||||||
await _hubContext.Clients.Group($"bundle-{requestId}").SendAsync("BundleBacktestUpdate", response);
|
await _hubContext.Clients.Group($"bundle-{requestId}").SendAsync("BundleBacktestUpdate", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the BundleBacktestGrain to process a bundle request synchronously (fire and forget)
|
||||||
|
/// </summary>
|
||||||
|
private void TriggerBundleBacktestGrain(Guid bundleRequestId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bundleBacktestGrain = _grainFactory.GetGrain<IBundleBacktestGrain>(bundleRequestId);
|
||||||
|
|
||||||
|
// Fire and forget - don't await
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await bundleBacktestGrain.ProcessBundleRequestAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error triggering BundleBacktestGrain for request {RequestId}",
|
||||||
|
bundleRequestId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error in TriggerBundleBacktestGrain for request {RequestId}", bundleRequestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the BundleBacktestGrain to process a bundle request asynchronously
|
||||||
|
/// </summary>
|
||||||
|
private Task TriggerBundleBacktestGrainAsync(Guid bundleRequestId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bundleBacktestGrain = _grainFactory.GetGrain<IBundleBacktestGrain>(bundleRequestId);
|
||||||
|
|
||||||
|
// Fire and forget - don't await the actual processing
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await bundleBacktestGrain.ProcessBundleRequestAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error triggering BundleBacktestGrain for request {RequestId}",
|
||||||
|
bundleRequestId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error in TriggerBundleBacktestGrainAsync for request {RequestId}",
|
||||||
|
bundleRequestId);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
|
|||||||
var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);
|
var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);
|
||||||
|
|
||||||
// Generate requestId if not provided
|
// Generate requestId if not provided
|
||||||
var finalRequestId = requestId ?? Guid.NewGuid().ToString();
|
var finalRequestId = requestId != null ? Guid.Parse(requestId) : Guid.NewGuid();
|
||||||
|
|
||||||
// Create backtest result with conditional candles and indicators values
|
// Create backtest result with conditional candles and indicators values
|
||||||
var result = new Backtest(config, tradingBot.Positions, tradingBot.Signals,
|
var result = new Backtest(config, tradingBot.Positions, tradingBot.Signals,
|
||||||
|
|||||||
411
src/Managing.Application/Grains/BundleBacktestGrain.cs
Normal file
411
src/Managing.Application/Grains/BundleBacktestGrain.cs
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Domain.Backtests;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.MoneyManagements;
|
||||||
|
using Managing.Domain.Scenarios;
|
||||||
|
using Managing.Domain.Strategies;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Orleans.Concurrency;
|
||||||
|
|
||||||
|
namespace Managing.Application.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stateless worker grain for processing bundle backtest requests
|
||||||
|
/// Uses the bundle request ID as the primary key (Guid)
|
||||||
|
/// Implements IRemindable for automatic retry of failed bundles
|
||||||
|
/// </summary>
|
||||||
|
[StatelessWorker]
|
||||||
|
public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
||||||
|
{
|
||||||
|
private readonly ILogger<BundleBacktestGrain> _logger;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
|
// Reminder configuration
|
||||||
|
private const string RETRY_REMINDER_NAME = "BundleBacktestRetry";
|
||||||
|
private static readonly TimeSpan RETRY_INTERVAL = TimeSpan.FromMinutes(30);
|
||||||
|
|
||||||
|
public BundleBacktestGrain(
|
||||||
|
ILogger<BundleBacktestGrain> logger,
|
||||||
|
IServiceScopeFactory scopeFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessBundleRequestAsync()
|
||||||
|
{
|
||||||
|
// Get the RequestId from the grain's primary key
|
||||||
|
var bundleRequestId = this.GetPrimaryKey();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create a new service scope to get fresh instances of services with scoped DbContext
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
|
||||||
|
var messengerService = scope.ServiceProvider.GetRequiredService<IMessengerService>();
|
||||||
|
|
||||||
|
// Get the specific bundle request by ID
|
||||||
|
var bundleRequest = await GetBundleRequestById(backtester, bundleRequestId);
|
||||||
|
if (bundleRequest == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Bundle request {RequestId} not found", bundleRequestId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process only this specific bundle request
|
||||||
|
await ProcessBundleRequest(bundleRequest, backtester, messengerService);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error in BundleBacktestGrain for request {RequestId}", bundleRequestId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BundleBacktestRequest> GetBundleRequestById(IBacktester backtester, Guid bundleRequestId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get pending and failed bundle backtest requests for retry capability
|
||||||
|
var pendingRequests = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Pending);
|
||||||
|
var failedRequests = await backtester.GetBundleBacktestRequestsByStatusAsync(BundleBacktestRequestStatus.Failed);
|
||||||
|
|
||||||
|
var allRequests = pendingRequests.Concat(failedRequests);
|
||||||
|
return allRequests.FirstOrDefault(r => r.RequestId == bundleRequestId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get bundle request {RequestId}", bundleRequestId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessBundleRequest(
|
||||||
|
BundleBacktestRequest bundleRequest,
|
||||||
|
IBacktester backtester,
|
||||||
|
IMessengerService messengerService)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting to process bundle backtest request {RequestId}", bundleRequest.RequestId);
|
||||||
|
|
||||||
|
// Update status to running
|
||||||
|
bundleRequest.Status = BundleBacktestRequestStatus.Running;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
|
// Deserialize the backtest requests as strongly-typed objects
|
||||||
|
var backtestRequests = JsonSerializer.Deserialize<List<RunBacktestRequest>>(bundleRequest.BacktestRequestsJson);
|
||||||
|
if (backtestRequests == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to deserialize backtest requests");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each backtest request sequentially
|
||||||
|
for (int i = 0; i < backtestRequests.Count; i++)
|
||||||
|
{
|
||||||
|
await ProcessSingleBacktest(backtester, backtestRequests[i], bundleRequest, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update final status and send notifications
|
||||||
|
await UpdateFinalStatus(bundleRequest, backtester, messengerService);
|
||||||
|
|
||||||
|
_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);
|
||||||
|
await HandleBundleRequestError(bundleRequest, backtester, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessSingleBacktest(
|
||||||
|
IBacktester backtester,
|
||||||
|
RunBacktestRequest runBacktestRequest,
|
||||||
|
BundleBacktestRequest bundleRequest,
|
||||||
|
int index)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get total count from deserialized requests instead of string splitting
|
||||||
|
var backtestRequests = JsonSerializer.Deserialize<List<RunBacktestRequest>>(bundleRequest.BacktestRequestsJson);
|
||||||
|
var totalCount = backtestRequests?.Count ?? 0;
|
||||||
|
|
||||||
|
// Update current backtest being processed
|
||||||
|
bundleRequest.CurrentBacktest = $"Backtest {index + 1} of {totalCount}";
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
|
// Run the backtest directly with the strongly-typed request
|
||||||
|
var backtestId = await RunSingleBacktest(backtester, runBacktestRequest, bundleRequest, index);
|
||||||
|
if (!string.IsNullOrEmpty(backtestId))
|
||||||
|
{
|
||||||
|
bundleRequest.Results.Add(backtestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update progress
|
||||||
|
bundleRequest.CompletedBacktests++;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
|
_logger.LogInformation("Completed backtest {Index} for bundle request {RequestId}",
|
||||||
|
index + 1, bundleRequest.RequestId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error processing backtest {Index} for bundle request {RequestId}",
|
||||||
|
index + 1, bundleRequest.RequestId);
|
||||||
|
bundleRequest.FailedBacktests++;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> RunSingleBacktest(
|
||||||
|
IBacktester backtester,
|
||||||
|
RunBacktestRequest runBacktestRequest,
|
||||||
|
BundleBacktestRequest bundleRequest,
|
||||||
|
int index)
|
||||||
|
{
|
||||||
|
if (runBacktestRequest?.Config == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Invalid RunBacktestRequest in bundle (null config)");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map MoneyManagement
|
||||||
|
MoneyManagement moneyManagement = null;
|
||||||
|
if (!string.IsNullOrEmpty(runBacktestRequest.Config.MoneyManagementName))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("MoneyManagementName provided but cannot resolve in grain context: {Name}",
|
||||||
|
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
|
||||||
|
LightScenario scenario = null;
|
||||||
|
if (runBacktestRequest.Config.Scenario != null)
|
||||||
|
{
|
||||||
|
var sReq = runBacktestRequest.Config.Scenario;
|
||||||
|
scenario = new LightScenario(sReq.Name, sReq.LoopbackPeriod)
|
||||||
|
{
|
||||||
|
Indicators = sReq.Indicators?.Select(i => new LightIndicator(i.Name, i.Type)
|
||||||
|
{
|
||||||
|
SignalType = i.SignalType,
|
||||||
|
MinimumHistory = i.MinimumHistory,
|
||||||
|
Period = i.Period,
|
||||||
|
FastPeriods = i.FastPeriods,
|
||||||
|
SlowPeriods = i.SlowPeriods,
|
||||||
|
SignalPeriods = i.SignalPeriods,
|
||||||
|
Multiplier = i.Multiplier,
|
||||||
|
SmoothPeriods = i.SmoothPeriods,
|
||||||
|
StochPeriods = i.StochPeriods,
|
||||||
|
CyclePeriods = i.CyclePeriods
|
||||||
|
}).ToList() ?? new List<LightIndicator>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ?? 1,
|
||||||
|
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
|
||||||
|
var result = await backtester.RunTradingBotBacktest(
|
||||||
|
backtestConfig,
|
||||||
|
runBacktestRequest.StartDate,
|
||||||
|
runBacktestRequest.EndDate,
|
||||||
|
bundleRequest.User,
|
||||||
|
true,
|
||||||
|
runBacktestRequest.WithCandles,
|
||||||
|
bundleRequest.RequestId.ToString()
|
||||||
|
);
|
||||||
|
|
||||||
|
_logger.LogInformation("Processed backtest for bundle request {RequestId}", bundleRequest.RequestId);
|
||||||
|
return result.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateFinalStatus(
|
||||||
|
BundleBacktestRequest bundleRequest,
|
||||||
|
IBacktester backtester,
|
||||||
|
IMessengerService messengerService)
|
||||||
|
{
|
||||||
|
if (bundleRequest.FailedBacktests == 0)
|
||||||
|
{
|
||||||
|
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
||||||
|
await NotifyUser(bundleRequest, messengerService);
|
||||||
|
}
|
||||||
|
else if (bundleRequest.CompletedBacktests == 0)
|
||||||
|
{
|
||||||
|
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
|
||||||
|
bundleRequest.ErrorMessage = "All backtests failed";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
||||||
|
bundleRequest.ErrorMessage = $"{bundleRequest.FailedBacktests} backtests failed";
|
||||||
|
await NotifyUser(bundleRequest, messengerService);
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleRequest.CompletedAt = DateTime.UtcNow;
|
||||||
|
bundleRequest.CurrentBacktest = null;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
|
// Unregister retry reminder since bundle completed
|
||||||
|
await UnregisterRetryReminder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleBundleRequestError(
|
||||||
|
BundleBacktestRequest bundleRequest,
|
||||||
|
IBacktester backtester,
|
||||||
|
Exception ex)
|
||||||
|
{
|
||||||
|
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
|
||||||
|
bundleRequest.ErrorMessage = ex.Message;
|
||||||
|
bundleRequest.CompletedAt = DateTime.UtcNow;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
|
// Register retry reminder for failed bundle
|
||||||
|
await RegisterRetryReminder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NotifyUser(BundleBacktestRequest bundleRequest, IMessengerService messengerService)
|
||||||
|
{
|
||||||
|
if (bundleRequest.User?.TelegramChannel != null)
|
||||||
|
{
|
||||||
|
var message = bundleRequest.FailedBacktests == 0
|
||||||
|
? $"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed successfully."
|
||||||
|
: $"⚠️ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed with {bundleRequest.FailedBacktests} failed backtests.";
|
||||||
|
|
||||||
|
await messengerService.SendMessage(message, bundleRequest.User.TelegramChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IRemindable Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles reminder callbacks for automatic retry of failed bundle backtests
|
||||||
|
/// </summary>
|
||||||
|
public async Task ReceiveReminder(string reminderName, TickStatus status)
|
||||||
|
{
|
||||||
|
if (reminderName != RETRY_REMINDER_NAME)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unknown reminder {ReminderName} received", reminderName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bundleRequestId = this.GetPrimaryKey();
|
||||||
|
_logger.LogInformation("Retry reminder triggered for bundle request {RequestId}", bundleRequestId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var backtester = scope.ServiceProvider.GetRequiredService<IBacktester>();
|
||||||
|
|
||||||
|
// Get the bundle request
|
||||||
|
var bundleRequest = await GetBundleRequestById(backtester, bundleRequestId);
|
||||||
|
if (bundleRequest == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Bundle request {RequestId} not found during retry", bundleRequestId);
|
||||||
|
await UnregisterRetryReminder();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bundle is still failed
|
||||||
|
if (bundleRequest.Status != BundleBacktestRequestStatus.Failed)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Bundle request {RequestId} is no longer failed (status: {Status}), unregistering reminder",
|
||||||
|
bundleRequestId, bundleRequest.Status);
|
||||||
|
await UnregisterRetryReminder();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry the bundle processing
|
||||||
|
_logger.LogInformation("Retrying failed bundle request {RequestId}", bundleRequestId);
|
||||||
|
|
||||||
|
// Reset status to pending for retry
|
||||||
|
bundleRequest.Status = BundleBacktestRequestStatus.Pending;
|
||||||
|
bundleRequest.ErrorMessage = null;
|
||||||
|
bundleRequest.CurrentBacktest = null;
|
||||||
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
|
||||||
|
// Process the bundle again
|
||||||
|
await ProcessBundleRequestAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during bundle backtest retry for request {RequestId}", bundleRequestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a retry reminder for this bundle request
|
||||||
|
/// </summary>
|
||||||
|
private async Task RegisterRetryReminder()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.RegisterOrUpdateReminder(RETRY_REMINDER_NAME, RETRY_INTERVAL, RETRY_INTERVAL);
|
||||||
|
_logger.LogInformation("Registered retry reminder for bundle request {RequestId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to register retry reminder for bundle request {RequestId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters the retry reminder for this bundle request
|
||||||
|
/// </summary>
|
||||||
|
private async Task UnregisterRetryReminder()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var reminder = await this.GetReminder(RETRY_REMINDER_NAME);
|
||||||
|
if (reminder != null)
|
||||||
|
{
|
||||||
|
await this.UnregisterReminder(reminder);
|
||||||
|
_logger.LogInformation("Unregistered retry reminder for bundle request {RequestId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to unregister retry reminder for bundle request {RequestId}", this.GetPrimaryKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
@@ -277,7 +277,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
|
|||||||
bundleRequest.User, // No user context in worker
|
bundleRequest.User, // No user context in worker
|
||||||
true,
|
true,
|
||||||
runBacktestRequest.WithCandles,
|
runBacktestRequest.WithCandles,
|
||||||
bundleRequest.RequestId // Use bundleRequestId as requestId for traceability
|
bundleRequest.RequestId.ToString() // Use bundleRequestId as requestId for traceability
|
||||||
);
|
);
|
||||||
|
|
||||||
_logger.LogInformation("Processed backtest for bundle request {RequestId}", bundleRequest.RequestId);
|
_logger.LogInformation("Processed backtest for bundle request {RequestId}", bundleRequest.RequestId);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
|
|||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IHubContext<BacktestHub> _hubContext;
|
private readonly IHubContext<BacktestHub> _hubContext;
|
||||||
private readonly ConcurrentDictionary<string, HashSet<string>> _sentBacktestIds = new();
|
private readonly ConcurrentDictionary<Guid, HashSet<string>> _sentBacktestIds = new();
|
||||||
|
|
||||||
public NotifyBundleBacktestWorker(
|
public NotifyBundleBacktestWorker(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
@@ -39,7 +39,6 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
|
|||||||
foreach (var bundle in runningBundles)
|
foreach (var bundle in runningBundles)
|
||||||
{
|
{
|
||||||
var requestId = bundle.RequestId;
|
var requestId = bundle.RequestId;
|
||||||
if (string.IsNullOrEmpty(requestId)) continue;
|
|
||||||
|
|
||||||
// Fetch all backtests for this bundle
|
// Fetch all backtests for this bundle
|
||||||
var (backtests, _) = backtester.GetBacktestsByRequestIdPaginated(requestId, 1, 100);
|
var (backtests, _) = backtester.GetBacktestsByRequestIdPaginated(requestId, 1, 100);
|
||||||
|
|||||||
@@ -501,10 +501,12 @@ public static class ApiBootstrap
|
|||||||
services.AddHostedService<GeneticAlgorithmWorker>();
|
services.AddHostedService<GeneticAlgorithmWorker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("WorkerBundleBacktest", false))
|
// DEPRECATED: BundleBacktestWorker has been replaced by BundleBacktestGrain
|
||||||
{
|
// Bundle backtest processing is now handled by Orleans grain triggered directly from Backtester.cs
|
||||||
services.AddHostedService<BundleBacktestWorker>();
|
// if (configuration.GetValue<bool>("WorkerBundleBacktest", false))
|
||||||
}
|
// {
|
||||||
|
// services.AddHostedService<BundleBacktestWorker>();
|
||||||
|
// }
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class Backtest
|
|||||||
[Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
|
[Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
|
||||||
[Required] public User User { get; set; }
|
[Required] public User User { get; set; }
|
||||||
[Required] public double Score { get; set; }
|
[Required] public double Score { get; set; }
|
||||||
public string RequestId { get; set; }
|
public Guid RequestId { get; set; }
|
||||||
public object? Metadata { get; set; }
|
public object? Metadata { get; set; }
|
||||||
public string ScoreMessage { get; set; } = string.Empty;
|
public string ScoreMessage { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class BundleBacktestRequest
|
|||||||
{
|
{
|
||||||
public BundleBacktestRequest()
|
public BundleBacktestRequest()
|
||||||
{
|
{
|
||||||
RequestId = Guid.NewGuid().ToString();
|
RequestId = Guid.NewGuid();
|
||||||
CreatedAt = DateTime.UtcNow;
|
CreatedAt = DateTime.UtcNow;
|
||||||
Status = BundleBacktestRequestStatus.Pending;
|
Status = BundleBacktestRequestStatus.Pending;
|
||||||
Results = new List<string>();
|
Results = new List<string>();
|
||||||
@@ -21,7 +21,7 @@ public class BundleBacktestRequest
|
|||||||
/// Constructor that allows setting a specific ID
|
/// Constructor that allows setting a specific ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="requestId">The specific ID to use</param>
|
/// <param name="requestId">The specific ID to use</param>
|
||||||
public BundleBacktestRequest(string requestId)
|
public BundleBacktestRequest(Guid requestId)
|
||||||
{
|
{
|
||||||
RequestId = requestId;
|
RequestId = requestId;
|
||||||
CreatedAt = DateTime.UtcNow;
|
CreatedAt = DateTime.UtcNow;
|
||||||
@@ -34,7 +34,7 @@ public class BundleBacktestRequest
|
|||||||
/// Unique identifier for the bundle backtest request
|
/// Unique identifier for the bundle backtest request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
public string RequestId { get; set; }
|
public Guid RequestId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user who created this request
|
/// The user who created this request
|
||||||
|
|||||||
1438
src/Managing.Infrastructure.Database/Migrations/20250915052125_ChangeRequestIdToGuid.Designer.cs
generated
Normal file
1438
src/Managing.Infrastructure.Database/Migrations/20250915052125_ChangeRequestIdToGuid.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Databases.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ChangeRequestIdToGuid : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// Convert BundleBacktestRequests.RequestId from varchar to uuid
|
||||||
|
// First, ensure all values are valid UUIDs or convert them
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
UPDATE ""BundleBacktestRequests""
|
||||||
|
SET ""RequestId"" = gen_random_uuid()::text
|
||||||
|
WHERE ""RequestId"" IS NULL OR ""RequestId"" = '' OR
|
||||||
|
""RequestId"" !~ '^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$';
|
||||||
|
");
|
||||||
|
|
||||||
|
// Now convert the column type using the USING clause
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
ALTER TABLE ""BundleBacktestRequests""
|
||||||
|
ALTER COLUMN ""RequestId"" TYPE uuid USING ""RequestId""::uuid;
|
||||||
|
");
|
||||||
|
|
||||||
|
// Convert Backtests.RequestId from varchar to uuid
|
||||||
|
// First, ensure all values are valid UUIDs or convert them
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
UPDATE ""Backtests""
|
||||||
|
SET ""RequestId"" = gen_random_uuid()::text
|
||||||
|
WHERE ""RequestId"" IS NULL OR ""RequestId"" = '' OR
|
||||||
|
""RequestId"" !~ '^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$';
|
||||||
|
");
|
||||||
|
|
||||||
|
// Now convert the column type using the USING clause
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
ALTER TABLE ""Backtests""
|
||||||
|
ALTER COLUMN ""RequestId"" TYPE uuid USING ""RequestId""::uuid;
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// Convert BundleBacktestRequests.RequestId from uuid back to varchar
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
ALTER TABLE ""BundleBacktestRequests""
|
||||||
|
ALTER COLUMN ""RequestId"" TYPE character varying(255) USING ""RequestId""::text;
|
||||||
|
");
|
||||||
|
|
||||||
|
// Convert Backtests.RequestId from uuid back to varchar
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
ALTER TABLE ""Backtests""
|
||||||
|
ALTER COLUMN ""RequestId"" TYPE character varying(255) USING ""RequestId""::text;
|
||||||
|
");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -176,10 +176,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("jsonb");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
b.Property<string>("RequestId")
|
b.Property<Guid>("RequestId")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
.HasColumnType("character varying(255)");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<double>("Score")
|
b.Property<double>("Score")
|
||||||
.HasColumnType("double precision");
|
.HasColumnType("double precision");
|
||||||
@@ -339,10 +338,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
b.Property<string>("ProgressInfo")
|
b.Property<string>("ProgressInfo")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("RequestId")
|
b.Property<Guid>("RequestId")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
.HasColumnType("character varying(255)");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("ResultsJson")
|
b.Property<string>("ResultsJson")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ public class BacktestEntity
|
|||||||
public string Identifier { get; set; } = string.Empty;
|
public string Identifier { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(255)]
|
public Guid RequestId { get; set; }
|
||||||
public string RequestId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column(TypeName = "decimal(18,8)")]
|
[Column(TypeName = "decimal(18,8)")]
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ public class BundleBacktestRequestEntity
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(255)]
|
public Guid RequestId { get; set; }
|
||||||
public string RequestId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
// Foreign key to User entity
|
// Foreign key to User entity
|
||||||
public int? UserId { get; set; }
|
public int? UserId { get; set; }
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
return entities.Select(PostgreSqlMappers.Map);
|
return entities.Select(PostgreSqlMappers.Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
public IEnumerable<Backtest> GetBacktestsByRequestId(Guid requestId)
|
||||||
{
|
{
|
||||||
var entities = _context.Backtests
|
var entities = _context.Backtests
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -101,7 +101,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
return entities.Select(PostgreSqlMappers.Map);
|
return entities.Select(PostgreSqlMappers.Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(string requestId)
|
public async Task<IEnumerable<Backtest>> GetBacktestsByRequestIdAsync(Guid requestId)
|
||||||
{
|
{
|
||||||
var entities = await _context.Backtests
|
var entities = await _context.Backtests
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -112,7 +112,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
return entities.Select(PostgreSqlMappers.Map);
|
return entities.Select(PostgreSqlMappers.Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId,
|
public (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(Guid requestId,
|
||||||
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
@@ -185,7 +185,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
|
public async Task<(IEnumerable<LightBacktest> Backtests, int TotalCount)> GetBacktestsByRequestIdPaginatedAsync(
|
||||||
string requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
Guid requestId, int page, int pageSize, string sortBy = "score", string sortOrder = "desc")
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteBacktestsByRequestId(string requestId)
|
public void DeleteBacktestsByRequestId(Guid requestId)
|
||||||
{
|
{
|
||||||
var entities = _context.Backtests
|
var entities = _context.Backtests
|
||||||
.AsTracking()
|
.AsTracking()
|
||||||
@@ -362,7 +362,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteBacktestsByRequestIdAsync(string requestId)
|
public async Task DeleteBacktestsByRequestIdAsync(Guid requestId)
|
||||||
{
|
{
|
||||||
var entities = await _context.Backtests
|
var entities = await _context.Backtests
|
||||||
.AsTracking()
|
.AsTracking()
|
||||||
@@ -580,7 +580,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
return entities.Select(PostgreSqlMappers.Map);
|
return entities.Select(PostgreSqlMappers.Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
|
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, Guid id)
|
||||||
{
|
{
|
||||||
var entity = _context.BundleBacktestRequests
|
var entity = _context.BundleBacktestRequests
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -590,7 +590,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
return entity != null ? PostgreSqlMappers.Map(entity) : null;
|
return entity != null ? PostgreSqlMappers.Map(entity) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, string id)
|
public async Task<BundleBacktestRequest?> GetBundleBacktestRequestByIdForUserAsync(User user, Guid id)
|
||||||
{
|
{
|
||||||
var entity = await _context.BundleBacktestRequests
|
var entity = await _context.BundleBacktestRequests
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -682,7 +682,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
|
public void DeleteBundleBacktestRequestByIdForUser(User user, Guid id)
|
||||||
{
|
{
|
||||||
var entity = _context.BundleBacktestRequests
|
var entity = _context.BundleBacktestRequests
|
||||||
.AsTracking()
|
.AsTracking()
|
||||||
@@ -695,7 +695,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, string id)
|
public async Task DeleteBundleBacktestRequestByIdForUserAsync(User user, Guid id)
|
||||||
{
|
{
|
||||||
var entity = await _context.BundleBacktestRequests
|
var entity = await _context.BundleBacktestRequests
|
||||||
.AsTracking()
|
.AsTracking()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {AccountClient, BacktestClient} from '../../generated/ManagingApi';
|
import {AccountClient, BacktestClient} from '../../generated/ManagingApi';
|
||||||
import type {
|
import type {
|
||||||
MoneyManagementRequest,
|
MoneyManagementRequest,
|
||||||
@@ -61,9 +61,6 @@ const BacktestBundleForm: React.FC = () => {
|
|||||||
const { data: accounts, isSuccess } = useQuery({
|
const { data: accounts, isSuccess } = useQuery({
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const fetchedAccounts = await accountClient.account_GetAccounts();
|
const fetchedAccounts = await accountClient.account_GetAccounts();
|
||||||
if (fetchedAccounts.length > 0 && accountName === 'default') {
|
|
||||||
setAccountName(fetchedAccounts[0].name);
|
|
||||||
}
|
|
||||||
return fetchedAccounts;
|
return fetchedAccounts;
|
||||||
},
|
},
|
||||||
queryKey: ['accounts'],
|
queryKey: ['accounts'],
|
||||||
@@ -88,13 +85,20 @@ const BacktestBundleForm: React.FC = () => {
|
|||||||
const [flipOnlyInProfit, setFlipOnlyInProfit] = useState(false);
|
const [flipOnlyInProfit, setFlipOnlyInProfit] = useState(false);
|
||||||
const [closeEarly, setCloseEarly] = useState(false);
|
const [closeEarly, setCloseEarly] = useState(false);
|
||||||
const [startingCapital, setStartingCapital] = useState(10000);
|
const [startingCapital, setStartingCapital] = useState(10000);
|
||||||
const [accountName, setAccountName] = useState(accounts?.[0]?.name ?? '');
|
const [accountName, setAccountName] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
const { scenario, setCustomScenario } = useCustomScenario();
|
const { scenario, setCustomScenario } = useCustomScenario();
|
||||||
|
|
||||||
|
// Set account name when accounts are loaded
|
||||||
|
useEffect(() => {
|
||||||
|
if (accounts && accounts.length > 0 && !accountName) {
|
||||||
|
setAccountName(accounts[0].name);
|
||||||
|
}
|
||||||
|
}, [accounts, accountName]);
|
||||||
|
|
||||||
// Placeholder for cart summary
|
// Placeholder for cart summary
|
||||||
const totalBacktests =
|
const totalBacktests =
|
||||||
moneyManagementVariants.length * timeRangeVariants.length * (selectedAssets.length || 1);
|
moneyManagementVariants.length * timeRangeVariants.length * (selectedAssets.length || 1);
|
||||||
@@ -159,6 +163,7 @@ const BacktestBundleForm: React.FC = () => {
|
|||||||
const client = new BacktestClient({} as any, apiUrl);
|
const client = new BacktestClient({} as any, apiUrl);
|
||||||
const requests = generateRequests();
|
const requests = generateRequests();
|
||||||
if (!strategyName) throw new Error('Strategy name is required');
|
if (!strategyName) throw new Error('Strategy name is required');
|
||||||
|
if (!accountName) throw new Error('Account selection is required');
|
||||||
if (requests.length === 0) throw new Error('No backtest variants to run');
|
if (requests.length === 0) throw new Error('No backtest variants to run');
|
||||||
await client.backtest_RunBundle({ name: strategyName, requests });
|
await client.backtest_RunBundle({ name: strategyName, requests });
|
||||||
setSuccess('Bundle backtest started successfully!');
|
setSuccess('Bundle backtest started successfully!');
|
||||||
@@ -195,12 +200,17 @@ const BacktestBundleForm: React.FC = () => {
|
|||||||
className="select select-bordered w-full"
|
className="select select-bordered w-full"
|
||||||
value={accountName}
|
value={accountName}
|
||||||
onChange={e => setAccountName(e.target.value)}
|
onChange={e => setAccountName(e.target.value)}
|
||||||
|
disabled={!accounts || accounts.length === 0}
|
||||||
>
|
>
|
||||||
{accounts?.map(account => (
|
{!accounts || accounts.length === 0 ? (
|
||||||
|
<option value="">Loading accounts...</option>
|
||||||
|
) : (
|
||||||
|
accounts.map(account => (
|
||||||
<option key={account.name} value={account.name}>
|
<option key={account.name} value={account.name}>
|
||||||
{account.name}
|
{account.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user