Implement WinRate, Bankroll, Positions UI and add Trades30d to Analytics
This commit is contained in:
@@ -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'} · ${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;">`;
|
||||
|
||||
Reference in New Issue
Block a user