Fix bundle completion
This commit is contained in:
@@ -410,12 +410,6 @@ public class BacktestExecutor
|
||||
if (save && user != null)
|
||||
{
|
||||
await _backtestRepository.InsertBacktestForUserAsync(user, result);
|
||||
|
||||
// Update bundle request if provided
|
||||
if (bundleRequestId.HasValue)
|
||||
{
|
||||
await UpdateBundleRequestWithBacktestResult(user, bundleRequestId.Value, result);
|
||||
}
|
||||
}
|
||||
|
||||
// Send notification if backtest meets criteria
|
||||
@@ -574,150 +568,6 @@ public class BacktestExecutor
|
||||
return tradingBot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized backtest step execution - delegate to standard Run but with backtest optimizations
|
||||
/// </summary>
|
||||
private async Task RunOptimizedBacktestStep(TradingBotBase tradingBot)
|
||||
{
|
||||
// Use the standard Run method but ensure it's optimized for backtests
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates bundle request with the completed backtest result
|
||||
/// </summary>
|
||||
private async Task UpdateBundleRequestWithBacktestResult(User user, Guid bundleRequestId, Backtest backtest)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var backtestRepository = scope.ServiceProvider.GetRequiredService<IBacktestRepository>();
|
||||
var jobRepository = scope.ServiceProvider.GetRequiredService<IJobRepository>();
|
||||
var webhookService = scope.ServiceProvider.GetRequiredService<IWebhookService>();
|
||||
|
||||
// Get bundle request
|
||||
var bundleRequest = backtestRepository.GetBundleBacktestRequestByIdForUser(user, bundleRequestId);
|
||||
if (bundleRequest == null)
|
||||
{
|
||||
_logger.LogWarning("Bundle request {BundleRequestId} not found for user {UserId}", bundleRequestId,
|
||||
user.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var previousStatus = bundleRequest.Status;
|
||||
|
||||
// Get all jobs for this bundle to calculate progress
|
||||
var jobs = await jobRepository.GetByBundleRequestIdAsync(bundleRequestId);
|
||||
var completedJobs = jobs.Count(j => j.Status == JobStatus.Completed);
|
||||
var failedJobs = jobs.Count(j => j.Status == JobStatus.Failed);
|
||||
var runningJobs = jobs.Count(j => j.Status == JobStatus.Running);
|
||||
var totalJobs = jobs.Count();
|
||||
|
||||
// Update bundle request progress (always update counters regardless of status)
|
||||
bundleRequest.CompletedBacktests = completedJobs;
|
||||
bundleRequest.FailedBacktests = failedJobs;
|
||||
bundleRequest.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
// CRITICAL: If bundle is already in a final state (Completed/Failed with CompletedAt set),
|
||||
// don't overwrite it unless we're detecting a legitimate change
|
||||
if (bundleRequest.CompletedAt.HasValue &&
|
||||
(bundleRequest.Status == BundleBacktestRequestStatus.Completed ||
|
||||
bundleRequest.Status == BundleBacktestRequestStatus.Failed))
|
||||
{
|
||||
// Bundle already finalized, only update if job counts indicate it should be re-opened
|
||||
// (This shouldn't happen in normal flow, but guards against race conditions)
|
||||
if (completedJobs + failedJobs == totalJobs)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Bundle {BundleRequestId} already completed/failed. Skipping status update.",
|
||||
bundleRequestId);
|
||||
// Progress counters already updated above, just return
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Bundle {BundleRequestId} was marked as completed/failed but has incomplete jobs ({Completed}+{Failed}/{Total}). Reopening.",
|
||||
bundleRequestId, completedJobs, failedJobs, totalJobs);
|
||||
// Allow the update to proceed to fix inconsistent state
|
||||
}
|
||||
}
|
||||
|
||||
// Update status based on job states
|
||||
if (completedJobs + failedJobs == totalJobs)
|
||||
{
|
||||
// All jobs completed or failed
|
||||
if (failedJobs == 0)
|
||||
{
|
||||
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
||||
}
|
||||
else if (completedJobs == 0)
|
||||
{
|
||||
bundleRequest.Status = BundleBacktestRequestStatus.Failed;
|
||||
bundleRequest.ErrorMessage = "All backtests failed";
|
||||
}
|
||||
else
|
||||
{
|
||||
bundleRequest.Status = BundleBacktestRequestStatus.Completed;
|
||||
bundleRequest.ErrorMessage = $"{failedJobs} backtests failed";
|
||||
}
|
||||
|
||||
bundleRequest.CompletedAt = DateTime.UtcNow;
|
||||
bundleRequest.CurrentBacktest = null;
|
||||
}
|
||||
else if (runningJobs > 0 || completedJobs > 0 || failedJobs > 0)
|
||||
{
|
||||
// Some jobs are running, or some have completed/failed (meaning work has started)
|
||||
// Once a bundle has started processing, it should stay "Running" until all jobs are done
|
||||
bundleRequest.Status = BundleBacktestRequestStatus.Running;
|
||||
}
|
||||
// If all jobs are still pending (completedJobs = 0, failedJobs = 0, runningJobs = 0),
|
||||
// keep the current status (likely Pending)
|
||||
|
||||
// Update results list with the new backtest ID
|
||||
var resultsList = bundleRequest.Results?.ToList() ?? new List<string>();
|
||||
if (!resultsList.Contains(backtest.Id))
|
||||
{
|
||||
resultsList.Add(backtest.Id);
|
||||
bundleRequest.Results = resultsList;
|
||||
}
|
||||
|
||||
await backtestRepository.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||
|
||||
// Send webhook notification if bundle request just completed
|
||||
if (previousStatus != BundleBacktestRequestStatus.Completed &&
|
||||
bundleRequest.Status == BundleBacktestRequestStatus.Completed &&
|
||||
!string.IsNullOrEmpty(user.TelegramChannel))
|
||||
{
|
||||
var message =
|
||||
$"✅ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) completed successfully. " +
|
||||
$"Completed: {completedJobs}/{totalJobs} backtests" +
|
||||
(failedJobs > 0 ? $", Failed: {failedJobs}" : "") +
|
||||
$". Results: {resultsList.Count} backtest(s) generated.";
|
||||
|
||||
await webhookService.SendMessage(message, user.TelegramChannel);
|
||||
}
|
||||
else if (previousStatus != BundleBacktestRequestStatus.Failed &&
|
||||
bundleRequest.Status == BundleBacktestRequestStatus.Failed &&
|
||||
!string.IsNullOrEmpty(user.TelegramChannel))
|
||||
{
|
||||
var message = $"❌ Bundle backtest '{bundleRequest.Name}' (ID: {bundleRequest.RequestId}) failed. " +
|
||||
$"All {totalJobs} backtests failed. Error: {bundleRequest.ErrorMessage}";
|
||||
|
||||
await webhookService.SendMessage(message, user.TelegramChannel);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Updated bundle request {BundleRequestId} with backtest {BacktestId}: {Completed}/{Total} completed, {Failed} failed, {Running} running",
|
||||
bundleRequestId, backtest.Id, completedJobs, totalJobs, failedJobs, runningJobs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to update bundle request {BundleRequestId} with backtest {BacktestId}",
|
||||
bundleRequestId, backtest.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends notification if backtest meets criteria
|
||||
/// </summary>
|
||||
|
||||
@@ -343,7 +343,18 @@ public class BacktestComputeWorker : BackgroundService
|
||||
"Completed backtest job {JobId}. Score: {Score}, PnL: {PnL}, Duration: {DurationMinutes:F1} minutes",
|
||||
job.Id, result.Score, result.FinalPnl, elapsedTime.TotalMinutes);
|
||||
|
||||
// Bundle request is now updated in the BacktestExecutor
|
||||
// Update bundle request progress if this job is part of a bundle
|
||||
if (job.BundleRequestId.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateBundleRequestProgress(job.BundleRequestId.Value, scope.ServiceProvider);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating bundle request progress for job {JobId}", job.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user