feat: Implement copytrading analytics and update WebUI

- Add v4 Tape-based CopytradingEstimator (Clustering, MAE, Tape Fills)
- Add CopytradingBacktestHarness (Walk-Forward testing, Shrinkage, LCB ranking)
- Add EF Core Migrations for FollowerFillPrice and Scoring fields
- Update WebUI: Fix Trader Detail tab layout blowout
- Update WebUI: Redesign Trader Detail menubar
- Update WebUI: Display separate Quality and Copyability metric cards
- Update WebUI: Add 'Highly Copyable' filter to Trader List
This commit is contained in:
Richard
2026-07-06 11:55:38 +02:00
parent c0c598b86a
commit a564c016bb
21 changed files with 1705 additions and 47 deletions
+12 -2
View File
@@ -275,9 +275,15 @@ async function loadDashboard() {
async function loadTraders() {
let url = '/api/traders?skip=0&take=100';
if (currentPlatform !== 'All') url += `&platform=${currentPlatform}`;
const hcCheckbox = document.getElementById('chk-highly-copyable');
if (hcCheckbox && hcCheckbox.checked) {
url += `&highlyCopyable=true`;
}
let data = await api(url);
const tbody = document.getElementById('allTradersBody');
if (!data || !data.length) { tbody.innerHTML = '<tr><td colspan="9"><div class="empty-state"><p>No traders tracked yet.</p></div></td></tr>'; return; }
if (!data || !data.length) { tbody.innerHTML = '<tr><td colspan="11"><div class="empty-state"><p>No traders tracked yet.</p></div></td></tr>'; return; }
// Sorting
if (currentSort === 'score') data.sort((a, b) => b.combinedScore - a.combinedScore);
@@ -287,9 +293,11 @@ async function loadTraders() {
tbody.innerHTML = data.map((t, i) => `
<tr>
<td>${i + 1}</td>
<td><strong><a href="#" onclick="viewTrader(${t.id}); return false;" style="color:var(--primary);text-decoration:none;">${t.displayName}</a></strong></td>
<td><strong><a href="#" onclick="viewTrader(${t.id}); return false;" style="color:var(--primary);text-decoration:none;">${t.displayName}</a></strong>${t.isSuspectedBot ? ' 🤖' : ''}</td>
<td>${t.platform}</td>
<td><strong>${Number(t.combinedScore).toFixed(1)}</strong></td>
<td>${Number(t.copytradingQualityScore || 0).toFixed(1)}</td>
<td>${Number(t.copytradingCopyabilityScore || 0).toFixed(1)}</td>
<td>${fmt.pct(t.winRate)}</td>
<td>${fmt.pnl(t.totalPnl)}</td>
<td>${fmt.tier(t.tier)}</td>
@@ -358,6 +366,8 @@ async function viewTrader(id) {
document.getElementById('td-pnl').innerHTML = fmt.pnl(t.totalPnl);
document.getElementById('td-trades').textContent = fmt.num(t.totalTrades);
document.getElementById('td-score').textContent = Number(t.combinedScore).toFixed(1);
document.getElementById('td-quality-score').textContent = Number(t.copytradingQualityScore || 0).toFixed(1);
document.getElementById('td-copyability-score').textContent = Number(t.copytradingCopyabilityScore || 0).toFixed(1);
document.getElementById('td-ai-summary').textContent = t.aiStrategySummary || 'Not analyzed yet.';
const refreshBtn = document.getElementById('btn-refresh-trader');