Fix worker and signal
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = "" }) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user