BrainSteel Fin v1.0 — 3-agent BTC trading pipeline
- Brief: Market Intelligence (Binance data + LLM analysis) - Decio: Strategy decision (BUY/HOLD/SELL) - PaperT: Order executor (Binance API) - Anime-style Flask dashboard - Traefik-ready Docker deployment
This commit is contained in:
+182
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
BrainSteel Fin — Decio Agent (v2)
|
||||
Chief Strategist & Risk Officer
|
||||
5-Pillar decision engine: Confiança < 75% → HOLD. BUY/SELL → SL 2% / TP 5%.
|
||||
"""
|
||||
import os, json, requests
|
||||
from datetime import datetime
|
||||
|
||||
class DecioAgent:
|
||||
def __init__(self, brief_data):
|
||||
self.name = "Decio"
|
||||
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"
|
||||
|
||||
# ── CORE RULE: confidence < 75% → always HOLD ─────────────────────────
|
||||
def _risk_filter(self, confidence, signal):
|
||||
"""Décio is a mathematician: no confidence ≥75%, no operation."""
|
||||
return confidence < 75 or signal not in ("ALTA", "BAIXA")
|
||||
|
||||
# ── Stop Loss / Take Profit ────────────────────────────────────────────────
|
||||
def _calc_levels(self, price, action):
|
||||
if action == "BUY":
|
||||
return round(price * 0.98, 2), round(price * 1.05, 2)
|
||||
elif action == "SELL":
|
||||
return round(price * 1.02, 2), round(price * 0.95, 2)
|
||||
return 0, 0
|
||||
|
||||
# ── LLM Decision (with 5-pillar context) ─────────────────────────────────
|
||||
def _decide_llm(self):
|
||||
if not self.openrouter_key:
|
||||
return None
|
||||
|
||||
briefing = self.brief_data.get("briefing_for_decio", self.brief_data.get("summary", ""))
|
||||
pillars = self.brief_data.get("_all_pillars", {})
|
||||
signal = self.brief_data.get("signal", "NEUTRO")
|
||||
confidence = self.brief_data.get("confidence", 50)
|
||||
price = self.brief_data.get("price", 0)
|
||||
|
||||
# Build rich context for LLM
|
||||
tech = pillars.get("Technical", {})
|
||||
deriv = pillars.get("Derivatives", {})
|
||||
liq = pillars.get("Liquidations", {})
|
||||
fgi = pillars.get("FearGreed", {})
|
||||
sent = pillars.get("Sentiment", {})
|
||||
dom = pillars.get("Dominance", {})
|
||||
|
||||
context = f"""Você é o Décio, estrategista soberano da BrainSteel Fin.
|
||||
Analítico, frio, matemático. Preservação de capital > lucro rápido.
|
||||
|
||||
BRIEFING DO BRIEF:
|
||||
{briefing}
|
||||
|
||||
PILARES DE ANÁLISE DO BRIEF:
|
||||
Technical: RSI={tech.get('rsi','?')} | MACD hist={tech.get('macd_histogram','?')} | Var 24h={tech.get('change_24h','?')}%
|
||||
Derivatives: Funding={deriv.get('funding_rate_pct','?')}%. Mark={deriv.get('mark_price','?')}
|
||||
Liquidations: Long Ratio={liq.get('long_ratio_pct','?')}%. Sentiment={liq.get('sentiment','?')}
|
||||
Fear&Greed: Index={fgi.get('fgi_value','?')} ({fgi.get('fgi_class','?')})
|
||||
Sentiment: {sent.get('sentiment_label','?')}
|
||||
BTC Dominance: {dom.get('btc_dominance_pct','?')}%
|
||||
Bullish signals: {self.brief_data.get('bullish_signals',0)} | Bearish: {self.brief_data.get('bearish_signals',0)}
|
||||
|
||||
REGRAS OPERACIONAIS:
|
||||
Confiança < 75% → decisão SEMPRE HOLD (nunca opera)
|
||||
Confiança ≥ 75% + sinal=ALTA → BUY
|
||||
Confiança ≥ 75% + sinal=BAIXA → SELL
|
||||
BUY/SELL → Stop Loss = preço × 0.98 (-2%) | Take Profit = preço × 1.05 (+5%)
|
||||
HOLD → stop_loss=0, take_profit=0
|
||||
|
||||
Responda EXCLUSIVAMENTE com JSON válido (sem texto fora do JSON):
|
||||
{{"decision": "BUY|SELL|HOLD", "confidence": N, "justification": "frase curta", "stop_loss": N, "take_profit": N}}"""
|
||||
|
||||
try:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.openrouter_key}",
|
||||
"Content-Type": "application/json",
|
||||
"HTTP-Referer": "https://brainsteel.fin",
|
||||
"X-Title": "BrainSteel Fin"
|
||||
}
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": [{"role": "user", "content": context}],
|
||||
"max_tokens": 400
|
||||
}
|
||||
r = requests.post(self.openrouter_url, json=payload, headers=headers, timeout=30)
|
||||
if r.status_code == 200:
|
||||
resp_data = r.json()
|
||||
content = resp_data["choices"][0]["message"]["content"].strip()
|
||||
content = content.strip("` \n")
|
||||
if content.startswith("json"):
|
||||
content = content[4:]
|
||||
data = json.loads(content)
|
||||
# Capture token usage
|
||||
usage = resp_data.get("usage", {})
|
||||
if usage:
|
||||
try:
|
||||
from agents.token_tracker import log_tokens
|
||||
log_tokens("Decio", self.model, usage)
|
||||
except:
|
||||
pass
|
||||
# Apply risk filter
|
||||
if self._risk_filter(data.get("confidence", 0), signal):
|
||||
data["decision"] = "HOLD"
|
||||
data["justification"] = "Confiança < 75% — preservando capital"
|
||||
data["stop_loss"] = 0
|
||||
data["take_profit"] = 0
|
||||
return data
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
# ── Local decision (fallback) ────────────────────────────────────────────
|
||||
def _local_decision(self):
|
||||
confidence = self.brief_data.get("confidence", 50)
|
||||
signal = self.brief_data.get("signal", "NEUTRO")
|
||||
price = self.brief_data.get("price", 0)
|
||||
net = self.brief_data.get("net_signal", 0)
|
||||
rsi = self.brief_data.get("rsi", 50)
|
||||
|
||||
# Overbought/oversold confirmation
|
||||
if signal == "ALTA" and rsi > 75:
|
||||
# Extremely overbought — skip BUY even if signal says ALTA
|
||||
decision = "HOLD"
|
||||
sl, tp = 0, 0
|
||||
justification = "ALTA sinal cancelada — RSI overbought (75+)"
|
||||
elif signal == "BAIXA" and rsi < 25:
|
||||
decision = "HOLD"
|
||||
sl, tp = 0, 0
|
||||
justification = "BAIXA sinal cancelada — RSI oversold (25-)"
|
||||
elif self._risk_filter(confidence, signal):
|
||||
decision = "HOLD"
|
||||
sl, tp = 0, 0
|
||||
justification = f"Confiança {confidence}% < 75% — preservando capital"
|
||||
elif signal == "ALTA":
|
||||
decision = "BUY"
|
||||
sl, tp = self._calc_levels(price, "BUY")
|
||||
justification = f"Sinal ALTA conf {confidence}%, RSI {rsi}, net {net:+d}"
|
||||
elif signal == "BAIXA":
|
||||
decision = "SELL"
|
||||
sl, tp = self._calc_levels(price, "SELL")
|
||||
justification = f"Sinal BAIXA conf {confidence}%, RSI {rsi}, net {net:+d}"
|
||||
else:
|
||||
decision = "HOLD"
|
||||
sl, tp = 0, 0
|
||||
justification = "Sinal neutro — aguardando clareza"
|
||||
|
||||
return {
|
||||
"decision": decision,
|
||||
"confidence": confidence,
|
||||
"justification": justification,
|
||||
"stop_loss": sl,
|
||||
"take_profit": tp,
|
||||
"signal": signal
|
||||
}
|
||||
|
||||
# ── Main run ───────────────────────────────────────────────────────────
|
||||
def run(self):
|
||||
llm_result = self._decide_llm()
|
||||
result = llm_result if llm_result else self._local_decision()
|
||||
|
||||
price = self.brief_data.get("price", 0)
|
||||
output = {
|
||||
"action": result["decision"],
|
||||
"amount_pct": 25 if result["decision"] in ("BUY", "SELL") else 0,
|
||||
"stop_loss": result.get("stop_loss", 0),
|
||||
"take_profit": result.get("take_profit", 0),
|
||||
"confidence": result.get("confidence", 50),
|
||||
"justification": result.get("justification", "")[:200],
|
||||
"signal": result.get("signal", self.brief_data.get("signal", "NEUTRO")),
|
||||
"price": price,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
decision_str = f"{output['action']} | Conf: {output['confidence']}%"
|
||||
if output["action"] in ("BUY", "SELL"):
|
||||
decision_str += f" | SL: ${output['stop_loss']:,.0f} | TP: ${output['take_profit']:,.0f}"
|
||||
decision_str += f" | {output['justification'][:80]}"
|
||||
|
||||
output["decision"] = decision_str
|
||||
output["agent"] = "Decio"
|
||||
return output
|
||||
Reference in New Issue
Block a user