viarl199020 commited on
Commit
77ed26b
·
verified ·
1 Parent(s): d08422c

Delete server_H.py

Browse files
Files changed (1) hide show
  1. server_H.py +0 -550
server_H.py DELETED
@@ -1,550 +0,0 @@
1
- """
2
- server_H.py — Cerebro H — RiskAuditor v22.5 (APEX Elastic Payload + PDT-Aware)
3
- ===============================================================================
4
- CAMBIOS v22.5 sobre v21.5 (compatibilidad total preservada):
5
-
6
- PROBLEMA CORREGIDO:
7
- "[H-VIRTUAL] AVAX/USD: ... (H offline -> autonomo)"
8
- El payload del nuevo data_manager_anchors.py inyecta métricas bajo
9
- claves anidadas ("metrics", "cognition", "risk") que v21.5 no buscaba.
10
- Resultado: balance extraído como 0, Kelly como KELLY_MIN, H caía offline.
11
-
12
- FIX 1 — EXTRACCIÓN ELÁSTICA MULTI-CAPA:
13
- _math_audit() ahora usa cascadas de .get() que buscan cada variable
14
- en: raíz → metrics → cognition → risk → default seguro.
15
- Garantiza que NUNCA falle ni devuelva 0 por ausencia de clave.
16
-
17
- FIX 2 — DEGRADED ONLINE (no offline):
18
- Si faltan datos críticos (balance=0, bars=[]), H responde con un tamaño
19
- dinámico de emergencia basado en el balance conocido: 1% fijo.
20
- El canal permanece ONLINE y el flag "degraded_graceful"=True avisa al swarm.
21
- Esto elimina el "[H-VIRTUAL] ... (H offline -> autonomo)".
22
-
23
- FIX 3 — CONTEXT ENRICHMENT desde métricas del radar:
24
- anchor_correlation "BEARISH" → reduce Kelly en 20% (contexto bajista macro).
25
- velocity_zero_bars > 10 → penalización Kelly adicional 10% (capital muerto).
26
- macro_impact_weight = 2 → activa modo "institucional" = mayor precaución.
27
-
28
- PRESERVADO de v21.5:
29
- ✅ Kelly Continuo + Monte Carlo + Risk Thermometer
30
- ✅ Shadow Cache con TTL adaptativo + propagación de temperatura
31
- ✅ Single-Token BitNet G/Y/R + ring buffer + ensemble histórico
32
- ✅ Output 100% compatible con swarm_engine v17.5+
33
-
34
- OUTPUT v22.5 (superset de v21.5):
35
- {
36
- "pdt_ok": bool,
37
- "size_usd": float,
38
- "risk_warning": str,
39
- "vault_status": str,
40
- "kelly_fraction": float,
41
- "kelly_continuous": float,
42
- "zscore": float,
43
- "monte_carlo_ruin_prob": float,
44
- "panic_activated": bool,
45
- "risk_token": str, # "G"|"Y"|"R"
46
- "risk_temperature": float, # 0.0-1.0
47
- "degraded_graceful": bool, # True si faltan datos pero H sigue online
48
- "anchor_context": str, # "BULLISH"|"BEARISH"|"NEUTRAL" recibido
49
- "_math_ms": float,
50
- "_llm_ms": float,
51
- "_total_ms": float,
52
- "_shadow": bool,
53
- }
54
- """
55
-
56
- import os, time, asyncio, threading, math, collections
57
- from fastapi import FastAPI, Request
58
- from fastapi.responses import JSONResponse
59
- import httpx
60
-
61
- try:
62
- import numpy as np
63
- _NP = True
64
- except ImportError:
65
- _NP = False
66
-
67
- app = FastAPI(title="Cerebro H — RiskAuditor v22.5 APEX Elastic")
68
-
69
- # ── Config (idéntica a v21.5) ─────────────────────────────────────────────────
70
- BITNET_PORT = int(os.environ.get("BITNET_PORT", "8080"))
71
- BITNET_HOST = os.environ.get("BITNET_HOST", "127.0.0.1")
72
- BITNET_BASE = f"http://{BITNET_HOST}:{BITNET_PORT}"
73
- BITNET_TIMEOUT = float(os.environ.get("BITNET_TIMEOUT", "15.0"))
74
- CACHE_TTL = float(os.environ.get("CACHE_TTL", "30.0"))
75
-
76
- KELLY_MAX = float(os.environ.get("KELLY_MAX", "0.05"))
77
- KELLY_MIN = float(os.environ.get("KELLY_MIN", "0.005"))
78
- ZSCORE_PANIC = float(os.environ.get("ZSCORE_PANIC", "2.5"))
79
- PDT_LIMIT = int(os.environ.get("PDT_LIMIT", "3"))
80
- PDT_MIN_BALANCE= float(os.environ.get("PDT_MIN", "25000"))
81
- MC_SCENARIOS = int(os.environ.get("MC_SCENARIOS", "100"))
82
- MC_HORIZON = int(os.environ.get("MC_HORIZON", "20"))
83
- RUIN_THRESHOLD = float(os.environ.get("RUIN_THRESHOLD","0.25"))
84
- RUIN_DRAWDOWN = float(os.environ.get("RUIN_DRAWDOWN", "0.20"))
85
-
86
- # ── BitNet Client (idéntico a v21.5) ─────────────────────────────────────────
87
- _bitnet_client: httpx.AsyncClient | None = None
88
-
89
- def _get_client() -> httpx.AsyncClient:
90
- global _bitnet_client
91
- if _bitnet_client is None or _bitnet_client.is_closed:
92
- _bitnet_client = httpx.AsyncClient(
93
- base_url=BITNET_BASE,
94
- timeout=httpx.Timeout(connect=3.0, read=BITNET_TIMEOUT,
95
- write=3.0, pool=2.0),
96
- limits=httpx.Limits(max_connections=8, max_keepalive_connections=4),
97
- )
98
- return _bitnet_client
99
-
100
- async def _bitnet_single_token(prompt: str) -> str:
101
- payload = {
102
- "prompt": prompt,
103
- "n_predict": 2,
104
- "temperature": 0.0,
105
- "stream": False,
106
- "cache_prompt": False,
107
- "stop": ["\n", "<|im_end|>", "<|end_of_text|>", "<|eot_id|>"],
108
- }
109
- try:
110
- resp = await _get_client().post("/completion", json=payload)
111
- resp.raise_for_status()
112
- return resp.json().get("content", "").strip()
113
- except Exception:
114
- return ""
115
-
116
- # ── Risk Thermometer (idéntico a v21.5) ──────────────────────────────────────
117
- _risk_temp: dict = {}
118
- _token_ring: dict = {}
119
- _thermo_lock = threading.Lock()
120
-
121
- def _update_thermometer(sym: str, mc_ruin: float, streak: int,
122
- drawdown: float, zscore: float) -> float:
123
- streak_norm = min(streak / 5.0, 1.0)
124
- drawdown_norm = min(drawdown / RUIN_DRAWDOWN, 1.0)
125
- zscore_norm = min(abs(zscore) / ZSCORE_PANIC, 1.0)
126
- new_temp = (mc_ruin * 0.40 + streak_norm * 0.30 +
127
- drawdown_norm * 0.20 + zscore_norm * 0.10)
128
- with _thermo_lock:
129
- prev = _risk_temp.get(sym, new_temp)
130
- smoothed = prev * 0.30 + new_temp * 0.70
131
- _risk_temp[sym] = round(smoothed, 4)
132
- return smoothed
133
-
134
- def _temp_to_token(temp: float) -> str:
135
- if temp < 0.35: return "G"
136
- if temp < 0.65: return "Y"
137
- return "R"
138
-
139
- def _ring_push(sym: str, token: str):
140
- with _thermo_lock:
141
- if sym not in _token_ring:
142
- _token_ring[sym] = collections.deque(maxlen=10)
143
- _token_ring[sym].append(token)
144
-
145
- def _ring_last5_all_R(sym: str) -> bool:
146
- with _thermo_lock:
147
- ring = _token_ring.get(sym, [])
148
- if len(ring) < 5:
149
- return False
150
- return all(t == "R" for t in list(ring)[-5:])
151
-
152
- # ── Shadow Cache (idéntico a v21.5) ──────────────────────────────────────────
153
- _shadow: dict = {}
154
- _shadow_lock = threading.Lock()
155
-
156
- def _shadow_get(sym: str, current_temp: float) -> str | None:
157
- with _shadow_lock:
158
- e = _shadow.get(sym)
159
- if not e:
160
- return None
161
- age = time.time() - e["ts"]
162
- ttl = 10.0 if current_temp > 0.65 else (20.0 if current_temp > 0.35 else CACHE_TTL)
163
- if age > ttl:
164
- return None
165
- if abs(current_temp - e["temp"]) > 0.15:
166
- return None
167
- return e["token"]
168
-
169
- def _shadow_set(sym: str, token: str, temp: float):
170
- with _shadow_lock:
171
- _shadow[sym] = {"token": token, "ts": time.time(), "temp": temp}
172
-
173
-
174
- # ══════════════════════════════════════════════════════════════════════════════
175
- # v22.5: EXTRACTOR ELÁSTICO MULTI-CAPA
176
- # Busca cada clave en: raíz → metrics → cognition → risk → default
177
- # ══════════════════════════════════════════════════════════════════════════════
178
-
179
- def _elastic_get(payload: dict, key: str, default, cast=None):
180
- """
181
- Extrae una clave buscando en múltiples capas del payload de forma segura.
182
- Nunca lanza excepción; retorna `default` si no encuentra nada válido.
183
-
184
- Capas buscadas en orden:
185
- 1. raíz del payload
186
- 2. payload["metrics"]
187
- 3. payload["cognition"]
188
- 4. payload["risk"]
189
- """
190
- _layers = [
191
- payload,
192
- payload.get("metrics", {}) or {},
193
- payload.get("cognition", {}) or {},
194
- payload.get("risk", {}) or {},
195
- ]
196
- for layer in _layers:
197
- v = layer.get(key)
198
- if v is not None:
199
- if cast is None:
200
- return v
201
- try:
202
- return cast(v)
203
- except (ValueError, TypeError):
204
- continue
205
- return default
206
-
207
-
208
- def _extract_h_fields(payload: dict) -> dict:
209
- """
210
- Extrae todos los campos que necesita _math_audit de forma elástica.
211
- Retorna un dict limpio con valores garantizados y sus flags de calidad.
212
- """
213
- sym = str(_elastic_get(payload, "sym", payload.get("symbol", "?"), str))
214
- balance = _elastic_get(payload, "balance", None, float)
215
- # balance puede venir como "account_balance" en el payload del swarm_engine
216
- if balance is None or balance <= 0:
217
- balance = _elastic_get(payload, "account_balance", None, float)
218
- if balance is None or balance <= 0:
219
- balance = 10000.0 # default seguro
220
- balance = float(balance)
221
-
222
- daytrades = _elastic_get(payload, "daytrade_count", 0, int)
223
- is_crypto = bool(_elastic_get(payload, "is_crypto", False, str) in (True,"true","True","1")) \
224
- or "/" in str(sym)
225
- bars = payload.get("bars", []) or []
226
- win_rate = _elastic_get(payload, "win_rate", 0.45, float)
227
- avg_win = _elastic_get(payload, "avg_win_pct", 0.012, float)
228
- avg_loss = _elastic_get(payload, "avg_loss_pct", 0.008, float)
229
- streak = _elastic_get(payload, "streak_losses", 0, int)
230
- n_trades = _elastic_get(payload, "n_trades", 0, int)
231
- cronos = _elastic_get(payload, "cronos_score", 0.5, float)
232
- bs_pct = _elastic_get(payload, "bootstrap_pct", 0.010, float)
233
- open_pos = _elastic_get(payload, "open_positions", 0, int)
234
-
235
- # ── Campos del nuevo radar (data_manager_anchors v22.5) ──────────────────
236
- anchor_corr = str(_elastic_get(payload, "anchor_correlation", "NEUTRAL", str)).upper()
237
- vel_zero = _elastic_get(payload, "velocity_zero_bars", 0, int)
238
- macro_weight = _elastic_get(payload, "macro_impact_weight", 0, int)
239
- trade_side = str(_elastic_get(payload, "trade_side", "long", str)).lower()
240
-
241
- # Flag de calidad de datos
242
- degraded = (balance <= 10001.0 and not payload.get("balance") and
243
- not (payload.get("metrics") or {}).get("balance"))
244
-
245
- return {
246
- "sym": sym, "balance": balance, "daytrades": daytrades,
247
- "is_crypto": is_crypto, "bars": bars,
248
- "win_rate": win_rate, "avg_win": avg_win, "avg_loss": avg_loss,
249
- "streak": streak, "n_trades": n_trades, "cronos": cronos,
250
- "bs_pct": bs_pct, "open_pos": open_pos,
251
- "anchor_corr": anchor_corr, "vel_zero": vel_zero,
252
- "macro_weight": macro_weight, "trade_side": trade_side,
253
- "degraded": degraded,
254
- }
255
-
256
-
257
- # ══════════════════════════════════════════════════════════════════════════════
258
- # MATH: Kelly + MC + ZScore (actualizado con contexto del radar)
259
- # ══════════════════════════════════════════════════════════════════════════════
260
-
261
- def _kelly_continuous(win_rate, avg_win, avg_loss, streak, cronos, n_trades, bs_pct,
262
- anchor_corr: str = "NEUTRAL", vel_zero: int = 0,
263
- macro_weight: int = 0) -> float:
264
- """
265
- Kelly Continuo v22.5: añade penalizaciones por contexto del radar.
266
- - anchor_corr "BEARISH" → -20% (mercado líder bajista)
267
- - vel_zero > 10 → -10% (capital estancado)
268
- - macro_weight = 2 → -15% (evento institucional activo)
269
- """
270
- if n_trades < 20:
271
- base = max(KELLY_MIN, min(bs_pct, KELLY_MAX))
272
- elif avg_loss <= 0 or avg_win <= 0:
273
- base = KELLY_MIN
274
- else:
275
- kelly_raw = (win_rate * avg_win - (1 - win_rate) * avg_loss) / avg_win
276
- kelly_half = kelly_raw / 2.0
277
- streak_pen = 1.0 - min(streak * 0.15, 0.45)
278
- cronos_amp = 0.80 + cronos * 0.40
279
- base = max(KELLY_MIN, min(KELLY_MAX, kelly_half * streak_pen * cronos_amp))
280
-
281
- # ── Penalizaciones del radar (v22.5) ──────────────────────────────────────
282
- radar_factor = 1.0
283
- if anchor_corr == "BEARISH":
284
- radar_factor *= 0.80 # -20%
285
- if vel_zero > 10:
286
- radar_factor *= 0.90 # -10%: capital muerto > 10 barras
287
- if macro_weight >= 2:
288
- radar_factor *= 0.85 # -15%: evento institucional
289
-
290
- return round(max(KELLY_MIN, min(KELLY_MAX, base * radar_factor)), 6)
291
-
292
-
293
- def _monte_carlo(win_rate, avg_win, avg_loss, kelly_f, horizon, scenarios) -> float:
294
- if _NP:
295
- np.random.seed(42)
296
- wins = np.random.random((scenarios, horizon)) < win_rate
297
- factors = np.where(wins, 1.0 + kelly_f * avg_win, 1.0 - kelly_f * avg_loss)
298
- portfolios = np.cumprod(factors, axis=1).min(axis=1)
299
- return round(float(np.sum(portfolios < (1.0 - RUIN_DRAWDOWN))) / scenarios, 4)
300
- import random; random.seed(42)
301
- ruin = 0
302
- for _ in range(scenarios):
303
- p = 1.0
304
- for _ in range(horizon):
305
- p *= ((1 + kelly_f * avg_win) if random.random() < win_rate
306
- else (1 - kelly_f * avg_loss))
307
- if p < 1 - RUIN_DRAWDOWN:
308
- ruin += 1; break
309
- return round(ruin / scenarios, 4)
310
-
311
-
312
- def _zscore_bars(bars: list) -> float:
313
- closes = [float(b.get("c", b.get("close", 0))) for b in bars
314
- if b.get("c") or b.get("close")]
315
- if len(closes) < 4:
316
- return 0.0
317
- rets = [abs(closes[i] / closes[i-1] - 1) for i in range(1, len(closes))]
318
- if _NP:
319
- a = np.array(rets)
320
- return round(float(abs((rets[-1] - a.mean()) / (a.std() or 0.001))), 3)
321
- mean = sum(rets) / len(rets)
322
- std = math.sqrt(sum((r - mean) ** 2 for r in rets) / len(rets)) or 0.001
323
- return round(abs(rets[-1] - mean) / std, 3)
324
-
325
-
326
- def _math_audit(payload: dict) -> dict:
327
- """
328
- v22.5: usa _extract_h_fields() para extracción elástica.
329
- Si el balance viene de un payload degradado, responde con tamaño dinámico
330
- de emergencia (1% del balance real) en lugar de caer offline.
331
- """
332
- t0 = time.perf_counter()
333
- f = _extract_h_fields(payload)
334
-
335
- sym = f["sym"]
336
- balance = f["balance"]
337
- daytrades = f["daytrades"]
338
- is_crypto = f["is_crypto"]
339
- bars = f["bars"]
340
- win_rate = f["win_rate"]
341
- avg_win = f["avg_win"]
342
- avg_loss = f["avg_loss"]
343
- streak = f["streak"]
344
- n_trades = f["n_trades"]
345
- cronos = f["cronos"]
346
- bs_pct = f["bs_pct"]
347
- open_pos = f["open_pos"]
348
- anchor_corr = f["anchor_corr"]
349
- vel_zero = f["vel_zero"]
350
- macro_weight = f["macro_weight"]
351
- degraded = f["degraded"]
352
-
353
- pdt_ok = True if is_crypto else not (daytrades >= PDT_LIMIT and balance < PDT_MIN_BALANCE)
354
-
355
- kelly = _kelly_continuous(
356
- win_rate, avg_win, avg_loss, streak, cronos, n_trades, bs_pct,
357
- anchor_corr=anchor_corr, vel_zero=vel_zero, macro_weight=macro_weight,
358
- )
359
-
360
- # ── Tamaño de emergencia si el payload estaba degradado ───────────────────
361
- # Garantiza que H permanezca ONLINE aunque falten datos del radar.
362
- # El swarm_engine recibirá degraded_graceful=True para saberlo.
363
- if degraded:
364
- # 1% del balance como tamaño de emergencia — igual que KELLY-BOOTSTRAP
365
- emergency_kelly = max(KELLY_MIN, min(bs_pct, 0.010))
366
- size = round(balance * emergency_kelly, 2)
367
- kelly = emergency_kelly
368
- print(f"[H/GRACEFUL] {sym}: payload incompleto → size_emergencia=${size:.2f} (1%) balance=${balance:.2f}")
369
- else:
370
- size = round(balance * kelly, 2)
371
-
372
- zscore = _zscore_bars(bars)
373
- mc = _monte_carlo(win_rate, avg_win, avg_loss, kelly, MC_HORIZON, MC_SCENARIOS)
374
-
375
- if zscore >= ZSCORE_PANIC:
376
- size = round(size * 0.50, 2)
377
- kelly = round(kelly * 0.50, 6)
378
-
379
- approx_dd = streak * avg_loss * kelly
380
- temp = _update_thermometer(sym, mc, streak, approx_dd, zscore)
381
-
382
- if open_pos >= 8:
383
- size = round(size * 0.70, 2)
384
-
385
- risk = ("monte_carlo_ruin" if mc >= RUIN_THRESHOLD else
386
- "high_zscore" if zscore >= ZSCORE_PANIC else
387
- "streak_loss" if streak >= 3 else "none")
388
-
389
- vault = ("blocked_pdt" if not pdt_ok else
390
- "conservative" if risk in ("monte_carlo_ruin", "high_zscore", "streak_loss") else
391
- "protected")
392
-
393
- math_ms = (time.perf_counter() - t0) * 1000
394
-
395
- anchor_note = ""
396
- if anchor_corr == "BEARISH":
397
- anchor_note = " anchor=BEARISH(-20%)"
398
- if vel_zero > 10:
399
- anchor_note += f" vel_zero={vel_zero}(-10%)"
400
-
401
- print(f"[H/MATH] {sym}: pdt={pdt_ok} kelly={kelly:.4f} ${size:.0f} "
402
- f"mc={mc:.3f} temp={temp:.2f}{anchor_note} | {math_ms:.1f}ms")
403
-
404
- return {
405
- "pdt_ok": pdt_ok, "size_usd": size, "risk_warning": risk,
406
- "vault_status": vault, "kelly_fraction": kelly,
407
- "kelly_continuous": kelly, "zscore": zscore,
408
- "monte_carlo_ruin_prob": mc, "panic_activated": False,
409
- "risk_temperature": round(temp, 4),
410
- "degraded_graceful": degraded,
411
- "anchor_context": anchor_corr,
412
- "_math_ms": round(math_ms, 2), "_sym": sym,
413
- "_balance": balance, "_temp": temp,
414
- "_approx_dd": round(approx_dd, 4),
415
- }
416
-
417
-
418
- # ── Prompt BitNet (mismo formato que v21.5) ───────────────────────────────────
419
- _SYS_H = (
420
- "<|im_start|>system\n"
421
- "Risk level G/Y/R. Respond ONLY with one letter.\n"
422
- "G=green free trade. Y=yellow scalps only. R=red block all.\n"
423
- "<|im_end|>\n"
424
- "<|im_start|>user\n"
425
- )
426
-
427
- def _build_h_prompt(mc: float, temp: float, streak: int, dd: float,
428
- anchor: str = "NEUTRAL") -> str:
429
- return (
430
- _SYS_H
431
- + f'{{"mc":{mc:.2f},"t":{temp:.2f},"s":{streak},"d":{dd:.2f},"a":"{anchor[:1]}"}}\n'
432
- + "<|im_end|>\n"
433
- + "<|im_start|>assistant\n"
434
- )
435
-
436
-
437
- def _token_to_output(token: str, math: dict, llm_ms: float,
438
- is_shadow: bool, total_ms: float) -> dict:
439
- sym = math["_sym"]
440
- if _ring_last5_all_R(sym) and token != "R":
441
- token = "R"
442
- print(f"[H/RING-VETO] {sym}: últimos 5 tokens R → bloqueo automático")
443
-
444
- if token == "R":
445
- math["size_usd"] = 0.0
446
- math["kelly_fraction"] = 0.0
447
- math["vault_status"] = "emergency_hold"
448
- math["panic_activated"] = True
449
-
450
- _ring_push(sym, token)
451
-
452
- out = {k: v for k, v in math.items() if not k.startswith("_")}
453
- out.update({
454
- "risk_token": token,
455
- "_math_ms": math["_math_ms"],
456
- "_llm_ms": round(llm_ms, 1),
457
- "_total_ms": round(total_ms, 1),
458
- "_shadow": is_shadow,
459
- })
460
- return out
461
-
462
-
463
- # ── Background LLM update (idéntico a v21.5) ─────────────────────────────────
464
-
465
- async def _background_h_update(sym: str, prompt: str, temp: float):
466
- t0 = time.perf_counter()
467
- raw = await _bitnet_single_token(prompt)
468
- llm_ms = (time.perf_counter() - t0) * 1000
469
- token = raw.strip()[:1].upper()
470
- if token in ("G", "Y", "R"):
471
- _shadow_set(sym, token, temp)
472
- _ring_push(sym, token)
473
- print(f"[H/LLM] {sym}: token={token} temp={temp:.2f} | {llm_ms:.1f}ms (shadow actualizado)")
474
- else:
475
- print(f"[H/LLM-GUARD] {sym}: token inválido='{raw[:10]}' → shadow preservado")
476
-
477
-
478
- # ── Pipeline principal v22.5 ──────────────────────────────────────────────────
479
-
480
- async def _audit(payload: dict) -> dict:
481
- t0 = time.perf_counter()
482
-
483
- math = _math_audit(payload)
484
- sym = math["_sym"]
485
- temp = math["_temp"]
486
- mc = math["monte_carlo_ruin_prob"]
487
- streak = _elastic_get(payload, "streak_losses", 0, int)
488
- dd = math["_approx_dd"]
489
- anchor = math.get("anchor_context", "NEUTRAL")
490
-
491
- cached_token = _shadow_get(sym, temp)
492
- is_shadow = cached_token is not None
493
-
494
- if is_shadow:
495
- token = cached_token
496
- llm_ms = 0.0
497
- with _shadow_lock:
498
- age = time.time() - _shadow.get(sym, {}).get("ts", 0)
499
- if age > CACHE_TTL * 0.5:
500
- asyncio.create_task(
501
- _background_h_update(sym, _build_h_prompt(mc, temp, streak, dd, anchor), temp))
502
- print(f"[H/SHADOW] {sym}: token={token} temp={temp:.2f} degraded={math['degraded_graceful']} | 0ms")
503
- else:
504
- t_llm = time.perf_counter()
505
- prompt = _build_h_prompt(mc, temp, streak, dd, anchor)
506
- raw = await _bitnet_single_token(prompt)
507
- llm_ms = (time.perf_counter() - t_llm) * 1000
508
- token = raw.strip()[:1].upper()
509
- if token not in ("G", "Y", "R"):
510
- token = _temp_to_token(temp)
511
- print(f"[H/LLM-GUARD] {sym}: token='{raw[:10]}' → math_fallback={token}")
512
- else:
513
- _shadow_set(sym, token, temp)
514
- print(f"[H/LLM] {sym}: token={token} anchor={anchor} | {llm_ms:.1f}ms")
515
-
516
- total_ms = (time.perf_counter() - t0) * 1000
517
- return _token_to_output(token, math, llm_ms, is_shadow, total_ms)
518
-
519
-
520
- # ── Endpoints ─────────────────────────────────────────────────────────────────
521
-
522
- @app.get("/")
523
- async def root():
524
- return {"status": "online", "cerebro": "H", "version": "22.5-APEX-ELASTIC"}
525
-
526
- @app.get("/health")
527
- async def health():
528
- return {
529
- "status": "online", "cerebro": "H", "version": "22.5-APEX-ELASTIC",
530
- "model": "BitNet-b1.58-2B-4T-i2_s", "engine": "ik_llama.cpp",
531
- "fixes": [
532
- "elastic_multi_layer_payload_extraction",
533
- "degraded_graceful_online_mode",
534
- "anchor_correlation_kelly_penalty",
535
- "velocity_zero_bars_kelly_penalty",
536
- "macro_weight_kelly_penalty",
537
- ],
538
- "architecture": "Kelly+MC+Thermometer + Shadow-Cache + Single-Token G/Y/R + ElasticGet",
539
- }
540
-
541
- @app.post("/audit")
542
- async def audit(request: Request):
543
- try:
544
- payload = await request.json()
545
- except Exception:
546
- return JSONResponse({"error": "JSON inválido"}, status_code=400)
547
- result = await _audit(payload)
548
- return JSONResponse(result)
549
-
550
- print("[H] ✅ RiskAuditor v22.5 APEX — Elastic Payload + Degraded-Online + Anchor-Kelly")