feat: Add category performance, update WebUI and OpenRouter integration, fix bugs
This commit is contained in:
@@ -18,6 +18,7 @@ public class AppDbContext : DbContext
|
||||
public DbSet<MarketAnalytics> MarketAnalytics => Set<MarketAnalytics>();
|
||||
public DbSet<TraderPosition> TraderPositions => Set<TraderPosition>();
|
||||
public DbSet<MarketOutcomePriceSnapshot> MarketOutcomePriceSnapshots => Set<MarketOutcomePriceSnapshot>();
|
||||
public DbSet<TraderCategoryPerformance> TraderCategoryPerformances => Set<TraderCategoryPerformance>();
|
||||
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
|
||||
@@ -54,7 +55,7 @@ public class AppDbContext : DbContext
|
||||
// Outcome: labels can be long (e.g. anime titles or sports match descriptions)
|
||||
e.Property(t => t.Outcome).HasMaxLength(128);
|
||||
// Price: 0.00–1.00 on prediction markets, 6 decimals sufficient
|
||||
e.Property(t => t.Price).HasPrecision(10, 6);
|
||||
e.Property(t => t.Price).HasPrecision(18, 6);
|
||||
// Size: number of shares, needs more integer digits
|
||||
e.Property(t => t.Size).HasPrecision(14, 6);
|
||||
e.Property(t => t.Amount).HasPrecision(18, 4);
|
||||
@@ -92,7 +93,8 @@ public class AppDbContext : DbContext
|
||||
e.Property(m => m.Question).HasMaxLength(1024);
|
||||
e.Property(m => m.Description).HasMaxLength(4096);
|
||||
e.Property(m => m.ImageUrl).HasMaxLength(1024);
|
||||
e.Property(m => m.Category).HasMaxLength(128);
|
||||
e.Property(m => m.Category).HasConversion<string>().HasMaxLength(64);
|
||||
e.Property(m => m.Subcategory).HasMaxLength(128);
|
||||
e.Property(m => m.Volume).HasPrecision(18, 4);
|
||||
e.Property(m => m.Volume24h).HasPrecision(18, 4);
|
||||
e.Property(m => m.Liquidity).HasPrecision(18, 4);
|
||||
@@ -142,6 +144,17 @@ public class AppDbContext : DbContext
|
||||
e.HasOne(a => a.Trader).WithMany().HasForeignKey(a => a.TraderId).OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
|
||||
// TraderCategoryPerformance
|
||||
mb.Entity<TraderCategoryPerformance>(e =>
|
||||
{
|
||||
e.HasKey(tcp => tcp.Id);
|
||||
e.HasOne(tcp => tcp.Trader).WithMany().HasForeignKey(tcp => tcp.TraderId).OnDelete(DeleteBehavior.Cascade);
|
||||
e.Property(tcp => tcp.Category).HasConversion<string>().HasMaxLength(64);
|
||||
e.Property(tcp => tcp.TotalVolume).HasPrecision(18, 4);
|
||||
e.Property(tcp => tcp.TotalPnL).HasPrecision(18, 4);
|
||||
e.HasIndex(tcp => new { tcp.TraderId, tcp.Category }).IsUnique();
|
||||
});
|
||||
|
||||
// PlatformConfig
|
||||
mb.Entity<PlatformConfig>(e =>
|
||||
{
|
||||
|
||||
@@ -192,8 +192,8 @@ public class MarketRepository : IMarketRepository
|
||||
existing.PlatformMarketId = updated.PlatformMarketId;
|
||||
existing.QuestionId = updated.QuestionId;
|
||||
existing.Description = updated.Description;
|
||||
existing.ImageUrl = updated.ImageUrl;
|
||||
existing.Category = updated.Category;
|
||||
existing.Subcategory = updated.Subcategory;
|
||||
existing.Volume = updated.Volume;
|
||||
existing.Volume24h = updated.Volume24h;
|
||||
existing.Liquidity = updated.Liquidity;
|
||||
@@ -230,7 +230,7 @@ public class MarketRepository : IMarketRepository
|
||||
market.Description = StringHelper.Truncate(market.Description, 4096);
|
||||
market.MarketSlug = StringHelper.Truncate(market.MarketSlug, 512) ?? "";
|
||||
market.ImageUrl = StringHelper.Truncate(market.ImageUrl, 1024);
|
||||
market.Category = StringHelper.Truncate(market.Category, 128) ?? "";
|
||||
market.Subcategory = StringHelper.Truncate(market.Subcategory, 128) ?? "";
|
||||
|
||||
foreach (var o in market.Outcomes)
|
||||
{
|
||||
|
||||
@@ -14,27 +14,27 @@ public class TradeRepository : ITradeRepository
|
||||
=> await _db.Trades.FirstOrDefaultAsync(t => t.Platform == platform && t.PlatformTradeId == platformTradeId, ct);
|
||||
|
||||
public async Task<IReadOnlyList<Trade>> GetByTraderIdAsync(int traderId, int skip = 0, int take = 50, CancellationToken ct = default)
|
||||
=> await _db.Trades.Include(t => t.Trader).Where(t => t.TraderId == traderId)
|
||||
=> await _db.Trades.Include(t => t.Trader).Include(t => t.DbMarket).Where(t => t.TraderId == traderId)
|
||||
.OrderByDescending(t => t.ExecutedAt).Skip(skip).Take(take).ToListAsync(ct);
|
||||
|
||||
public async Task<IReadOnlyList<Trade>> GetByDbMarketIdAsync(int dbMarketId, int skip = 0, int take = 50, CancellationToken ct = default)
|
||||
=> await _db.Trades.Include(t => t.Trader).Where(t => t.DbMarketId == dbMarketId)
|
||||
=> await _db.Trades.Include(t => t.Trader).Include(t => t.DbMarket).Where(t => t.DbMarketId == dbMarketId)
|
||||
.OrderByDescending(t => t.ExecutedAt).Skip(skip).Take(take).ToListAsync(ct);
|
||||
|
||||
public async Task<IReadOnlyList<Trade>> GetByMarketIdAsync(string platformMarketId, int skip = 0, int take = 50, CancellationToken ct = default)
|
||||
=> await _db.Trades.Include(t => t.Trader).Where(t => t.MarketId == platformMarketId)
|
||||
=> await _db.Trades.Include(t => t.Trader).Include(t => t.DbMarket).Where(t => t.MarketId == platformMarketId)
|
||||
.OrderByDescending(t => t.ExecutedAt).Skip(skip).Take(take).ToListAsync(ct);
|
||||
|
||||
public async Task<IReadOnlyList<Trade>> GetRecentAsync(int count = 50, PlatformType? platform = null, CancellationToken ct = default)
|
||||
{
|
||||
var q = _db.Trades.Include(t => t.Trader).AsQueryable();
|
||||
var q = _db.Trades.Include(t => t.Trader).Include(t => t.DbMarket).AsQueryable();
|
||||
if (platform.HasValue) q = q.Where(t => t.Platform == platform.Value);
|
||||
return await q.OrderByDescending(t => t.ExecutedAt).Take(count).ToListAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Trade>> GetLargestAsync(int count = 5, DateTime? since = null, CancellationToken ct = default)
|
||||
{
|
||||
var q = _db.Trades.Include(t => t.Trader).AsQueryable();
|
||||
var q = _db.Trades.Include(t => t.Trader).Include(t => t.DbMarket).AsQueryable();
|
||||
if (since.HasValue) q = q.Where(t => t.ExecutedAt >= since.Value);
|
||||
return await q.OrderByDescending(t => t.Amount).Take(count).ToListAsync(ct);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ 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).FirstOrDefaultAsync(t => t.Id == id, ct);
|
||||
=> await _db.Traders.Include(t => t.CurrentScore).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)
|
||||
=> await _db.Traders.Include(t => t.CurrentScore).Include(t => t.CategoryPerformances)
|
||||
.FirstOrDefaultAsync(t => t.Platform == platform && t.PlatformUserId == platformUserId, ct);
|
||||
|
||||
public async Task<IReadOnlyList<Trader>> GetAllAsync(PlatformType? platform = null, int skip = 0, int take = 50, CancellationToken ct = default)
|
||||
@@ -75,15 +75,18 @@ public class TraderRepository : ITraderRepository
|
||||
|
||||
public async Task<IReadOnlyList<Trader>> GetTradersDueForTradeUpdateAsync(int cooldownHours = 12, int take = 20, CancellationToken ct = default)
|
||||
{
|
||||
// Prioritize:
|
||||
// 1. Traders needing initial import (IsInitialImportComplete == false)
|
||||
// 2. Traders where LastTradesUpdatedAt < cutoff (cooldownHours)
|
||||
|
||||
var cutoff = DateTime.UtcNow.AddHours(-cooldownHours);
|
||||
var normalCutoff = DateTime.UtcNow.AddHours(-cooldownHours);
|
||||
var priorityCutoff = DateTime.UtcNow.AddHours(-1); // Sync priority traders more often, but not continuously
|
||||
|
||||
return await _db.Traders
|
||||
.Where(t => !t.IsInitialImportComplete || t.LastTradesUpdatedAt == null || t.LastTradesUpdatedAt < cutoff)
|
||||
.OrderBy(t => t.IsInitialImportComplete) // false (0) comes before true (1)
|
||||
.Include(t => t.WatchlistEntries)
|
||||
.Where(t => t.LastTradesUpdatedAt == null ||
|
||||
(!t.IsInitialImportComplete) ||
|
||||
((!t.IsAutoDiscovered || t.WatchlistEntries.Any()) && t.LastTradesUpdatedAt < priorityCutoff) ||
|
||||
(t.IsAutoDiscovered && t.LastTradesUpdatedAt < normalCutoff))
|
||||
.OrderBy(t => t.IsAutoDiscovered) // Manual first (false = 0)
|
||||
.ThenByDescending(t => t.WatchlistEntries.Any()) // Watchlisted next (true = 1)
|
||||
.ThenBy(t => t.IsInitialImportComplete) // New ones next (false = 0)
|
||||
.ThenBy(t => t.LastTradesUpdatedAt ?? DateTime.MinValue) // Oldest first
|
||||
.Take(take)
|
||||
.ToListAsync(ct);
|
||||
|
||||
Reference in New Issue
Block a user