Fingerprint-snapshot foundation + strategy-drift calculator (#3/#5 groundwork)
TraderAnalytics is one row per trader, overwritten every recalculation, so there
is no history to detect strategy drift (#3) or edge fade (#5) against. Add the
missing time series:
- TraderFingerprintSnapshot entity (score, category concentration, conviction,
P50/P90 sizing, hold duration, trades/week, category-mix JSON, trait-set JSON)
+ migration AddFingerprintSnapshots (indexed by TraderId, CapturedAt).
- FingerprintSnapshotService (Infrastructure): CaptureDueAsync snapshots every
copy-relevant trader (CopytradingScore >= 40) at most ~once/day; wired into
ScoringAndAlertsWorker. GetDriftAsync reads latest-vs-baseline drift.
- FingerprintDriftCalculator (pure, Application): flags score drop, concentration
shift, sizing jump, conviction sign-flip, category-mix TVD, trait-set change.
- GET /api/traders/{id}/fingerprint-drift?baselineDays=14 read endpoint.
- Tests: drift calculator (4 scenarios) + capture service (copy-relevance,
throttle, drift read).
This is the shared foundation both #3 (drift alarm) and #5 (edge freshness) build on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
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>
@
Insider-Follow feed: auto-watchlist possible-insider wallets + new-trade alerts
Makes the existing possible_insider trait actionable:
- New AlertType.InsiderActivity (severity 4).
- AlertService.EvaluateInsiderWatchAsync (run from EvaluateAlertsAsync every
15 min): auto-adds every possible_insider wallet to the watchlist, then fires
one InsiderActivity alert per new trade one of them places. These wallets
trade rarely, so a single new trade is the strongest copy signal.
- Dedup + no history spam via WatchlistEntry.LastInsiderAlertAt (migration
AddWatchlistLastInsiderAlertAt); a freshly auto-added wallet is baselined at
AddedAt so backfilled trades never alert.
- Repo support: ITraderRepository.GetByTraitAsync, IWatchlistRepository.UpdateAsync.
- UI: distinct 👁 icon for insider alerts (💰 for large positions).
- Tests: auto-add-without-history-alert, alert-on-new-trade-with-dedup.
Migration auto-applies on startup (DependencyInjection.MigrateAsync).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
UI-U1/U2/U4/U5: dashboard showcases + server-side leaderboard sort
- U1: render the /api/traders/showcases sections on the dashboard as
clickable curated leaderboard cards (red_flags visually accented).
- U2: loadTraders() now sends the sort key to the API (real top-N),
take raised to 200; only name/platform stay client-sorted.
- U4: #tradersSort dropdown extended with calmar/conviction/profitfactor/
pnl30d; currentSort synced before building the query.
- U5: fix empty-state colspan 11 -> 10 (table has 10 columns).
Includes FIXPLAN-UI-Ranglisten.md documenting the plan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
UI-U3: server-side min-winrate/min-copyability filters
Add optional minWinRate/minCopyability params to GetTradersAsync so the
leaderboard min-value filters see the full pool instead of being applied
client-side to only the first page of results. Covered by a new
AnalyticsService test (three traders, both filters).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
- GET /api/traders/showcases: curated dashboard sections (copy-ready, smooth
operators, rising stars, high conviction, specialists, insider watch, red
flags), each encoding a selection funnel over the persisted analytics/traits.
Pure ShowcaseBuilder holds the ranking logic (+6 unit tests).
- GET /api/traders?sort=: leaderboard sort keys (pnl, pnl30d, winrate,
copytrading, calmar, conviction, profitfactor) over the loaded set.
Read-only, persisted-data-only (public-tier safe). 76 tests, 1 skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two deeper analyses, both pure from existing data (no new API cost):
- ConvictionEdgePct: return% of the biggest-bet third minus the smallest-bet
third of closed markets. Positive => sizing carries information (copy
size-weighted); negative => overbets losers (red flag). CalculateMarketWinRates
now emits per-market (invested, returnPct) pairs consumed by
StrategyMetricsCalculator.ComputeConvictionEdge.
- CategoryConcentration: Herfindahl index of category volume shares
(specialist vs generalist), from the category-performance dict.
Stored on TraderAnalytics, exposed on TraderDetailDto. Migration
AddStrategyFingerprintMetrics. +7 unit tests (70 total, 1 skip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New RiskMetricsCalculator (pure) derives max drawdown (USD), daily-PnL volatility
and longest losing streak from the trader's TraderDailySnapshot equity curve; the
PnL engine computes them each recalc and stores them on TraderAnalytics (+ computed
ReturnOverMaxDrawdown, Calmar-like). Two traders with identical final PnL but a
smoother path are very differently copyable — this captures that. Exposed on
TraderDetailDto. Migration AddRiskAdjustedMetrics. +5 unit tests (63 total, 1 skip).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Aggregated ingest tier bucketed trades by hour incl. the current, still-growing
hour, then upserted via ON DUPLICATE KEY UPDATE (mutable rows). The PnL engine
checkpoints positions by row Id, so a bucket that keeps growing after being applied
had its later growth silently skipped (Id <= LastAppliedTradeId).
Extract the duplicated aggregation logic from PollingWorker + TradeHistoryWorker into
TradeAggregation.AggregateCompletedHours, which only aggregates COMPLETED hours; the
current hour is deferred (re-fetched next cycle) so every persisted aggregate is
immutable. +3 unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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
- TraderDetailDto now exposes PnL7d/WinRate7d/PnL24h/WinRate24h and
CurrentBalance — the engine has computed these all along but the API
never delivered them.
- Removed POST /api/dev/repair-db: its raw SQL referenced non-existent
columns/tables (Trades.Type/Payout, Traders.LastPositionsUpdatedAt,
table "Jobs") and would have deleted ALL TraderPositions including
pruned-history conserves. The supported repair path is the WinForms
"Recalculate All Traders" action.
- Swagger tags for Jobs and Dev groups; full endpoint reference in
docs/API.md (kept generic — external consumers like PolyTrader adapt
to our API, not vice versa).
- FIXPLAN Teil E: master-selection gap analysis as generic extensions
(profile endpoint, out-of-sample window metrics, price-band profile
with per-band win rate, stop-loss ratio, copyability aggregates with
category fees, correlation endpoint, martingale trait).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Win rate alone hides the payoff profile: an arb grinder (95% wins at +2%)
and a longshot holder (25% wins at +400%) need entirely different copy
strategies. D2c adds median+avg return of won AND lost closed markets,
profit factor, UI cards, and two derived traits (thin_margin_wins,
high_payoff_wins). Median leads (outlier-robust), the loss side is
mandatory — high win rates with rare total losses (resolution farming)
are exactly what the win rate alone obscures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Trait filtering is now a binding requirement: trait query parameter on
GET /api/traders (EXISTS-based), GET /api/traits with counts, dropdown
plus clickable chips in the UI.
- New trait possible_insider (D2b): detection via market-surprise
likelihood under market prices (won longshots count, won favorites do
not), with base filters (rare, large stakes, resolution farmers
excluded) and a v2 roadmap (buys-before-news timing, one-shot wallets,
topic concentration, urgency/slippage, niche markets).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- MarketCategoryMapper: classify from question text (the Gamma /markets
endpoint delivers neither category nor event tags, so on-demand markets
had no signal at all), match short tokens on word boundaries ("eth" no
longer hits inside "whether", "pop" not inside "popular"), widen the
keyword lists across all categories.
- UpdateMarketFields: never overwrite a tag-derived category with an
uninformative "Other" from the on-demand path.
- PositionPnLEngine: sync Trader.TotalTrades to the actual replayed row
count — the worker-side increment counters drift (INSERT IGNORE,
deletions, historic imports) and produced Trades30d > TotalTrades.
- Tests: 14 new (mapper classification + word-boundary regression,
TotalTrades sync + Trades30d invariant, category update guard via
SQLite) — suite now 32 green + 1 skip.
- FIXPLAN Teil D for the larger rebuilds (AggregatedCount column,
TraderTraits heuristics, IngestMode tiering for ultra-HF traders).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reconciliation worker resets position checkpoints via bulk UPDATE while
the analytics worker saves recalculated positions for the same trader —
MySQL then rejects the stale save ("Record has changed since last read").
The engine run is now retried once with a fresh context (re-reading the
concurrent change); if it still collides, it logs a warning and lets the
next cycle pick the trader up again (LastAnalyzedAt stays unset).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Shared ApiConfiguration.MapPredictalyticsEndpoints() used by both the
standalone API and the embedded WinForms Kestrel server: the embedded
host was missing /api/watchlist and /api/dev (empty watchlist page),
the standalone host was missing /api/search.
- New Development menu action "Recalculate All Traders": backfills
ResolutionOutcome from snapped outcome prices, wipes derived analytics
(snapshots, category stats, rebuildable positions), zeroes aggregates
and marks every trader for re-analysis. Raw trades untouched.
- Static files now served with Cache-Control: no-cache in both hosts,
plus ?v= cache-buster on app.js/style.css (stale browser JS masked
earlier UI fixes).
- Watchlist hardened: endpoint null-safe, real Analytics.CopytradingScore,
Platform field; repository includes Trader.Analytics; page shows a
visible error row instead of staying silently blank.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Added B6 to UMSETZUNGSPLAN.md: unbounded MarketSyncWorker re-sync of all
closed markets, watchlisted traders not exempt from auto-cleanup deletion,
full trade-history load in GetKnownPlatformTradeIdsAsync, a race condition
in MarketRepository.AddOrUpdateAsync under concurrent access, and an
early-break assumption in TradeHistoryWorker that isn't applied consistently.
No functional code changed yet — these are earmarked for Phase 2 (B3-adjacent).
- Design-time AppDbContextFactory now builds its connection string via
MySqlConnectionStringBuilder from PREDICTALYTICS_DB_* env vars instead of a
hardcoded local default, so no secret needs to live in source/config to run
migrations against any target database.
- InitialBaseline migration applied end-to-end against a fresh dev MySQL DB
and confirmed via `dotnet ef migrations list`.
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.