Initial commit: Predictalytics solution

Clean Architecture .NET 8 solution (Domain/Application/Infrastructure/Api/Worker/WinFormsHost)
for analyzing Polymarket traders for copytrading/strategy-replication candidates.

Includes EF Core InitialBaseline migration and DB secrets removed from source/config
in preparation for version control.
This commit is contained in:
Richard
2026-07-01 19:53:29 +02:00
commit afb251acfc
107 changed files with 9613 additions and 0 deletions
@@ -0,0 +1,157 @@
using Predictalytics.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Predictalytics.Infrastructure.Data;
public class AppDbContext : DbContext
{
public DbSet<Trader> Traders => Set<Trader>();
public DbSet<Trade> Trades => Set<Trade>();
public DbSet<Market> Markets => Set<Market>();
public DbSet<MarketOutcome> MarketOutcomes => Set<MarketOutcome>();
public DbSet<TraderScore> TraderScores => Set<TraderScore>();
public DbSet<WatchlistEntry> WatchlistEntries => Set<WatchlistEntry>();
public DbSet<Alert> Alerts => Set<Alert>();
public DbSet<PlatformConfig> PlatformConfigs => Set<PlatformConfig>();
public DbSet<TraderAnalytics> TraderAnalytics => Set<TraderAnalytics>();
public DbSet<MarketAnalytics> MarketAnalytics => Set<MarketAnalytics>();
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder mb)
{
// Trader
mb.Entity<Trader>(e =>
{
e.HasKey(t => t.Id);
e.HasIndex(t => new { t.Platform, t.PlatformUserId }).IsUnique();
e.Property(t => t.PlatformUserId).HasMaxLength(128);
e.Property(t => t.DisplayName).HasMaxLength(256);
e.Property(t => t.TotalPnl).HasPrecision(18, 4);
e.Property(t => t.WinRate).HasPrecision(8, 4);
e.HasOne(t => t.CurrentScore).WithOne(s => s.Trader)
.HasForeignKey<TraderScore>(s => s.TraderId).OnDelete(DeleteBehavior.Cascade);
});
// Trade
mb.Entity<Trade>(e =>
{
e.HasKey(t => t.Id);
e.HasIndex(t => new { t.Platform, t.PlatformTradeId }).IsUnique();
e.HasIndex(t => t.TraderId);
e.HasIndex(t => t.ExecutedAt);
e.HasIndex(t => t.AssetId);
e.HasIndex(t => t.DbMarketId);
// PlatformTradeId: legacy data up to 256 chars; new trades use shorter format
e.Property(t => t.PlatformTradeId).HasMaxLength(256);
// MarketId: Polymarket ConditionId is always 66 hex chars
e.Property(t => t.MarketId).HasMaxLength(66);
// AssetId: clobTokenId; Polymarket uses decimal strings up to 78 chars
e.Property(t => t.AssetId).HasMaxLength(80);
// Outcome: labels can be long (e.g. anime titles or sports match descriptions)
e.Property(t => t.Outcome).HasMaxLength(128);
// Price: 0.001.00 on prediction markets, 6 decimals sufficient
e.Property(t => t.Price).HasPrecision(10, 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);
// TransactionHash: 0x + 64 hex = 66 chars
e.Property(t => t.TransactionHash).HasMaxLength(66);
e.HasOne(t => t.Trader).WithMany(tr => tr.Trades).HasForeignKey(t => t.TraderId);
e.HasOne(t => t.MarketOutcome).WithMany().HasForeignKey(t => t.MarketOutcomeId)
.OnDelete(DeleteBehavior.SetNull);
e.HasOne(t => t.DbMarket).WithMany().HasForeignKey(t => t.DbMarketId)
.OnDelete(DeleteBehavior.SetNull);
});
// Market
mb.Entity<Market>(e =>
{
e.HasKey(m => m.Id);
e.HasIndex(m => new { m.Platform, m.PlatformMarketId }).IsUnique();
e.Property(m => m.PlatformMarketId).HasMaxLength(256);
e.Property(m => m.MarketSlug).HasMaxLength(512);
e.Property(m => m.EventSlug).HasMaxLength(512);
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.Volume).HasPrecision(18, 4);
e.Property(m => m.Liquidity).HasPrecision(18, 4);
e.HasMany(m => m.Outcomes).WithOne(o => o.Market).HasForeignKey(o => o.MarketId)
.OnDelete(DeleteBehavior.Cascade);
});
// MarketOutcome
mb.Entity<MarketOutcome>(e =>
{
e.HasKey(o => o.Id);
e.HasIndex(o => o.TokenId);
e.HasIndex(o => new { o.MarketId, o.OutcomeIndex }).IsUnique();
e.Property(o => o.Label).HasMaxLength(256);
e.Property(o => o.TokenId).HasMaxLength(256);
e.Property(o => o.CurrentPrice).HasPrecision(18, 8);
});
// TraderScore
mb.Entity<TraderScore>(e =>
{
e.HasKey(s => s.Id);
e.Property(s => s.ActivityScore).HasPrecision(8, 4);
e.Property(s => s.QualityScore).HasPrecision(8, 4);
e.Property(s => s.CombinedScore).HasPrecision(8, 4);
e.Property(s => s.VolumeScore).HasPrecision(8, 4);
e.Property(s => s.TimingScore).HasPrecision(8, 4);
});
// WatchlistEntry
mb.Entity<WatchlistEntry>(e =>
{
e.HasKey(w => w.Id);
e.HasIndex(w => w.TraderId).IsUnique();
e.Property(w => w.Label).HasMaxLength(256);
e.HasOne(w => w.Trader).WithMany(t => t.WatchlistEntries).HasForeignKey(w => w.TraderId);
});
// Alert
mb.Entity<Alert>(e =>
{
e.HasKey(a => a.Id);
e.HasIndex(a => a.CreatedAt);
e.Property(a => a.Title).HasMaxLength(512);
e.Property(a => a.Message).HasMaxLength(4096);
e.HasOne(a => a.Trader).WithMany().HasForeignKey(a => a.TraderId).OnDelete(DeleteBehavior.SetNull);
});
// PlatformConfig
mb.Entity<PlatformConfig>(e =>
{
e.HasKey(p => p.Id);
e.Property(p => p.Name).HasMaxLength(128);
e.Property(p => p.DisplayName).HasMaxLength(256);
e.Property(p => p.BaseUrl).HasMaxLength(1024);
});
// TraderAnalytics
mb.Entity<TraderAnalytics>(e =>
{
e.HasKey(a => a.TraderId);
e.Property(a => a.OverallPnL).HasPrecision(18, 4);
e.Property(a => a.OverallWinRate).HasPrecision(8, 4);
e.Property(a => a.PnL30d).HasPrecision(18, 4);
e.Property(a => a.WinRate30d).HasPrecision(8, 4);
e.Property(a => a.PnL7d).HasPrecision(18, 4);
e.Property(a => a.WinRate7d).HasPrecision(8, 4);
e.Property(a => a.PnL24h).HasPrecision(18, 4);
e.Property(a => a.WinRate24h).HasPrecision(8, 4);
});
// MarketAnalytics
mb.Entity<MarketAnalytics>(e =>
{
e.HasKey(a => a.MarketId);
e.Property(a => a.BotActivityScore).HasPrecision(8, 4);
e.Property(a => a.AverageTradeSize).HasPrecision(18, 4);
});
}
}