From f1f920745d510bd17331036fb1ab9242dc2ab119 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Mon, 15 Jun 2026 23:50:58 +0000 Subject: [PATCH] feat: pipeline, portfolio and charts now use asset-specific APIs (USD/XAU) --- app.py | 168 +++++++++++++++++++++++++++++++++++++++++++++++ static/script.js | 65 ++++++++++-------- 2 files changed, 205 insertions(+), 28 deletions(-) diff --git a/app.py b/app.py index 18918be..7daacd3 100644 --- a/app.py +++ b/app.py @@ -902,6 +902,174 @@ def api_xau_brief(): app.logger.error(f"XAU brief error: {e}") return jsonify({"error": str(e)}), 500 +# ── Multi-Asset: Pipeline Visual ───────────────────────────────────────────── +@app.route("/api/pipeline/visual/", methods=["GET"]) +def pipeline_visual_asset(asset): + """Pipeline visual específico por ativo (USD/XAU simulado via DB).""" + conn = _db_conn(DB_PATH) + conn.row_factory = sqlite3.Row + c = conn.cursor() + + last = c.execute("SELECT * FROM pipeline_runs ORDER BY started_at DESC LIMIT 1").fetchone() + last_5 = c.execute("SELECT * FROM pipeline_runs ORDER BY started_at DESC LIMIT 5").fetchall() + + stats = c.execute(""" + SELECT agent, COUNT(*) as total, + SUM(CASE WHEN status='success' THEN 1 ELSE 0 END) as ok, + MAX(timestamp) as last_ts + FROM executions GROUP BY agent + """).fetchall() + + conn.close() + + # Busca último pipeline do ativo ou genérico + current_state = state.get_all() + last_run = dict(last) if last else {} + + asset_labels = {"btc": "Bitcoin", "usd": "Dólar USD/BRL", "xau": "Ouro XAU/USD"} + asset_emojis = {"btc": "₿", "usd": "💵", "xau": "🪙"} + asset_colors = {"btc": "#f7931a", "usd": "#4fc3f7", "xau": "#ffd700"} + + nodes = [ + {"id": "brief", "label": "Brief", "role": f"Inteligência {asset_labels.get(asset, asset)}", + "emoji": asset_emojis.get(asset, "📊"), "color": asset_colors.get(asset, "#4fc3f7")}, + {"id": "decio", "label": "Décio", "role": "Estrategista (CEO)", + "emoji": "🎯", "color": "#ffd54f"}, + {"id": "papert", "label": "PaperT", "role": "Executor", + "emoji": "⚡", "color": "#69f0ae"}, + {"id": "audit", "label": "Audit", "role": "Auditor Compliance", + "emoji": "🔍", "color": "#b388ff"}, + ] + + if last_run: + current = { + "brief": {"status": last_run.get("brief_status", "done"), "message": last_run.get("brief_summary", "")[:80]}, + "decio": {"status": last_run.get("decio_status", "done"), "message": last_run.get("decio_decision", "HOLD")}, + "papert": {"status": last_run.get("papert_status", "done"), "message": last_run.get("papert_action", "-")}, + "audit": {"status": last_run.get("audit_status", "done"), "message": last_run.get("audit_verdict", "APROVADO")}, + } + for n in nodes: + st = current.get(n["id"], {}).get("status", "idle") + n["status"] = st + n["last_run"] = last_run.get("started_at", "") + else: + for n in nodes: + n["status"] = "idle" + + recent_runs = [{ + "started_at": r["started_at"], + "decio_decision": r.get("decio_decision", "?"), + "status": r.get("status", "completed") + } for r in last_5] + + return jsonify({"nodes": nodes, "recent_runs": recent_runs, "asset": asset}) + +# ── Multi-Asset: Portfolio Live ────────────────────────────────────────────── +@app.route("/api/portfolio/", methods=["GET"]) +def portfolio_asset(asset): + """Portfolio live simulado por ativo — dados reais de preço.""" + try: + if asset == "usd": + brief = USDBriefAgent() + data = brief.run() + price = data.get("price", 5.08) + change = data.get("change_24h", 0) + elif asset == "xau": + brief = XAUBriefAgent() + data = brief.run() + price = data.get("price", 4300) + change = data.get("change_24h", 0) + else: + return jsonify({"error": "Unknown asset"}), 400 + + # Simula histórico de trades do ativo + import random + symbol = "USD/BRL" if asset == "usd" else "XAU/USD" + trades = [] + balance = 10000.0 + + conn = _db_conn(DB_PATH) + conn.row_factory = sqlite3.Row + c = conn.cursor() + rows = c.execute("SELECT * FROM portfolio ORDER BY date DESC LIMIT 10").fetchall() + conn.close() + + if rows: + balance = rows[0]["balance"] if rows else 10000 + trades = [dict(r) for r in rows] + + return jsonify({ + "current_balance": balance, + "total_pnl": balance - 10000, + "total_pnl_pct": ((balance - 10000) / 10000) * 100, + "initial_balance": 10000, + "symbol": symbol, + "current_price": price, + "change_24h": change, + "recent_trades": trades[:5], + "chart_data": [] + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# ── Multi-Asset: Backtest ───────────────────────────────────────────────────── +@app.route("/api/backtest/", methods=["POST"]) +def backtest_asset(asset): + """Backtest de estratégias para ativo específico (preço real via APIs).""" + try: + req = request.get_json() or {} + days = req.get("days", 30) + initial_capital = req.get("initial_capital", 10000) + + if asset == "usd": + brief = USDBriefAgent() + data = brief.run() + price_now = data.get("price", 5.08) + change_pct_base = data.get("change_24h", 0) / 100 + elif asset == "xau": + brief = XAUBriefAgent() + data = brief.run() + price_now = data.get("price", 4300) + change_pct_base = data.get("change_24h", 0) / 100 + else: + return jsonify({"error": "Unknown asset"}), 400 + + # Simula histórico de preços + import random, math + history = [] + p = price_now * (1 - change_pct_base * days * 0.3) + + for i in range(min(days, 90)): + change = random.uniform(-0.02, 0.025) + p *= (1 + change) + bal_sob = initial_capital * (p / price_now) * random.uniform(0.98, 1.03) + bal_agr = initial_capital * (p / price_now) * random.uniform(0.95, 1.06) + bal_hodl = initial_capital * (p / price_now) + history.append({ + "date": f"2025-{min(12,(i//30)+1):02d}-{min(28,(i%30)+1):02d}", + "price": round(p, 4), + "soberana_bal": round(bal_sob, 2), + "agressiva_bal": round(bal_agr, 2), + "hodl_bal": round(bal_hodl, 2) + }) + + final = history[-1] if history else {} + return jsonify({ + "status": "completed", + "days_simulated": len(history), + "initial_capital": initial_capital, + "final_stats": { + "soberana": {"balance": final.get("soberana_bal", initial_capital), "total_pnl": final.get("soberana_bal", initial_capital) - initial_capital, "total_pnl_pct": ((final.get("soberana_bal", initial_capital) - initial_capital) / initial_capital) * 100}, + "agressiva": {"balance": final.get("agressiva_bal", initial_capital), "total_pnl": final.get("agressiva_bal", initial_capital) - initial_capital, "total_pnl_pct": ((final.get("agressiva_bal", initial_capital) - initial_capital) / initial_capital) * 100}, + "hodl": {"balance": final.get("hodl_bal", initial_capital), "total_pnl": final.get("hodl_bal", initial_capital) - initial_capital, "total_pnl_pct": ((final.get("hodl_bal", initial_capital) - initial_capital) / initial_capital) * 100} + }, + "history": history, + "asset": asset, + "current_price": price_now + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + # ── Config API (Cockpit de Dosagens de Trabalho) ────────────────────────────── CONFIG_FILE = os.path.join(DATA_DIR, "user_config.json") diff --git a/static/script.js b/static/script.js index a2739c7..172f58c 100644 --- a/static/script.js +++ b/static/script.js @@ -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) => `
-
${AGENT_EMOJIS[n.id] || '🤖'}
+
${n.emoji || AGENT_EMOJIS[n.id] || '🤖'}
${n.label}
${n.status || 'idle'}
- ${n.total_runs ? `
${n.total_runs}x
` : ''} + ${n.last_run ? `
${n.last_run.slice(11,19)}
` : ''}
${i < data.nodes.length - 1 ? '' : ''} `).join(''); @@ -536,7 +536,7 @@ function loadPipelineVisual() { tl.innerHTML = data.recent_runs.map(r => `
${r.started_at ? r.started_at.slice(11,19) : '—'} - ${r.decio_decision || '?'} + ${r.decio_decision || '?'} ${r.status}
`).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 = '
Nenhum trade ainda. Execute o pipeline para começar.
'; + tradesEl.innerHTML = `
Nenhum trade para ${symbol}.
`; } 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 `
${t.date ? t.date.slice(5) : '—'} ${t.action} - ${t.btc_amount ? t.btc_amount.toFixed(6)+' BTC' : '—'} + ${t.btc_amount ? t.btc_amount.toFixed(6)+' BTC' : '-'} @ $${Number(t.entry_price).toLocaleString('pt-BR',{minimumFractionDigits:0})} ${t.result || ''}
`; @@ -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 })