Enhance UI, add AI integration, improve logging and database stats
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
using Predictalytics.Domain.Entities;
|
||||
using Predictalytics.Domain.Interfaces;
|
||||
using Predictalytics.Infrastructure.Providers.Polymarket;
|
||||
using Predictalytics.Application.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Predictalytics.Worker.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Retroactively enriches trades of top/watchlisted traders with high-resolution
|
||||
/// 1-minute price contexts immediately before and after execution.
|
||||
/// Avoids burdening the live PollingWorker.
|
||||
/// </summary>
|
||||
public class TradeContextEnrichmentWorker : BackgroundService
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<TradeContextEnrichmentWorker> _logger;
|
||||
|
||||
public TradeContextEnrichmentWorker(IServiceProvider services, ILogger<TradeContextEnrichmentWorker> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("🧠 TradeContextEnrichmentWorker started");
|
||||
await Task.Delay(10000, stoppingToken); // Wait for app startup
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _services.CreateScope();
|
||||
var tradeRepo = scope.ServiceProvider.GetRequiredService<ITradeRepository>();
|
||||
var polymarketClient = scope.ServiceProvider.GetRequiredService<PolymarketApiClient>();
|
||||
var rateLimiter = scope.ServiceProvider.GetRequiredService<IRateLimiter>();
|
||||
|
||||
// Fetch a batch of unenriched trades
|
||||
var unenrichedTrades = await tradeRepo.GetTradesForContextEnrichmentAsync(500, stoppingToken);
|
||||
|
||||
if (unenrichedTrades.Count == 0)
|
||||
{
|
||||
// No work to do, sleep longer
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group by AssetId to minimize API calls (1 call per asset fetches the whole 1m history)
|
||||
var tradesByAsset = unenrichedTrades.GroupBy(t => t.AssetId).ToList();
|
||||
_logger.LogInformation("Enriching {TradeCount} trades across {AssetCount} assets...", unenrichedTrades.Count, tradesByAsset.Count);
|
||||
|
||||
int updatedCount = 0;
|
||||
|
||||
foreach (var group in tradesByAsset)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
var assetId = group.Key;
|
||||
|
||||
try
|
||||
{
|
||||
// Wait for rate limiter to respect global limits
|
||||
await rateLimiter.WaitAsync(Predictalytics.Domain.Enums.PlatformType.Polymarket, stoppingToken);
|
||||
|
||||
// Fetch the 10-minute history for the entire market (using "max" since "1m" is invalid for full history)
|
||||
var history = await polymarketClient.GetPricesHistoryAsync(assetId, "max", stoppingToken);
|
||||
if (history == null || history.Count == 0)
|
||||
{
|
||||
// If history is not available, mark as enriched to prevent infinite loops,
|
||||
// but prices remain null.
|
||||
foreach (var trade in group)
|
||||
{
|
||||
trade.IsContextEnriched = true;
|
||||
await tradeRepo.UpdateAsync(trade, stoppingToken);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Order history chronologically for safe binary search / LINQ
|
||||
var orderedHistory = history.OrderBy(h => h.Timestamp).ToList();
|
||||
|
||||
foreach (var trade in group)
|
||||
{
|
||||
var tradeTimeUnix = ((DateTimeOffset)trade.ExecutedAt).ToUnixTimeSeconds();
|
||||
|
||||
// Find the closest point BEFORE the trade (approx 1 min before)
|
||||
var prePoint = orderedHistory
|
||||
.LastOrDefault(h => h.Timestamp < tradeTimeUnix);
|
||||
|
||||
// Find the closest point AFTER the trade (approx 1 min after)
|
||||
var postPoint = orderedHistory
|
||||
.FirstOrDefault(h => h.Timestamp > tradeTimeUnix);
|
||||
|
||||
trade.PreTradePrice1m = prePoint != null ? (decimal)prePoint.Price : null;
|
||||
trade.PostTradePrice1m = postPoint != null ? (decimal)postPoint.Price : null;
|
||||
trade.IsContextEnriched = true;
|
||||
|
||||
await tradeRepo.UpdateAsync(trade, stoppingToken);
|
||||
updatedCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to enrich asset {AssetId}", assetId);
|
||||
// Do NOT mark as enriched on failure, try again later
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("✅ Enriched {UpdatedCount} trades in this cycle.", updatedCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in TradeContextEnrichmentWorker loop");
|
||||
}
|
||||
|
||||
// Sleep briefly before next batch
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation("🧠 TradeContextEnrichmentWorker stopped");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user