diff --git a/src/Predictalytics.Api/wwwroot/js/app.js b/src/Predictalytics.Api/wwwroot/js/app.js
index ba7c2ed..c909b25 100644
--- a/src/Predictalytics.Api/wwwroot/js/app.js
+++ b/src/Predictalytics.Api/wwwroot/js/app.js
@@ -28,6 +28,7 @@ document.querySelectorAll('.nav-item[data-page]').forEach(item => {
if (page === 'markets') loadMarkets();
if (page === 'jobs') loadJobs();
if (page === 'watchlist') loadWatchlist();
+ if (page === 'insiders') loadInsiders();
});
});
@@ -514,6 +515,32 @@ async function loadTraders() {
`).join('');
}
+async function loadInsiders() {
+ const tbody = document.getElementById('insidersBody');
+ const data = await api('/api/traders/insiders');
+ if (!Array.isArray(data)) {
+ tbody.innerHTML = '
| ⚠ Insider-Liste konnte nicht geladen werden (siehe Konsole / Server-Log). |
';
+ return;
+ }
+ if (!data.length) {
+ tbody.innerHTML = '
Noch keine possible-insider Wallets erkannt. |
';
+ return;
+ }
+ tbody.innerHTML = data.map(t => `
+
+ | ${t.displayName} |
+ ${t.platform} |
+ ${Number(t.surpriseValue).toFixed(1)} |
+ ${Number(t.combinedScore).toFixed(1)} |
+ ${Number(t.copytradingCopyabilityScore || 0).toFixed(1)} |
+ ${fmt.pct(t.winRate)} |
+ ${fmt.pnl(t.totalPnl)} |
+ ${fmt.num(t.totalTrades)} |
+ ${t.firstDetectedAt ? fmt.time(t.firstDetectedAt) : '—'} |
+
+ `).join('');
+}
+
async function loadWatchlist() {
const tbody = document.getElementById('watchlistBody');
const data = await api(`/api/watchlist`);
diff --git a/src/Predictalytics.Application.Tests/Services/AlertServiceTests.cs b/src/Predictalytics.Application.Tests/Services/AlertServiceTests.cs
index 6d04c29..42d9d9d 100644
--- a/src/Predictalytics.Application.Tests/Services/AlertServiceTests.cs
+++ b/src/Predictalytics.Application.Tests/Services/AlertServiceTests.cs
@@ -23,18 +23,18 @@ public class AlertServiceTests
new AlertRepository(db),
new TradeRepository(db, NullLogger
.Instance),
new TraderRepository(db),
- new WatchlistRepository(db),
+ new InsiderWatchRepository(db),
NullLogger.Instance);
[Fact]
- public async Task InsiderWatch_AutoAddsInsider_WithoutAlertingOnHistory()
+ public async Task InsiderWatch_RegistersInsider_WithoutAlertingOnHistory()
{
using var db = CreateDbContext();
var trader = new Trader { Id = 1, PlatformUserId = "0xI", DisplayName = "QuietWhale" };
trader.Traits.Add(new TraderTrait { TraderId = 1, Trait = "possible_insider", Value = 4.2m });
db.Traders.Add(trader);
- // A historical trade (predates the auto-add) must NOT produce an alert.
+ // A historical trade (predates detection) must NOT produce an alert.
db.Trades.Add(new Trade
{
Id = 10, TraderId = 1, DbMarketId = 100, Platform = PlatformType.Polymarket,
@@ -46,7 +46,9 @@ public class AlertServiceTests
var svc = CreateService(db);
await svc.EvaluateInsiderWatchAsync();
- Assert.Single(db.WatchlistEntries.Where(w => w.TraderId == 1));
+ // Registered in the system-level insider registry, NOT the user watchlist.
+ Assert.Single(db.InsiderWatches.Where(i => i.TraderId == 1));
+ Assert.Empty(db.WatchlistEntries);
Assert.Empty(db.Alerts.Where(a => a.Type == AlertType.InsiderActivity));
}
@@ -58,13 +60,14 @@ public class AlertServiceTests
var trader = new Trader { Id = 2, PlatformUserId = "0xJ", DisplayName = "Insider2" };
trader.Traits.Add(new TraderTrait { TraderId = 2, Trait = "possible_insider", Value = 5m });
db.Traders.Add(trader);
- // Already watched, added an hour ago.
- db.WatchlistEntries.Add(new WatchlistEntry
+ // Already registered an hour ago; high-water mark predates the new trade.
+ db.InsiderWatches.Add(new InsiderWatch
{
- Id = 5, TraderId = 2, Label = "watched", AlertsEnabled = true,
- AddedAt = DateTime.UtcNow.AddHours(-1)
+ Id = 3, TraderId = 2,
+ FirstDetectedAt = DateTime.UtcNow.AddHours(-1),
+ LastAlertedTradeAt = DateTime.UtcNow.AddHours(-1)
});
- // A trade placed AFTER the entry was added -> should alert exactly once.
+ // A trade placed AFTER the high-water mark -> should alert exactly once.
db.Trades.Add(new Trade
{
Id = 20, TraderId = 2, DbMarketId = 200, Platform = PlatformType.Polymarket,
@@ -82,8 +85,8 @@ public class AlertServiceTests
Assert.Equal(2, alerts[0].TraderId);
// High-water mark advanced; a second run must not re-alert.
- var entry = db.WatchlistEntries.First(w => w.Id == 5);
- Assert.NotNull(entry.LastInsiderAlertAt);
+ var watch = db.InsiderWatches.First(i => i.Id == 3);
+ Assert.True(watch.LastAlertedTradeAt > DateTime.UtcNow.AddMinutes(-25));
await svc.EvaluateInsiderWatchAsync();
Assert.Single(db.Alerts.Where(a => a.Type == AlertType.InsiderActivity));
diff --git a/src/Predictalytics.Application/DTOs/TraderDto.cs b/src/Predictalytics.Application/DTOs/TraderDto.cs
index a98666a..173798e 100644
--- a/src/Predictalytics.Application/DTOs/TraderDto.cs
+++ b/src/Predictalytics.Application/DTOs/TraderDto.cs
@@ -147,3 +147,19 @@ public record TraderCorrelationDto(
decimal IntersectionRatioB,
decimal AgreementRatio
);
+
+/// One row of the dedicated Insider view (system-level, not a user watchlist).
+public record InsiderDto(
+ int Id,
+ string Platform,
+ string DisplayName,
+ string PlatformUserId,
+ decimal CombinedScore,
+ decimal CopytradingCopyabilityScore,
+ decimal WinRate,
+ decimal TotalPnl,
+ int TotalTrades,
+ decimal SurpriseValue,
+ DateTime? FirstDetectedAt,
+ DateTime? LastPolledAt
+);
diff --git a/src/Predictalytics.Application/Services/AlertService.cs b/src/Predictalytics.Application/Services/AlertService.cs
index 7e6e5cb..fa13043 100644
--- a/src/Predictalytics.Application/Services/AlertService.cs
+++ b/src/Predictalytics.Application/Services/AlertService.cs
@@ -15,7 +15,7 @@ public class AlertService : IAlertService
private readonly IAlertRepository _alertRepo;
private readonly ITradeRepository _tradeRepo;
private readonly ITraderRepository _traderRepo;
- private readonly IWatchlistRepository _watchlistRepo;
+ private readonly IInsiderWatchRepository _insiderRepo;
private readonly ILogger _logger;
// Alert thresholds (configurable in future)
@@ -28,13 +28,13 @@ public class AlertService : IAlertService
IAlertRepository alertRepo,
ITradeRepository tradeRepo,
ITraderRepository traderRepo,
- IWatchlistRepository watchlistRepo,
+ IInsiderWatchRepository insiderRepo,
ILogger logger)
{
_alertRepo = alertRepo;
_tradeRepo = tradeRepo;
_traderRepo = traderRepo;
- _watchlistRepo = watchlistRepo;
+ _insiderRepo = insiderRepo;
_logger = logger;
}
@@ -70,11 +70,12 @@ public class AlertService : IAlertService
}
///
- /// Insider-Follow feed: keeps every possible_insider wallet on the watchlist and fires a
- /// high-severity alert for each new trade one of them places. These wallets trade rarely, so a
- /// single new trade is the strongest copy signal we have. Dedup is via
- /// ; a freshly auto-added wallet is baselined at
- /// its so historical trades never trigger a backlog of alerts.
+ /// Insider-Follow feed: maintains the system-level registry for every
+ /// possible_insider wallet and fires a high-severity alert for each new trade one of them
+ /// places. These wallets trade rarely, so a single new trade is the strongest copy signal we have.
+ /// This is deliberately decoupled from (per-user) watchlists. Dedup is via
+ /// ; a freshly detected wallet is seeded at detection
+ /// time so historical (backfilled) trades never trigger a backlog of alerts.
///
public async Task EvaluateInsiderWatchAsync(CancellationToken ct = default)
{
@@ -82,27 +83,24 @@ public class AlertService : IAlertService
foreach (var trader in insiders)
{
- var entry = trader.WatchlistEntries.FirstOrDefault();
+ var watch = await _insiderRepo.GetByTraderIdAsync(trader.Id, ct);
- // Auto-add newly detected insiders; baseline at now so we don't alert on their history.
- if (entry == null)
+ // Register newly detected insiders; seed the high-water mark at "now" so we don't alert
+ // on their history. No alerts on the detection cycle itself.
+ if (watch == null)
{
- await _watchlistRepo.AddAsync(new WatchlistEntry
+ await _insiderRepo.AddAsync(new InsiderWatch
{
TraderId = trader.Id,
- Label = "Auto: Possible Insider",
- Notes = "Automatisch aufgenommen (possible_insider-Trait).",
- AlertsEnabled = true
+ FirstDetectedAt = DateTime.UtcNow,
+ LastAlertedTradeAt = DateTime.UtcNow
}, ct);
- _logger.LogInformation("👁 Insider-Watch: auto-added {Trader} to watchlist", trader.DisplayName);
+ _logger.LogInformation("👁 Insider-Watch: detected new possible-insider {Trader}", trader.DisplayName);
continue;
}
- if (!entry.AlertsEnabled) continue;
-
- var since = entry.LastInsiderAlertAt ?? entry.AddedAt;
var recent = await _tradeRepo.GetByTraderIdAsync(trader.Id, 0, 50, ct);
- var newTrades = recent.Where(t => t.ExecutedAt > since).OrderBy(t => t.ExecutedAt).ToList();
+ var newTrades = recent.Where(t => t.ExecutedAt > watch.LastAlertedTradeAt).OrderBy(t => t.ExecutedAt).ToList();
if (newTrades.Count == 0) continue;
foreach (var trade in newTrades)
@@ -121,8 +119,8 @@ public class AlertService : IAlertService
}, ct);
}
- entry.LastInsiderAlertAt = newTrades.Max(t => t.ExecutedAt);
- await _watchlistRepo.UpdateAsync(entry, ct);
+ watch.LastAlertedTradeAt = newTrades.Max(t => t.ExecutedAt);
+ await _insiderRepo.UpdateAsync(watch, ct);
}
}
diff --git a/src/Predictalytics.Domain/Entities/InsiderWatch.cs b/src/Predictalytics.Domain/Entities/InsiderWatch.cs
new file mode 100644
index 0000000..3a3fd84
--- /dev/null
+++ b/src/Predictalytics.Domain/Entities/InsiderWatch.cs
@@ -0,0 +1,27 @@
+namespace Predictalytics.Domain.Entities;
+
+///
+/// System-level registry of wallets flagged with the possible_insider trait. This is a
+/// global, system-owned list — deliberately separate from per-user
+/// watchlists (which will become user-specific once the product is offered commercially). It backs
+/// the dedicated "Insider" view and carries the high-water mark used to de-duplicate activity alerts.
+///
+public class InsiderWatch
+{
+ public int Id { get; set; }
+
+ /// Foreign key to the flagged trader (unique — one row per trader).
+ public int TraderId { get; set; }
+
+ /// When this wallet was first detected as a possible insider.
+ public DateTime FirstDetectedAt { get; set; } = DateTime.UtcNow;
+
+ ///
+ /// High-water mark for activity alerts: the ExecutedAt of the newest trade already alerted on.
+ /// Seeded at detection time so historical (backfilled) trades never trigger a backlog of alerts.
+ ///
+ public DateTime LastAlertedTradeAt { get; set; }
+
+ // Navigation
+ public Trader Trader { get; set; } = null!;
+}
diff --git a/src/Predictalytics.Domain/Entities/WatchlistEntry.cs b/src/Predictalytics.Domain/Entities/WatchlistEntry.cs
index 246e583..44aaf07 100644
--- a/src/Predictalytics.Domain/Entities/WatchlistEntry.cs
+++ b/src/Predictalytics.Domain/Entities/WatchlistEntry.cs
@@ -22,13 +22,6 @@ public class WatchlistEntry
/// When this entry was added to the watchlist.
public DateTime AddedAt { get; set; } = DateTime.UtcNow;
- ///
- /// High-water mark for insider-activity alerts: the ExecutedAt of the newest trade already
- /// alerted on. Null until the first insider alert fires; new-trade detection uses
- /// as the baseline so we never alert on backfilled history.
- ///
- public DateTime? LastInsiderAlertAt { get; set; }
-
// Navigation
public Trader Trader { get; set; } = null!;
}
diff --git a/src/Predictalytics.Domain/Interfaces/IInsiderWatchRepository.cs b/src/Predictalytics.Domain/Interfaces/IInsiderWatchRepository.cs
new file mode 100644
index 0000000..0f3cf1b
--- /dev/null
+++ b/src/Predictalytics.Domain/Interfaces/IInsiderWatchRepository.cs
@@ -0,0 +1,11 @@
+using Predictalytics.Domain.Entities;
+
+namespace Predictalytics.Domain.Interfaces;
+
+public interface IInsiderWatchRepository
+{
+ Task> GetAllAsync(CancellationToken ct = default);
+ Task GetByTraderIdAsync(int traderId, CancellationToken ct = default);
+ Task AddAsync(InsiderWatch entry, CancellationToken ct = default);
+ Task UpdateAsync(InsiderWatch entry, CancellationToken ct = default);
+}
diff --git a/src/Predictalytics.Domain/Interfaces/IWatchlistRepository.cs b/src/Predictalytics.Domain/Interfaces/IWatchlistRepository.cs
index 155c4d2..f9af932 100644
--- a/src/Predictalytics.Domain/Interfaces/IWatchlistRepository.cs
+++ b/src/Predictalytics.Domain/Interfaces/IWatchlistRepository.cs
@@ -7,6 +7,5 @@ public interface IWatchlistRepository
Task> GetAllAsync(CancellationToken ct = default);
Task GetByTraderIdAsync(int traderId, CancellationToken ct = default);
Task AddAsync(WatchlistEntry entry, CancellationToken ct = default);
- Task UpdateAsync(WatchlistEntry entry, CancellationToken ct = default);
Task RemoveAsync(int id, CancellationToken ct = default);
}
diff --git a/src/Predictalytics.Infrastructure/Data/AppDbContext.cs b/src/Predictalytics.Infrastructure/Data/AppDbContext.cs
index 25b8d49..6f85412 100644
--- a/src/Predictalytics.Infrastructure/Data/AppDbContext.cs
+++ b/src/Predictalytics.Infrastructure/Data/AppDbContext.cs
@@ -25,6 +25,7 @@ public class AppDbContext : DbContext
public DbSet BackgroundJobs => Set();
public DbSet TraderTraits => Set();
public DbSet TraderWindowMetrics => Set();
+ public DbSet InsiderWatches => Set();
private readonly bool _isReadOnly;
@@ -207,6 +208,14 @@ public class AppDbContext : DbContext
e.HasOne(w => w.Trader).WithMany(t => t.WatchlistEntries).HasForeignKey(w => w.TraderId);
});
+ // InsiderWatch (system-level, one row per flagged trader)
+ mb.Entity(e =>
+ {
+ e.HasKey(i => i.Id);
+ e.HasIndex(i => i.TraderId).IsUnique();
+ e.HasOne(i => i.Trader).WithMany().HasForeignKey(i => i.TraderId);
+ });
+
// Alert
mb.Entity(e =>
{
diff --git a/src/Predictalytics.Infrastructure/Data/Repositories/InsiderWatchRepository.cs b/src/Predictalytics.Infrastructure/Data/Repositories/InsiderWatchRepository.cs
new file mode 100644
index 0000000..9a5f894
--- /dev/null
+++ b/src/Predictalytics.Infrastructure/Data/Repositories/InsiderWatchRepository.cs
@@ -0,0 +1,23 @@
+using Predictalytics.Domain.Entities;
+using Predictalytics.Domain.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace Predictalytics.Infrastructure.Data.Repositories;
+
+public class InsiderWatchRepository : IInsiderWatchRepository
+{
+ private readonly AppDbContext _db;
+ public InsiderWatchRepository(AppDbContext db) => _db = db;
+
+ public async Task> GetAllAsync(CancellationToken ct = default)
+ => await _db.InsiderWatches.ToListAsync(ct);
+
+ public async Task GetByTraderIdAsync(int traderId, CancellationToken ct = default)
+ => await _db.InsiderWatches.FirstOrDefaultAsync(i => i.TraderId == traderId, ct);
+
+ public async Task AddAsync(InsiderWatch entry, CancellationToken ct = default)
+ { _db.InsiderWatches.Add(entry); await _db.SaveChangesAsync(ct); }
+
+ public async Task UpdateAsync(InsiderWatch entry, CancellationToken ct = default)
+ { _db.InsiderWatches.Update(entry); await _db.SaveChangesAsync(ct); }
+}
diff --git a/src/Predictalytics.Infrastructure/Data/Repositories/TraderRepository.cs b/src/Predictalytics.Infrastructure/Data/Repositories/TraderRepository.cs
index 56cc9d4..39b2989 100644
--- a/src/Predictalytics.Infrastructure/Data/Repositories/TraderRepository.cs
+++ b/src/Predictalytics.Infrastructure/Data/Repositories/TraderRepository.cs
@@ -48,7 +48,8 @@ public class TraderRepository : ITraderRepository
public async Task> GetByTraitAsync(string trait, CancellationToken ct = default)
=> await _db.Traders
.Include(t => t.Traits)
- .Include(t => t.WatchlistEntries)
+ .Include(t => t.Analytics)
+ .Include(t => t.CurrentScore)
.Where(t => t.Traits.Any(tr => tr.Trait == trait))
.ToListAsync(ct);
diff --git a/src/Predictalytics.Infrastructure/Data/Repositories/WatchlistRepository.cs b/src/Predictalytics.Infrastructure/Data/Repositories/WatchlistRepository.cs
index 24433a5..0b93763 100644
--- a/src/Predictalytics.Infrastructure/Data/Repositories/WatchlistRepository.cs
+++ b/src/Predictalytics.Infrastructure/Data/Repositories/WatchlistRepository.cs
@@ -21,9 +21,6 @@ public class WatchlistRepository : IWatchlistRepository
public async Task AddAsync(WatchlistEntry entry, CancellationToken ct = default)
{ _db.WatchlistEntries.Add(entry); await _db.SaveChangesAsync(ct); }
- public async Task UpdateAsync(WatchlistEntry entry, CancellationToken ct = default)
- { _db.WatchlistEntries.Update(entry); await _db.SaveChangesAsync(ct); }
-
public async Task RemoveAsync(int id, CancellationToken ct = default)
{
var e = await _db.WatchlistEntries.FindAsync(new object[] { id }, ct);
diff --git a/src/Predictalytics.Infrastructure/DependencyInjection.cs b/src/Predictalytics.Infrastructure/DependencyInjection.cs
index c5a59ac..c5ffebb 100644
--- a/src/Predictalytics.Infrastructure/DependencyInjection.cs
+++ b/src/Predictalytics.Infrastructure/DependencyInjection.cs
@@ -66,6 +66,7 @@ public static class DependencyInjection
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/src/Predictalytics.Infrastructure/Migrations/20260723164157_AddWatchlistLastInsiderAlertAt.cs b/src/Predictalytics.Infrastructure/Migrations/20260723164157_AddWatchlistLastInsiderAlertAt.cs
deleted file mode 100644
index be26ca8..0000000
--- a/src/Predictalytics.Infrastructure/Migrations/20260723164157_AddWatchlistLastInsiderAlertAt.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Predictalytics.Infrastructure.Migrations
-{
- ///
- public partial class AddWatchlistLastInsiderAlertAt : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AddColumn(
- name: "LastInsiderAlertAt",
- table: "WatchlistEntries",
- type: "datetime(6)",
- nullable: true);
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropColumn(
- name: "LastInsiderAlertAt",
- table: "WatchlistEntries");
- }
- }
-}
diff --git a/src/Predictalytics.Infrastructure/Migrations/20260723164157_AddWatchlistLastInsiderAlertAt.Designer.cs b/src/Predictalytics.Infrastructure/Migrations/20260723184034_AddInsiderWatch.Designer.cs
similarity index 97%
rename from src/Predictalytics.Infrastructure/Migrations/20260723164157_AddWatchlistLastInsiderAlertAt.Designer.cs
rename to src/Predictalytics.Infrastructure/Migrations/20260723184034_AddInsiderWatch.Designer.cs
index 9c280f7..bbddd53 100644
--- a/src/Predictalytics.Infrastructure/Migrations/20260723164157_AddWatchlistLastInsiderAlertAt.Designer.cs
+++ b/src/Predictalytics.Infrastructure/Migrations/20260723184034_AddInsiderWatch.Designer.cs
@@ -12,8 +12,8 @@ using Predictalytics.Infrastructure.Data;
namespace Predictalytics.Infrastructure.Migrations
{
[DbContext(typeof(AppDbContext))]
- [Migration("20260723164157_AddWatchlistLastInsiderAlertAt")]
- partial class AddWatchlistLastInsiderAlertAt
+ [Migration("20260723184034_AddInsiderWatch")]
+ partial class AddInsiderWatch
{
///
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -181,6 +181,31 @@ namespace Predictalytics.Infrastructure.Migrations
b.ToTable("Events");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.InsiderWatch", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("FirstDetectedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LastAlertedTradeAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TraderId")
+ .IsUnique();
+
+ b.ToTable("InsiderWatches");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
{
b.Property("Id")
@@ -1041,9 +1066,6 @@ namespace Predictalytics.Infrastructure.Migrations
.HasMaxLength(256)
.HasColumnType("varchar(256)");
- b.Property("LastInsiderAlertAt")
- .HasColumnType("datetime(6)");
-
b.Property("Notes")
.HasColumnType("longtext");
@@ -1078,6 +1100,17 @@ namespace Predictalytics.Infrastructure.Migrations
b.Navigation("Trader");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.InsiderWatch", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany()
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trader");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
{
b.HasOne("Predictalytics.Domain.Entities.Event", "Event")
diff --git a/src/Predictalytics.Infrastructure/Migrations/20260723184034_AddInsiderWatch.cs b/src/Predictalytics.Infrastructure/Migrations/20260723184034_AddInsiderWatch.cs
new file mode 100644
index 0000000..65e8210
--- /dev/null
+++ b/src/Predictalytics.Infrastructure/Migrations/20260723184034_AddInsiderWatch.cs
@@ -0,0 +1,51 @@
+using System;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Predictalytics.Infrastructure.Migrations
+{
+ ///
+ public partial class AddInsiderWatch : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "InsiderWatches",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ TraderId = table.Column(type: "int", nullable: false),
+ FirstDetectedAt = table.Column(type: "datetime(6)", nullable: false),
+ LastAlertedTradeAt = table.Column(type: "datetime(6)", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_InsiderWatches", x => x.Id);
+ table.ForeignKey(
+ name: "FK_InsiderWatches_Traders_TraderId",
+ column: x => x.TraderId,
+ principalTable: "Traders",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_InsiderWatches_TraderId",
+ table: "InsiderWatches",
+ column: "TraderId",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "InsiderWatches");
+ }
+ }
+}
diff --git a/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
index 76f9dce..05dd5b1 100644
--- a/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
+++ b/src/Predictalytics.Infrastructure/Migrations/AppDbContextModelSnapshot.cs
@@ -178,6 +178,31 @@ namespace Predictalytics.Infrastructure.Migrations
b.ToTable("Events");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.InsiderWatch", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id"));
+
+ b.Property("FirstDetectedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("LastAlertedTradeAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("TraderId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TraderId")
+ .IsUnique();
+
+ b.ToTable("InsiderWatches");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
{
b.Property("Id")
@@ -1038,9 +1063,6 @@ namespace Predictalytics.Infrastructure.Migrations
.HasMaxLength(256)
.HasColumnType("varchar(256)");
- b.Property("LastInsiderAlertAt")
- .HasColumnType("datetime(6)");
-
b.Property("Notes")
.HasColumnType("longtext");
@@ -1075,6 +1097,17 @@ namespace Predictalytics.Infrastructure.Migrations
b.Navigation("Trader");
});
+ modelBuilder.Entity("Predictalytics.Domain.Entities.InsiderWatch", b =>
+ {
+ b.HasOne("Predictalytics.Domain.Entities.Trader", "Trader")
+ .WithMany()
+ .HasForeignKey("TraderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Trader");
+ });
+
modelBuilder.Entity("Predictalytics.Domain.Entities.Market", b =>
{
b.HasOne("Predictalytics.Domain.Entities.Event", "Event")