diff --git a/src/Predictalytics.Domain/Entities/Trade.cs b/src/Predictalytics.Domain/Entities/Trade.cs
index aebbea9..3f7bece 100644
--- a/src/Predictalytics.Domain/Entities/Trade.cs
+++ b/src/Predictalytics.Domain/Entities/Trade.cs
@@ -91,5 +91,9 @@ public class Trade
// ── Navigation ────────────────────────────────────────────────────────
public Trader Trader { get; set; } = null!;
public MarketOutcome? MarketOutcome { get; set; }
+
+ /// Analytics context containing high-resolution price data and slippage (if collected).
+ public TradeContext? Context { get; set; }
+
public Market? DbMarket { get; set; }
}
diff --git a/src/Predictalytics.Domain/Entities/TradeContext.cs b/src/Predictalytics.Domain/Entities/TradeContext.cs
new file mode 100644
index 0000000..bc99f15
--- /dev/null
+++ b/src/Predictalytics.Domain/Entities/TradeContext.cs
@@ -0,0 +1,29 @@
+using Predictalytics.Domain.Enums;
+
+namespace Predictalytics.Domain.Entities;
+
+///
+/// High-resolution analytics context for a specific trade.
+/// Collected asynchronously after a trade is discovered to calculate Slippage and Edge.
+/// Only collected for Watchlisted or high-scoring traders to conserve database space.
+///
+public class TradeContext
+{
+ public long Id { get; set; }
+
+ /// The ID of the parent trade.
+ public long TradeId { get; set; }
+ public Trade Trade { get; set; } = null!;
+
+ /// The estimated mid-price of the asset roughly 1 minute before the trade execution.
+ public decimal? PriceBefore1m { get; set; }
+
+ /// The estimated mid-price of the asset roughly 1 minute after the trade execution.
+ public decimal? PriceAfter1m { get; set; }
+
+ /// Calculated slippage: execution price vs PriceBefore1m.
+ public decimal? EstimatedSlippage { get; set; }
+
+ /// Estimated order type based on fee or exact price matching.
+ public OrderType EstimatedOrderType { get; set; } = OrderType.Unknown;
+}
diff --git a/src/Predictalytics.Domain/Entities/Trader.cs b/src/Predictalytics.Domain/Entities/Trader.cs
index d8ce5e3..c97f973 100644
--- a/src/Predictalytics.Domain/Entities/Trader.cs
+++ b/src/Predictalytics.Domain/Entities/Trader.cs
@@ -64,6 +64,9 @@ public class Trader
/// When the AI strategy summary was last updated.
public DateTime? AiStrategyUpdatedAt { get; set; }
+ /// When the TraderAnalyticsWorker last performed a deep dive on this trader.
+ public DateTime? LastAnalyzedAt { get; set; }
+
/// Total number of trades tracked.
public int TotalTrades { get; set; }
diff --git a/src/Predictalytics.Domain/Entities/TraderCategoryPerformance.cs b/src/Predictalytics.Domain/Entities/TraderCategoryPerformance.cs
index 73b6116..e1c0b97 100644
--- a/src/Predictalytics.Domain/Entities/TraderCategoryPerformance.cs
+++ b/src/Predictalytics.Domain/Entities/TraderCategoryPerformance.cs
@@ -11,6 +11,9 @@ public class TraderCategoryPerformance
public MarketCategory Category { get; set; }
+ /// Subcategory (e.g. "Basketball", "Elections", "Bitcoin").
+ public string Subcategory { get; set; } = string.Empty;
+
/// Total volume traded in this category (USD).
public decimal TotalVolume { get; set; }
diff --git a/src/Predictalytics.Domain/Enums/OrderType.cs b/src/Predictalytics.Domain/Enums/OrderType.cs
new file mode 100644
index 0000000..3ab671a
--- /dev/null
+++ b/src/Predictalytics.Domain/Enums/OrderType.cs
@@ -0,0 +1,8 @@
+namespace Predictalytics.Domain.Enums;
+
+public enum OrderType
+{
+ Unknown = 0,
+ Maker = 1,
+ Taker = 2
+}
diff --git a/src/Predictalytics.Infrastructure/Data/AppDbContext.cs b/src/Predictalytics.Infrastructure/Data/AppDbContext.cs
index 14e2cff..0e68b96 100644
--- a/src/Predictalytics.Infrastructure/Data/AppDbContext.cs
+++ b/src/Predictalytics.Infrastructure/Data/AppDbContext.cs
@@ -19,6 +19,7 @@ public class AppDbContext : DbContext
public DbSet TraderPositions => Set();
public DbSet MarketOutcomePriceSnapshots => Set();
public DbSet TraderCategoryPerformances => Set();
+ public DbSet TradeContexts => Set();
public AppDbContext(DbContextOptions options) : base(options) { }
@@ -150,9 +151,21 @@ public class AppDbContext : DbContext
e.HasKey(tcp => tcp.Id);
e.HasOne(tcp => tcp.Trader).WithMany(t => t.CategoryPerformances).HasForeignKey(tcp => tcp.TraderId).OnDelete(DeleteBehavior.Cascade);
e.Property(tcp => tcp.Category).HasConversion().HasMaxLength(64);
+ e.Property(tcp => tcp.Subcategory).HasMaxLength(128);
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();
+ e.HasIndex(tcp => new { tcp.TraderId, tcp.Category, tcp.Subcategory }).IsUnique();
+ });
+
+ // TradeContext
+ mb.Entity(e =>
+ {
+ e.HasKey(tc => tc.Id);
+ e.HasOne(tc => tc.Trade).WithOne(t => t.Context).HasForeignKey(tc => tc.TradeId).OnDelete(DeleteBehavior.Cascade);
+ e.Property(tc => tc.PriceBefore1m).HasPrecision(18, 4);
+ e.Property(tc => tc.PriceAfter1m).HasPrecision(18, 4);
+ e.Property(tc => tc.EstimatedSlippage).HasPrecision(18, 4);
+ e.Property(tc => tc.EstimatedOrderType).HasConversion().HasMaxLength(32);
});
// PlatformConfig
diff --git a/src/Predictalytics.Infrastructure/Migrations/20260705165602_AddTradeContextAndSubcategory.Designer.cs b/src/Predictalytics.Infrastructure/Migrations/20260705165602_AddTradeContextAndSubcategory.Designer.cs
new file mode 100644
index 0000000..2290094
--- /dev/null
+++ b/src/Predictalytics.Infrastructure/Migrations/20260705165602_AddTradeContextAndSubcategory.Designer.cs
@@ -0,0 +1,966 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Predictalytics.Infrastructure.Data;
+
+#nullable disable
+
+namespace Predictalytics.Infrastructure.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20260705165602_AddTradeContextAndSubcategory")]
+ partial class AddTradeContextAndSubcategory
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Alert", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("IsRead")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("varchar(4096)");
+
+ b.Property("Platform")
+ .HasColumnType("int");
+
+ b.Property("Severity")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("varchar(512)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.Property("Type")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt");
+
+ b.HasIndex("TraderId");
+
+ b.ToTable("Alerts");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Event", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DbCreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Description")
+ .HasMaxLength(4096)
+ .HasColumnType("varchar(4096)");
+
+ b.Property("EndDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("ImageUrl")
+ .HasMaxLength(1024)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("IsActive")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsClosed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LastUpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Platform")
+ .HasColumnType("int");
+
+ b.Property("PlatformEventId")
+ .HasColumnType("bigint");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("varchar(512)");
+
+ b.Property("StartDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Tags")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("varchar(1024)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Platform", "PlatformEventId")
+ .IsUnique();
+
+ b.ToTable("Events");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Category")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("varchar(64)");
+
+ b.Property("ConditionId")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DbCreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Description")
+ .HasMaxLength(4096)
+ .HasColumnType("varchar(4096)");
+
+ b.Property("EndDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("EventId")
+ .HasColumnType("int");
+
+ b.Property("ImageUrl")
+ .HasMaxLength(1024)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("IsResolved")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LastTradesUpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LastUpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Liquidity")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("MarketSlug")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("varchar(512)");
+
+ b.Property("Platform")
+ .HasColumnType("int");
+
+ b.Property("PlatformMarketId")
+ .HasColumnType("bigint");
+
+ b.Property("Question")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("QuestionId")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("ResolutionOutcome")
+ .HasColumnType("longtext");
+
+ b.Property("StartDate")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Subcategory")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("Volume")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("Volume24h")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EventId");
+
+ b.HasIndex("Platform", "PlatformMarketId")
+ .IsUnique();
+
+ b.ToTable("Markets");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.MarketAnalytics", b =>
+ {
+ b.Property("MarketId")
+ .HasColumnType("int");
+
+ b.Property("AverageTradeSize")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("BotActivityScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("LastCalculatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("UniqueTradersCount")
+ .HasColumnType("int");
+
+ b.HasKey("MarketId");
+
+ b.ToTable("MarketAnalytics");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.MarketOutcome", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("CurrentPrice")
+ .HasPrecision(18, 8)
+ .HasColumnType("decimal(18,8)");
+
+ b.Property("Label")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("MarketId")
+ .HasColumnType("int");
+
+ b.Property("OutcomeIndex")
+ .HasColumnType("int");
+
+ b.Property("TokenId")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TokenId");
+
+ b.HasIndex("MarketId", "OutcomeIndex")
+ .IsUnique();
+
+ b.ToTable("MarketOutcomes");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.MarketOutcomePriceSnapshot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("MarketOutcomeId")
+ .HasColumnType("int");
+
+ b.Property("Price")
+ .HasPrecision(10, 6)
+ .HasColumnType("decimal(10,6)");
+
+ b.Property("Timestamp")
+ .HasColumnType("datetime(6)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MarketOutcomeId", "Timestamp");
+
+ b.ToTable("MarketOutcomePriceSnapshots");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.PlatformConfig", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("int");
+
+ b.Property("BaseUrl")
+ .HasMaxLength(1024)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("IsActive")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("SettingsJson")
+ .HasColumnType("longtext");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.HasKey("Id");
+
+ b.ToTable("PlatformConfigs");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Trade", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Amount")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("AssetId")
+ .IsRequired()
+ .HasMaxLength(80)
+ .HasColumnType("varchar(80)");
+
+ b.Property("DbMarketId")
+ .HasColumnType("int");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("IsContextEnriched")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("MarketId")
+ .IsRequired()
+ .HasMaxLength(66)
+ .HasColumnType("varchar(66)");
+
+ b.Property("MarketOutcomeId")
+ .HasColumnType("int");
+
+ b.Property("Outcome")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("Platform")
+ .HasColumnType("int");
+
+ b.Property("PlatformTradeId")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("PostTradePrice1m")
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PreTradePrice1m")
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("Price")
+ .HasPrecision(18, 6)
+ .HasColumnType("decimal(18,6)");
+
+ b.Property("Side")
+ .HasColumnType("int");
+
+ b.Property("Size")
+ .HasPrecision(14, 6)
+ .HasColumnType("decimal(14,6)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.Property("TransactionHash")
+ .HasMaxLength(66)
+ .HasColumnType("varchar(66)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AssetId");
+
+ b.HasIndex("DbMarketId");
+
+ b.HasIndex("ExecutedAt");
+
+ b.HasIndex("MarketOutcomeId");
+
+ b.HasIndex("TraderId");
+
+ b.HasIndex("Platform", "PlatformTradeId")
+ .IsUnique();
+
+ b.ToTable("Trades");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TradeContext", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("EstimatedOrderType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("varchar(32)");
+
+ b.Property("EstimatedSlippage")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PriceAfter1m")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PriceBefore1m")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TradeId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TradeId")
+ .IsUnique();
+
+ b.ToTable("TradeContexts");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Trader", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AiStrategySummary")
+ .HasColumnType("longtext");
+
+ b.Property("AiStrategyUpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("IsAutoDiscovered")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsInitialImportComplete")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsSuspectedBot")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LastAnalyzedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LastApiErrorAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LastPolledAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LastTradesUpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("ManualPriorityOverride")
+ .HasColumnType("int");
+
+ b.Property("Notes")
+ .HasColumnType("longtext");
+
+ b.Property("Platform")
+ .HasColumnType("int");
+
+ b.Property("PlatformUserId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("Strategy")
+ .HasColumnType("int");
+
+ b.Property("Tier")
+ .HasColumnType("int");
+
+ b.Property("TotalPnl")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TotalTrades")
+ .HasColumnType("int");
+
+ b.Property("WinRate")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Platform", "PlatformUserId")
+ .IsUnique();
+
+ b.ToTable("Traders");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderAnalytics", b =>
+ {
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.Property("LastCalculatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("OverallPnL")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("OverallWinRate")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("PnL24h")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PnL30d")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PnL7d")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("WinRate24h")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("WinRate30d")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("WinRate7d")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.HasKey("TraderId");
+
+ b.ToTable("TraderAnalytics");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderCategoryPerformance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("Category")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Subcategory")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("TotalPnL")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TotalTrades")
+ .HasColumnType("int");
+
+ b.Property("TotalVolume")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.Property("WinningTrades")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TraderId", "Category", "Subcategory")
+ .IsUnique();
+
+ b.ToTable("TraderCategoryPerformances");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderPosition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AvgCost")
+ .HasPrecision(10, 6)
+ .HasColumnType("decimal(10,6)");
+
+ b.Property("LastUpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("MarketOutcomeId")
+ .HasColumnType("int");
+
+ b.Property("RealizedPnl")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("SharesHeld")
+ .HasPrecision(14, 6)
+ .HasColumnType("decimal(14,6)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MarketOutcomeId");
+
+ b.HasIndex("TraderId", "MarketOutcomeId")
+ .IsUnique();
+
+ b.ToTable("TraderPositions");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderScore", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("ActivityScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("CalculatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("CombinedScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("CopytradingScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("QualityScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("Rank")
+ .HasColumnType("int");
+
+ b.Property("TimingScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.Property("VolumeScore")
+ .HasPrecision(8, 4)
+ .HasColumnType("decimal(8,4)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TraderId")
+ .IsUnique();
+
+ b.ToTable("TraderScores");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.WatchlistEntry", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("AddedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("AlertsEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Label")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("Notes")
+ .HasColumnType("longtext");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TraderId")
+ .IsUnique();
+
+ b.ToTable("WatchlistEntries");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Alert", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany()
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Event", "Event")
+ .WithMany("Markets")
+ .HasForeignKey("EventId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Event");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.MarketAnalytics", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Market", "Market")
+ .WithOne("Analytics")
+ .HasForeignKey("Predictalytics.Domain.Entities.MarketAnalytics", "MarketId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Market");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.MarketOutcome", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Market", "Market")
+ .WithMany("Outcomes")
+ .HasForeignKey("MarketId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Market");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.MarketOutcomePriceSnapshot", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.MarketOutcome", "MarketOutcome")
+ .WithMany()
+ .HasForeignKey("MarketOutcomeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("MarketOutcome");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Trade", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Market", "DbMarket")
+ .WithMany()
+ .HasForeignKey("DbMarketId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("Predictalytics.Domain.Entities.MarketOutcome", "MarketOutcome")
+ .WithMany()
+ .HasForeignKey("MarketOutcomeId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany("Trades")
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("DbMarket");
+
+ b.Navigation("MarketOutcome");
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TradeContext", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trade", "Trade")
+ .WithOne("Context")
+ .HasForeignKey("Predictalytics.Domain.Entities.TradeContext", "TradeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trade");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderAnalytics", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithOne("Analytics")
+ .HasForeignKey("Predictalytics.Domain.Entities.TraderAnalytics", "TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderCategoryPerformance", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany("CategoryPerformances")
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderPosition", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.MarketOutcome", "MarketOutcome")
+ .WithMany()
+ .HasForeignKey("MarketOutcomeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany("Positions")
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("MarketOutcome");
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TraderScore", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithOne("CurrentScore")
+ .HasForeignKey("Predictalytics.Domain.Entities.TraderScore", "TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.WatchlistEntry", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany("WatchlistEntries")
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trader");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Event", b =>
+ {
+ b.Navigation("Markets");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
+ {
+ b.Navigation("Analytics");
+
+ b.Navigation("Outcomes");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Trade", b =>
+ {
+ b.Navigation("Context");
+ });
+
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Trader", b =>
+ {
+ b.Navigation("Analytics");
+
+ b.Navigation("CategoryPerformances");
+
+ b.Navigation("CurrentScore");
+
+ b.Navigation("Positions");
+
+ b.Navigation("Trades");
+
+ b.Navigation("WatchlistEntries");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Predictalytics.Infrastructure/Migrations/20260705165602_AddTradeContextAndSubcategory.cs b/src/Predictalytics.Infrastructure/Migrations/20260705165602_AddTradeContextAndSubcategory.cs
new file mode 100644
index 0000000..105adf3
--- /dev/null
+++ b/src/Predictalytics.Infrastructure/Migrations/20260705165602_AddTradeContextAndSubcategory.cs
@@ -0,0 +1,97 @@
+using System;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Predictalytics.Infrastructure.Migrations
+{
+ ///
+ public partial class AddTradeContextAndSubcategory : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_TraderCategoryPerformances_TraderId_Category",
+ table: "TraderCategoryPerformances");
+
+ migrationBuilder.AddColumn(
+ name: "LastAnalyzedAt",
+ table: "Traders",
+ type: "datetime(6)",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "Subcategory",
+ table: "TraderCategoryPerformances",
+ type: "varchar(128)",
+ maxLength: 128,
+ nullable: false,
+ defaultValue: "")
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "TradeContexts",
+ columns: table => new
+ {
+ Id = table.Column(type: "bigint", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ TradeId = table.Column(type: "bigint", nullable: false),
+ PriceBefore1m = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: true),
+ PriceAfter1m = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: true),
+ EstimatedSlippage = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: true),
+ EstimatedOrderType = table.Column(type: "varchar(32)", maxLength: 32, nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_TradeContexts", x => x.Id);
+ table.ForeignKey(
+ name: "FK_TradeContexts_Trades_TradeId",
+ column: x => x.TradeId,
+ principalTable: "Trades",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TraderCategoryPerformances_TraderId_Category_Subcategory",
+ table: "TraderCategoryPerformances",
+ columns: new[] { "TraderId", "Category", "Subcategory" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TradeContexts_TradeId",
+ table: "TradeContexts",
+ column: "TradeId",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "TradeContexts");
+
+ migrationBuilder.DropIndex(
+ name: "IX_TraderCategoryPerformances_TraderId_Category_Subcategory",
+ table: "TraderCategoryPerformances");
+
+ migrationBuilder.DropColumn(
+ name: "LastAnalyzedAt",
+ table: "Traders");
+
+ migrationBuilder.DropColumn(
+ name: "Subcategory",
+ table: "TraderCategoryPerformances");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TraderCategoryPerformances_TraderId_Category",
+ table: "TraderCategoryPerformances",
+ columns: new[] { "TraderId", "Category" },
+ unique: true);
+ }
+ }
+}
diff --git a/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
index dd8bdc6..ff72cfb 100644
--- a/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
+++ b/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
@@ -446,6 +446,42 @@ namespace Predictalytics.Infrastructure.Migrations
b.ToTable("Trades");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TradeContext", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("EstimatedOrderType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("varchar(32)");
+
+ b.Property("EstimatedSlippage")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PriceAfter1m")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("PriceBefore1m")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TradeId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TradeId")
+ .IsUnique();
+
+ b.ToTable("TradeContexts");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.Trader", b =>
{
b.Property("Id")
@@ -477,6 +513,9 @@ namespace Predictalytics.Infrastructure.Migrations
b.Property("IsSuspectedBot")
.HasColumnType("tinyint(1)");
+ b.Property("LastAnalyzedAt")
+ .HasColumnType("datetime(6)");
+
b.Property("LastApiErrorAt")
.HasColumnType("datetime(6)");
@@ -583,6 +622,11 @@ namespace Predictalytics.Infrastructure.Migrations
.HasMaxLength(64)
.HasColumnType("varchar(64)");
+ b.Property("Subcategory")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
b.Property("TotalPnL")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
@@ -602,7 +646,7 @@ namespace Predictalytics.Infrastructure.Migrations
b.HasKey("Id");
- b.HasIndex("TraderId", "Category")
+ b.HasIndex("TraderId", "Category", "Subcategory")
.IsUnique();
b.ToTable("TraderCategoryPerformances");
@@ -808,6 +852,17 @@ namespace Predictalytics.Infrastructure.Migrations
b.Navigation("Trader");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.TradeContext", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trade", "Trade")
+ .WithOne("Context")
+ .HasForeignKey("Predictalytics.Domain.Entities.TradeContext", "TradeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trade");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.TraderAnalytics", b =>
{
b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
@@ -883,6 +938,11 @@ namespace Predictalytics.Infrastructure.Migrations
b.Navigation("Outcomes");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.Trade", b =>
+ {
+ b.Navigation("Context");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.Trader", b =>
{
b.Navigation("Analytics");