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);
|
||||
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 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)
|
||||
=> await _db.Traders.Include(t => t.CurrentScore).Include(t => t.CategoryPerformances)
|
||||
|
||||
@@ -58,8 +58,6 @@ public partial class MainForm : Form
|
||||
// Wire up button events
|
||||
btn_serverstart.Click += Btn_serverstart_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("Press 'Start Server' to begin polling & discovery.");
|
||||
|
||||
@@ -181,16 +181,11 @@ public class EmbeddedWebServer
|
||||
await marketRepo.AddOrUpdateEventsAsync(events, ct);
|
||||
|
||||
totalSynced += events.Count;
|
||||
offset += batchSize;
|
||||
offset += events.Count;
|
||||
|
||||
if (totalSynced % 500 == 0 || events.Count < batchSize)
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Predictalytics.Infrastructure.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Predictalytics.Worker.Services;
|
||||
|
||||
@@ -75,12 +76,15 @@ public class MarketHistoryWorker : BackgroundService
|
||||
.Distinct()
|
||||
.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)
|
||||
{
|
||||
var existing = await traderRepo.GetByPlatformIdAsync(
|
||||
market.Platform, wallet, stoppingToken);
|
||||
|
||||
if (existing == null)
|
||||
if (existing == null && isEnabled)
|
||||
{
|
||||
var trader = new Domain.Entities.Trader
|
||||
{
|
||||
|
||||
@@ -80,7 +80,7 @@ public class MarketSyncWorker : BackgroundService
|
||||
passSynced += events.Count;
|
||||
cycleTotalSynced += events.Count;
|
||||
_statsService.TrackMarketSync(provider.Platform, events.Count);
|
||||
offset += batchSize;
|
||||
offset += events.Count;
|
||||
|
||||
if (passSynced % 500 == 0)
|
||||
_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.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Predictalytics.Worker.Services;
|
||||
|
||||
@@ -36,15 +37,18 @@ public class TopHolderDiscoveryWorker : BackgroundService
|
||||
var providers = scope.ServiceProvider.GetRequiredService<IEnumerable<IPlatformProvider>>();
|
||||
var rateLimiter = scope.ServiceProvider.GetRequiredService<IRateLimiter>();
|
||||
|
||||
// Get top active markets by volume
|
||||
var activeMarkets = await marketRepo.GetActiveAsync(100, stoppingToken);
|
||||
_logger.LogInformation("👥 Scanning top holders across {Count} active markets", activeMarkets.Count);
|
||||
|
||||
var config = scope.ServiceProvider.GetRequiredService<Microsoft.Extensions.Configuration.IConfiguration>();
|
||||
int totalDiscovered = 0;
|
||||
|
||||
foreach (var market in activeMarkets)
|
||||
{
|
||||
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);
|
||||
if (provider == null) continue;
|
||||
|
||||
@@ -62,12 +62,17 @@ public class TraderAnalyticsWorker : BackgroundService
|
||||
else
|
||||
{
|
||||
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.
|
||||
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
|
||||
.Where(t => t.LastAnalyzedAt == null ||
|
||||
(t.LastAnalyzedAt < cooldown && t.Trades.Any(tr => tr.ExecutedAt > t.LastAnalyzedAt)))
|
||||
.Where(t =>
|
||||
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)
|
||||
.ThenBy(t => t.LastAnalyzedAt)
|
||||
.Select(t => t.Id)
|
||||
@@ -111,14 +116,21 @@ public class TraderAnalyticsWorker : BackgroundService
|
||||
|
||||
var analyticsObj = trader.Analytics ?? new Predictalytics.Domain.Entities.TraderAnalytics { TraderId = trader.Id };
|
||||
// Persist advanced copyability and quality scores derived from tape replay
|
||||
analyticsObj.CopytradingScore = estScores.CopyabilityScore;
|
||||
analyticsObj.CopytradingScore = estScores.CombinedScore;
|
||||
analyticsObj.CopytradingQualityScore = estScores.QualityScore;
|
||||
analyticsObj.CopytradingCopyabilityScore = estScores.CopyabilityScore;
|
||||
|
||||
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>();
|
||||
if (statsService != null)
|
||||
|
||||
Reference in New Issue
Block a user