Fix worker and signal

This commit is contained in:
2025-07-21 23:30:54 +07:00
parent 83ed78a1fa
commit 27c1e9d1ba
13 changed files with 132 additions and 60 deletions

View File

@@ -8,8 +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);
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId); IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
(IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, 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"); (IEnumerable<LightBacktest> Backtests, int TotalCount) GetBacktestsByRequestIdPaginated(string requestId, 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");
Backtest GetBacktestByIdForUser(User user, string id); Backtest GetBacktestByIdForUser(User user, string id);
void DeleteBacktestByIdForUser(User user, string id); void DeleteBacktestByIdForUser(User user, string id);
void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids); void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
@@ -22,5 +27,5 @@ public interface IBacktestRepository
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id); BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest); void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
void DeleteBundleBacktestRequestByIdForUser(User user, string id); void DeleteBundleBacktestRequestByIdForUser(User user, string id);
IEnumerable<BundleBacktestRequest> GetPendingBundleBacktestRequests(); IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
} }

View File

@@ -68,7 +68,7 @@ namespace Managing.Application.Abstractions.Services
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id); BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest); void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
void DeleteBundleBacktestRequestByIdForUser(User user, string id); void DeleteBundleBacktestRequestByIdForUser(User user, string id);
IEnumerable<BundleBacktestRequest> GetPendingBundleBacktestRequests(); IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status);
} }

View File

@@ -37,7 +37,7 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
try try
{ {
// Get pending bundle backtest requests // Get pending bundle backtest requests
var pendingRequests = _backtester.GetPendingBundleBacktestRequests(); var pendingRequests = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Pending);
foreach (var bundleRequest in pendingRequests) foreach (var bundleRequest in pendingRequests)
{ {
@@ -46,6 +46,8 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
await ProcessBundleRequest(bundleRequest, cancellationToken); await ProcessBundleRequest(bundleRequest, cancellationToken);
} }
await RetryUnfinishedBacktestsInFailedBundles(cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -87,7 +89,11 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
_backtester.UpdateBundleBacktestRequest(bundleRequest); _backtester.UpdateBundleBacktestRequest(bundleRequest);
// Run the backtest directly with the strongly-typed request // Run the backtest directly with the strongly-typed request
await RunSingleBacktest(runBacktestRequest, bundleRequest, i, cancellationToken); var backtestId = await RunSingleBacktest(runBacktestRequest, bundleRequest, i, cancellationToken);
if (!string.IsNullOrEmpty(backtestId))
{
bundleRequest.Results.Add(backtestId);
}
// Update progress // Update progress
bundleRequest.CompletedBacktests++; bundleRequest.CompletedBacktests++;
@@ -140,13 +146,14 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
} }
// Change RunSingleBacktest to accept RunBacktestRequest directly // Change RunSingleBacktest to accept RunBacktestRequest directly
private async Task RunSingleBacktest(RunBacktestRequest runBacktestRequest, BundleBacktestRequest bundleRequest, private async Task<string> RunSingleBacktest(RunBacktestRequest runBacktestRequest,
BundleBacktestRequest bundleRequest,
int index, CancellationToken cancellationToken) int index, CancellationToken cancellationToken)
{ {
if (runBacktestRequest == null || runBacktestRequest.Config == null) if (runBacktestRequest == null || runBacktestRequest.Config == null)
{ {
_logger.LogError("Invalid RunBacktestRequest in bundle (null config)"); _logger.LogError("Invalid RunBacktestRequest in bundle (null config)");
return; return string.Empty;
} }
// Map MoneyManagement // Map MoneyManagement
@@ -232,11 +239,48 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
runBacktestRequest.StartDate, runBacktestRequest.StartDate,
runBacktestRequest.EndDate, runBacktestRequest.EndDate,
bundleRequest.User, // No user context in worker bundleRequest.User, // No user context in worker
runBacktestRequest.Save, true,
runBacktestRequest.WithCandles, runBacktestRequest.WithCandles,
bundleRequest.RequestId // Use bundleRequestId as requestId for traceability bundleRequest.RequestId // 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);
// Assume the backtest is created and you have its ID (e.g., backtest.Id)
// Return the backtest ID
return result.Id;
}
private async Task RetryUnfinishedBacktestsInFailedBundles(CancellationToken cancellationToken)
{
var failedBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Failed);
foreach (var failedBundle in failedBundles)
{
if (cancellationToken.IsCancellationRequested)
break;
// Use Results property to determine which backtests need to be retried
var succeededIds = new HashSet<string>(failedBundle.Results ?? new List<string>());
// Deserialize the original requests
var originalRequests =
JsonSerializer
.Deserialize<List<RunBacktestRequest>>(failedBundle.BacktestRequestsJson);
if (originalRequests == null) continue;
for (int i = 0; i < originalRequests.Count; i++)
{
var expectedId = /* logic to compute expected backtest id for this request */ string.Empty;
// If this backtest was not run or did not succeed, re-run it
if (!succeededIds.Contains(expectedId))
{
var backtestId = await RunSingleBacktest(originalRequests[i], failedBundle, i, cancellationToken);
if (!string.IsNullOrEmpty(backtestId))
{
failedBundle.Results?.Add(backtestId);
_backtester.UpdateBundleBacktestRequest(failedBundle);
}
}
}
}
} }
} }

