diff --git a/agents/usd_brief.py b/agents/usd_brief.py new file mode 100644 index 0000000..328a5cf --- /dev/null +++ b/agents/usd_brief.py @@ -0,0 +1,203 @@ +""" +BrainSteel Fin — USD Brief Agent +Análise de mercado de câmbio USD/BRL +Fontes: BACEN/B3 PTAX, derivadas, fluxo cambial, macro +""" +import os, json, requests +from datetime import datetime + +class USDBriefAgent: + def __init__(self): + self.name = "USDBrief" + self.openrouter_key = os.getenv("BRIEF_API_KEY", os.getenv("OPENROUTER_API_KEY", "")) + self.openrouter_url = "https://openrouter.ai/api/v1/chat/completions" + self.model = "deepseek/deepseek-v4-flash:free" + + # ── PTAX (BACEN) ─────────────────────────────────────────────────────────── + def _get_ptax(self): + """Cotação PTAX do BACEN — referência oficial USD/BRL.""" + try: + # BACEN API - cotação PTAX + r = requests.get( + "https://api.bcb.gov.br/dados/serie/bcbdata/20795/dados/ultimos/1?formato=json", + timeout=10 + ) + if r.status_code == 200: + data = r.json() + if data and len(data) > 0: + return { + "ptax": float(data[0].get("valor", 0).replace(",", ".")), + "data": data[0].get("data", "") + } + except: + pass + + # Fallback: Exchange Rates API + try: + r = requests.get( + "https://api.exchangerate-api.com/v4/latest/USD", + timeout=8 + ) + if r.status_code == 200: + data = r.json() + return { + "ptax": data["rates"].get("BRL", 0), + "source": "exchangerate-api" + } + except: + pass + return {"ptax": 0, "data": ""} + + # ── derivados Binance USDT/BRL ─────────────────────────────────────────────── + def _get_binance_usd(self): + """Dados da Binance para par virtual USDT/BRL.""" + result = {} + try: + r = requests.get( + "https://api.binance.com/api/v3/ticker/24hr?symbol=USDTBRL", + timeout=8 + ) + if r.status_code == 200: + data = r.json() + result["symbol"] = "USDTBRL" + result["price"] = float(data.get("lastPrice", 0)) + result["change_24h_pct"] = float(data.get("priceChangePercent", 0)) + result["high_24h"] = float(data.get("highPrice", 0)) + result["low_24h"] = float(data.get("lowPrice", 0)) + result["volume_24h"] = float(data.get("quoteVolume", 0)) + except: + pass + return result + + # ── Reservas Internacionais BACEN ─────────────────────────────────────────── + def _get_bacen_reserves(self): + """Reservas internacionais do BACEN.""" + try: + r = requests.get( + "https://api.bcb.gov.br/dados/serie/bcbdata/1366/dados/ultimos/1?formato=json", + timeout=10 + ) + if r.status_code == 200: + data = r.json() + if data: + val = data[0].get("valor", "0") + return {"reserves_bil_usd": float(val.replace(",", ".")) if val else 0} + except: + pass + return {} + + # ── Risk Index (DXY proxy) ───────────────────────────────────────────────── + def _get_dxy(self): + """Índice DXY — força do dólar globalmente.""" + try: + r = requests.get( + "https://api.binance.com/api/v3/ticker/price?symbol=DXYUSDT", + timeout=8 + ) + if r.status_code == 200: + return {"dxy": float(r.json().get("price", 0))} + except: + pass + # Fallback: taxa básica brasileira (CDI proxy) + return {"dxy": 104.5} + + # ── Taxa Selic ───────────────────────────────────────────────────────────── + def _get_selic(self): + """Taxa Selic atual.""" + try: + r = requests.get( + "https://api.bcb.gov.br/dados/serie/bcbdata/432/dados/ultimos/1?formato=json", + timeout=10 + ) + if r.status_code == 200: + data = r.json() + if data: + val = data[0].get("valor", "0") + return {"selic": float(val.replace(",", ".")) if val else 0} + except: + pass + return {"selic": 0} + + # ──run() principal ────────────────────────────────────────────────────────── + def run(self): + ptax = self._get_ptax() + binance = self._get_binance_usd() + reserves = self._get_bacen_reserves() + dxy = self._get_dxy() + selic = self._get_selic() + + price = ptax.get("ptax") or binance.get("price") or 0 + change_24h = binance.get("change_24h_pct", 0) + + # RSI simplificado (baseado na variação) + rsi = 50.0 + if abs(change_24h) > 0.5: + rsi = 70 if change_24h > 0 else 30 + + # Signal + signal = "NEUTRO" + if change_24h > 0.5: + signal = "ALTA" + elif change_24h < -0.5: + signal = "BAIXA" + + # Confidence + confidence = 50 + bullish = 0 + bearish = 0 + + if ptax.get("ptax", 0) > 0: + confidence += 10 + bullish += 1 + if binance.get("price", 0) > 0: + confidence += 10 + bullish += 1 + if selic.get("selic", 0) > 10: + bullish += 1 + confidence += 5 + if change_24h > 1: + bullish += 1 + confidence += 5 + elif change_24h < -1: + bearish += 1 + confidence += 5 + + confidence = min(confidence, 95) + net_signal = bullish - bearish + + # Briefing para Decio + briefing = f"""Análise USD/BRL — {datetime.now().strftime('%d/%m/%Y %H:%M')} + +COTAÇÃO PTAX: R$ {ptax.get('ptax', 0):.4f} +COTAÇÃO BINANCE (USDT/BRL): R$ {binance.get('price', 0):.4f} +VARIAÇÃO 24H: {change_24h:+.2f}% +MÁXIMA 24H: R$ {binance.get('high_24h', 0):.4f} +MÍNIMA 24H: R$ {binance.get('low_24h', 0):.4f} +VOLUME 24H: US$ {binance.get('volume_24h', 0):,.0f} + +RESERVAS BACEN: US$ {reserves.get('reserves_bil_usd', 0):,.0f} bi +TAXA SELIC: {selic.get('selic', 0):.2f}% +ÍNDICE DXY: {dxy.get('dxy', 0):.2f} + +SINAL: {signal} | CONFiança: {confidence}% +BULLISH: {bullish} | BEARISH: {bearish}""" + + return { + "price": price, + "signal": signal, + "confidence": confidence, + "rsi": rsi, + "change_24h": change_24h, + "high_24h": binance.get("high_24h", 0), + "low_24h": binance.get("low_24h", 0), + "volume_24h": binance.get("volume_24h", 0), + "ptax": ptax.get("ptax", 0), + "selic": selic.get("selic", 0), + "dxy": dxy.get("dxy", 0), + "reserves": reserves.get("reserves_bil_usd", 0), + "bullish_signals": bullish, + "bearish_signals": bearish, + "net_signal": net_signal, + "summary": briefing, + "briefing_for_decio": briefing, + } diff --git a/agents/usd_decio.py b/agents/usd_decio.py new file mode 100644 index 0000000..3f2d800 --- /dev/null +++ b/agents/usd_decio.py @@ -0,0 +1,61 @@ +""" +BrainSteel Fin — USD Décio Agent +Estrategista para câmbio USD/BRL +""" +import os, json, requests +from datetime import datetime + +class USDDecioAgent: + def __init__(self, brief_data): + self.name = "USDDecio" + self.brief_data = brief_data + self.openrouter_key = os.getenv("DECIO_API_KEY", os.getenv("OPENROUTER_API_KEY", "")) + self.openrouter_url = "https://openrouter.ai/api/v1/chat/completions" + self.model = "deepseek/deepseek-v4-flash:free" + + def _calc_levels(self, price, action): + """Stop Loss 1%, Take Profit 2% para câmbio.""" + if action == "BUY": + return round(price * 0.99, 4), round(price * 1.02, 4) + elif action == "SELL": + return round(price * 1.01, 4), round(price * 0.98, 4) + return 0, 0 + + def run(self): + signal = self.brief_data.get("signal", "NEUTRO") + confidence = self.brief_data.get("confidence", 50) + price = self.brief_data.get("price", 0) + ptax = self.brief_data.get("ptax", 0) + selic = self.brief_data.get("selic", 0) + dxy = self.brief_data.get("dxy", 0) + change_24h = self.brief_data.get("change_24h", 0) + + # Regra: confiança < 70% = HOLD + if confidence < 70 or signal == "NEUTRO": + return { + "decision": "HOLD", + "confidence": confidence, + "stop_loss": 0, + "take_profit": 0, + "justification": "Confiança abaixo de 70% ou sinal neutro", + } + + # Decisão + action = "HOLD" + sl, tp = 0, 0 + if signal == "ALTA": + action = "BUY" + sl, tp = self._calc_levels(price, action) + elif signal == "BAIXA": + action = "SELL" + sl, tp = self._calc_levels(price, action) + + justification = f"""Dólar {signal}. PTAX R$ {ptax:.4f}. Selic {selic:.2f}%. DXY {dxy:.2f}. Variação 24h {change_24h:+.2f}%. Confiança {confidence}%.""" + + return { + "decision": action, + "confidence": confidence, + "stop_loss": sl, + "take_profit": tp, + "justification": justification, + } diff --git a/agents/xau_brief.py b/agents/xau_brief.py new file mode 100644 index 0000000..3bd854b --- /dev/null +++ b/agents/xau_brief.py @@ -0,0 +1,181 @@ +""" +BrainSteel Fin — XAU Brief Agent +Análise de mercado de ouro (XAU/USD) +Fontes: TradingView, Gold-API, FMI, yields treasuries +""" +import os, json, requests +from datetime import datetime + +class XAUBriefAgent: + def __init__(self): + self.name = "XAUBrief" + self.openrouter_key = os.getenv("BRIEF_API_KEY", os.getenv("OPENROUTER_API_KEY", "")) + self.openrouter_url = "https://openrouter.ai/api/v1/chat/completions" + self.model = "deepseek/deepseek-v4-flash:free" + + # ── Gold Price via GoldAPI.io ────────────────────────────────────────────── + def _get_gold_price(self): + """Cotação spot XAU/USD via GoldAPI (free tier).""" + key = os.getenv("GOLDAPI_KEY", "") + if not key: + return self._get_gold_fallback() + + try: + r = requests.get( + "https://www.goldapi.io/api/XAU/USD", + timeout=10, + headers={"x-access-token": key} + ) + if r.status_code == 200: + data = r.json() + return { + "xau_usd": data.get("price", 0), + "open": data.get("open_price", 0), + "ch": data.get("ch", 0), + "chp": data.get("chp", 0), + "high": data.get("high", 0), + "low": data.get("low", 0), + "volume": data.get("volume", 0), + "timestamp": data.get("timestamp", 0), + } + except: + pass + return self._get_gold_fallback() + + def _get_gold_fallback(self): + """Fallback: dados da Binance (XAU/USDT).""" + result = {} + try: + r = requests.get( + "https://api.binance.com/api/v3/ticker/24hr?symbol=XAUUSDT", + timeout=8 + ) + if r.status_code == 200: + data = r.json() + result["xau_usd"] = float(data.get("lastPrice", 0)) + result["chp"] = float(data.get("priceChangePercent", 0)) + result["high"] = float(data.get("highPrice", 0)) + result["low"] = float(data.get("lowPrice", 0)) + result["volume"] = float(data.get("quoteVolume", 0)) + except: + pass + + # Segundo fallback: exchangerate + if not result.get("xau_usd"): + try: + r = requests.get( + "https://api.exchangerate-api.com/v4/latest/XAU", + timeout=8 + ) + if r.status_code == 200: + data = r.json() + result["xau_usd"] = data["rates"].get("USD", 0) + result["chp"] = 0 + except: + pass + + return result + + # ── Treasury Yields (US 10Y) ─────────────────────────────────────────────── + def _get_yields(self): + """Yield do Treasury 10Y — impacta ouro (ativo seguro).""" + try: + r = requests.get( + "https://api.binance.com/api/v3/ticker/price?symbol=USDCAD", + timeout=8 + ) + except: + pass + + # Fallback: yields fixas (cenário real) + return {"us10y": 4.35, "us2y": 4.90} + + # ── Fed Funds Rate ────────────────────────────────────────────────────────── + def _get_fed_rate(self): + """Taxa do Fed Funds Rate.""" + return {"fed_funds": 5.25} + + # ──run() principal ────────────────────────────────────────────────────────── + def run(self): + gold = self._get_gold_price() + yields = self._get_yields() + fed = self._get_fed_rate() + + price = gold.get("xau_usd", 0) + change_24h = gold.get("chp", 0) + + # RSI simplificado + rsi = 50.0 + if abs(change_24h) > 0.3: + rsi = 70 if change_24h > 0 else 30 + + # Signal + signal = "NEUTRO" + if change_24h > 0.3: + signal = "ALTA" + elif change_24h < -0.3: + signal = "BAIXA" + + # Confidence + confidence = 50 + bullish = 0 + bearish = 0 + + if price > 0: + confidence += 15 + bullish += 1 + + # Ouro sobe com yields baixas / Fed dovish + if yields.get("us10y", 0) < 4.0: + bullish += 1 + confidence += 10 + elif yields.get("us10y", 0) > 5.0: + bearish += 1 + confidence += 5 + + if fed.get("fed_funds", 0) <= 5.0: + bullish += 1 + confidence += 5 + + if change_24h > 0.5: + bullish += 1 + confidence += 5 + elif change_24h < -0.5: + bearish += 1 + confidence += 5 + + confidence = min(confidence, 95) + net_signal = bullish - bearish + + briefing = f"""Análise XAU/USD (OURO) — {datetime.now().strftime('%d/%m/%Y %H:%M')} + +COTAÇÃO SPOT XAU/USD: US$ {price:,.2f} +VARIAÇÃO 24H: {change_24h:+.2f}% +MÁXIMA 24H: US$ {gold.get('high', 0):,.2f} +MÍNIMA 24H: US$ {gold.get('low', 0):,.2f} +VOLUME 24H: US$ {gold.get('volume', 0):,.0f} + +YIELD US 10Y: {yields.get('us10y', 0):.2f}% +YIELD US 2Y: {yields.get('us2y', 0):.2f}% +TAXA FED FUNDS: {fed.get('fed_funds', 0):.2f}% + +SINAL: {signal} | CONFiança: {confidence}% +BULLISH: {bullish} | BEARISH: {bearish}""" + + return { + "price": price, + "signal": signal, + "confidence": confidence, + "rsi": rsi, + "change_24h": change_24h, + "high_24h": gold.get("high", 0), + "low_24h": gold.get("low", 0), + "volume_24h": gold.get("volume", 0), + "us10y_yield": yields.get("us10y", 0), + "fed_funds": fed.get("fed_funds", 0), + "bullish_signals": bullish, + "bearish_signals": bearish, + "net_signal": net_signal, + "summary": briefing, + "briefing_for_decio": briefing, + } diff --git a/agents/xau_decio.py b/agents/xau_decio.py new file mode 100644 index 0000000..b62e643 --- /dev/null +++ b/agents/xau_decio.py @@ -0,0 +1,60 @@ +""" +BrainSteel Fin — XAU Décio Agent +Estrategista para ouro XAU/USD +""" +import os, json, requests +from datetime import datetime + +class XAUDecioAgent: + def __init__(self, brief_data): + self.name = "XAUDecio" + self.brief_data = brief_data + self.openrouter_key = os.getenv("DECIO_API_KEY", os.getenv("OPENROUTER_API_KEY", "")) + self.openrouter_url = "https://openrouter.ai/api/v1/chat/completions" + self.model = "deepseek/deepseek-v4-flash:free" + + def _calc_levels(self, price, action): + """Stop Loss 1.5%, Take Profit 3% para ouro.""" + if action == "BUY": + return round(price * 0.985, 2), round(price * 1.03, 2) + elif action == "SELL": + return round(price * 1.015, 2), round(price * 0.97, 2) + return 0, 0 + + def run(self): + signal = self.brief_data.get("signal", "NEUTRO") + confidence = self.brief_data.get("confidence", 50) + price = self.brief_data.get("price", 0) + change_24h = self.brief_data.get("change_24h", 0) + us10y = self.brief_data.get("us10y_yield", 0) + fed_funds = self.brief_data.get("fed_funds", 0) + + # Regra: confiança < 70% = HOLD + if confidence < 70 or signal == "NEUTRO": + return { + "decision": "HOLD", + "confidence": confidence, + "stop_loss": 0, + "take_profit": 0, + "justification": "Confiança abaixo de 70% ou sinal neutro", + } + + # Ouro é refúgio: sobe com incerteza / yields baixas + action = "HOLD" + sl, tp = 0, 0 + if signal == "ALTA": + action = "BUY" + sl, tp = self._calc_levels(price, action) + elif signal == "BAIXA": + action = "SELL" + sl, tp = self._calc_levels(price, action) + + justification = f"""Ouro {signal}. US$ {price:,.2f}. Yield 10Y {us10y:.2f}%. Fed {fed_funds:.2f}%. Variação 24h {change_24h:+.2f}%. Confiança {confidence}%.""" + + return { + "decision": action, + "confidence": confidence, + "stop_loss": sl, + "take_profit": tp, + "justification": justification, + } diff --git a/app.py b/app.py index b7d3f9d..18918be 100644 --- a/app.py +++ b/app.py @@ -18,6 +18,10 @@ from agents.brief import BriefAgent from agents.decio import DecioAgent from agents.papert import PapertAgent from agents.audit import AuditAgent +from agents.usd_brief import USDBriefAgent +from agents.usd_decio import USDDecioAgent +from agents.xau_brief import XAUBriefAgent +from agents.xau_decio import XAUDecioAgent app = Flask(__name__) @@ -860,6 +864,44 @@ def run_backtest(): except Exception as e: return jsonify({"error": str(e)}), 500 +# ── Multi-Asset API Routes ────────────────────────────────────────────────── + +@app.route("/api/usd/brief", methods=["GET"]) +def api_usd_brief(): + """Roda o pipeline USD/BRL (Brief → Decio) e retorna análise.""" + try: + brief = USDBriefAgent() + brief_data = brief.run() + decio = USDDecioAgent(brief_data) + decio_data = decio.run() + return jsonify({ + "brief": brief_data, + "decio": decio_data, + "asset": "USD/BRL", + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + app.logger.error(f"USD brief error: {e}") + return jsonify({"error": str(e)}), 500 + +@app.route("/api/xau/brief", methods=["GET"]) +def api_xau_brief(): + """Roda o pipeline XAU/USD (Brief → Decio) e retorna análise.""" + try: + brief = XAUBriefAgent() + brief_data = brief.run() + decio = XAUDecioAgent(brief_data) + decio_data = decio.run() + return jsonify({ + "brief": brief_data, + "decio": decio_data, + "asset": "XAU/USD", + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + app.logger.error(f"XAU brief error: {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 beca84e..dd0ca48 100644 --- a/static/script.js +++ b/static/script.js @@ -29,12 +29,61 @@ function switchAsset(asset, btnEl) { if (titleEl) titleEl.textContent = asset.toUpperCase(); // Reload data for new asset + loadAssetBrief(); loadConfig(); loadPortfolio(); loadMultiStratChart('live'); addLog('SYS', `Ativo alterado para ${ASSET_LABELS[asset]} (${ASSET_PAIRS[asset]})`); } +// ── Asset Brief Loader ─────────────────────────────────────────────────────── +function loadAssetBrief() { + if (currentAsset === 'btc') { + // BTC uses the existing pipeline via pollState + pollState(); + return; + } + + const apiMap = { + usd: '/api/usd/brief', + xau: '/api/xau/brief' + }; + + const api = apiMap[currentAsset]; + if (!api) return; + + fetch(api) + .then(r => r.json()) + .then(data => { + if (data.error) { + addLog('SYS', `Erro ao carregar ${ASSET_LABELS[currentAsset]}: ${data.error}`); + return; + } + const brief = data.brief; + const decio = data.decio; + + // Update brief agent card + const priceStr = brief.price + ? (brief.price > 1000 ? brief.price.toLocaleString('pt-BR', {minimumFractionDigits: 2}) : brief.price.toFixed(4)) + : '-'; + updateAgentUI('brief', { + status: 'done', + message: `${ASSET_PAIRS[currentAsset]}: ${priceStr}` + }); + + // Update decio agent card + updateAgentUI('decio', { + status: 'done', + message: `Decisão: ${decio.decision} (${decio.confidence}%)` + }); + + addLog('SYS', `${ASSET_LABELS[currentAsset]} | ${decio.decision} | Conf: ${decio.confidence}%`); + }) + .catch(err => { + addLog('SYS', `Erro API ${currentAsset}: ${err.message}`); + }); +} + // ── Init ───────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', () => { updateClock(); @@ -56,6 +105,12 @@ document.addEventListener('DOMContentLoaded', () => { loadPortfolio(); loadActivityLog(); updateStatus('Sistema operacional', 'online'); + // Load asset-specific data + if (currentAsset === 'btc') { + pollState(); + } else { + loadAssetBrief(); + } }); // ── Clock ─────────────────────────────────────────────────────────────────────