Implement WinRate, Bankroll, Positions UI and add Trades30d to Analytics

This commit is contained in:
Richard
2026-07-09 12:44:32 +02:00
parent 3b11b71188
commit 7964194e9e
16 changed files with 1318 additions and 28 deletions
+63 -12
View File
@@ -221,8 +221,7 @@ async function loadDashboard() {
<td onclick="viewTrader(${t.id})" style="cursor:pointer"><strong>${Number(t.combinedScore).toFixed(1)}</strong></td>
<td onclick="viewTrader(${t.id})" style="cursor:pointer">${fmt.pct(t.winRate)}</td>
<td onclick="viewTrader(${t.id})" style="cursor:pointer">${fmt.pnl(t.totalPnl)}</td>
<td onclick="viewTrader(${t.id})" style="cursor:pointer">${fmt.tier(t.tier)}</td>
<td onclick="viewTrader(${t.id})" style="cursor:pointer">${fmt.num(t.totalTrades)}</td>
<td onclick="viewTrader(${t.id})" style="cursor:pointer">${t.trades30d} | ${t.totalTrades}</td>
</tr>
`).join('');
@@ -319,6 +318,7 @@ async function loadTraders() {
else if (currentSort === 'copyability') { valA = a.copytradingCopyabilityScore || 0; valB = b.copytradingCopyabilityScore || 0; }
else if (currentSort === 'winrate') { valA = a.winRate; valB = b.winRate; }
else if (currentSort === 'pnl') { valA = a.totalPnl; valB = b.totalPnl; }
else if (currentSort === 'trades') { valA = a.trades30d; valB = b.trades30d; }
else if (currentSort === 'name') { return a.displayName.localeCompare(b.displayName) * sortDirection; }
else if (currentSort === 'platform') { return a.platform.localeCompare(b.platform) * sortDirection; }
else { valA = a.combinedScore; valB = b.combinedScore; }
@@ -336,7 +336,7 @@ async function loadTraders() {
<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>
<td>${t.trades30d} | ${t.totalTrades}</td>
<td>${t.strategy}</td>
<td>
<div style="display:flex; gap:4px;">
@@ -370,6 +370,17 @@ async function loadAlerts() {
async function loadMarkets() {
let url = '/api/markets?skip=0&take=100';
if (currentPlatform !== 'All') url += `&platform=${currentPlatform}`;
const catSelect = document.getElementById('marketsCategory');
if (catSelect && catSelect.value !== 'All') {
url += `&category=${catSelect.value}`;
}
const searchInput = document.getElementById('marketsSearchInput');
if (searchInput && searchInput.value) {
url += `&query=${encodeURIComponent(searchInput.value)}`;
}
let data = await api(url);
const tbody = document.getElementById('allMarketsBody');
if (!data || !data.length) { tbody.innerHTML = '<tr><td colspan="6"><div class="empty-state"><p>No markets found.</p></div></td></tr>'; return; }
@@ -392,6 +403,7 @@ async function loadMarkets() {
}
async function viewTrader(id) {
window.currentTraderId = id;
navigateTo('trader-detail');
const t = await api(`/api/traders/${id}`);
if (!t) return;
@@ -405,27 +417,32 @@ async function viewTrader(id) {
document.getElementById('td-tier').innerHTML = fmt.tier(t.tier);
document.getElementById('td-strategy').textContent = t.strategy;
document.getElementById('td-winrate').innerHTML = fmt.pct(t.winRate);
document.getElementById('td-winrate30d').innerHTML = fmt.pct(t.winRate30d);
document.getElementById('td-pnl').innerHTML = fmt.pnl(t.totalPnl);
document.getElementById('td-pnl30d').innerHTML = fmt.pnl(t.pnL30d);
document.getElementById('td-trades').textContent = fmt.num(t.totalTrades);
document.getElementById('td-bankroll').textContent = fmt.usd(t.estimatedBankroll);
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');
refreshBtn.onclick = () => manualUpdateTrader(id);
const syncBtn = document.getElementById('btn-sync-trader');
if (syncBtn) {
syncBtn.onclick = () => manualUpdateTrader(id);
}
const forceBtn = document.getElementById('btn-force-analyze');
if(forceBtn) {
forceBtn.onclick = async () => {
forceBtn.disabled = true;
forceBtn.textContent = '...';
const analyzeBtn = document.getElementById('btn-analyze-trader');
if (analyzeBtn) {
analyzeBtn.onclick = async () => {
analyzeBtn.disabled = true;
analyzeBtn.textContent = '...';
try {
await api(`/api/traders/${id}/force-analyze`, { method: 'POST' });
alert('Deep Analysis queued! Please wait a moment and then refresh.');
} finally {
forceBtn.disabled = false;
forceBtn.textContent = 'Recalculate';
analyzeBtn.disabled = false;
analyzeBtn.textContent = '⚙ Analyze';
}
};
}
@@ -479,6 +496,37 @@ async function viewTrader(id) {
<td>${fmt.usd(tr.amount)}</td>
</tr>
`).join('');
loadTraderPositions(id);
}
async function loadTraderPositions(id) {
const tbody = document.getElementById('td-positionsBody');
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;">Loading...</td></tr>';
try {
const data = await api(`/api/traders/${id}/positions`);
if (!data || !data.length) {
tbody.innerHTML = '<tr><td colspan="6"><div class="empty-state"><p>No active positions.</p></div></td></tr>';
return;
}
tbody.innerHTML = data.map(p => `
<tr>
<td title="Market ID: ${p.marketId}">
<div style="font-weight:600">${p.marketName || p.marketId}</div>
<div style="font-size:0.85em; color:var(--text-muted)">${p.category || 'Other'} &middot; ${p.outcomeToken || 'Unknown'}</div>
</td>
<td>${fmt.num(p.sharesHeld)}</td>
<td>${Number(p.avgCost).toFixed(2)}</td>
<td>${Number(p.currentPrice).toFixed(2)}</td>
<td>${fmt.pnl(p.realizedPnl)}</td>
<td>${fmt.pnl(p.unrealizedPnl)}</td>
</tr>
`).join('');
} catch (e) {
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center; color:var(--pnl-negative);">Error loading positions.</td></tr>';
}
}
function switchTraderTab(tabId) {
@@ -505,6 +553,9 @@ async function viewMarket(id) {
document.getElementById('md-volume').textContent = fmt.usd(m.volume);
document.getElementById('md-liquidity').textContent = fmt.usd(m.liquidity);
document.getElementById('md-status').textContent = m.isResolved ? 'Resolved' : 'Active';
document.getElementById('md-bot-activity').textContent = m.botActivityScore ? Number(m.botActivityScore).toFixed(1) : '0';
document.getElementById('md-unique-traders').textContent = fmt.num(m.uniqueTradersCount);
document.getElementById('md-avg-trade-size').textContent = fmt.usd(m.averageTradeSize);
const imgContainer = document.getElementById('md-image');
if (m.imageUrl) imgContainer.innerHTML = `<img src="${m.imageUrl}" alt="Market" style="width:100%; border-radius:8px; margin-bottom:16px;">`;