View File

@@ -200,7 +200,7 @@ namespace Managing.Application.Backtesting
} }
tradingBot.User = user; tradingBot.User = user;
await tradingBot.LoadAccount(); tradingBot.Account = await GetAccountFromConfig(config);
var result = var result =
await GetBacktestingResult(config, tradingBot, candles, user, withCandles, requestId, metadata); await GetBacktestingResult(config, tradingBot, candles, user, withCandles, requestId, metadata);
@@ -215,7 +215,16 @@ namespace Managing.Application.Backtesting
private async Task<Account> GetAccountFromConfig(TradingBotConfig config) private async Task<Account> GetAccountFromConfig(TradingBotConfig config)
{ {
var account = await _accountService.GetAccount(config.AccountName, false, false); var accounts = _accountService.GetAccounts(false, false).ToArray();
var account = accounts.FirstOrDefault(a =>
a.Name.Equals(config.AccountName, StringComparison.OrdinalIgnoreCase) &&
a.Exchange == TradingExchanges.GmxV2);
if (account == null && accounts.Any())
{
account = accounts.First();
}
if (account != null) if (account != null)
{ {
return account; return account;
@@ -606,9 +615,10 @@ namespace Managing.Application.Backtesting
_backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id); _backtestRepository.DeleteBundleBacktestRequestByIdForUser(user, id);
} }
public IEnumerable<BundleBacktestRequest> GetPendingBundleBacktestRequests() public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{ {
return _backtestRepository.GetPendingBundleBacktestRequests(); // Use the repository method to get all bundles, then filter by status
return _backtestRepository.GetBundleBacktestRequestsByStatus(status);
} }
/// <summary> /// <summary>

View File

@@ -167,6 +167,7 @@ public class TradingBot : Bot, ITradingBot
public async Task LoadAccount() public async Task LoadAccount()
{ {
if (Config.IsForBacktest) return;
var account = await AccountService.GetAccount(Config.AccountName, false, false); var account = await AccountService.GetAccount(Config.AccountName, false, false);
if (account == null) if (account == null)
{ {

View File

@@ -31,9 +31,7 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
try try
{ {
// Fetch all running bundle requests // Fetch all running bundle requests
var runningBundles = _backtester.GetPendingBundleBacktestRequests() var runningBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Running);
.Where(b => b.Status == BundleBacktestRequestStatus.Running)
.ToList();
foreach (var bundle in runningBundles) foreach (var bundle in runningBundles)
{ {
@@ -53,7 +51,8 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
var lightResponse = backtest as LightBacktest; var lightResponse = backtest as LightBacktest;
if (lightResponse != null) if (lightResponse != null)
{ {
await _hubContext.Clients.Group($"bundle-{requestId}").SendAsync("BundleBacktestUpdate", lightResponse, stoppingToken); await _hubContext.Clients.Group($"bundle-{requestId}")
.SendAsync("BundleBacktestUpdate", lightResponse, stoppingToken);
_sentBacktestIds[requestId].Add(backtest.Id); _sentBacktestIds[requestId].Add(backtest.Id);
} }
} }

View File

@@ -13,7 +13,7 @@ public class BundleBacktestRequest
RequestId = Guid.NewGuid().ToString(); RequestId = Guid.NewGuid().ToString();
CreatedAt = DateTime.UtcNow; CreatedAt = DateTime.UtcNow;
Status = BundleBacktestRequestStatus.Pending; Status = BundleBacktestRequestStatus.Pending;
Results = new List<Backtest>(); Results = new List<string>();
BacktestRequestsJson = string.Empty; BacktestRequestsJson = string.Empty;
} }
@@ -26,7 +26,7 @@ public class BundleBacktestRequest
RequestId = requestId; RequestId = requestId;
CreatedAt = DateTime.UtcNow; CreatedAt = DateTime.UtcNow;
Status = BundleBacktestRequestStatus.Pending; Status = BundleBacktestRequestStatus.Pending;
Results = new List<Backtest>(); Results = new List<string>();
BacktestRequestsJson = string.Empty; BacktestRequestsJson = string.Empty;
} }
@@ -74,7 +74,7 @@ public class BundleBacktestRequest
/// <summary> /// <summary>
/// The results of the bundle backtest execution /// The results of the bundle backtest execution
/// </summary> /// </summary>
public List<Backtest> Results { get; set; } = new(); public List<string> Results { get; set; } = new();
/// <summary> /// <summary>
/// Total number of backtests in the bundle /// Total number of backtests in the bundle

View File

@@ -320,8 +320,24 @@ public class BacktestRepository : IBacktestRepository
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user) public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
{ {
var bundleRequests = _bundleBacktestRepository.AsQueryable() var projection = Builders<BundleBacktestRequestDto>.Projection
.Where(b => b.User.Name == user.Name) .Include(b => b.RequestId)
.Include(b => b.Status)
.Include(b => b.CreatedAt)
.Include(b => b.CurrentBacktest)
.Include(b => b.EstimatedTimeRemainingSeconds)
.Include(b => b.TotalBacktests)
.Include(b => b.CurrentBacktest)
.Include(b => b.CompletedAt)
.Include(b => b.ErrorMessage)
.Include(b => b.ProgressInfo)
.Include(b => b.Name)
.Include(b => b.User);
var filter = Builders<BundleBacktestRequestDto>.Filter.Eq(b => b.User.Name, user.Name);
var bundleRequests = _bundleBacktestRepository.GetCollection()
.Find(filter)
.Project<BundleBacktestRequestDto>(projection)
.ToList(); .ToList();
return bundleRequests.Select(MongoMappers.Map); return bundleRequests.Select(MongoMappers.Map);
@@ -329,7 +345,7 @@ public class BacktestRepository : IBacktestRepository
public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id) public BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id)
{ {
var bundleRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == id); var bundleRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == id && b.User.Name == user.Name);
if (bundleRequest != null && bundleRequest.User.Name == user.Name) if (bundleRequest != null && bundleRequest.User.Name == user.Name)
{ {
@@ -341,8 +357,13 @@ public class BacktestRepository : IBacktestRepository
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest) public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{ {
var dto = MongoMappers.Map(bundleRequest); var existingRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == bundleRequest.RequestId);
_bundleBacktestRepository.ReplaceOne(dto); if (existingRequest != null)
{
var dto = MongoMappers.Map(bundleRequest);
dto.Id = existingRequest.Id; // Preserve the MongoDB ObjectId
_bundleBacktestRepository.ReplaceOne(dto);
}
} }
public void DeleteBundleBacktestRequestByIdForUser(User user, string id) public void DeleteBundleBacktestRequestByIdForUser(User user, string id)
@@ -355,12 +376,12 @@ public class BacktestRepository : IBacktestRepository
} }
} }
public IEnumerable<BundleBacktestRequest> GetPendingBundleBacktestRequests() public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{ {
var pendingRequests = _bundleBacktestRepository.AsQueryable() var requests = _bundleBacktestRepository.AsQueryable()
.Where(b => b.Status == BundleBacktestRequestStatus.Pending) .Where(b => b.Status == status)
.ToList(); .ToList();
return pendingRequests.Select(MongoMappers.Map); return requests.Select(MongoMappers.Map);
} }
} }

