Fix bundle completion
This commit is contained in:
@@ -410,12 +410,6 @@ public class BacktestExecutor
|
|||||||
if (save && user != null)
|
if (save && user != null)
|
||||||
{
|
{
|
||||||
await _backtestRepository.InsertBacktestForUserAsync(user, result);
|
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
|
// Send notification if backtest meets criteria
|
||||||
@@ -574,150 +568,6 @@ public class BacktestExecutor
|
|||||||
return tradingBot;
|
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>
|
/// <summary>
|
||||||
/// Sends notification if backtest meets criteria
|
/// Sends notification if backtest meets criteria
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -343,7 +343,18 @@ public class BacktestComputeWorker : BackgroundService
|
|||||||
"Completed backtest job {JobId}. Score: {Score}, PnL: {PnL}, Duration: {DurationMinutes:F1} minutes",
|
"Completed backtest job {JobId}. Score: {Score}, PnL: {PnL}, Duration: {DurationMinutes:F1} minutes",
|
||||||
job.Id, result.Score, result.FinalPnl, elapsedTime.TotalMinutes);
|
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)
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user