@
Rework insider feed: system-level InsiderWatch + dedicated view (no watchlist writes) Watchlists will become per-user once the product is offered commercially, so the system must not auto-add/remove traders there. Decouple insider tracking entirely: - New system-owned entity InsiderWatch (TraderId unique, FirstDetectedAt, LastAlertedTradeAt) + IInsiderWatchRepository; migration AddInsiderWatch. - AlertService.EvaluateInsiderWatchAsync now maintains InsiderWatch (not the watchlist): registers each possible_insider wallet, seeds the high-water mark at detection time, and fires one InsiderActivity alert per new trade. Dedup via LastAlertedTradeAt. - Dedicated "Insider" view: GET /api/traders/insiders + InsiderDto + a new Insider-Radar page (sorted by market-surprise). Read-only, separate from watchlist. - Revert the WatchlistEntry.LastInsiderAlertAt field + its migration (unapplied); drop the now-unused IWatchlistRepository.UpdateAsync. - Tests updated to assert InsiderWatch registry (and that no WatchlistEntry is created). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
This commit is contained in:
@@ -23,18 +23,18 @@ public class AlertServiceTests
|
||||
new AlertRepository(db),
|
||||
new TradeRepository(db, NullLogger<TradeRepository>.Instance),
|
||||
new TraderRepository(db),
|
||||
new WatchlistRepository(db),
|
||||
new InsiderWatchRepository(db),
|
||||
NullLogger<AlertService>.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));
|
||||
|
||||
Reference in New Issue
Block a user