APEX_BRAIN_F / server_F.py
blueface35's picture
Upload server_F.py
d95a8b3 verified
Raw
History Blame Contribute Delete
45.1 kB
"""
server_F.py — Cerebro F — The Liquidator v6.0 APEX POLIMÓRFICO
===============================================================
NUEVAS CAPACIDADES v6.0 (sobre v5.0 OSI+LLM):
1. COGNITIVE TRAILING STOP ASÍNCRONO
Si profit > +0.50% Y velocity_score cae a 0 durante >10 barras → MARKET_EXIT inmediato.
No espera el OSI ni el LLM. Embolsa el flotante antes de que el mercado se pudra.
Umbral configurable por env: VELOCITY_STALL_BARS (default=10), PROFIT_FLOOR_SCALP=0.50%.
2. BIDIRECCIONAL NATIVO (LONG/SHORT)
Acepta trade_side = "long" | "short" en el payload.
Para SHORT: la lógica OSI se invierte (delta_cvd positivo = presión alcista = KILL_SHORT).
Output incluye "close_action": "buy" | "sell" para que orders_processor.py sepa
qué orden de cierre mandar a Alpaca.
3. VWAP DINÁMICO (Herramienta 1)
Calcula VWAP rodante desde las barras disponibles.
Valida dirección del trade vs posición precio/VWAP:
LONG válido → precio < VWAP (compra con descuento institucional)
SHORT válido → precio > VWAP (reversión corta confirmada)
Flag: price_vs_vwap = "ABOVE" | "BELOW" | "AT"
4. ENTROPY Z-SCORE OBI (Herramienta 2)
Cruza OBI velocity con RSI adaptativo de micro-temporalidad.
Si Z-Score de impulso cae < -1.5σ → MARKET_EXHAUSTION = True → KILL inmediato.
Elimina el "Síndrome del Capital Atrapado" a las 10 barras, no a las 120.
5. CROSS-ASSET CORRELATION (Herramienta 3)
Circuit breaker: si BTC/ETH rompen VWAP a la baja con volumen, bloquea LONGs.
Recibe anchor_correlation del payload (calculado por data_manager.py).
6. NEWS IMPACT WEIGHT (Herramienta 4)
Recibe macro_impact_weight del Cerebro E.
Si peso institucional > 2 y bearish → fuerza KILL independientemente del OSI.
7. STYLE POLYMORPHISM
Acepta c_style = "SCALP" | "MOMENTUM" del Cerebro C.
SCALP: umbrales más agresivos de salida (profit_floor=0.50%, velocity_bars=8)
MOMENTUM: aguanta más (profit_floor=1.00%, velocity_bars=15)
8. SHORT HARD STOP MATEMÁTICO (Cerebro H integration)
Para shorts: short_stop_price se calcula desde entry_price × (1 + 0.015).
Si current_price >= short_stop_price → KILL_SHORT incondicional, no consultado.
OUTPUT v6.0 (100% compatible con swarm_engine via legacy aliases):
{
"decision": "SELL|HOLD",
"close_action": "sell|buy", # sell=cierra long, buy=cierra short
"trade_side": "long|short",
"confidence": float,
"kill_score": float,
"trigger": str,
"urgency": "critical|high|medium|low",
"osi": float,
"osi_zone": str,
"vwap": float,
"price_vs_vwap": "ABOVE|BELOW|AT",
"market_exhaustion": bool,
"exhaustion_zscore": float,
"anchor_blocked": bool,
"macro_kill": bool,
"c_style": "SCALP|MOMENTUM",
"cognitive_trailing_triggered": bool,
"short_stop_triggered": bool,
"orderflow": dict,
"_math_ms": float,
"_llm_ms": float,
"_total_ms": float,
}
TELEMETRÍA: [F/MATH] | [F/CTS] Cognitive Trailing | [F/LLM] | [F/TOTAL]
"""
import os, json, re, time, threading, math, collections
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
import httpx
app = FastAPI(title="Cerebro F — The Liquidator v6.0 APEX POLIMÓRFICO")
# ── Configuración base (heredada de v5.0) ─────────────────────────────────────
MODEL_PATH = os.environ.get("MODEL_PATH", "/models/ggml-model-i2_s.gguf")
KILL_THRESHOLD = float(os.environ.get("KILL_THRESHOLD", "0.82"))
KILL_THRESHOLD_SCALP = float(os.environ.get("KILL_THRESHOLD_SCALP", "0.80"))
HOLD_THRESHOLD = float(os.environ.get("HOLD_THRESHOLD", "0.18"))
STAGNATION_BARS = int(os.environ.get("STAGNATION_BARS", "5"))
# OSI Zones
OSI_FAST_EXIT = float(os.environ.get("OSI_FAST_EXIT", "80"))
OSI_KILL_ZONE = float(os.environ.get("OSI_KILL_ZONE", "60"))
OSI_HOLD_ZONE = float(os.environ.get("OSI_HOLD_ZONE", "30"))
OSI_FALLBACK_KILL = float(os.environ.get("OSI_FALLBACK_KILL", "55"))
# ── v6.0: Nuevas configuraciones ─────────────────────────────────────────────
# Cognitive Trailing Stop
CTS_PROFIT_FLOOR_SCALP = float(os.environ.get("CTS_PROFIT_FLOOR_SCALP", "0.50")) # %
CTS_PROFIT_FLOOR_MOMENTUM = float(os.environ.get("CTS_PROFIT_FLOOR_MOMENTUM", "1.00")) # %
CTS_VELOCITY_STALL_BARS = int(os.environ.get("CTS_VELOCITY_STALL_BARS", "10"))
CTS_VELOCITY_STALL_BARS_M = int(os.environ.get("CTS_VELOCITY_STALL_BARS_M", "15")) # MOMENTUM
# Entropy Z-Score
EXHAUSTION_ZSCORE_THRESH = float(os.environ.get("EXHAUSTION_ZSCORE_THRESH", "-1.5"))
# Short protection
SHORT_STOP_PCT = float(os.environ.get("SHORT_STOP_PCT", "0.015")) # 1.5%
MAX_ACCOUNT_RISK_PCT = float(os.environ.get("MAX_ACCOUNT_RISK_PCT", "0.005")) # 0.5%
# BitNet
BITNET_PORT = int(os.environ.get("BITNET_PORT", "8080"))
BITNET_HOST = os.environ.get("BITNET_HOST", "127.0.0.1")
BITNET_BASE = f"http://{BITNET_HOST}:{BITNET_PORT}"
BITNET_TIMEOUT = float(os.environ.get("BITNET_TIMEOUT", "30.0"))
LLM_MAX_TOKENS = int(os.environ.get("LLM_MAX_TOKENS", "48"))
_bitnet_client = None
def _get_bitnet_client():
global _bitnet_client
if _bitnet_client is None or _bitnet_client.is_closed:
_bitnet_client = httpx.AsyncClient(
base_url=BITNET_BASE,
timeout=httpx.Timeout(BITNET_TIMEOUT),
limits=httpx.Limits(max_connections=4, max_keepalive_connections=2),
)
return _bitnet_client
async def _bitnet_infer(prompt_text: str, max_tokens: int = 48) -> dict:
client = _get_bitnet_client()
payload = {
"prompt": prompt_text, "n_predict": max_tokens, "temperature": 0.0,
"top_p": 0.95, "top_k": 5, "repeat_penalty": 1.0, "stream": False,
"cache_prompt": False,
"stop": ["<|eot_id|>", "<|end_of_text|>", "<|im_end|>"],
}
try:
resp = await client.post("/completion", json=payload)
resp.raise_for_status()
return {"raw": resp.json().get("content", "").strip(), "ok": True}
except httpx.TimeoutException:
return {"raw": "", "ok": False, "error": "TIMEOUT"}
except Exception as e:
return {"raw": "", "ok": False, "error": str(e)[:80]}
# ══════════════════════════════════════════════════════════════════════════════
# HERRAMIENTA 1: VWAP DINÁMICO DE ALTA FRECUENCIA
# ══════════════════════════════════════════════════════════════════════════════
def _compute_vwap(bars: list) -> tuple:
"""
VWAP rodante desde barras OHLCV.
Usa precio típico = (H+L+C)/3 × volumen.
Retorna (vwap: float, price_vs_vwap: str)
"""
if not bars or len(bars) < 2:
return 0.0, "AT"
cum_pv = 0.0
cum_v = 0.0
for b in bars:
h = b.get("high", b.get("h", 0))
l = b.get("low", b.get("l", 0))
c = b.get("close", b.get("c", 0))
v = b.get("volume", b.get("v", 0))
if h == 0 or v == 0:
continue
typical = (h + l + c) / 3.0
cum_pv += typical * v
cum_v += v
if cum_v == 0:
return 0.0, "AT"
vwap = round(cum_pv / cum_v, 8)
last_close = bars[-1].get("close", bars[-1].get("c", vwap))
if last_close > vwap * 1.0005:
pos = "ABOVE"
elif last_close < vwap * 0.9995:
pos = "BELOW"
else:
pos = "AT"
return vwap, pos
# ══════════════════════════════════════════════════════════════════════════════
# HERRAMIENTA 2: ENTROPY Z-SCORE OBI — Filtro de Fatiga del Impulso
# ══════════════════════════════════════════════════════════════════════════════
def _compute_exhaustion_zscore(bars: list, velocity_history: list = None) -> tuple:
"""
Cruza OBI Velocity (desequilibrio bid/ask por barra) con RSI micro-adaptativo.
Retorna (market_exhaustion: bool, zscore: float, rsi_micro: float)
velocity_history: lista de OBI scores pasados del shadow cache del símbolo.
Si no hay historial externo, se recalcula desde las barras.
Z-Score < EXHAUSTION_ZSCORE_THRESH (-1.5) → MARKET_EXHAUSTION = True
"""
if not bars or len(bars) < 8:
return False, 0.0, 50.0
# ── OBI Velocity por barra ────────────────────────────────────────────────
obi_series = []
for b in bars[-20:]:
bid_v = b.get("bid_volume", b.get("volume", 0)) * 0.55 # estimación
ask_v = b.get("ask_volume", b.get("volume", 0)) * 0.45
total = bid_v + ask_v + 1e-9
obi_series.append((bid_v - ask_v) / total)
if len(obi_series) < 5:
return False, 0.0, 50.0
# ── RSI Micro-adaptativo (14 periodos sobre returns de closes) ────────────
closes = [b.get("close", b.get("c", 0)) for b in bars[-16:] if b.get("close", b.get("c", 0)) > 0]
rsi_micro = 50.0
if len(closes) >= 3:
gains = [max(0, closes[i] - closes[i-1]) for i in range(1, len(closes))]
losses = [max(0, closes[i-1] - closes[i]) for i in range(1, len(closes))]
avg_gain = sum(gains) / max(len(gains), 1)
avg_loss = sum(losses) / max(len(losses), 1) + 1e-9
rs = avg_gain / avg_loss
rsi_micro = round(100 - (100 / (1 + rs)), 2)
# ── Z-Score del OBI velocity ──────────────────────────────────────────────
series = obi_series
n = len(series)
mu = sum(series) / n
var = sum((x - mu) ** 2 for x in series) / n
std = math.sqrt(var) + 1e-9
z_score = round((series[-1] - mu) / std, 3)
# Combinar: Z-Score OBI + RSI sobrecomprado/vendido
# Si RSI > 70 Y Z < -1.0 → agotamiento claro
rsi_factor = -0.5 if rsi_micro > 68 else (0.3 if rsi_micro < 35 else 0.0)
combined_z = round(z_score + rsi_factor, 3)
exhausted = combined_z < EXHAUSTION_ZSCORE_THRESH
return exhausted, combined_z, rsi_micro
# ══════════════════════════════════════════════════════════════════════════════
# COGNITIVE TRAILING STOP (CTS) — Erradicación del Estancamiento
# ══════════════════════════════════════════════════════════════════════════════
def _cognitive_trailing_stop(
current_pnl_pct: float,
velocity_zero_bars: int,
c_style: str,
trade_side: str,
market_exhaustion: bool,
exhaustion_zscore: float,
) -> tuple:
"""
Dispara KILL si el capital flotante positivo está en riesgo por inactividad.
Lógica:
- Si profit >= profit_floor Y (velocity_stall >= umbral O market_exhaustion)
→ KILL inmediato para embolsar el flotante
Retorna (triggered: bool, reason: str)
"""
is_scalp = c_style.upper() != "MOMENTUM"
profit_floor = CTS_PROFIT_FLOOR_SCALP if is_scalp else CTS_PROFIT_FLOOR_MOMENTUM
stall_bars = CTS_VELOCITY_STALL_BARS if is_scalp else CTS_VELOCITY_STALL_BARS_M
in_profit = current_pnl_pct >= profit_floor
if not in_profit:
return False, "below_floor"
# Stagnation por velocidad nula
if velocity_zero_bars >= stall_bars:
reason = (f"cts_velocity_stall bars={velocity_zero_bars}>={stall_bars} "
f"profit={current_pnl_pct:.2f}%")
return True, reason
# Market exhaustion detectado por Z-Score
if market_exhaustion:
reason = (f"cts_market_exhaustion z={exhaustion_zscore:.2f}<{EXHAUSTION_ZSCORE_THRESH} "
f"profit={current_pnl_pct:.2f}%")
return True, reason
return False, "no_trigger"
# ══════════════════════════════════════════════════════════════════════════════
# HERRAMIENTA 3: CROSS-ASSET CORRELATION CIRCUIT BREAKER
# ══════════════════════════════════════════════════════════════════════════════
def _check_anchor_correlation(
trade_side: str,
anchor_correlation: str,
price_vs_vwap: str,
a_bias: str = "N",
) -> tuple:
"""
Circuit breaker basado en correlación cruzada BTC/ETH/SPY.
anchor_correlation: "BULLISH" | "BEARISH" | "NEUTRAL" (del shadow_cache)
Reglas:
LONG + BEARISH anchor + precio ABOVE VWAP → GATEKEEPER REJECT
SHORT + BULLISH anchor + precio BELOW VWAP → GATEKEEPER REJECT
Retorna (blocked: bool, reason: str)
"""
corr = anchor_correlation.upper() if anchor_correlation else "NEUTRAL"
if trade_side == "long":
if corr == "BEARISH" and price_vs_vwap == "ABOVE":
return True, "anchor_bearish_long_above_vwap"
# Señal adicional: si sesgo A también es bear, refuerza el bloqueo
if corr == "BEARISH" and a_bias == "bear":
return True, "anchor_bearish_a_bear_confluence"
elif trade_side == "short":
if corr == "BULLISH" and price_vs_vwap == "BELOW":
return True, "anchor_bullish_short_below_vwap"
if corr == "BULLISH" and a_bias == "bull":
return True, "anchor_bullish_a_bull_confluence"
return False, "anchor_ok"
# ══════════════════════════════════════════════════════════════════════════════
# HERRAMIENTA 4: NEWS IMPACT WEIGHT — Cerebro E integration
# ══════════════════════════════════════════════════════════════════════════════
def _check_macro_kill(
trade_side: str,
e_sentiment: str,
macro_impact_weight: int,
a_bias: str = "N",
) -> tuple:
"""
Si el peso institucional de noticias es bearish y alto → MACRO_KILL.
macro_impact_weight: 0=standard, 1=alert, 2=institutional
Reglas:
weight=2 (institutional) + sentiment S (bearish) → KILL long
weight=2 (institutional) + sentiment B (bullish) → KILL short
"""
if macro_impact_weight < 2:
return False, "macro_weight_low"
sent = e_sentiment.upper() if e_sentiment else "N"
if trade_side == "long" and sent == "S":
return True, f"macro_institutional_bearish weight={macro_impact_weight}"
if trade_side == "short" and sent == "B":
return True, f"macro_institutional_bullish_vs_short weight={macro_impact_weight}"
return False, "macro_neutral"
# ══════════════════════════════════════════════════════════════════════════════
# SHORT HARD STOP — Cerebro H Integration
# ══════════════════════════════════════════════════════════════════════════════
def _check_short_hard_stop(
trade_side: str,
current_price: float,
entry_price: float,
account_balance: float = 72696.0,
) -> tuple:
"""
Para SHORT: si el precio sube más del SHORT_STOP_PCT desde entrada → KILL incondicional.
Garantiza que el riesgo nunca supere MAX_ACCOUNT_RISK_PCT del capital.
Cálculo del lot size máximo para short:
max_loss_usd = account_balance × MAX_ACCOUNT_RISK_PCT
stop_distance = entry_price × SHORT_STOP_PCT
max_qty = max_loss_usd / stop_distance
Retorna (triggered: bool, stop_price: float, max_qty_hint: float)
"""
if trade_side != "short" or entry_price <= 0:
return False, 0.0, 0.0
short_stop_price = round(entry_price * (1.0 + SHORT_STOP_PCT), 8)
max_loss_usd = account_balance * MAX_ACCOUNT_RISK_PCT
stop_distance = entry_price * SHORT_STOP_PCT
max_qty = round(max_loss_usd / max(stop_distance, 1e-9), 6)
triggered = current_price >= short_stop_price
return triggered, short_stop_price, max_qty
# ══════════════════════════════════════════════════════════════════════════════
# MOTOR DE ORDERFLOW v5.0 (heredado — NO MODIFICADO)
# ══════════════════════════════════════════════════════════════════════════════
def _compute_orderflow_from_bars(bars: list) -> dict:
if not bars or len(bars) < 3:
return {
"delta_cvd": 0.0, "prev_delta_cvd": 0.0, "absorption_score": 0.5,
"imbalance_score": 0.0, "pv_divergence": False,
"cvd_price_divergence": False, "wick_rejection": False,
"vol_climax": False, "delta_acceleration": 0.0,
"delta_accelerating_negative": False, "bars_used": 0,
}
recent = bars[-15:]
total_vol = sum(b.get("volume", b.get("v", 1)) for b in recent) or 1
deltas = []
for b in recent:
o = b.get("open", b.get("o", 0)); h = b.get("high", b.get("h", 0))
l = b.get("low", b.get("l", 0)); c = b.get("close", b.get("c", 0))
vol = b.get("volume", b.get("v", 0))
if h == l or vol == 0: deltas.append(0.0); continue
close_pos = (c - l) / (h - l)
deltas.append((close_pos * 2 - 1) * vol)
cumulative_delta = sum(deltas)
delta_cvd = max(-1.0, min(1.0, cumulative_delta / total_vol))
half = max(1, len(deltas) // 2)
prev_vol = sum(b.get("volume", b.get("v", 1)) for b in recent[:half]) or 1
prev_delta_cvd = max(-1.0, min(1.0, sum(deltas[:half]) / prev_vol))
if len(deltas) >= 6:
v_r = sum(b.get("volume", b.get("v", 1)) for b in recent[-3:]) or 1
v_p = sum(b.get("volume", b.get("v", 1)) for b in recent[-6:-3]) or 1
delta_acceleration = sum(deltas[-3:]) / v_r - sum(deltas[-6:-3]) / v_p
else:
delta_acceleration = 0.0
delta_accelerating_negative = (delta_cvd < -0.10 and delta_acceleration < -0.10)
absorption_scores = []
for b in recent:
o = b.get("open", b.get("o", 0)); h = b.get("high", b.get("h", 0))
l = b.get("low", b.get("l", 0)); c = b.get("close", b.get("c", 0))
vol = b.get("volume", b.get("v", 0))
body = abs(c - o); rang = h - l
if rang == 0 or total_vol == 0: continue
absorption = (vol / (total_vol / len(recent))) * (1.0 - body / rang)
absorption_scores.append(absorption)
absorption_score = min(1.0, sum(absorption_scores) / max(len(absorption_scores), 1) / 2.0)
bull_vol = sum(b.get("volume", b.get("v", 0)) for b in recent if b.get("close", b.get("c", 0)) >= b.get("open", b.get("o", 0)))
bear_vol = sum(b.get("volume", b.get("v", 0)) for b in recent if b.get("close", b.get("c", 0)) < b.get("open", b.get("o", 0)))
imbalance_score = bear_vol / (bull_vol + bear_vol + 1)
pv_divergence = False
if len(recent) >= 5:
closes_r = [b.get("close", b.get("c", 0)) for b in recent[-5:]]
vols_r = [b.get("volume", b.get("v", 0)) for b in recent[-5:]]
pv_divergence = (closes_r[-1] > closes_r[0]) and (vols_r[-1] < vols_r[0])
cvd_price_divergence = False
if len(recent) >= 6:
current_close = recent[-1].get("close", recent[-1].get("c", 0))
mid_close = recent[len(recent)//2].get("close", recent[len(recent)//2].get("c", 0))
cvd_price_divergence = (current_close > mid_close) and (delta_cvd < prev_delta_cvd)
wick_rejection = False
for b in recent[-5:]:
o = b.get("open", b.get("o", 0)); h = b.get("high", b.get("h", 0))
c_ = b.get("close", b.get("c", 0))
body = abs(c_ - o); upper_wick = h - max(o, c_)
if body > 0 and upper_wick > body * 1.5:
wick_rejection = True; break
avg_vol = total_vol / len(recent)
vol_climax = any(
b.get("close", b.get("c", 0)) < b.get("open", b.get("o", 0)) and
b.get("volume", b.get("v", 0)) > avg_vol * 2.0
for b in recent[-3:])
return {
"delta_cvd": round(delta_cvd, 4),
"prev_delta_cvd": round(prev_delta_cvd, 4),
"absorption_score": round(absorption_score, 4),
"imbalance_score": round(imbalance_score, 4),
"pv_divergence": pv_divergence,
"cvd_price_divergence": cvd_price_divergence,
"wick_rejection": wick_rejection,
"vol_climax": vol_climax,
"delta_acceleration": round(delta_acceleration, 4),
"delta_accelerating_negative": delta_accelerating_negative,
"bars_used": len(recent),
}
# ══════════════════════════════════════════════════════════════════════════════
# OSI v5.0 con ajuste bidireccional v6.0
# ══════════════════════════════════════════════════════════════════════════════
def _compute_osi(of: dict, bars_held: int, current_pnl: float,
trade_type: str, trade_side: str = "long") -> tuple:
"""
Para SHORT: invertimos la presión dominante.
delta_cvd positivo en un short = precio sube = presión de cierre.
"""
delta_cvd = of.get("delta_cvd", 0.0)
# Para SHORT, presión de cierre es cuando delta_cvd > 0 (precio sube)
if trade_side == "short":
# Invertir delta_cvd para que el OSI siga midiendo "presión de cierre"
effective_delta = -delta_cvd
else:
effective_delta = delta_cvd
delta_score = max(0.0, min(30.0, ((-effective_delta + 1) / 2) * 30))
imb = of.get("imbalance_score", 0.0)
# Para SHORT: imbalance bull = presión al alza = riesgo
if trade_side == "short":
imb = 1.0 - imb
imb_score = imb * 20
abs_score = of.get("absorption_score", 0.0) * 15
div_score = 0.0
if of.get("cvd_price_divergence"): div_score += 8.0
if of.get("pv_divergence"): div_score += 4.0
if of.get("wick_rejection"): div_score += 3.0
div_score = min(15.0, div_score)
stag_score = 0.0
if bars_held >= STAGNATION_BARS:
stag_score = min(10.0, (bars_held - STAGNATION_BARS + 1) * 2.0)
pnl_score = 0.0
if current_pnl < 0:
pnl_score = min(10.0, abs(current_pnl) * 100)
osi = delta_score + imb_score + abs_score + div_score + stag_score + pnl_score
osi = round(min(100.0, max(0.0, osi)), 2)
kill_score = round(osi / 100.0, 4)
kill_threshold = KILL_THRESHOLD_SCALP if trade_type == "scalp" else KILL_THRESHOLD
components = {
"delta_score": round(delta_score, 2),
"imb_score": round(imb_score, 2),
"abs_score": round(abs_score, 2),
"div_score": round(div_score, 2),
"stag_score": round(stag_score, 2),
"pnl_score": round(pnl_score, 2),
}
return osi, kill_score, kill_threshold, components
# ══════════════════════════════════════════════════════════════════════════════
# LLM PANIC TRIGGER (heredado v5.0 con fix bug raw_out)
# ══════════════════════════════════════════════════════════════════════════════
async def _llm_panic_trigger(osi: float, kill_score: float, of: dict,
sym: str, trade_type: str, bars_held: int,
trade_side: str = "long") -> dict:
t0_llm = time.perf_counter()
cvd = of.get("delta_cvd", 0.0)
cvd_div = "Y" if of.get("cvd_price_divergence") else "N"
vol_cl = "Y" if of.get("vol_climax") else "N"
accel_n = "Y" if of.get("delta_accelerating_negative") else "N"
side_t = trade_side[0].upper() # L o S
llm_prompt = (
"<|im_start|>system\n"
f'Exit trigger {side_t}. SOLO JSON: {{"action":"HOLD"}} o {{"action":"KILL_TRADE"}}. Sin pensar.\n'
"<|im_end|>\n"
"<|im_start|>user\n"
f'{{"s":"{sym[:6]}","osi":{osi:.0f},"cvd":{cvd:.2f},'
f'"div":"{cvd_div}","vc":"{vol_cl}","acc":"{accel_n}",'
f'"bh":{bars_held},"t":"{trade_type[0]}","side":"{side_t}"}}\n'
"<|im_end|>\n"
"<|im_start|>assistant\n{"
)
try:
result = await _bitnet_infer(llm_prompt)
raw_out_text = result.get("raw", "")
llm_ms = (time.perf_counter() - t0_llm) * 1000
raw_t = "{" + raw_out_text
m = re.search(r'"action"\s*:\s*"(HOLD|KILL_TRADE)"', raw_t, re.IGNORECASE)
if m:
action = m.group(1).upper()
print(f"[F/LLM] {sym}: action={action} osi={osi:.0f} side={side_t} | {llm_ms:.1f}ms")
return {"action": action, "_llm_ms": round(llm_ms, 1)}
action = "KILL_TRADE" if "KILL" in raw_t.upper() else "HOLD"
print(f"[F/LLM] {sym}: partial={action} | {llm_ms:.1f}ms")
return {"action": action, "_llm_ms": round(llm_ms, 1)}
except Exception as e:
llm_ms = (time.perf_counter() - t0_llm) * 1000
print(f"[F/LLM-ERR] {sym}: {type(e).__name__} | {llm_ms:.1f}ms → OSI fallback")
action = "KILL_TRADE" if osi > OSI_FALLBACK_KILL else "HOLD"
return {"action": action, "_llm_ms": round(llm_ms, 1), "_fallback": True}
# ══════════════════════════════════════════════════════════════════════════════
# PIPELINE PRINCIPAL v6.0 — POLIMÓRFICO
# ══════════════════════════════════════════════════════════════════════════════
async def _liquidate(payload: dict) -> dict:
t0 = time.perf_counter()
sym = str(payload.get("symbol", "?"))
bars = payload.get("bars", [])
bars_held = int(payload.get("bars_held", 0))
current_pnl = float(payload.get("current_pnl", 0.0)) # decimal, ej 0.0102 = +1.02%
entry_price = float(payload.get("entry_price", 0.0))
trade_type = str(payload.get("trade_type", "scalp")).lower()
tp = float(payload.get("tp", 0.0))
sl = float(payload.get("sl", 0.0))
current_price = float(payload.get("current_price", entry_price))
# ── v6.0: Extracción elástica multi-capa del snapshot cognitivo ──────────
# El payload puede venir con las métricas en la raíz, bajo "metrics",
# bajo "cognition", o bajo "risk" según quién lo envíe (swarm_engine,
# orders_processor, o el nuevo data_manager_anchors).
# La cascada de .get() garantiza que NUNCA quede vacío ni lance excepción.
_metrics = payload.get("metrics", {}) or {}
_cognition = payload.get("cognition", {}) or {}
_risk = payload.get("risk", {}) or {}
def _mget(key: str, default, cast=str):
"""Busca una clave en raíz → metrics → cognition → risk, con cast seguro."""
for src in (payload, _metrics, _cognition, _risk):
v = src.get(key)
if v is not None:
try:
return cast(v)
except (ValueError, TypeError):
continue
return default
trade_side = _mget("trade_side", "long", str).lower()
c_style = _mget("C_style", _mget("c_style", "SCALP", str), str).upper()
a_bias_raw = _mget("A_bias", _mget("a_bias", "N", str), str)
a_bias = a_bias_raw.lower() # "bull" | "bear" | "neutral" | "l" | "s" | "n"
# Normaliza tokens ternarios de A (L/S/N) a los equivalentes semánticos
_a_map = {"l": "bull", "s": "bear", "n": "neutral"}
a_bias = _a_map.get(a_bias, a_bias)
e_sentiment_raw = _mget("E_sentiment", _mget("e_sentiment", "N", str), str)
e_sentiment = e_sentiment_raw.upper()[:1] # primer char: "B" | "N" | "S"
try:
macro_impact_weight = int(_mget("macro_impact_weight", 0, int))
except (ValueError, TypeError):
macro_impact_weight = 0
anchor_correlation = _mget("anchor_correlation", "NEUTRAL", str).upper()
try:
velocity_zero_bars = int(_mget("velocity_zero_bars", 0, int))
except (ValueError, TypeError):
velocity_zero_bars = 0
try:
account_balance = float(
payload.get("account_balance",
_risk.get("account_balance",
_metrics.get("account_balance", 72696.0)))
)
except (ValueError, TypeError):
account_balance = 72696.0
# Override VWAP si ya viene calculado por el radar (evita recalcularlo desde barras)
_pvwap_pre = _mget("price_vs_vwap", None, str)
_pvwap_pre = _pvwap_pre.upper() if _pvwap_pre and _pvwap_pre.upper() in ("ABOVE","BELOW","AT") else None
# Override market_exhaustion si ya viene del radar
_exh_pre = _mget("market_exhaustion", None, str)
_exh_pre = (str(_exh_pre).lower() == "true") if _exh_pre is not None else None
# current_pnl en porcentaje para CTS
current_pnl_pct = current_pnl * 100.0 # 0.0102 → 1.02%
# Diagnóstico de origen del payload
_has_radar_data = bool(_metrics or _cognition)
if not _has_radar_data and bars_held > 2:
print(f"[F/PAYLOAD] {sym}: payload plano sin metrics/cognition — usando defaults seguros")
# ── FASE MATH: OrderFlow ──────────────────────────────────────────────────
of = _compute_orderflow_from_bars(bars)
# ── VWAP (Herramienta 1) — usa el pre-calculado del radar si existe ───────
vwap_from_bars, pvwap_from_bars = _compute_vwap(bars)
if _pvwap_pre is not None:
vwap, price_vs_vwap = vwap_from_bars, _pvwap_pre
else:
vwap, price_vs_vwap = vwap_from_bars, pvwap_from_bars
# ── Entropy Z-Score / Market Exhaustion (Herramienta 2) ───────────────────
market_exhaustion, exhaustion_zscore, rsi_micro = _compute_exhaustion_zscore(bars)
if _exh_pre is not None:
market_exhaustion = _exh_pre
# ── OSI (bidireccional v6.0) ──────────────────────────────────────────────
osi, kill_score, kill_threshold, osi_components = _compute_osi(
of, bars_held, current_pnl, trade_type, trade_side
)
math_ms = (time.perf_counter() - t0) * 1000
# Zona OSI
if osi >= OSI_FAST_EXIT:
osi_zone = "fast_exit"
elif osi >= OSI_KILL_ZONE:
osi_zone = "kill"
elif osi <= OSI_HOLD_ZONE:
osi_zone = "hold"
else:
osi_zone = "watch"
print(f"[F/MATH] {sym}({trade_side}): OSI={osi:.1f} zone={osi_zone} "
f"vwap={price_vs_vwap} exh={market_exhaustion}(z={exhaustion_zscore:.2f}) "
f"| {math_ms:.1f}ms")
# ── COGNITIVE TRAILING STOP (máxima prioridad sobre profit flotante) ──────
cts_triggered, cts_reason = _cognitive_trailing_stop(
current_pnl_pct, velocity_zero_bars, c_style,
trade_side, market_exhaustion, exhaustion_zscore,
)
if cts_triggered:
close_act = "buy" if trade_side == "short" else "sell"
total_ms = (time.perf_counter() - t0) * 1000
print(f"[F/CTS] {sym}: COGNITIVE TRAILING STOP → {close_act.upper()} | {cts_reason} | {total_ms:.1f}ms")
return _build_result(
decision="SELL", confidence=0.92, kill_score=kill_score,
trigger=f"cognitive_trailing_stop|{cts_reason}",
urgency="critical", osi=osi, osi_zone=osi_zone,
vwap=vwap, price_vs_vwap=price_vs_vwap,
market_exhaustion=market_exhaustion, exhaustion_zscore=exhaustion_zscore,
anchor_blocked=False, macro_kill=False,
cts_triggered=True, short_stop_triggered=False,
trade_side=trade_side, close_action=close_act,
c_style=c_style, of=of, osi_components=osi_components,
math_ms=math_ms, llm_ms=0.0, total_ms=total_ms,
rsi_micro=rsi_micro,
)
# ── SHORT HARD STOP (Herramienta H — incondicional) ──────────────────────
short_stop_hit, short_stop_price, max_qty_hint = _check_short_hard_stop(
trade_side, current_price, entry_price, account_balance
)
if short_stop_hit:
total_ms = (time.perf_counter() - t0) * 1000
print(f"[F/SHORT-STOP] {sym}: SHORT HARD STOP precio={current_price:.6f} >= stop={short_stop_price:.6f} | {total_ms:.1f}ms")
return _build_result(
decision="SELL", confidence=0.99, kill_score=1.0,
trigger=f"short_hard_stop|price={current_price:.4f}>=stop={short_stop_price:.4f}",
urgency="critical", osi=osi, osi_zone=osi_zone,
vwap=vwap, price_vs_vwap=price_vs_vwap,
market_exhaustion=market_exhaustion, exhaustion_zscore=exhaustion_zscore,
anchor_blocked=False, macro_kill=False,
cts_triggered=False, short_stop_triggered=True,
trade_side=trade_side, close_action="buy",
c_style=c_style, of=of, osi_components=osi_components,
math_ms=math_ms, llm_ms=0.0, total_ms=total_ms,
rsi_micro=rsi_micro,
extra={"short_stop_price": short_stop_price, "max_qty_hint": max_qty_hint},
)
# ── MACRO KILL (Herramienta 4) ────────────────────────────────────────────
macro_kill, macro_reason = _check_macro_kill(
trade_side, e_sentiment, macro_impact_weight, a_bias
)
if macro_kill:
close_act = "buy" if trade_side == "short" else "sell"
total_ms = (time.perf_counter() - t0) * 1000
print(f"[F/MACRO] {sym}: MACRO KILL → {macro_reason} | {total_ms:.1f}ms")
return _build_result(
decision="SELL", confidence=0.88, kill_score=kill_score,
trigger=f"macro_kill|{macro_reason}",
urgency="high", osi=osi, osi_zone=osi_zone,
vwap=vwap, price_vs_vwap=price_vs_vwap,
market_exhaustion=market_exhaustion, exhaustion_zscore=exhaustion_zscore,
anchor_blocked=False, macro_kill=True,
cts_triggered=False, short_stop_triggered=False,
trade_side=trade_side, close_action=close_act,
c_style=c_style, of=of, osi_components=osi_components,
math_ms=math_ms, llm_ms=0.0, total_ms=total_ms,
rsi_micro=rsi_micro,
)
# ── ANCHOR CORRELATION CIRCUIT BREAKER (Herramienta 3) ────────────────────
anchor_blocked, anchor_reason = _check_anchor_correlation(
trade_side, anchor_correlation, price_vs_vwap, a_bias
)
if anchor_blocked:
# Solo bloquea NUEVAS entradas; si ya estamos en posición, no cierra
# (el bloqueo de entrada lo maneja orders_processor.py con este flag)
print(f"[F/ANCHOR] {sym}: ANCHOR BLOCKED ({anchor_reason}) — flag en payload")
# ── PIPELINE OSI + LLM (heredado v5.0) ────────────────────────────────────
llm_ms = 0.0
close_action = "buy" if trade_side == "short" else "sell"
if osi_zone == "fast_exit":
decision = "SELL"
confidence = round(kill_score, 4)
trigger = f"osi_fast_exit={osi:.0f}"
urgency = "critical"
elif osi_zone == "hold":
decision = "HOLD"
confidence = round(1.0 - kill_score, 4)
trigger = f"osi_hold={osi:.0f}"
urgency = "low"
elif osi_zone == "kill" and kill_score >= kill_threshold:
decision = "SELL"
confidence = round(kill_score, 4)
trigger = f"osi_kill_math={osi:.0f}"
urgency = "high"
else:
llm_result = await _llm_panic_trigger(
osi, kill_score, of, sym, trade_type, bars_held, trade_side
)
llm_ms = llm_result.get("_llm_ms", 0.0)
action = llm_result.get("action", "HOLD")
decision = "SELL" if action == "KILL_TRADE" else "HOLD"
confidence = round(kill_score if decision == "SELL" else 1.0 - kill_score, 4)
trigger = f"osi_watch_llm={osi:.0f}_action={action}"
urgency = "medium" if decision == "SELL" else "low"
# Override kill_score >= 0.85 (heredado)
if kill_score >= 0.85 and decision == "HOLD":
decision = "SELL"
confidence = kill_score
trigger = f"f_override_osi={osi:.0f}"
urgency = "critical"
total_ms = (time.perf_counter() - t0) * 1000
print(f"[F/TOTAL] {sym}({trade_side}): {decision} osi={osi:.0f} zone={osi_zone} "
f"conf={confidence:.3f} math={math_ms:.1f}ms llm={llm_ms:.1f}ms total={total_ms:.1f}ms")
return _build_result(
decision=decision, confidence=confidence, kill_score=kill_score,
trigger=trigger, urgency=urgency,
osi=osi, osi_zone=osi_zone,
vwap=vwap, price_vs_vwap=price_vs_vwap,
market_exhaustion=market_exhaustion, exhaustion_zscore=exhaustion_zscore,
anchor_blocked=anchor_blocked, macro_kill=macro_kill,
cts_triggered=False, short_stop_triggered=False,
trade_side=trade_side, close_action=close_action,
c_style=c_style, of=of, osi_components=osi_components,
math_ms=math_ms, llm_ms=llm_ms, total_ms=total_ms,
rsi_micro=rsi_micro,
)
def _build_result(
decision, confidence, kill_score, trigger, urgency,
osi, osi_zone, vwap, price_vs_vwap,
market_exhaustion, exhaustion_zscore,
anchor_blocked, macro_kill,
cts_triggered, short_stop_triggered,
trade_side, close_action, c_style,
of, osi_components, math_ms, llm_ms, total_ms,
rsi_micro=50.0, extra: dict = None,
) -> dict:
"""Construye el payload de respuesta unificado v6.0."""
result = {
# Core (compatibilidad v5.0)
"decision": decision,
"confidence": confidence,
"kill_score": kill_score,
"trigger": trigger,
"urgency": urgency,
"action": decision, # alias legacy
"kill": decision == "SELL",
# v6.0: Bidireccional
"trade_side": trade_side,
"close_action": close_action, # "sell"=cierra long, "buy"=cierra short
"c_style": c_style,
# v6.0: Herramientas cognitivas
"vwap": round(vwap, 8),
"price_vs_vwap": price_vs_vwap,
"market_exhaustion": market_exhaustion,
"exhaustion_zscore": exhaustion_zscore,
"rsi_micro": rsi_micro,
"anchor_blocked": anchor_blocked,
"macro_kill": macro_kill,
"cognitive_trailing_triggered": cts_triggered,
"short_stop_triggered": short_stop_triggered,
# OSI
"osi": osi,
"osi_zone": osi_zone,
"osi_components": osi_components,
# OrderFlow (legacy)
"orderflow": of,
# Telemetría
"_math_ms": round(math_ms, 2),
"_llm_ms": round(llm_ms, 1),
"_total_ms": round(total_ms, 1),
"cerebro": "F",
"version": "6.0-APEX-POLIMÓRFICO",
}
if extra:
result.update(extra)
return result
# ── Endpoints FastAPI ─────────────────────────────────────────────────────────
@app.get("/")
async def root():
return {"status": "online", "cerebro": "F", "version": "6.0-APEX-POLIMÓRFICO"}
@app.get("/health")
async def health():
return {
"status": "online", "cerebro": "F", "version": "6.0-APEX-POLIMÓRFICO",
"features": ["cognitive_trailing_stop", "bidirectional_long_short",
"vwap_dynamic", "entropy_zscore_obi", "cross_asset_correlation",
"news_impact_weight", "short_hard_stop"],
"osi_thresholds": {"fast_exit": OSI_FAST_EXIT, "kill_zone": OSI_KILL_ZONE, "hold_zone": OSI_HOLD_ZONE},
"cts_config": {
"profit_floor_scalp_pct": CTS_PROFIT_FLOOR_SCALP,
"profit_floor_momentum_pct": CTS_PROFIT_FLOOR_MOMENTUM,
"velocity_stall_bars_scalp": CTS_VELOCITY_STALL_BARS,
"velocity_stall_bars_mom": CTS_VELOCITY_STALL_BARS_M,
},
"short_config": {"stop_pct": SHORT_STOP_PCT, "max_account_risk_pct": MAX_ACCOUNT_RISK_PCT},
}
@app.post("/analyze_exit")
async def analyze_exit(request: Request):
try: payload = await request.json()
except Exception: return JSONResponse({"error": "JSON inválido"}, status_code=400)
result = await _liquidate(payload)
return JSONResponse(result)
@app.post("/analyze")
async def analyze_compat(request: Request):
return await analyze_exit(request)
@app.post("/analyze_batch")
async def analyze_batch(request: Request):
"""Procesamiento batch paralelo — compatible con CerebroFMux."""
import asyncio
try: payload = await request.json()
except Exception: return JSONResponse({"error": "JSON inválido"}, status_code=400)
positions = payload.get("positions", [])
if not positions:
return JSONResponse({"results": [], "latency_ms": 0})
t0 = time.perf_counter()
tasks = [_liquidate(pos) for pos in positions]
results = await asyncio.gather(*tasks, return_exceptions=True)
safe_results = []
for r in results:
if isinstance(r, Exception):
safe_results.append({"decision": "HOLD", "confidence": 0.5,
"kill_score": 0.25, "trigger": f"batch_error:{str(r)[:40]}",
"urgency": "low", "action": "HOLD", "kill": False})
else:
safe_results.append(r)
lat = round((time.perf_counter() - t0) * 1000, 1)
print(f"[F/BATCH] {len(safe_results)} posiciones en {lat}ms")
return JSONResponse({"results": safe_results, "latency_ms": lat})
@app.post("/orderflow")
async def orderflow_only(request: Request):
try: payload = await request.json()
except Exception: return JSONResponse({"error": "JSON inválido"}, status_code=400)
bars = payload.get("bars", [])
of = _compute_orderflow_from_bars(bars)
vwap, pvwap = _compute_vwap(bars)
exh, ezsc, rsi = _compute_exhaustion_zscore(bars)
of.update({"vwap": vwap, "price_vs_vwap": pvwap,
"market_exhaustion": exh, "exhaustion_zscore": ezsc, "rsi_micro": rsi})
return JSONResponse(of)
print("[F] ✅ The Liquidator v6.0 APEX POLIMÓRFICO — CTS + Bidireccional + 4 Herramientas cognitivas")