View File

@@ -20,4 +20,5 @@ public class BundleBacktestRequestDto : Document
public string? CurrentBacktest { get; set; } public string? CurrentBacktest { get; set; }
public int? EstimatedTimeRemainingSeconds { get; set; } public int? EstimatedTimeRemainingSeconds { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public List<string> Results { get; set; } = new();
} }

View File

@@ -36,6 +36,8 @@ public class IndexService
// Create indexes for BacktestDto // Create indexes for BacktestDto
await CreateBacktestIndexesAsync(); await CreateBacktestIndexesAsync();
await CreateBundleBacktestIndexesAsync();
// Create indexes for GeneticRequestDto // Create indexes for GeneticRequestDto
await CreateGeneticRequestIndexesAsync(); await CreateGeneticRequestIndexesAsync();
@@ -74,6 +76,17 @@ public class IndexService
} }
} }
private async Task CreateBundleBacktestIndexesAsync()
{
var bundleCollection = _database.GetCollection<BundleBacktestRequestDto>("BundleBacktestRequests");
// Index on RequestId (unique)
var requestIdIndex = Builders<BundleBacktestRequestDto>.IndexKeys.Ascending(b => b.RequestId);
await bundleCollection.Indexes.CreateOneAsync(new CreateIndexModel<BundleBacktestRequestDto>(requestIdIndex, new CreateIndexOptions { Unique = true }));
// Index on User.Name (non-unique)
var userNameIndex = Builders<BundleBacktestRequestDto>.IndexKeys.Ascending("User.Name");
await bundleCollection.Indexes.CreateOneAsync(new CreateIndexModel<BundleBacktestRequestDto>(userNameIndex));
}
/// <summary> /// <summary>
/// Creates indexes for the GeneticRequestDto collection /// Creates indexes for the GeneticRequestDto collection
/// </summary> /// </summary>

