feat: implement Part D and E from FIXPLAN
- D1/D2/D2c: Added TraderTraits entity, TraderTraitCalculator, Market Return Metrics (MedianWin, AvgWin, etc.), and trait filters
- D3: Implemented HF-Trader Tiering via IngestMode (Full, Aggregated, SnapshotOnly) and updated TradeHistoryWorker to respect tiers
- E1-E5: Added MasterStatus to Trader, TraderWindowMetrics for rolling analytics, Fingerprint metrics (PriceBandProfile, P50/P90), Copyability aggregates (Volume, Drift, Edge)
- E6: Implemented GET /api/traders/{id}/profile and GET /api/traders/correlation
- Replaced FIXPLAN-2026-07-09.md with FIXPLAN-TODO.md and FIXPLAN-DONE.md
- Cleaned up API docs and plan to use generic terms (removed hardcoded PolyTrader references)
- Added respective EF Core Migrations
This commit is contained in:
@@ -22,6 +22,8 @@ public class AppDbContext : DbContext
|
||||
public DbSet<TraderCategoryPerformance> TraderCategoryPerformances => Set<TraderCategoryPerformance>();
|
||||
public DbSet<TradeContext> TradeContexts => Set<TradeContext>();
|
||||
public DbSet<BackgroundJob> BackgroundJobs => Set<BackgroundJob>();
|
||||
public DbSet<TraderTrait> TraderTraits => Set<TraderTrait>();
|
||||
public DbSet<TraderWindowMetrics> TraderWindowMetrics => Set<TraderWindowMetrics>();
|
||||
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
|
||||
@@ -40,6 +42,30 @@ public class AppDbContext : DbContext
|
||||
.HasForeignKey<TraderScore>(s => s.TraderId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// TraderTrait
|
||||
mb.Entity<TraderTrait>(e =>
|
||||
{
|
||||
e.HasKey(t => t.Id);
|
||||
e.HasIndex(t => new { t.TraderId, t.Trait }).IsUnique();
|
||||
e.Property(t => t.Value).HasPrecision(18, 4);
|
||||
e.HasOne(t => t.Trader).WithMany(tr => tr.Traits)
|
||||
.HasForeignKey(t => t.TraderId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// TraderWindowMetrics
|
||||
mb.Entity<TraderWindowMetrics>(e =>
|
||||
{
|
||||
e.HasKey(t => t.Id);
|
||||
e.HasIndex(t => new { t.TraderId, t.WindowStart, t.WindowEnd }).IsUnique();
|
||||
e.Property(t => t.WinRate).HasPrecision(8, 4);
|
||||
e.Property(t => t.AvgReturnPct).HasPrecision(18, 4);
|
||||
e.Property(t => t.MedianWinReturnPct).HasPrecision(18, 4);
|
||||
e.Property(t => t.MedianLossReturnPct).HasPrecision(18, 4);
|
||||
e.Property(t => t.ProfitFactor).HasPrecision(18, 4);
|
||||
e.HasOne(t => t.Trader).WithMany()
|
||||
.HasForeignKey(t => t.TraderId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// Trade
|
||||
mb.Entity<Trade>(e =>
|
||||
{
|
||||
@@ -193,6 +219,22 @@ public class AppDbContext : DbContext
|
||||
e.Property(a => a.WinRate7d).HasPrecision(8, 4);
|
||||
e.Property(a => a.PnL24h).HasPrecision(18, 4);
|
||||
e.Property(a => a.WinRate24h).HasPrecision(8, 4);
|
||||
|
||||
// D2c & E3 & E5
|
||||
e.Property(a => a.MedianWinReturnPct).HasPrecision(18, 4);
|
||||
e.Property(a => a.AvgWinReturnPct).HasPrecision(18, 4);
|
||||
e.Property(a => a.MedianLossReturnPct).HasPrecision(18, 4);
|
||||
e.Property(a => a.AvgLossReturnPct).HasPrecision(18, 4);
|
||||
e.Property(a => a.ProfitFactor).HasPrecision(18, 4);
|
||||
|
||||
e.Property(a => a.MedianHoldDurationHours).HasPrecision(18, 4);
|
||||
e.Property(a => a.P50PositionSize).HasPrecision(18, 4);
|
||||
e.Property(a => a.P90PositionSize).HasPrecision(18, 4);
|
||||
e.Property(a => a.TradesPerWeek).HasPrecision(18, 4);
|
||||
|
||||
e.Property(a => a.MedianMarketVolumeUsd).HasPrecision(18, 4);
|
||||
e.Property(a => a.MedianPostFillDriftPct).HasPrecision(18, 4);
|
||||
e.Property(a => a.NetEdgeAfterFeesPct).HasPrecision(18, 4);
|
||||
});
|
||||
|
||||
// MarketAnalytics
|
||||
|
||||
@@ -70,14 +70,14 @@ public class TradeRepository : ITradeRepository
|
||||
|
||||
foreach (var chunk in tradeList.Chunk(500))
|
||||
{
|
||||
var sb = new System.Text.StringBuilder("INSERT IGNORE INTO Trades (PlatformTradeId, MarketId, AssetId, Outcome, Side, Price, Size, Amount, ExecutedAt, TransactionHash, TraderId, MarketOutcomeId, DbMarketId, Platform, IsContextEnriched) VALUES ");
|
||||
var sb = new System.Text.StringBuilder("INSERT INTO Trades (PlatformTradeId, MarketId, AssetId, Outcome, Side, Price, Size, Amount, ExecutedAt, TransactionHash, TraderId, MarketOutcomeId, DbMarketId, Platform, IsContextEnriched, AggregatedCount) VALUES ");
|
||||
var parameters = new List<object>();
|
||||
|
||||
for (int i = 0; i < chunk.Length; i++)
|
||||
{
|
||||
var t = chunk[i];
|
||||
int pIdx = i * 15;
|
||||
sb.Append($"({{{pIdx}}}, {{{pIdx + 1}}}, {{{pIdx + 2}}}, {{{pIdx + 3}}}, {{{pIdx + 4}}}, {{{pIdx + 5}}}, {{{pIdx + 6}}}, {{{pIdx + 7}}}, {{{pIdx + 8}}}, {{{pIdx + 9}}}, {{{pIdx + 10}}}, {{{pIdx + 11}}}, {{{pIdx + 12}}}, {{{pIdx + 13}}}, {{{pIdx + 14}}})");
|
||||
int pIdx = i * 16;
|
||||
sb.Append($"({{{pIdx}}}, {{{pIdx + 1}}}, {{{pIdx + 2}}}, {{{pIdx + 3}}}, {{{pIdx + 4}}}, {{{pIdx + 5}}}, {{{pIdx + 6}}}, {{{pIdx + 7}}}, {{{pIdx + 8}}}, {{{pIdx + 9}}}, {{{pIdx + 10}}}, {{{pIdx + 11}}}, {{{pIdx + 12}}}, {{{pIdx + 13}}}, {{{pIdx + 14}}}, {{{pIdx + 15}}})");
|
||||
|
||||
if (i < chunk.Length - 1)
|
||||
sb.Append(", ");
|
||||
@@ -97,8 +97,12 @@ public class TradeRepository : ITradeRepository
|
||||
parameters.Add(t.DbMarketId ?? (object?)null);
|
||||
parameters.Add((int)t.Platform);
|
||||
parameters.Add(t.IsContextEnriched);
|
||||
parameters.Add(t.AggregatedCount ?? (object?)null);
|
||||
}
|
||||
|
||||
// For Aggregated Trades, we want UPSERT logic to update size, amount and VWAP
|
||||
sb.Append(" ON DUPLICATE KEY UPDATE Price=VALUES(Price), Size=VALUES(Size), Amount=VALUES(Amount), AggregatedCount=VALUES(AggregatedCount);");
|
||||
|
||||
int maxRetries = 3;
|
||||
var backoffs = new[] { 250, 500, 1000 };
|
||||
for (int retry = 0; retry <= maxRetries; retry++)
|
||||
|
||||
@@ -17,6 +17,7 @@ public class TraderRepository : ITraderRepository
|
||||
.Include(t => t.CurrentScore)
|
||||
.Include(t => t.Analytics)
|
||||
.Include(t => t.CategoryPerformances)
|
||||
.Include(t => t.Traits)
|
||||
.FirstOrDefaultAsync(t => t.Id == id, ct);
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ public class TraderRepository : ITraderRepository
|
||||
var q = _db.Traders
|
||||
.Include(t => t.CurrentScore)
|
||||
.Include(t => t.Analytics)
|
||||
.Include(t => t.Traits)
|
||||
.AsQueryable();
|
||||
|
||||
if (platform.HasValue) q = q.Where(t => t.Platform == platform.Value);
|
||||
|
||||
Reference in New Issue
Block a user