Spaces:
Sleeping
Sleeping
| """ | |
| server_G.py β Cerebro G β LiquidityStrategist v4.0 (APEX KMeans+LLM Auditor) | |
| ============================================================================= | |
| v4.0 β RemodelaciΓ³n segΓΊn Plan Maestro APEX-ASYMMETRIC SWARM (Fase 3): | |
| ARQUITECTURA LLM+MATH (filosofΓa de Cerebro A): | |
| FASE MATH (<5ms) β LocalizaciΓ³n de zonas institucionales: | |
| Β· Volume Profile completo: POC, VAL, VAH (70% del volumen). | |
| Β· K-Means Clustering (numpy puro): agrupa los picos de volumen histΓ³rico | |
| en K=3 clusters para detectar los Nodos de Alto Volumen (HVN) EXACTOS. | |
| Sin aproximaciones: el centroide de cada cluster = HVN real del mercado. | |
| Β· Fibonacci Pivots: niveles F236, F382, F500, F618, F786. | |
| Β· Breakout Score: quΓ© tan cerca estΓ‘ el precio de cruzar un HVN. | |
| Β· SeΓ±al clara (POC_HOT, POC_BOUNCE, ABOVE_VAH, BELOW_VAL): retorno | |
| inmediato sin LLM (<5ms) β [G/MATH-CLEAR]. | |
| FASE LLM (Auditor de Rupturas) β SOLO en zona ambigua: | |
| Β· El LLM actΓΊa ΓNICAMENTE como auditor de breakouts. | |
| Β· Recibe: {"hvn_dist_pct": X, "price_vs_poc": Y, "vol_trend": "up|down"} | |
| Β· Pregunta: ΒΏEs este breakout de HVN genuino o una trampa institucional? | |
| Β· Output estricto: {"decision": "BUY|WAIT", "type": "scalp|long", "conf": 0.XX} | |
| Β· Si LLM falla β Math da la decisiΓ³n conservadora (WAIT). | |
| ASIMETRΓA por RANK (heredada de v3.1, mejorada): | |
| RANK 1: Math + LLM si ambiguo | |
| RANK 2+: 100% Math (LLM PROHIBIDA) β [G/MATH-ONLY] | |
| ENDPOINTS: | |
| POST /batch β lote asimΓ©trico (endpoint principal) | |
| POST /analyze_liquidity β anΓ‘lisis individual | |
| GET /health | |
| GET / | |
| TELEMETRΓA: | |
| [G/MATH-CLEAR] sym: decision conf | Xms | |
| [G/LLM] sym: decision conf | Xms (auditor de ruptura) | |
| [G/MATH-ONLY] sym: decision conf | Xms (rank 2+) | |
| [G/BATCH] N activos | LLM=X Clear=X Math=X | Xms | |
| """ | |
| import os, json, re, time, threading, math | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import JSONResponse | |
| import httpx # BitNet v6.0 | |
| try: | |
| import numpy as np | |
| _NP_OK = True | |
| except ImportError: | |
| _NP_OK = False | |
| app = FastAPI(title="Cerebro G β LiquidityStrategist v4.0 KMeans+LLM") | |
| # ββ ConfiguraciΓ³n ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| MODEL_PATH = os.environ.get("MODEL_PATH", "/models/ggml-model-i2_s.gguf") | |
| N_CTX = int(os.environ.get("N_CTX", "128")) | |
| N_THREADS = int(os.environ.get("N_THREADS", "2")) | |
| N_BATCH = int(os.environ.get("N_BATCH", "64")) | |
| # ββ BitNet Infrastructure v6.0 βββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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.05, | |
| "top_p": 0.95, "top_k": 5, "repeat_penalty": 1.0, "stream": False, | |
| "stop": ["<|eot_id|>", "<|end_of_text|>"]} | |
| 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]} | |
| VP_MIN_BARS = int(float(os.environ.get("VP_MIN_BARS", "5"))) | |
| KILL_ZONE_PCT = float(os.environ.get("KILL_ZONE_PCT", "0.020")) | |
| HOT_ZONE_PCT = float(os.environ.get("HOT_ZONE_PCT", "0.005")) | |
| CONF_VP_STRONG = float(os.environ.get("CONF_VP_STRONG", "0.72")) | |
| CONF_VP_WEAK = float(os.environ.get("CONF_VP_WEAK", "0.28")) | |
| CACHE_TTL = float(os.environ.get("CACHE_TTL", "90.0")) | |
| AMBIG_CONF_LOW = float(os.environ.get("AMBIG_CONF_LOW", "0.48")) | |
| AMBIG_CONF_HIGH = float(os.environ.get("AMBIG_CONF_HIGH", "0.58")) | |
| KMEANS_K = int(os.environ.get("KMEANS_K", "3")) # clusters HVN | |
| # ββ BitNet v6.0: modelo servido por llama-server externo (ik_llama.cpp) ββββββ | |
| # llm = Llama(...) eliminado β inferencias via HTTP a BITNET_BASE | |
| print(f"[G] β LiquidityStrategist v4.0 KMeans+BitNet β numpy={'β ' if _NP_OK else 'β οΈ'}") | |
| print(f"[G] BitNet server: {BITNET_BASE} | n_ctx={N_CTX}") | |
| # ββ Cache ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _G_CACHE: dict = {} | |
| _G_LOCK = threading.Lock() | |
| def _cache_get(sym: str): | |
| with _G_LOCK: | |
| e = _G_CACHE.get(sym) | |
| if e and (time.time() - e["ts"]) < CACHE_TTL: | |
| age = int(time.time() - e["ts"]) | |
| return {**e["result"], "_cached": True, "_cache_age_s": age} | |
| return None | |
| def _cache_set(sym: str, result: dict): | |
| with _G_LOCK: | |
| _G_CACHE[sym] = {"result": result, "ts": time.time()} | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # HELPER: extracciΓ³n robusta de barras desde cualquier payload | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _extract_bars(payload: dict) -> list: | |
| bars = payload.get("bars") or payload.get("b") or [] | |
| return bars if isinstance(bars, list) else [] | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # K-MEANS CLUSTERING (numpy puro β sin sklearn) | |
| # Detecta Nodos de Alto Volumen (HVN) exactos desde el Volume Profile | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _kmeans_hvn(price_levels: list, vol_levels: list, k: int = 3, | |
| max_iter: int = 20) -> list: | |
| """ | |
| K-Means simplificado para detectar HVN (High Volume Nodes). | |
| Entrada: | |
| price_levels: lista de precios de cada nivel del VP | |
| vol_levels: volumen correspondiente a cada nivel | |
| k: nΓΊmero de clusters (default 3 = bajo/medio/alto volumen) | |
| Retorna: | |
| lista de k HVNs (precios de los centroides con mayor volumen acumulado) | |
| Algoritmo: | |
| 1. Inicializar centroides con K-Means++ (distribuciΓ³n uniforme en el rango) | |
| 2. Iterar: asignar cada nivel al centroide mΓ‘s cercano | |
| 3. Recalcular centroides como promedio ponderado por volumen | |
| 4. Seleccionar los k centroides con mayor volumen promedio del cluster | |
| """ | |
| if not price_levels or len(price_levels) < k: | |
| return price_levels[:k] if price_levels else [] | |
| if _NP_OK: | |
| prices = np.array(price_levels, dtype=float) | |
| vols = np.array(vol_levels, dtype=float) | |
| # InicializaciΓ³n: centroides uniformemente distribuidos en el rango | |
| p_min, p_max = float(prices.min()), float(prices.max()) | |
| if p_min == p_max: | |
| return [p_min] * k | |
| centroids = np.linspace(p_min, p_max, k) | |
| for _ in range(max_iter): | |
| # AsignaciΓ³n: cada precio al centroide mΓ‘s cercano | |
| dists = np.abs(prices[:, None] - centroids[None, :]) | |
| labels = np.argmin(dists, axis=1) | |
| # ActualizaciΓ³n: centroide = media ponderada por volumen del cluster | |
| new_centroids = np.zeros(k) | |
| for j in range(k): | |
| mask = labels == j | |
| if mask.sum() > 0: | |
| w = vols[mask] | |
| new_centroids[j] = float(np.average(prices[mask], weights=w)) | |
| else: | |
| new_centroids[j] = centroids[j] | |
| if np.allclose(centroids, new_centroids, rtol=1e-4): | |
| break | |
| centroids = new_centroids | |
| # Seleccionar los HVNs: centroides con mayor volumen promedio del cluster | |
| cluster_vols = [] | |
| labels = np.argmin(np.abs(prices[:, None] - centroids[None, :]), axis=1) | |
| for j in range(k): | |
| mask = labels == j | |
| avg_vol = float(vols[mask].mean()) if mask.sum() > 0 else 0.0 | |
| cluster_vols.append((avg_vol, float(centroids[j]))) | |
| # Ordenar por volumen descendente y retornar precios | |
| cluster_vols.sort(key=lambda x: x[0], reverse=True) | |
| return [round(p, 6) for _, p in cluster_vols] | |
| else: | |
| # Fallback puro-Python: Top-K por volumen sin clustering real | |
| paired = sorted(zip(vol_levels, price_levels), reverse=True) | |
| return [round(p, 6) for _, p in paired[:k]] | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # VOLUME PROFILE + FIBONACCI + HVN K-MEANS (FASE MATH COMPLETA) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _compute_vp_kmeans(bars: list, px: float) -> dict: | |
| """ | |
| Volume Profile completo + K-Means HVN + Fibonacci en <5ms. | |
| Nuevo en v4.0: | |
| hvn_levels: lista de 3 HVNs exactos por K-Means (precios) | |
| hvn_nearest: HVN mΓ‘s cercano al precio actual | |
| hvn_dist_pct: distancia % del precio al HVN mΓ‘s cercano | |
| breakout_score: quΓ© tan cerca estΓ‘ el precio de cruzar un HVN (0-1) | |
| vol_trend: tendencia del volumen (last 3 vs prev 3 barras) | |
| """ | |
| t0 = time.perf_counter() | |
| closes, volumes = [], [] | |
| for b in bars: | |
| c = b.get("c") or b.get("close") or 0.0 | |
| v = b.get("v") or b.get("volume") or 0.0 | |
| if c > 0: | |
| closes.append(float(c)) | |
| volumes.append(float(v) or 1.0) | |
| def math_ms(): | |
| return (time.perf_counter() - t0) * 1000 | |
| if len(closes) < VP_MIN_BARS or px <= 0: | |
| return { | |
| "poc": px, "val": px, "vah": px, | |
| "poc_dist_pct": 0.0, "zone": "degenerate", | |
| "conf_math": CONF_VP_WEAK, "is_ambiguous": False, | |
| "fib_levels": {}, "dec_math": "WAIT", | |
| "hvn_levels": [], "hvn_nearest": px, "hvn_dist_pct": 0.0, | |
| "breakout_score": 0.0, "vol_trend": "flat", | |
| "logic": f"Sin datos VP ({len(closes)} barras < {VP_MIN_BARS}). Degradado.", | |
| "_math_ms": round(math_ms(), 2), | |
| } | |
| price_min, price_max = min(closes), max(closes) | |
| # ββ Fibonacci βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| fib_range = price_max - price_min | |
| fib_levels = {} | |
| if fib_range > 0: | |
| for ratio, name in [(0.236, "F236"), (0.382, "F382"), (0.500, "F500"), | |
| (0.618, "F618"), (0.786, "F786")]: | |
| fib_levels[name] = round(price_max - fib_range * ratio, 6) | |
| if price_max == price_min: | |
| poc = val = vah = price_min | |
| conf_math = CONF_VP_WEAK | |
| zone = "degenerate" | |
| logic = f"VAL=VAH=${poc:.4f}. Sin rango detectable." | |
| poc_dist_pct = 0.0 | |
| vp_prices = [price_min] * len(closes) | |
| vp_vols = volumes | |
| else: | |
| n_levels = min(10, len(closes)) | |
| step = (price_max - price_min) / n_levels | |
| levels = [] | |
| for i in range(n_levels): | |
| ll = price_min + i * step | |
| lh = ll + step | |
| lv = sum(v for c, v in zip(closes, volumes) if ll <= c < lh) | |
| mid = (ll + lh) / 2 | |
| levels.append({"low": ll, "high": lh, "mid": mid, "vol": lv}) | |
| poc_level = max(levels, key=lambda x: x["vol"]) | |
| poc = poc_level["mid"] | |
| total_vol = sum(lv["vol"] for lv in levels) or 1.0 | |
| sorted_levels = sorted(levels, key=lambda x: x["vol"], reverse=True) | |
| cum_vol, val, vah = 0.0, poc, poc | |
| for lv in sorted_levels: | |
| cum_vol += lv["vol"] | |
| val = min(val, lv["low"]) | |
| vah = max(vah, lv["high"]) | |
| if cum_vol >= total_vol * 0.70: | |
| break | |
| poc_dist_pct = abs(px - poc) / poc if poc > 0 else 0.0 | |
| vp_prices = [lv["mid"] for lv in levels] | |
| vp_vols = [lv["vol"] for lv in levels] | |
| if poc_dist_pct <= HOT_ZONE_PCT: | |
| zone, conf_math = "POC_HOT", CONF_VP_STRONG | |
| logic = f"Precio en POC. Magneto {poc_dist_pct*100:.2f}% del POC ${poc:.4f}." | |
| elif poc_dist_pct <= KILL_ZONE_PCT: | |
| zone, conf_math = "POC_BOUNCE", 0.60 | |
| logic = f"Precio en Kill Zone. POC ${poc:.4f} dist={poc_dist_pct*100:.2f}%." | |
| elif px > vah: | |
| zone, conf_math = "ABOVE_VAH", 0.62 | |
| logic = f"Precio ENCIMA del VAH ${vah:.4f}. Zona de extensiΓ³n." | |
| elif px < val: | |
| zone, conf_math = "BELOW_VAL", CONF_VP_WEAK | |
| logic = f"Precio DEBAJO del VAL ${val:.4f}. Zona de riesgo." | |
| else: | |
| zone, conf_math = "VALUE_AREA", 0.50 | |
| logic = f"Precio dentro del Value Area [{val:.4f}β{vah:.4f}]." | |
| # ββ K-Means HVN βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| hvn_levels = _kmeans_hvn(vp_prices, vp_vols, k=KMEANS_K) | |
| hvn_nearest = min(hvn_levels, key=lambda h: abs(px - h)) if hvn_levels else px | |
| hvn_dist_pct = abs(px - hvn_nearest) / hvn_nearest if hvn_nearest > 0 else 0.0 | |
| # Breakout Score: 1.0 si estΓ‘ cruzando el HVN ahora, 0.0 si estΓ‘ lejos | |
| breakout_score = max(0.0, 1.0 - hvn_dist_pct / max(KILL_ZONE_PCT, 0.001)) | |
| breakout_score = round(min(1.0, breakout_score), 3) | |
| # Tendencia del volumen: last 3 vs prev 3 barras | |
| vol_trend = "flat" | |
| if len(volumes) >= 6: | |
| v_recent = sum(volumes[-3:]) / 3 | |
| v_prev = sum(volumes[-6:-3]) / 3 | |
| if v_recent > v_prev * 1.2: | |
| vol_trend = "up" | |
| elif v_recent < v_prev * 0.8: | |
| vol_trend = "down" | |
| # DecisiΓ³n matemΓ‘tica base | |
| if zone in ("POC_HOT", "POC_BOUNCE", "ABOVE_VAH"): | |
| dec_math = "BUY" | |
| elif zone == "BELOW_VAL": | |
| dec_math = "WAIT" | |
| else: | |
| dec_math = "WAIT" # VALUE_AREA ambigua β LLM decide | |
| # AmbigΓΌedad: VALUE_AREA + conf_math entre AMBIG_CONF_LOW y AMBIG_CONF_HIGH | |
| is_ambiguous = (zone == "VALUE_AREA" and | |
| AMBIG_CONF_LOW <= conf_math <= AMBIG_CONF_HIGH) | |
| return { | |
| "poc": round(poc, 6), | |
| "val": round(val, 6), | |
| "vah": round(vah, 6), | |
| "poc_dist_pct": round(poc_dist_pct, 5), | |
| "zone": zone, | |
| "conf_math": round(conf_math, 4), | |
| "is_ambiguous": is_ambiguous, | |
| "fib_levels": fib_levels, | |
| "dec_math": dec_math, | |
| # KMeans HVN (nuevos v4.0) | |
| "hvn_levels": hvn_levels, | |
| "hvn_nearest": round(hvn_nearest, 6), | |
| "hvn_dist_pct": round(hvn_dist_pct, 5), | |
| "breakout_score": breakout_score, | |
| "vol_trend": vol_trend, | |
| "logic": logic, | |
| "_math_ms": round(math_ms(), 2), | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # TIPO DE TRADE (heredado de v3.1) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _classify_trade_type(zone: str, conf: float, cronos: float, px: float, | |
| poc: float, vah: float, val: float) -> str: | |
| poc_dist = abs(px - poc) / poc if poc > 0 else 0.0 | |
| if poc_dist <= HOT_ZONE_PCT and cronos >= 0.70: | |
| return "scalp" | |
| if zone == "ABOVE_VAH" and conf >= 0.65: | |
| return "long" | |
| if zone in ("POC_BOUNCE", "POC_HOT") and conf >= 0.65: | |
| return "scalp" | |
| return "scalp" | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FASE LLM β Auditor de Rupturas (solo rank=1 en zona ambigua) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def _llm_breakout_audit(math_data: dict, sym: str, px: float, | |
| cronos: float, a_data: dict, b_data: dict) -> dict: | |
| """ | |
| El LLM actΓΊa como Auditor de Rupturas β NO calcula nada. | |
| Recibe el panel de control destilado por MATH: | |
| {"hvn_dist_pct": X, "price_vs_poc": Y, "vol_trend": "up|down", | |
| "breakout_score": Z, "zone": "VALUE_AREA", "cronos": X} | |
| Pregunta: ΒΏEs este breakout de HVN genuino o una trampa institucional? | |
| Output: {"decision": "BUY|WAIT", "type": "scalp|long", "conf": 0.XX} | |
| """ | |
| t0_llm = time.perf_counter() | |
| hvn_dist = math_data.get("hvn_dist_pct", 0.0) | |
| bk_score = math_data.get("breakout_score", 0.0) | |
| vol_trend = math_data.get("vol_trend", "flat")[0] # "u"/"d"/"f" | |
| zone = math_data.get("zone", "VALUE_AREA")[:3] | |
| poc_d = math_data.get("poc_dist_pct", 0.0) | |
| g_sent = a_data.get("bias", "neutral")[0] if a_data else "n" | |
| b_trend = b_data.get("trend", "side")[0] if b_data else "s" | |
| llm_prompt = ( | |
| "<|im_start|>system\n" | |
| 'Breakout auditor. SOLO JSON: {"decision":"BUY|WAIT","type":"scalp|long","conf":0.XX}.\n' | |
| "true=breakout real | false=trampa. Sin pensar.\n" | |
| "<|im_end|>\n" | |
| "<|im_start|>user\n" | |
| f'{{"s":"{sym[:8]}","cr":{cronos:.2f},"hvn":{hvn_dist:.3f},' | |
| f'"bk":{bk_score:.2f},"vt":"{vol_trend}","z":"{zone}",' | |
| f'"poc":{poc_d:.3f},"a":"{g_sent}","b":"{b_trend}"}}\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 = "{" + raw_out_text | |
| m = re.search(r'\{[^{}]*"decision"\s*:\s*"(BUY|WAIT)"[^{}]*\}', raw, re.IGNORECASE) | |
| if m: | |
| parsed = json.loads(m.group()) | |
| dec = parsed.get("decision", "WAIT").upper() | |
| ttype = parsed.get("type", "scalp").lower() | |
| conf = float(parsed.get("conf", 0.55)) | |
| if dec not in ("BUY", "WAIT"): | |
| dec = "WAIT" | |
| if ttype not in ("scalp", "long"): | |
| ttype = "scalp" | |
| conf = round(max(0.10, min(0.95, conf)), 4) | |
| print(f"[G/LLM] {sym}: {dec} {ttype} conf={conf:.2f} | {llm_ms:.1f}ms") | |
| return {"decision": dec, "type": ttype, "confidence": conf, | |
| "_llm_ms": round(llm_ms, 1), "_source": "llm"} | |
| # Parseo parcial | |
| dec = "BUY" if "BUY" in raw.upper() else "WAIT" | |
| print(f"[G/LLM] {sym}: partial={dec} | {llm_ms:.1f}ms") | |
| return {"decision": dec, "type": "scalp", "confidence": 0.52, | |
| "_llm_ms": round(llm_ms, 1), "_source": "llm_partial"} | |
| except Exception as e: | |
| llm_ms = (time.perf_counter() - t0_llm) * 1000 | |
| print(f"[G/LLM-ERR] {sym}: {type(e).__name__}: {str(e)[:50]} | {llm_ms:.1f}ms β WAIT") | |
| return {"decision": "WAIT", "type": "scalp", "confidence": 0.45, | |
| "_llm_ms": round(llm_ms, 1), "_source": "llm_fallback"} | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ANΓLISIS INDIVIDUAL (combina MATH + LLM si ambiguo) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def _analyze_single(sym: str, px: float, bars: list, cronos: float, | |
| a_data: dict, b_data: dict, rank: int = 1) -> dict: | |
| """ | |
| Pipeline completo para un solo activo. | |
| rank=1: MATH + LLM si ambiguo β [G/MATH-CLEAR] o [G/LLM] | |
| rank>1: solo MATH β [G/MATH-ONLY] | |
| """ | |
| t0 = time.perf_counter() | |
| # ββ FASE MATH βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| math_data = _compute_vp_kmeans(bars, px) | |
| math_ms = math_data["_math_ms"] | |
| zone = math_data["zone"] | |
| conf_math = math_data["conf_math"] | |
| dec_math = math_data["dec_math"] | |
| is_ambiguous = math_data["is_ambiguous"] | |
| poc = math_data["poc"] | |
| vah = math_data["vah"] | |
| val = math_data["val"] | |
| logic = math_data["logic"] | |
| trade_type = _classify_trade_type(zone, conf_math, cronos, px, poc, vah, val) | |
| # ββ SeΓ±al clara β sin LLM βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if zone != "VALUE_AREA" or not is_ambiguous or rank > 1: | |
| label = "MATH-CLEAR" if rank == 1 else "MATH-ONLY" | |
| total_ms = (time.perf_counter() - t0) * 1000 | |
| print(f"[G/{label}] {sym}: {dec_math} {trade_type} conf={conf_math:.2f} " | |
| f"zone={zone} | {total_ms:.1f}ms") | |
| return { | |
| "decision": dec_math, | |
| "type": trade_type, | |
| "confidence": conf_math, | |
| "poc": poc, | |
| "vah": vah, | |
| "val": val, | |
| "logic": logic, | |
| "zone": zone, | |
| "hvn_levels": math_data["hvn_levels"], | |
| "hvn_nearest": math_data["hvn_nearest"], | |
| "breakout_score": math_data["breakout_score"], | |
| "fib_levels": math_data["fib_levels"], | |
| "cerebro": "G", | |
| "_math_ms": round(math_ms, 2), | |
| "_llm_ms": 0.0, | |
| "_total_ms": round(total_ms, 1), | |
| } | |
| # ββ Zona ambigua (VALUE_AREA) + rank=1 β LLM Auditor ββββββββββββββββββββ | |
| llm_result = await _llm_breakout_audit(math_data, sym, px, cronos, a_data, b_data) | |
| llm_ms = llm_result.get("_llm_ms", 0.0) | |
| decision = llm_result.get("decision", dec_math) | |
| trade_type = llm_result.get("type", trade_type) | |
| confidence = llm_result.get("confidence", conf_math) | |
| total_ms = (time.perf_counter() - t0) * 1000 | |
| return { | |
| "decision": decision, | |
| "type": trade_type, | |
| "confidence": confidence, | |
| "poc": poc, | |
| "vah": vah, | |
| "val": val, | |
| "logic": logic + f" | LLM-audit: {decision}", | |
| "zone": zone, | |
| "hvn_levels": math_data["hvn_levels"], | |
| "hvn_nearest": math_data["hvn_nearest"], | |
| "breakout_score": math_data["breakout_score"], | |
| "fib_levels": math_data["fib_levels"], | |
| "cerebro": "G", | |
| "_math_ms": round(math_ms, 2), | |
| "_llm_ms": round(llm_ms, 1), | |
| "_total_ms": round(total_ms, 1), | |
| } | |
| # ββ Endpoints FastAPI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def root(): | |
| return { | |
| "status": "online", | |
| "cerebro": "G", | |
| "version": "4.0-APEX-KMeans+LLM", | |
| "endpoints": ["/batch", "/analyze_liquidity", "/health"], | |
| } | |
| async def health(): | |
| return { | |
| "status": "online", | |
| "cerebro": "G", | |
| "version": "4.0-APEX-KMeans+LLM", | |
| "model": "BitNet-b1.58-2B-4T-i2_s (ik_llama.cpp)", "bitnet_server": BITNET_BASE, | |
| "numpy_ok": _NP_OK, | |
| "kmeans_k": KMEANS_K, | |
| "features": { | |
| "math": "VP + K-Means HVN + Fibonacci (<5ms)", | |
| "llm": "Auditor de Rupturas (solo rank=1 zona ambigua)", | |
| "async": "rank=1 LLM|CLEAR + rank2+ MATH-ONLY", | |
| }, | |
| } | |
| async def analyze_liquidity(request: Request): | |
| try: | |
| payload = await request.json() | |
| except Exception: | |
| return JSONResponse({"error": "JSON invΓ‘lido"}, status_code=400) | |
| sym = str(payload.get("sym", "?")) | |
| px = float(payload.get("px", 0)) | |
| cronos = float(payload.get("cronos_score", 0.5)) | |
| rank = int(payload.get("rank", 1)) | |
| bars = _extract_bars(payload) | |
| a_data = payload.get("A", {}) | |
| b_data = payload.get("B", {}) | |
| cached = _cache_get(sym) | |
| if cached: | |
| return JSONResponse(cached) | |
| result = await _analyze_single(sym, px, bars, cronos, a_data, b_data, rank) | |
| _cache_set(sym, result) | |
| return JSONResponse(result) | |
| async def batch(request: Request): | |
| """ | |
| Procesamiento asimΓ©trico en lote: | |
| Activo rank=1 β Math + LLM si ambiguo | |
| Activos rank>1 β 100% Math (LLM prohibida) | |
| """ | |
| try: | |
| payload = await request.json() | |
| except Exception: | |
| return JSONResponse({"error": "JSON invΓ‘lido"}, status_code=400) | |
| assets = payload.get("assets", []) | |
| if not assets: | |
| return JSONResponse({"error": "assets vacΓo"}, status_code=400) | |
| t0_batch = time.perf_counter() | |
| results = {} | |
| llm_count, clear_count, math_count = 0, 0, 0 | |
| for asset in assets: | |
| sym = str(asset.get("sym", "?")) | |
| px = float(asset.get("px", 0)) | |
| cronos = float(asset.get("cronos_score", 0.5)) | |
| rank = int(asset.get("rank", asset.get("_rank", 0)) + 1) # 1-based | |
| bars = _extract_bars(asset) | |
| a_data = asset.get("A", {}) | |
| b_data = asset.get("B", {}) | |
| cached = _cache_get(sym) | |
| if cached: | |
| results[sym] = cached | |
| math_count += 1 | |
| continue | |
| result = await _analyze_single(sym, px, bars, cronos, a_data, b_data, rank) | |
| _cache_set(sym, result) | |
| results[sym] = result | |
| src = result.get("_source", "math") | |
| if "llm" in str(src): | |
| llm_count += 1 | |
| elif result.get("_llm_ms", 0) == 0.0 and rank == 1: | |
| clear_count += 1 | |
| else: | |
| math_count += 1 | |
| batch_ms = (time.perf_counter() - t0_batch) * 1000 | |
| print(f"[G/BATCH] {len(assets)} activos | LLM={llm_count} Clear={clear_count} " | |
| f"Math={math_count} | {batch_ms:.0f}ms") | |
| return JSONResponse({ | |
| "results": results, | |
| "llm_count": llm_count, | |
| "math_count": math_count, | |
| "cache_count": 0, | |
| "latency_ms": round(batch_ms, 1), | |
| }) | |
| async def slot_lock(request: Request): | |
| """Endpoint de notificaciΓ³n de slot (heredado β no altera la lΓ³gica).""" | |
| try: | |
| body = await request.json() | |
| except Exception: | |
| return JSONResponse({"ok": True}) | |
| sym = body.get("sym", "?") | |
| print(f"[G/SLOT-LOCK] {sym}: trade abierto β monitoreando") | |
| return JSONResponse({"ok": True, "sym": sym}) | |