Enhance UI, add AI integration, improve logging and database stats

This commit is contained in:
Richard
2026-07-04 21:11:31 +02:00
parent 7a44914d9d
commit d102af2965
57 changed files with 4025 additions and 229 deletions
@@ -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");
}
}