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,19 +8,24 @@ public interface IBacktestRepository
void InsertBacktestForUser(User user, Backtest result);
IEnumerable<Backtest> GetBacktestsByUser(User user);
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);
void DeleteBacktestByIdForUser(User user, string id);
void DeleteBacktestsByIdsForUser(User user, IEnumerable<string> ids);
void DeleteAllBacktestsForUser(User user);
void DeleteBacktestsByRequestId(string requestId);
// Bundle backtest methods
void InsertBundleBacktestRequestForUser(User user, BundleBacktestRequest bundleRequest);
IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user);
BundleBacktestRequest? GetBundleBacktestRequestByIdForUser(User user, string id);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
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);
void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest);
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
{
// Get pending bundle backtest requests
var pendingRequests = _backtester.GetPendingBundleBacktestRequests();
var pendingRequests = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Pending);
foreach (var bundleRequest in pendingRequests)
{
@@ -46,6 +46,8 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
await ProcessBundleRequest(bundleRequest, cancellationToken);
}
await RetryUnfinishedBacktestsInFailedBundles(cancellationToken);
}
catch (Exception ex)
{
@@ -87,7 +89,11 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
_backtester.UpdateBundleBacktestRequest(bundleRequest);
// 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
bundleRequest.CompletedBacktests++;
@@ -140,13 +146,14 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
}
// 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)
{
if (runBacktestRequest == null || runBacktestRequest.Config == null)
{
_logger.LogError("Invalid RunBacktestRequest in bundle (null config)");
return;
return string.Empty;
}
// Map MoneyManagement
@@ -232,11 +239,48 @@ public class BundleBacktestWorker : BaseWorker<BundleBacktestWorker>
runBacktestRequest.StartDate,
runBacktestRequest.EndDate,
bundleRequest.User, // No user context in worker
runBacktestRequest.Save,
true,
runBacktestRequest.WithCandles,
bundleRequest.RequestId // Use bundleRequestId as requestId for traceability
);
_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;
await tradingBot.LoadAccount();
tradingBot.Account = await GetAccountFromConfig(config);
var result =
await GetBacktestingResult(config, tradingBot, candles, user, withCandles, requestId, metadata);
@@ -215,7 +215,16 @@ namespace Managing.Application.Backtesting
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)
{
return account;
@@ -606,9 +615,10 @@ namespace Managing.Application.Backtesting
_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>

View File

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

View File

@@ -31,9 +31,7 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
try
{
// Fetch all running bundle requests
var runningBundles = _backtester.GetPendingBundleBacktestRequests()
.Where(b => b.Status == BundleBacktestRequestStatus.Running)
.ToList();
var runningBundles = _backtester.GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus.Running);
foreach (var bundle in runningBundles)
{
@@ -53,7 +51,8 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
var lightResponse = backtest as LightBacktest;
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);
}
}
@@ -70,4 +69,4 @@ public class NotifyBundleBacktestWorker : BaseWorker<NotifyBundleBacktestWorker>
_logger.LogError(ex, "Error in NotifyBundleBacktestWorker");
}
}
}
}

View File

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

View File

@@ -320,8 +320,24 @@ public class BacktestRepository : IBacktestRepository
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByUser(User user)
{
var bundleRequests = _bundleBacktestRepository.AsQueryable()
.Where(b => b.User.Name == user.Name)
var projection = Builders<BundleBacktestRequestDto>.Projection
.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();
return bundleRequests.Select(MongoMappers.Map);
@@ -329,7 +345,7 @@ public class BacktestRepository : IBacktestRepository
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)
{
@@ -341,8 +357,13 @@ public class BacktestRepository : IBacktestRepository
public void UpdateBundleBacktestRequest(BundleBacktestRequest bundleRequest)
{
var dto = MongoMappers.Map(bundleRequest);
_bundleBacktestRepository.ReplaceOne(dto);
var existingRequest = _bundleBacktestRepository.FindOne(b => b.RequestId == bundleRequest.RequestId);
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)
@@ -355,12 +376,12 @@ public class BacktestRepository : IBacktestRepository
}
}
public IEnumerable<BundleBacktestRequest> GetPendingBundleBacktestRequests()
public IEnumerable<BundleBacktestRequest> GetBundleBacktestRequestsByStatus(BundleBacktestRequestStatus status)
{
var pendingRequests = _bundleBacktestRepository.AsQueryable()
.Where(b => b.Status == BundleBacktestRequestStatus.Pending)
var requests = _bundleBacktestRepository.AsQueryable()
.Where(b => b.Status == status)
.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 int? EstimatedTimeRemainingSeconds { get; set; }
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
await CreateBacktestIndexesAsync();
await CreateBundleBacktestIndexesAsync();
// Create indexes for GeneticRequestDto
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>
/// Creates indexes for the GeneticRequestDto collection
/// </summary>

View File

@@ -1128,7 +1128,8 @@ public static class MongoMappers
ProgressInfo = domain.ProgressInfo,
CurrentBacktest = domain.CurrentBacktest,
EstimatedTimeRemainingSeconds = domain.EstimatedTimeRemainingSeconds,
Name = domain.Name
Name = domain.Name,
Results = domain.Results
};
}
@@ -1150,7 +1151,8 @@ public static class MongoMappers
ProgressInfo = dto.ProgressInfo,
CurrentBacktest = dto.CurrentBacktest,
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
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 = "" }) => {

View File

@@ -6,6 +6,7 @@ import Toast from '../../components/mollecules/Toast/Toast';
import {useQuery} from '@tanstack/react-query';
import * as signalR from '@microsoft/signalr';
import AuthorizedApiBase from '../../generated/AuthorizedApiBase';
import BacktestTable from '../../components/organism/Backtest/backtestTable';
interface BundleRequestModalProps {
open: boolean;
@@ -125,32 +126,7 @@ const BundleRequestModal: React.FC<BundleRequestModalProps> = ({ open, onClose,
) : queryError ? (
<div className="text-error">{(queryError as any)?.message || 'Failed to fetch backtests'}</div>
) : (
<div className="overflow-x-auto max-h-96">
<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>
<BacktestTable list={backtests} />
)}
<div className="modal-action">
<button className="btn" onClick={onClose}>Close</button>