diff --git a/query.csx b/query.csx new file mode 100644 index 0000000..4150902 Binary files /dev/null and b/query.csx differ diff --git a/src/Predictalytics.Worker/Services/TradeReconciliationWorker.cs b/src/Predictalytics.Worker/Services/TradeReconciliationWorker.cs index fffd70f..b75b68e 100644 --- a/src/Predictalytics.Worker/Services/TradeReconciliationWorker.cs +++ b/src/Predictalytics.Worker/Services/TradeReconciliationWorker.cs @@ -16,7 +16,7 @@ public class TradeReconciliationWorker : BackgroundService private readonly ILogger _logger; private const int BatchSize = 250; - private const int IntervalMinutes = 15; + private const int IntervalSeconds = 30; public TradeReconciliationWorker(IServiceProvider services, ILogger logger) { @@ -26,7 +26,7 @@ public class TradeReconciliationWorker : BackgroundService protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - _logger.LogInformation("🛠️ TradeReconciliationWorker started (batch: {Batch}, interval: {Min}m)", BatchSize, IntervalMinutes); + _logger.LogInformation("🛠️ TradeReconciliationWorker started (batch: {Batch}, interval: {Sec}s)", BatchSize, IntervalSeconds); // Initial delay to let other workers settle await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); @@ -52,7 +52,7 @@ public class TradeReconciliationWorker : BackgroundService _logger.LogError(ex, "Error in TradeReconciliationWorker cycle"); } - await Task.Delay(TimeSpan.FromMinutes(IntervalMinutes), stoppingToken); + await Task.Delay(TimeSpan.FromSeconds(IntervalSeconds), stoppingToken); } _logger.LogInformation("🛠️ TradeReconciliationWorker stopped"); @@ -61,37 +61,44 @@ public class TradeReconciliationWorker : BackgroundService private async Task ReconcileBatchAsync(CancellationToken ct) { using var scope = _services.CreateScope(); - var tradeRepo = scope.ServiceProvider.GetRequiredService(); - var marketRepo = scope.ServiceProvider.GetRequiredService(); + var db = scope.ServiceProvider.GetRequiredService(); - var orphanedTrades = await tradeRepo.GetOrphanedTradesAsync(BatchSize, ct); - if (orphanedTrades.Count == 0) return 0; - - int count = 0; - foreach (var trade in orphanedTrades) + // Bulk update orphaned trades + int reconciledCount = 0; + try { - if (ct.IsCancellationRequested) break; + reconciledCount = await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(db.Database, @" + UPDATE Trades t + INNER JOIN MarketOutcomes o ON t.AssetId = o.TokenId + INNER JOIN Markets m ON o.MarketId = m.Id + SET t.MarketOutcomeId = o.Id, + t.Outcome = o.Label, + t.DbMarketId = m.Id + WHERE t.MarketOutcomeId IS NULL AND t.AssetId != ''; + ", ct); - try + if (reconciledCount > 0) { - var outcome = await marketRepo.GetOutcomeByTokenIdAsync(trade.AssetId, ct); - if (outcome != null) - { - trade.MarketOutcomeId = outcome.Id; - trade.Outcome = outcome.Label; - if (outcome.Market != null) - trade.DbMarketId = outcome.Market.Id; - - await tradeRepo.UpdateAsync(trade, ct); - count++; - } - } - catch (Exception ex) - { - _logger.LogWarning("Failed to reconcile trade {TradeId}: {Msg}", trade.Id, ex.Message); + // Force re-analysis of traders who now have new linked trades that haven't been applied + await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(db.Database, @" + UPDATE Traders t + SET LastAnalyzedAt = NULL + WHERE LastAnalyzedAt IS NOT NULL + AND EXISTS ( + SELECT 1 FROM Trades tr + LEFT JOIN TraderPositions tp ON tr.TraderId = tp.TraderId AND tr.MarketOutcomeId = tp.MarketOutcomeId + WHERE tr.TraderId = t.Id + AND tr.MarketOutcomeId IS NOT NULL + AND (tp.Id IS NULL OR tr.Id > tp.LastAppliedTradeId) + ); + ", ct); } } + catch (Exception ex) + { + _logger.LogError(ex, "Error executing bulk reconciliation SQL"); + } - return count; + return reconciledCount; } }