View File

@@ -1128,7 +1128,8 @@ public static class MongoMappers
ProgressInfo = domain.ProgressInfo, ProgressInfo = domain.ProgressInfo,
CurrentBacktest = domain.CurrentBacktest, CurrentBacktest = domain.CurrentBacktest,
EstimatedTimeRemainingSeconds = domain.EstimatedTimeRemainingSeconds, EstimatedTimeRemainingSeconds = domain.EstimatedTimeRemainingSeconds,
Name = domain.Name Name = domain.Name,
Results = domain.Results
}; };
} }
@@ -1150,7 +1151,8 @@ public static class MongoMappers
ProgressInfo = dto.ProgressInfo, ProgressInfo = dto.ProgressInfo,
CurrentBacktest = dto.CurrentBacktest, CurrentBacktest = dto.CurrentBacktest,
EstimatedTimeRemainingSeconds = dto.EstimatedTimeRemainingSeconds, EstimatedTimeRemainingSeconds = dto.EstimatedTimeRemainingSeconds,
Name = dto.Name Name = dto.Name,
Results = dto.Results
}; };
} }

View File

@@ -42,7 +42,7 @@ const getBadgeColor = (signalType: SignalType) => {
// Helper function to format indicator type for display // Helper function to format indicator type for display
const formatIndicatorType = (type: IndicatorType) => { const formatIndicatorType = (type: IndicatorType) => {
return type.replace(/([A-Z])/g, ' $1').trim(); return type.length > 0 ? type.replace(/([A-Z])/g, ' $1').trim() : 'Unknown';
}; };
const IndicatorsDisplay: React.FC<IndicatorsDisplayProps> = ({ indicators, className = "" }) => { const IndicatorsDisplay: React.FC<IndicatorsDisplayProps> = ({ indicators, className = "" }) => {

View File

@@ -6,6 +6,7 @@ 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 * as signalR from '@microsoft/signalr';
import AuthorizedApiBase from '../../generated/AuthorizedApiBase'; import AuthorizedApiBase from '../../generated/AuthorizedApiBase';
import BacktestTable from '../../components/organism/Backtest/backtestTable';
interface BundleRequestModalProps { interface BundleRequestModalProps {
open: boolean; open: boolean;
@@ -125,32 +126,7 @@ const BundleRequestModal: React.FC<BundleRequestModalProps> = ({ open, onClose,
) : queryError ? ( ) : queryError ? (
<div className="text-error">{(queryError as any)?.message || 'Failed to fetch backtests'}</div> <div className="text-error">{(queryError as any)?.message || 'Failed to fetch backtests'}</div>
) : ( ) : (
<div className="overflow-x-auto max-h-96"> <BacktestTable list={backtests} />
<table className="table table-zebra w-full text-xs">
<thead>
<tr>
<th>ID</th>
<th>Final PnL</th>
<th>Win Rate</th>
<th>Growth %</th>
<th>Start</th>
<th>End</th>
</tr>
</thead>
<tbody>
{backtests.map((b) => (
<tr key={b.id}>
<td className="font-mono">{b.id}</td>
<td>{b.finalPnl}</td>
<td>{b.winRate}</td>
<td>{b.growthPercentage}</td>
<td>{b.startDate ? new Date(b.startDate).toLocaleString() : '-'}</td>
<td>{b.endDate ? new Date(b.endDate).toLocaleString() : '-'}</td>
</tr>
))}
</tbody>
</table>
</div>
)} )}
<div className="modal-action"> <div className="modal-action">
<button className="btn" onClick={onClose}>Close</button> <button className="btn" onClick={onClose}>Close</button>