Fix embedded-server endpoint drift, add Recalculate All Traders button

- 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>
This commit is contained in:
Richard
2026-07-10 17:58:10 +02:00
co-authored by Claude Fable 5
parent 1787422243
commit 850c47a404
8 changed files with 155 additions and 30 deletions
+11 -7
View File
@@ -346,22 +346,26 @@ async function loadTraders() {
}
async function loadWatchlist() {
const data = await api(`/api/watchlist`);
if (!data) return;
const tbody = document.getElementById('watchlistBody');
const data = await api(`/api/watchlist`);
if (!Array.isArray(data)) {
// api() returns null on HTTP errors — show it instead of a silently empty page.
tbody.innerHTML = '<tr><td colspan="8">⚠ Failed to load watchlist (see browser console / server log).</td></tr>';
return;
}
tbody.innerHTML = data.map(w => `
<tr>
<td class="trader-name">
<div class="avatar">${w.displayName.substring(0, 2).toUpperCase()}</div>
<div class="avatar">${(w.displayName || '?').substring(0, 2).toUpperCase()}</div>
<div>
<strong>${w.displayName}</strong><br>
<span style="font-size:12px; color:var(--text-secondary)">${w.platformUserId.substring(0, 8)}...</span>
<strong>${w.displayName || '?'}</strong><br>
<span style="font-size:12px; color:var(--text-secondary)">${(w.platformUserId || '').substring(0, 8)}...</span>
</div>
</td>
<td>${w.platform ?? 'Unknown'}</td>
<td>${Number(w.copytradingScore || 0).toFixed(1)}</td>
<td class="${w.winRate > 0.5 ? 'text-green' : 'text-red'}">${fmt.pct(w.winRate)}</td>
<td>${fmt.pct(w.winRate)}</td>
<td class="${w.totalPnl >= 0 ? 'text-green' : 'text-red'}">${fmt.pnl(w.totalPnl)}</td>
<td>${w.label || ''}</td>
<td>${new Date(w.createdAt).toLocaleDateString()}</td>