feat: implement Part D and E from FIXPLAN
- 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
This commit is contained in:
@@ -292,6 +292,11 @@ async function loadTraders() {
|
||||
url += `&highlyCopyable=true`;
|
||||
}
|
||||
|
||||
const filterTrait = document.getElementById('filterTrait');
|
||||
if (filterTrait && filterTrait.value) {
|
||||
url += `&trait=${encodeURIComponent(filterTrait.value)}`;
|
||||
}
|
||||
|
||||
let data = await api(url);
|
||||
const tbody = document.getElementById('allTradersBody');
|
||||
if (!data || !data.length) { tbody.innerHTML = '<tr><td colspan="11"><div class="empty-state"><p>No traders tracked yet.</p></div></td></tr>'; return; }
|
||||
@@ -333,7 +338,10 @@ async function loadTraders() {
|
||||
<td>${fmt.pct(t.winRate)}</td>
|
||||
<td>${fmt.pnl(t.totalPnl)}</td>
|
||||
<td>${t.trades30d} | ${t.totalTrades}</td>
|
||||
<td>${t.strategy}</td>
|
||||
<td>
|
||||
${t.strategy}
|
||||
${t.traits ? '<div style="display:flex; flex-wrap:wrap; gap:4px; margin-top:4px;">' + t.traits.map(tr => `<span style="font-size:10px; padding:2px 6px; background:var(--bg-input); border-radius:10px;">${tr}</span>`).join('') + '</div>' : ''}
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<button class="btn-sm" onclick="viewTrader(${t.id})">Details</button>
|
||||
@@ -451,6 +459,16 @@ async function viewTrader(id) {
|
||||
document.getElementById('td-platformId').textContent = t.platformUserId;
|
||||
document.getElementById('td-tier').innerHTML = fmt.tier(t.tier);
|
||||
document.getElementById('td-strategy').textContent = t.strategy;
|
||||
|
||||
const traitsContainer = document.getElementById('td-traits-container');
|
||||
const traitsEl = document.getElementById('td-traits');
|
||||
if (t.traits && t.traits.length > 0) {
|
||||
traitsContainer.style.display = 'block';
|
||||
traitsEl.innerHTML = t.traits.map(tr => `<span title="Value: ${Number(tr.value).toFixed(4)}" style="font-size:11px; padding:2px 8px; background:var(--bg-input); border-radius:12px; border:1px solid var(--border);">${tr.trait}</span>`).join('');
|
||||
} else {
|
||||
traitsContainer.style.display = 'none';
|
||||
traitsEl.innerHTML = '';
|
||||
}
|
||||
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);
|
||||
@@ -460,6 +478,15 @@ async function viewTrader(id) {
|
||||
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);
|
||||
|
||||
const winRateDec = t.winRate / 100;
|
||||
const medianWin = t.medianWinReturnPct || 0;
|
||||
const medianLoss = t.medianLossReturnPct || 0;
|
||||
const expectancy = (winRateDec * medianWin) - ((1 - winRateDec) * Math.abs(medianLoss));
|
||||
|
||||
document.getElementById('td-median-win-loss').innerHTML = `<span class="side-buy">+${Number(medianWin).toFixed(1)}%</span> / <span class="side-sell">${Number(medianLoss).toFixed(1)}%</span>`;
|
||||
document.getElementById('td-profit-factor').textContent = t.profitFactor ? Number(t.profitFactor).toFixed(2) : '—';
|
||||
document.getElementById('td-expectancy').innerHTML = expectancy > 0 ? `<span class="side-buy">+${expectancy.toFixed(1)}%</span>` : `<span class="side-sell">${expectancy.toFixed(1)}%</span>`;
|
||||
document.getElementById('td-ai-summary').textContent = t.aiStrategySummary || 'Not analyzed yet.';
|
||||
|
||||
const syncBtn = document.getElementById('btn-sync-trader');
|
||||
@@ -722,4 +749,23 @@ async function queueBacklogAnalysis() {
|
||||
}
|
||||
|
||||
// Initialize Dashboard
|
||||
loadDashboard();
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Load traits for filter
|
||||
try {
|
||||
const traits = await api('/api/traders/traits');
|
||||
if (traits && Array.isArray(traits)) {
|
||||
const filterTrait = document.getElementById('filterTrait');
|
||||
if (filterTrait) {
|
||||
traits.forEach(t => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = t;
|
||||
opt.textContent = t;
|
||||
filterTrait.appendChild(opt);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load traits', e);
|
||||
}
|
||||
loadDashboard();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user