feat: pipeline, portfolio and charts now use asset-specific APIs (USD/XAU)

This commit is contained in:
2026-06-15 23:50:58 +00:00
parent 9cb12ea6e6
commit f1f920745d
2 changed files with 205 additions and 28 deletions
+37 -28
View File
@@ -514,7 +514,7 @@ function closeService() {
// ── Item 4: Pipeline Visual ───────────────────────────────────────────────────
function loadPipelineVisual() {
fetch('/api/pipeline/visual')
fetch(`/api/pipeline/visual/${currentAsset}`)
.then(r => r.json())
.then(data => {
// Visual flow
@@ -522,10 +522,10 @@ function loadPipelineVisual() {
if (flow) {
flow.innerHTML = data.nodes.map((n, i) => `
<div class="visual-node" id="vis-${n.id}">
<div style="font-size:1.4rem">${AGENT_EMOJIS[n.id] || '🤖'}</div>
<div style="font-size:1.4rem">${n.emoji || AGENT_EMOJIS[n.id] || '🤖'}</div>
<div class="visual-node-label">${n.label}</div>
<div class="visual-node-status">${n.status || 'idle'}</div>
${n.total_runs ? `<div style="font-size:0.6rem;color:var(--text-secondary)">${n.total_runs}x</div>` : ''}
${n.last_run ? `<div style="font-size:0.6rem;color:var(--text-secondary)">${n.last_run.slice(11,19)}</div>` : ''}
</div>
${i < data.nodes.length - 1 ? '<span class="visual-arrow">→</span>' : ''}
`).join('');
@@ -536,7 +536,7 @@ function loadPipelineVisual() {
tl.innerHTML = data.recent_runs.map(r => `
<div class="timeline-item">
<span class="timeline-ts">${r.started_at ? r.started_at.slice(11,19) : '—'}</span>
<span class="timeline-decision ${r.decio_decision}">${r.decio_decision || '?'}</span>
<span class="timeline-decision ${r.decio_decision?.toLowerCase() || ''}">${r.decio_decision || '?'}</span>
<span style="font-size:0.65rem;color:var(--text-secondary)">${r.status}</span>
</div>
`).join('');
@@ -620,11 +620,15 @@ setInterval(() => {
loadPipelineVisual();
loadPortfolio();
loadActivityLog();
if (currentAsset === 'btc') {
pollState();
}
}, 15000);
// ── Portfolio ───────────────────────────────────────────────────────────────
function loadPortfolio() {
fetch('/api/portfolio')
const api = currentAsset === 'btc' ? '/api/portfolio' : `/api/portfolio/${currentAsset}`;
fetch(api)
.then(r => r.json())
.then(data => {
if (data.error) return;
@@ -636,35 +640,45 @@ function loadPortfolio() {
const losses = data.losses || 0;
const wr = data.win_rate || 0;
const init = data.initial_balance || 10000;
const symbol = data.symbol || 'BTC/USD';
const price = data.current_price;
const change = data.change_24h || 0;
document.getElementById('pfBalance').textContent = `$${bal.toLocaleString('pt-BR', {minimumFractionDigits:2})}`;
// Header: show price + symbol
const pnlEl = document.getElementById('pfPnl');
pnlEl.textContent = `P&L: ${pnl >= 0 ? '+' : ''}$${pnl.toLocaleString('pt-BR',{minimumFractionDigits:2})} (${pnlPct >= 0 ? '+' : ''}${pnlPct.toFixed(1)}%)`;
pnlEl.textContent = `P&L: ${pnl >= 0 ? '+' : ''}${pnl.toLocaleString('pt-BR',{minimumFractionDigits:2})} (${pnlPct >= 0 ? '+' : ''}${pnlPct.toFixed(1)}%)`;
pnlEl.style.color = pnl >= 0 ? '#69f0ae' : '#ff6b6b';
document.getElementById('pfTrades').textContent = `${trades} trades | ${wins}W/${losses}L | WR: ${wr}%`;
const total = data.total_value || init;
const daysRunning = data.chart_data ? data.chart_data.length : 0;
const pfTotalEl = document.getElementById('pfTotal');
if (pfTotalEl) pfTotalEl.textContent = `Patrimônio: $${total.toLocaleString('pt-BR', {minimumFractionDigits:2})}`;
if (pfTotalEl) {
let totalText = `${symbol}: ${price ? (price > 100 ? price.toLocaleString('pt-BR',{minimumFractionDigits:2}) : price.toFixed(4)) : '-'}`;
if (change !== 0) totalText += ` (${change >= 0 ? '+' : ''}${change.toFixed(2)}%)`;
pfTotalEl.textContent = totalText;
pfTotalEl.style.color = change >= 0 ? '#69f0ae' : '#ff6b6b';
}
const pfDaysEl = document.getElementById('pfDays');
if (pfDaysEl) pfDaysEl.textContent = `Dias Rodando: ${daysRunning}`;
if (pfDaysEl) pfDaysEl.textContent = `Banca: $${bal.toLocaleString('pt-BR', {minimumFractionDigits:2})}`;
// Draw chart
drawChart(data.chart_data || [], init, bal);
if (data.chart_data && data.chart_data.length) {
drawChart(data.chart_data, init, bal);
}
// Recent trades list
const tradesEl = document.getElementById('portfolioTrades');
const recent = data.recent_trades || [];
if (recent.length === 0) {
tradesEl.innerHTML = '<div style="color:var(--text-secondary);font-size:0.8rem;padding:8px">Nenhum trade ainda. Execute o pipeline para começar.</div>';
tradesEl.innerHTML = `<div style="color:var(--text-secondary);font-size:0.8rem;padding:8px">Nenhum trade para ${symbol}.</div>`;
} else {
tradesEl.innerHTML = recent.slice(0, 10).map(t => {
const cls = t.action === 'BUY' ? '#69f0ae' : t.action === 'SELL' ? (t.result === 'WIN' ? '#69f0ae' : '#ff6b6b') : '#888';
return `<div style="display:flex;gap:10px;padding:6px 8px;background:var(--bg-card-hover);border-radius:6px;margin-bottom:4px;font-size:0.78rem">
<span style="color:var(--text-secondary)">${t.date ? t.date.slice(5) : '—'}</span>
<span style="font-weight:700;color:${cls}">${t.action}</span>
<span>${t.btc_amount ? t.btc_amount.toFixed(6)+' BTC' : ''}</span>
<span>${t.btc_amount ? t.btc_amount.toFixed(6)+' BTC' : '-'}</span>
<span>@ $${Number(t.entry_price).toLocaleString('pt-BR',{minimumFractionDigits:0})}</span>
<span style="color:${t.result==='WIN'?'#69f0ae':t.result==='LOSS'?'#ff6b6b':'#888'}">${t.result || ''}</span>
</div>`;
@@ -963,28 +977,23 @@ function loadMultiStratChart(mode) {
if (mode === 'live') {
if (spinner) spinner.style.display = 'none';
fetch('/api/portfolio')
const api = currentAsset === 'btc' ? '/api/backtest/live' : `/api/backtest/${currentAsset}`;
fetch(api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ days: 1, initial_capital: parseFloat(document.getElementById('cockpitCapital').value) || 10000 })
})
.then(r => r.json())
.then(data => {
const init = data.initial_balance || 10000;
const hist = (data.recent_trades || []).reverse().map((t, idx) => {
return {
date: t.date || `Trade #${idx+1}`,
soberana_bal: init + (t.result === 'WIN' ? 150 : t.result === 'LOSS' ? -100 : 0) * (idx+1),
agressiva_bal: init + (t.result === 'WIN' ? 250 : t.result === 'LOSS' ? -200 : 0) * (idx+1),
hodl_bal: init * (1 + (idx*0.01)),
rsi: 50 + (Math.sin(idx) * 20)
};
});
if (hist.length === 0) {
hist.push({ date: 'Início', soberana_bal: init, agressiva_bal: init, hodl_bal: init, rsi: 50 });
if (data.history && data.history.length) {
drawMultiStratCanvas(data.history, data.initial_capital || 10000);
}
drawMultiStratCanvas(hist, init);
})
.catch(e => console.error('Erro multistrat live:', e));
} else {
if (spinner) spinner.style.display = 'block';
fetch('/api/backtest', {
const api = currentAsset === 'btc' ? '/api/backtest' : `/api/backtest/${currentAsset}`;
fetch(api, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ days: mode, initial_capital: parseFloat(document.getElementById('cockpitCapital').value) || 10000 })