""" BrainSteel Fin — PaperT Agent + Portfolio Simulator Simulation Execution Officer + Virtual Portfolio Tracker Saldo virtual: $10.000 USDT | tracking de P&L diário | gráfico de evolução """ import os, json, requests, sqlite3 from datetime import datetime, date from pathlib import Path # ── Portfolio Config ────────────────────────────────────────────────────────── INITIAL_BALANCE = 200.0 # USDT virtual para simulação ( BrainSteel Fin ) DATA_DIR = Path("/app/data") PORTFOLIO_DB = DATA_DIR / "portfolio.db" class Portfolio: """Portfolio simulation — tracking de saldo, trades e P&L.""" def __init__(self): self._init_db() def _init_db(self): DATA_DIR.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(str(PORTFOLIO_DB)) c = conn.cursor() c.execute(""" CREATE TABLE IF NOT EXISTS portfolio ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, balance REAL, btc_held REAL DEFAULT 0, unrealized_pnl REAL DEFAULT 0, total_trades INTEGER DEFAULT 0, wins INTEGER DEFAULT 0, losses INTEGER DEFAULT 0, created_at TEXT, UNIQUE(date) ) """) c.execute(""" CREATE TABLE IF NOT EXISTS trades ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, action TEXT, entry_price REAL, amount_usdt REAL, btc_amount REAL, exit_price REAL, pnl_usdt REAL, pnl_pct REAL, stop_loss REAL, take_profit REAL, fee_usdt REAL, exit_reason TEXT, result TEXT, created_at TEXT ) """) c.execute(""" CREATE TABLE IF NOT EXISTS daily_snapshot ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT UNIQUE, balance REAL, btc_price REAL, btc_held REAL, open_trades INTEGER DEFAULT 0, closed_trades INTEGER DEFAULT 0, day_pnl REAL DEFAULT 0, total_pnl REAL DEFAULT 0, win_rate REAL DEFAULT 0, created_at TEXT ) """) # Ensure today's row exists today = date.today().isoformat() row = c.execute("SELECT balance FROM portfolio WHERE date=?", (today,)).fetchone() if not row: # Seed from last known balance or initial last = c.execute("SELECT balance, btc_held FROM portfolio ORDER BY date DESC LIMIT 1").fetchone() balance = last[0] if last else INITIAL_BALANCE btc_held = last[1] if last else 0.0 c.execute("INSERT INTO portfolio (date,balance,btc_held,created_at) VALUES (?,?,?,?)", (today, balance, btc_held, datetime.now().isoformat())) conn.commit() conn.close() @property def balance(self): conn = sqlite3.connect(str(PORTFOLIO_DB)) c = conn.cursor() today = date.today().isoformat() row = c.execute("SELECT balance, btc_held FROM portfolio WHERE date=?", (today,)).fetchone() conn.close() return {"balance": row[0] if row else INITIAL_BALANCE, "btc_held": row[1] if row else 0.0} def buy(self, price, amount_pct=25, stop_loss=0, take_profit=0): """Executa BUY simulado.""" return self._execute_trade("BUY", price, amount_pct, stop_loss, take_profit) def sell(self, price, amount_pct=25, stop_loss=0, take_profit=0): """Executa SELL simulado (fecha posição).""" return self._execute_trade("SELL", price, amount_pct, stop_loss, take_profit) def _execute_trade(self, action, price, amount_pct, stop_loss, take_profit): conn = sqlite3.connect(str(PORTFOLIO_DB)) c = conn.cursor() today = date.today().isoformat() now = datetime.now().isoformat() current = self.balance balance = current["balance"] btc_held = current["btc_held"] fee_rate = 0.001 # 0.1% result = {} pnl = 0.0 pnl_pct = 0.0 exit_reason = "" btc_amount = 0.0 amount_usdt = 0.0 if action == "BUY": allocation = balance * (amount_pct / 100) fee = allocation * fee_rate net_cost = allocation - fee btc_amount = net_cost / price new_balance = balance - allocation new_btc_held = btc_held + btc_amount amount_usdt = allocation exit_reason = "entry" result_str = f"🟢 BUY | ${allocation:.2f} alocado | {btc_amount:.6f} BTC @ ${price:,.0f} | Fee: ${fee:.2f}" elif action == "SELL" and btc_held > 0: # Sell portion of BTC held sell_pct = amount_pct / 100 btc_to_sell = btc_held * sell_pct gross = btc_to_sell * price fee = gross * fee_rate net_proceeds = gross - fee amount_usdt = gross pnl = net_proceeds - (btc_to_sell * price) # simplified pnl_pct = (pnl / (btc_to_sell * price)) * 100 if btc_to_sell > 0 else 0 new_balance = balance + net_proceeds new_btc_held = btc_held - btc_to_sell btc_amount = btc_to_sell # Determine exit reason if stop_loss and price <= stop_loss: exit_reason = "stop_loss_hit" elif take_profit and price >= take_profit: exit_reason = "take_profit_hit" else: exit_reason = "decio_signal" result_str = f"🔴 SELL | {btc_to_sell:.6f} BTC @ ${price:,.0f} | Net: ${net_proceeds:.2f} | Fee: ${fee:.2f}" else: new_balance = balance new_btc_held = btc_held result_str = f"⚪ SELL ignorado | BTC held: {btc_held:.6f}" # Record trade if it happened if action in ("BUY", "SELL") and (action == "BUY" or btc_held > 0): is_win = pnl > 0 if action == "SELL" and btc_held > 0 else None c.execute(""" INSERT INTO trades (date,action,entry_price,amount_usdt,btc_amount,exit_price,pnl_usdt,pnl_pct,stop_loss,take_profit,fee_usdt,exit_reason,result,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, (today, action, price, amount_usdt, btc_amount, price if action == "SELL" else 0, pnl, pnl_pct, stop_loss, take_profit, amount_usdt * fee_rate if action == "BUY" else amount_usdt * fee_rate if action == "SELL" else 0, exit_reason, "WIN" if is_win else ("LOSS" if is_win is False else "PENDING"), now)) # Update daily stats c.execute("UPDATE portfolio SET balance=?, btc_held=?, total_trades=total_trades+1 WHERE date=?", (new_balance, new_btc_held, today)) if is_win is True: c.execute("UPDATE portfolio SET wins=wins+1 WHERE date=?", (today,)) elif is_win is False: c.execute("UPDATE portfolio SET losses=losses+1 WHERE date=?", (today,)) conn.commit() conn.close() return { "action": action, "balance": new_balance if action != "SELL" or btc_held > 0 else balance, "btc_held": new_btc_held, "pnl": pnl, "result_str": result_str } def get_stats(self): """Returns aggregate stats for dashboard.""" conn = sqlite3.connect(str(PORTFOLIO_DB)) c = conn.cursor() # All trades trades = c.execute("SELECT action,pnl_usdt,pnl_pct,result,date FROM trades ORDER BY created_at DESC").fetchall() total_trades = len(trades) wins = sum(1 for t in trades if t[3] == "WIN") losses = sum(1 for t in trades if t[3] == "LOSS") # Current balance today = date.today().isoformat() cur = c.execute("SELECT balance, btc_held FROM portfolio WHERE date=?", (today,)).fetchone() current_balance = cur[0] if cur else INITIAL_BALANCE btc_held = cur[1] if cur else 0.0 # Portfolio value in USD (btc_held at current price) try: r = requests.get("https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT", timeout=5) btc_price = float(r.json()["price"]) if r.status_code == 200 else 0 except: btc_price = 0 total_value = current_balance + (btc_held * btc_price) # Daily history for chart days = c.execute("SELECT date,balance FROM portfolio ORDER BY date ASC").fetchall() conn.close() # Calculate metrics total_pnl = total_value - INITIAL_BALANCE total_pnl_pct = (total_pnl / INITIAL_BALANCE) * 100 win_rate = (wins / total_trades * 100) if total_trades > 0 else 0 # Drawdown peak = INITIAL_BALANCE max_drawdown = 0 for _, bal in days: if bal > peak: peak = bal dd = (peak - bal) / peak * 100 if dd > max_drawdown: max_drawdown = dd return { "initial_balance": INITIAL_BALANCE, "current_balance": round(current_balance, 2), "btc_held": round(btc_held, 8), "btc_price": btc_price, "total_value": round(total_value, 2), "total_pnl": round(total_pnl, 2), "total_pnl_pct": round(total_pnl_pct, 2), "total_trades": total_trades, "wins": wins, "losses": losses, "win_rate": round(win_rate, 1), "max_drawdown": round(max_drawdown, 2), "chart_data": [(d, round(b, 2)) for d, b in days], "btc_value_usdt": round(btc_held * btc_price, 2) } def snapshot(self): """Save daily snapshot for chart history.""" conn = sqlite3.connect(str(PORTFOLIO_DB)) c = conn.cursor() today = date.today().isoformat() stats = self.get_stats() # Get closed trades today today_trades = c.execute("SELECT COUNT(*),SUM(pnl_usdt) FROM trades WHERE date=? AND result IN ('WIN','LOSS')", (today,)).fetchone() c.execute(""" INSERT OR REPLACE INTO daily_snapshot (date,balance,btc_price,btc_held,open_trades,closed_trades,day_pnl,total_pnl,win_rate,created_at) VALUES (?,?,?,?,?,?,?,?,?,?) """, ( today, stats["current_balance"], stats["btc_price"], stats["btc_held"], 0, # open_trades today_trades[0] if today_trades else 0, today_trades[1] if today_trades and today_trades[1] else 0, stats["total_pnl"], stats["win_rate"], datetime.now().isoformat() )) conn.commit() conn.close() class PapertAgent: """Agent executor que também atualiza portfolio simulado.""" def __init__(self, decio_data): self.name = "PaperT" self.decio_data = decio_data self.portfolio = Portfolio() self.openrouter_key = os.getenv("PAPERT_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 _validate_order(self): action = self.decio_data.get("action", self.decio_data.get("decision", "HOLD")) if isinstance(action, str): for w in ["BUY", "SELL", "HOLD"]: if w in action.upper(): action = w break return True, action def _format_log_entry(self, action, entry_price, stop_loss, take_profit): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") fee = round(entry_price * 0.001, 2) entry = f"[{now}] BTC/USDT | {action} | Entry: ${entry_price:,.2f} | Fee: ${fee:.2f} | SL: ${stop_loss:,.2f} | TP: ${take_profit:,.2f}" return entry def _write_log(self, log_line): for path in ["/data/trading_history.log", "/app/data/trading_history.log"]: try: os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "a") as f: f.write(log_line + "\n") return True except: pass return False def _current_price(self): try: r = requests.get("https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT", timeout=10) if r.status_code == 200: return float(r.json()["price"]) except: return self.decio_data.get("price", 0) def run(self): valid, action = self._validate_order() entry_price = self._current_price() stop_loss = self.decio_data.get("stop_loss", 0) or 0 take_profit = self.decio_data.get("take_profit", 0) or 0 amount_pct = self.decio_data.get("amount_pct", 0) or 25 # Execute portfolio trade portfolio_result = self.portfolio._execute_trade(action, entry_price, amount_pct, stop_loss, take_profit) # Log entry log_entry = self._format_log_entry(action, entry_price, stop_loss, take_profit) logged = self._write_log(log_entry) # Save daily snapshot try: self.portfolio.snapshot() except: pass # Build result stats = self.portfolio.get_stats() fee = round(entry_price * 0.001, 2) result_str = f"[EXEC] {datetime.now().strftime('%Y-%m-%dT%H:%M:%S')} | {action} | ${entry_price:,.0f}" if action in ("BUY", "SELL"): result_str += f" | Bal: ${stats['current_balance']:.2f} | P&L total: ${stats['total_pnl']:+.2f} ({stats['total_pnl_pct']:+.1f}%)" return { "status": "validated", "exec_log": log_entry, "action": action, "entry_price": entry_price, "amount_pct": amount_pct, "stop_loss": stop_loss, "take_profit": take_profit, "fee_usdt": fee, "log_appended": logged, "result": result_str, # Portfolio info for dashboard "portfolio": { "balance": stats["current_balance"], "total_value": stats["total_value"], "total_pnl": stats["total_pnl"], "total_pnl_pct": stats["total_pnl_pct"], "total_trades": stats["total_trades"], "wins": stats["wins"], "losses": stats["losses"], "win_rate": stats["win_rate"], "btc_held": stats["btc_held"], "btc_price": stats["btc_price"] }, "agent": "PaperT", "timestamp": datetime.now().isoformat() }