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:
Richard
2026-07-14 09:04:31 +02:00
parent a1fcb4ace5
commit 16431f38a5
39 changed files with 9028 additions and 167 deletions
@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Predictalytics.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddAggregatedCountToTrade : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AggregatedCount",
table: "Trades",
type: "int",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AggregatedCount",
table: "Trades");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Predictalytics.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddTraderTraits : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TraderTraits",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
TraderId = table.Column<int>(type: "int", nullable: false),
Trait = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Value = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
ComputedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TraderTraits", x => x.Id);
table.ForeignKey(
name: "FK_TraderTraits_Traders_TraderId",
column: x => x.TraderId,
principalTable: "Traders",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_TraderTraits_TraderId_Trait",
table: "TraderTraits",
columns: new[] { "TraderId", "Trait" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TraderTraits");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Predictalytics.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddMarketReturnMetrics : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "AvgLossReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "AvgWinReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "MedianLossReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "MedianWinReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "ProfitFactor",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AvgLossReturnPct",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "AvgWinReturnPct",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "MedianLossReturnPct",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "MedianWinReturnPct",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "ProfitFactor",
table: "TraderAnalytics");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Predictalytics.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddTraderIngestMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "IngestMode",
table: "Traders",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IngestMode",
table: "Traders");
}
}
}
@@ -0,0 +1,69 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Predictalytics.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddMasterStatusAndWindowMetrics : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "MasterStatus",
table: "Traders",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "TraderWindowMetrics",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
TraderId = table.Column<int>(type: "int", nullable: false),
WindowStart = table.Column<DateTime>(type: "datetime(6)", nullable: false),
WindowEnd = table.Column<DateTime>(type: "datetime(6)", nullable: false),
ClosedMarkets = table.Column<int>(type: "int", nullable: false),
WinRate = table.Column<decimal>(type: "decimal(8,4)", precision: 8, scale: 4, nullable: false),
AvgReturnPct = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
MedianWinReturnPct = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
MedianLossReturnPct = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
ProfitFactor = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: true),
ComputedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TraderWindowMetrics", x => x.Id);
table.ForeignKey(
name: "FK_TraderWindowMetrics_Traders_TraderId",
column: x => x.TraderId,
principalTable: "Traders",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_TraderWindowMetrics_TraderId_WindowStart_WindowEnd",
table: "TraderWindowMetrics",
columns: new[] { "TraderId", "WindowStart", "WindowEnd" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TraderWindowMetrics");
migrationBuilder.DropColumn(
name: "MasterStatus",
table: "Traders");
}
}
}
@@ -0,0 +1,222 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Predictalytics.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddTraderAnalyticsFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<decimal>(
name: "ProfitFactor",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: true,
oldClrType: typeof(decimal),
oldType: "decimal(65,30)",
oldNullable: true);
migrationBuilder.AlterColumn<decimal>(
name: "MedianWinReturnPct",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(65,30)");
migrationBuilder.AlterColumn<decimal>(
name: "MedianLossReturnPct",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(65,30)");
migrationBuilder.AlterColumn<decimal>(
name: "AvgWinReturnPct",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(65,30)");
migrationBuilder.AlterColumn<decimal>(
name: "AvgLossReturnPct",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(65,30)");
migrationBuilder.AddColumn<decimal>(
name: "MedianHoldDurationHours",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "MedianMarketVolumeUsd",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "MedianPostFillDriftPct",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "NetEdgeAfterFeesPct",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "P50PositionSize",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<decimal>(
name: "P90PositionSize",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
migrationBuilder.AddColumn<string>(
name: "PriceBandProfileJson",
table: "TraderAnalytics",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<decimal>(
name: "TradesPerWeek",
table: "TraderAnalytics",
type: "decimal(18,4)",
precision: 18,
scale: 4,
nullable: false,
defaultValue: 0m);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MedianHoldDurationHours",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "MedianMarketVolumeUsd",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "MedianPostFillDriftPct",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "NetEdgeAfterFeesPct",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "P50PositionSize",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "P90PositionSize",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "PriceBandProfileJson",
table: "TraderAnalytics");
migrationBuilder.DropColumn(
name: "TradesPerWeek",
table: "TraderAnalytics");
migrationBuilder.AlterColumn<decimal>(
name: "ProfitFactor",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: true,
oldClrType: typeof(decimal),
oldType: "decimal(18,4)",
oldPrecision: 18,
oldScale: 4,
oldNullable: true);
migrationBuilder.AlterColumn<decimal>(
name: "MedianWinReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(18,4)",
oldPrecision: 18,
oldScale: 4);
migrationBuilder.AlterColumn<decimal>(
name: "MedianLossReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(18,4)",
oldPrecision: 18,
oldScale: 4);
migrationBuilder.AlterColumn<decimal>(
name: "AvgWinReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(18,4)",
oldPrecision: 18,
oldScale: 4);
migrationBuilder.AlterColumn<decimal>(
name: "AvgLossReturnPct",
table: "TraderAnalytics",
type: "decimal(65,30)",
nullable: false,
oldClrType: typeof(decimal),
oldType: "decimal(18,4)",
oldPrecision: 18,
oldScale: 4);
}
}
}
@@ -419,6 +419,9 @@ namespace Predictalytics.Infrastructure.Migrations
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<int?>("AggregatedCount")
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
@@ -564,6 +567,9 @@ namespace Predictalytics.Infrastructure.Migrations
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<int>("IngestMode")
.HasColumnType("int");
b.Property<bool>("IsAutoDiscovered")
.HasColumnType("tinyint(1)");
@@ -588,6 +594,9 @@ namespace Predictalytics.Infrastructure.Migrations
b.Property<int?>("ManualPriorityOverride")
.HasColumnType("int");
b.Property<int>("MasterStatus")
.HasColumnType("int");
b.Property<string>("Notes")
.HasColumnType("longtext");
@@ -629,6 +638,14 @@ namespace Predictalytics.Infrastructure.Migrations
b.Property<int>("TraderId")
.HasColumnType("int");
b.Property<decimal>("AvgLossReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("AvgWinReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("CopytradingCopyabilityScore")
.HasColumnType("decimal(65,30)");
@@ -647,6 +664,30 @@ namespace Predictalytics.Infrastructure.Migrations
b.Property<DateTime>("LastCalculatedAt")
.HasColumnType("datetime(6)");
b.Property<decimal>("MedianHoldDurationHours")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("MedianLossReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("MedianMarketVolumeUsd")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("MedianPostFillDriftPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("MedianWinReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("NetEdgeAfterFeesPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("OverallPnL")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
@@ -655,6 +696,14 @@ namespace Predictalytics.Infrastructure.Migrations
.HasPrecision(8, 4)
.HasColumnType("decimal(8,4)");
b.Property<decimal>("P50PositionSize")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("P90PositionSize")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("PnL24h")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
@@ -667,9 +716,20 @@ namespace Predictalytics.Infrastructure.Migrations
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<string>("PriceBandProfileJson")
.HasColumnType("longtext");
b.Property<decimal?>("ProfitFactor")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<int>("Trades30d")
.HasColumnType("int");
b.Property<decimal>("TradesPerWeek")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("WinRate24h")
.HasPrecision(8, 4)
.HasColumnType("decimal(8,4)");
@@ -853,6 +913,88 @@ namespace Predictalytics.Infrastructure.Migrations
b.ToTable("TraderScores");
});
modelBuilder.Entity("Predictalytics.Domain.Entities.TraderTrait", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("ComputedAt")
.HasColumnType("datetime(6)");
b.Property<int>("TraderId")
.HasColumnType("int");
b.Property<string>("Trait")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.Property<decimal>("Value")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.HasKey("Id");
b.HasIndex("TraderId", "Trait")
.IsUnique();
b.ToTable("TraderTraits");
});
modelBuilder.Entity("Predictalytics.Domain.Entities.TraderWindowMetrics", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<decimal>("AvgReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<int>("ClosedMarkets")
.HasColumnType("int");
b.Property<DateTime>("ComputedAt")
.HasColumnType("datetime(6)");
b.Property<decimal>("MedianLossReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal>("MedianWinReturnPct")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<decimal?>("ProfitFactor")
.HasPrecision(18, 4)
.HasColumnType("decimal(18,4)");
b.Property<int>("TraderId")
.HasColumnType("int");
b.Property<decimal>("WinRate")
.HasPrecision(8, 4)
.HasColumnType("decimal(8,4)");
b.Property<DateTime>("WindowEnd")
.HasColumnType("datetime(6)");
b.Property<DateTime>("WindowStart")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("TraderId", "WindowStart", "WindowEnd")
.IsUnique();
b.ToTable("TraderWindowMetrics");
});
modelBuilder.Entity("Predictalytics.Domain.Entities.WatchlistEntry", b =>
{
b.Property<int>("Id")
@@ -1049,6 +1191,28 @@ namespace Predictalytics.Infrastructure.Migrations
b.Navigation("Trader");
});
modelBuilder.Entity("Predictalytics.Domain.Entities.TraderTrait", b =>
{
b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
.WithMany("Traits")
.HasForeignKey("TraderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Trader");
});
modelBuilder.Entity("Predictalytics.Domain.Entities.TraderWindowMetrics", b =>
{
b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
.WithMany()
.HasForeignKey("TraderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Trader");
});
modelBuilder.Entity("Predictalytics.Domain.Entities.WatchlistEntry", b =>
{
b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
@@ -1089,6 +1253,8 @@ namespace Predictalytics.Infrastructure.Migrations
b.Navigation("Trades");
b.Navigation("Traits");
b.Navigation("WatchlistEntries");
});
#pragma warning restore 612, 618