Fix worker pagination, apply Analytics fixes, add analyze backlog endpoint
This commit is contained in:
@@ -53,5 +53,33 @@ public static class JobEndpoints
|
|||||||
await repo.AddAsync(job, ct);
|
await repo.AddAsync(job, ct);
|
||||||
return Results.Ok(job.Id);
|
return Results.Ok(job.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group.MapPost("/analyze-backlog", async (int? take, IJobRepository repo, Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
|
||||||
|
{
|
||||||
|
int batchSize = take ?? 50;
|
||||||
|
|
||||||
|
var traderIds = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(
|
||||||
|
db.Traders
|
||||||
|
.Where(t => t.LastAnalyzedAt == null || (t.LastTradesUpdatedAt != null && t.LastTradesUpdatedAt > t.LastAnalyzedAt))
|
||||||
|
.OrderBy(t => t.LastAnalyzedAt == null ? 0 : 1)
|
||||||
|
.ThenByDescending(t => t.TotalTrades)
|
||||||
|
.Select(t => t.Id)
|
||||||
|
.Take(batchSize),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
int queued = 0;
|
||||||
|
foreach (var tId in traderIds)
|
||||||
|
{
|
||||||
|
var job = new BackgroundJob
|
||||||
|
{
|
||||||
|
JobType = JobType.TraderAnalysis,
|
||||||
|
Status = JobStatus.Pending,
|
||||||
|
TraderId = tId
|
||||||
|
};
|
||||||
|
await repo.AddAsync(job, ct);
|
||||||
|
queued++;
|
||||||
|
}
|
||||||
|
return Results.Ok(new { Queued = queued });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ public class TraderRepository : ITraderRepository
|
|||||||
public TraderRepository(AppDbContext db) => _db = db;
|
public TraderRepository(AppDbContext db) => _db = db;
|
||||||
|
|
||||||
public async Task<Trader?> GetByIdAsync(int id, CancellationToken ct = default)
|
public async Task<Trader?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||||
=> await _db.Traders.Include(t => t.CurrentScore).Include(t => t.CategoryPerformances).FirstOrDefaultAsync(t => t.Id == id, ct);
|
{
|
||||||
|
return await _db.Traders
|
||||||
|
.Include(t => t.CurrentScore)
|
||||||
|
.Include(t => t.Analytics)
|
||||||
|
.Include(t => t.CategoryPerformances)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == id, ct);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Trader?> GetByPlatformIdAsync(PlatformType platform, string platformUserId, CancellationToken ct = default)
|
public async Task<Trader?> GetByPlatformIdAsync(PlatformType platform, string platformUserId, CancellationToken ct = default)
|
||||||
=> await _db.Traders.Include(t => t.CurrentScore).Include(t => t.CategoryPerformances)
|
=> await _db.Traders.Include(t => t.CurrentScore).Include(t => t.CategoryPerformances)
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ public partial class MainForm : Form
|
|||||||
// Wire up button events
|
// Wire up button events
|
||||||
btn_serverstart.Click += Btn_serverstart_Click;
|
btn_serverstart.Click += Btn_serverstart_Click;
|
||||||
btn_localWebserver.Click += Btn_localWebserver_Click;
|
btn_localWebserver.Click += Btn_localWebserver_Click;
|
||||||
btn_syncmarkets.Click += syncMarketsaToolStripMenuItem_Click;
|
|
||||||
btn_dbUpdate.Click += btn_dbUpdate_Click;
|
|
||||||
|
|
||||||
Log.Information("MainForm initialized. Ready.");
|
Log.Information("MainForm initialized. Ready.");
|
||||||
Log.Information("Press 'Start Server' to begin polling & discovery.");
|
Log.Information("Press 'Start Server' to begin polling & discovery.");
|
||||||
|
|||||||
@@ -181,16 +181,11 @@ public class EmbeddedWebServer
|
|||||||
await marketRepo.AddOrUpdateEventsAsync(events, ct);
|
await marketRepo.AddOrUpdateEventsAsync(events, ct);
|
||||||
|
|
||||||
totalSynced += events.Count;
|
totalSynced += events.Count;
|
||||||
offset += batchSize;
|
offset += events.Count;
|
||||||
|
|
||||||
if (totalSynced % 500 == 0 || events.Count < batchSize)
|
if (totalSynced % 500 == 0 || events.Count < batchSize)
|
||||||
Log.Information("[{Platform}] Synced {Total} events so far (offset={Offset}, includeClosed={Closed})...", provider.PlatformName, totalSynced, offset, includeClosed);
|
Log.Information("[{Platform}] Synced {Total} events so far (offset={Offset}, includeClosed={Closed})...", provider.PlatformName, totalSynced, offset, includeClosed);
|
||||||
|
|
||||||
if (events.Count < batchSize)
|
|
||||||
{
|
|
||||||
Log.Warning("[{Platform}] Batch was smaller than limit ({Count}/{Limit}), assuming end of list.", provider.PlatformName, events.Count, batchSize);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Predictalytics.Infrastructure.Logging;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Predictalytics.Worker.Services;
|
namespace Predictalytics.Worker.Services;
|
||||||
|
|
||||||
@@ -75,12 +76,15 @@ public class MarketHistoryWorker : BackgroundService
|
|||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var config = scope.ServiceProvider.GetRequiredService<Microsoft.Extensions.Configuration.IConfiguration>();
|
||||||
|
bool isEnabled = config.GetValue<bool>($"PlatformSettings:{market.Platform}:EnableCrawling", market.Platform == PlatformType.Polymarket);
|
||||||
|
|
||||||
foreach (var wallet in wallets)
|
foreach (var wallet in wallets)
|
||||||
{
|
{
|
||||||
var existing = await traderRepo.GetByPlatformIdAsync(
|
var existing = await traderRepo.GetByPlatformIdAsync(
|
||||||
market.Platform, wallet, stoppingToken);
|
market.Platform, wallet, stoppingToken);
|
||||||
|
|
||||||
if (existing == null)
|
if (existing == null && isEnabled)
|
||||||
{
|
{
|
||||||
var trader = new Domain.Entities.Trader
|
var trader = new Domain.Entities.Trader
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class MarketSyncWorker : BackgroundService
|
|||||||
passSynced += events.Count;
|
passSynced += events.Count;
|
||||||
cycleTotalSynced += events.Count;
|
cycleTotalSynced += events.Count;
|
||||||
_statsService.TrackMarketSync(provider.Platform, events.Count);
|
_statsService.TrackMarketSync(provider.Platform, events.Count);
|
||||||
offset += batchSize;
|
offset += events.Count;
|
||||||
|
|
||||||
if (passSynced % 500 == 0)
|
if (passSynced % 500 == 0)
|
||||||
_logger.LogWarning("[{Platform}] Synced {Total} markets so far (includeClosed={Closed})...", p.PlatformName, passSynced, includeClosed);
|
_logger.LogWarning("[{Platform}] Synced {Total} markets so far (includeClosed={Closed})...", p.PlatformName, passSynced, includeClosed);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Predictalytics.Infrastructure.Logging;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Predictalytics.Worker.Services;
|
namespace Predictalytics.Worker.Services;
|
||||||
|
|
||||||
@@ -36,15 +37,18 @@ public class TopHolderDiscoveryWorker : BackgroundService
|
|||||||
var providers = scope.ServiceProvider.GetRequiredService<IEnumerable<IPlatformProvider>>();
|
var providers = scope.ServiceProvider.GetRequiredService<IEnumerable<IPlatformProvider>>();
|
||||||
var rateLimiter = scope.ServiceProvider.GetRequiredService<IRateLimiter>();
|
var rateLimiter = scope.ServiceProvider.GetRequiredService<IRateLimiter>();
|
||||||
|
|
||||||
// Get top active markets by volume
|
|
||||||
var activeMarkets = await marketRepo.GetActiveAsync(100, stoppingToken);
|
var activeMarkets = await marketRepo.GetActiveAsync(100, stoppingToken);
|
||||||
_logger.LogInformation("👥 Scanning top holders across {Count} active markets", activeMarkets.Count);
|
_logger.LogInformation("👥 Scanning top holders across {Count} active markets", activeMarkets.Count);
|
||||||
|
|
||||||
|
var config = scope.ServiceProvider.GetRequiredService<Microsoft.Extensions.Configuration.IConfiguration>();
|
||||||
int totalDiscovered = 0;
|
int totalDiscovered = 0;
|
||||||
|
|
||||||
foreach (var market in activeMarkets)
|
foreach (var market in activeMarkets)
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
|
bool isEnabled = config.GetValue<bool>($"PlatformSettings:{market.Platform}:EnableCrawling", market.Platform == PlatformType.Polymarket);
|
||||||
|
if (!isEnabled) continue;
|
||||||
|
|
||||||
var provider = providers.FirstOrDefault(p => p.Platform == market.Platform && p.IsImplemented);
|
var provider = providers.FirstOrDefault(p => p.Platform == market.Platform && p.IsImplemented);
|
||||||
if (provider == null) continue;
|
if (provider == null) continue;
|
||||||
|
|||||||
@@ -62,12 +62,17 @@ public class TraderAnalyticsWorker : BackgroundService
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
// Find traders who have never been analyzed, or whose last analysis was before their latest trade.
|
|
||||||
// Bug 5 Fix: Add 30-minute cooldown to prevent CPU looping.
|
// Bug 5 Fix: Add 30-minute cooldown to prevent CPU looping.
|
||||||
var cooldown = DateTime.UtcNow.AddMinutes(-30);
|
var cooldown = DateTime.UtcNow.AddMinutes(-30);
|
||||||
|
|
||||||
|
// Find traders who have never been analyzed, or whose last analysis was before their latest trade.
|
||||||
|
// Include Resolution-Trigger: Traders with open positions in resolved markets.
|
||||||
traderIds = await db.Traders
|
traderIds = await db.Traders
|
||||||
.Where(t => t.LastAnalyzedAt == null ||
|
.Where(t =>
|
||||||
(t.LastAnalyzedAt < cooldown && t.Trades.Any(tr => tr.ExecutedAt > t.LastAnalyzedAt)))
|
t.LastAnalyzedAt == null ||
|
||||||
|
(t.LastAnalyzedAt < cooldown && t.LastTradesUpdatedAt != null && t.LastTradesUpdatedAt > t.LastAnalyzedAt) ||
|
||||||
|
(t.LastAnalyzedAt < cooldown && t.Positions.Any(p => p.SharesHeld > 0 && p.MarketOutcome != null && p.MarketOutcome.Market != null && p.MarketOutcome.Market.IsResolved))
|
||||||
|
)
|
||||||
.OrderBy(t => t.LastAnalyzedAt == null ? 0 : 1)
|
.OrderBy(t => t.LastAnalyzedAt == null ? 0 : 1)
|
||||||
.ThenBy(t => t.LastAnalyzedAt)
|
.ThenBy(t => t.LastAnalyzedAt)
|
||||||
.Select(t => t.Id)
|
.Select(t => t.Id)
|
||||||
@@ -111,14 +116,21 @@ public class TraderAnalyticsWorker : BackgroundService
|
|||||||
|
|
||||||
var analyticsObj = trader.Analytics ?? new Predictalytics.Domain.Entities.TraderAnalytics { TraderId = trader.Id };
|
var analyticsObj = trader.Analytics ?? new Predictalytics.Domain.Entities.TraderAnalytics { TraderId = trader.Id };
|
||||||
// Persist advanced copyability and quality scores derived from tape replay
|
// Persist advanced copyability and quality scores derived from tape replay
|
||||||
analyticsObj.CopytradingScore = estScores.CopyabilityScore;
|
analyticsObj.CopytradingScore = estScores.CombinedScore;
|
||||||
analyticsObj.CopytradingQualityScore = estScores.QualityScore;
|
analyticsObj.CopytradingQualityScore = estScores.QualityScore;
|
||||||
analyticsObj.CopytradingCopyabilityScore = estScores.CopyabilityScore;
|
analyticsObj.CopytradingCopyabilityScore = estScores.CopyabilityScore;
|
||||||
|
|
||||||
trader.Analytics = analyticsObj;
|
trader.Analytics = analyticsObj;
|
||||||
|
|
||||||
|
// Only stamp if there were actually trades to analyze
|
||||||
|
trader.LastAnalyzedAt = DateTime.UtcNow;
|
||||||
|
await traderRepo.UpdateAsync(trader, ct);
|
||||||
|
}
|
||||||
|
else if (trader.LastAnalyzedAt == null)
|
||||||
|
{
|
||||||
|
// If it's a completely new trader with no trades, we still don't stamp LastAnalyzedAt
|
||||||
|
// so it remains null until the TradeHistoryWorker pulls trades.
|
||||||
}
|
}
|
||||||
trader.LastAnalyzedAt = DateTime.UtcNow;
|
|
||||||
await traderRepo.UpdateAsync(trader, ct);
|
|
||||||
|
|
||||||
var statsService = traderScope.ServiceProvider.GetService<IPlatformStatisticsService>();
|
var statsService = traderScope.ServiceProvider.GetService<IPlatformStatisticsService>();
|
||||||
if (statsService != null)
|
if (statsService != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user