fix(live): cap di fallback legato all'ultima equity osservata + cap config 300 -> 3000
L'operatore ha autorizzato "si alza il cap a 3000". Verificato PRIMA di eseguire: il
deposito non era ancora arrivato (equity reale $596.92), e alzare il cap in quel momento
sarebbe stato pericoloso NEL VERSO OPPOSTO.
IL PROBLEMA. Quando l'equity reale non e' leggibile, shadow_report ripiega su paper_cap =
$2.000 NOMINALI, che non e' il conto vero: il sizing diventa 0.5 x 2000 x (...) = fino a
$1.000/asset, e il cap fisso e' l'unica cosa che impedisce a quel nominale di diventare
leva reale.
cap $300 -> fallback $600 lordi su $597 = 1.00x OK
cap $3000 -> fallback $2.000 lordi su $597 = 3.35x NO
Cioe' il cap sarebbe stato troppo alto proprio nel momento in cui non si sa quanto c'e'
sul conto, e con disaster_sl_pct -30% quella e' la configurazione peggiore possibile.
LA CORREZIONE. Invece di alzare un numero da ricordare, il cap di fallback e' legato al
conto: cap_fallback = min(cap_fisso_di_config, ultima_equity_reale_osservata * frac), con
watermark in data/live/equity_seen.json scritto da book_report ogni volta che l'equity
reale e' leggibile. Il cap di config torna a essere un TETTO DICHIARATO.
Senza watermark (primo avvio, file cancellato) -> CAP_UNKNOWN_USD = $300: non si sa niente
del conto, si usa la taglia storicamente sicura.
VERIFICATO SUL CONTO VERO, nei due regimi:
oggi, watermark $596.92 -> $298.46/asset = leva 1.00x
dopo il versamento, $6.047 -> $3,000.00/asset = leva 0.99x
Sicuro in entrambi gli ordini di eventi, senza dipendere da un'azione manuale al deposito.
Questo CHIUDE l'azione pre-registrata il 2026-07-02 ("al deposito alzare il cap a
equity/2"), rimasta ineseguita per 24 giorni — non eseguendola, ma rendendola non
necessaria.
REGOLA: un parametro di sicurezza che va aggiornato a mano a ogni cambio di scala e' un
difetto, non una configurazione. Lo stesso numero era sbagliato in un verso prima del
deposito e nell'altro dopo: la soluzione non era scegliere il valore giusto, era legarlo
alla grandezza che lo determina.
Guardie: tests/test_cap_watermark.py (11), A DUE LATI — il cap non puo' ne' superare il
conto reale (leva nascosta) ne' restare sotto dopo un deposito (strozzatura).
Dry-run sul conto reale: cap/asset $298, book flat, nessun ordine. 391 test verdi (+11).
Strategia, pesi, cron INVARIATI. Cambia solo config/live.json e il calcolo del cap di
fallback.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+51
-5
@@ -43,6 +43,8 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from src.live.deribit import INSTRUMENT, notional_to_amount
|
||||
from src.live.shadow import ASSETS, WEIGHT, shadow_report
|
||||
from src.portfolio.sleeves import _skyhook_positions
|
||||
@@ -56,21 +58,62 @@ W_SKH = 0.25
|
||||
FLAT_USD = 1.0
|
||||
|
||||
|
||||
EQUITY_WATERMARK = PROJECT_ROOT / "data" / "live" / "equity_seen.json"
|
||||
# Se non si e' MAI vista l'equity reale non si sa niente del conto: si usa la taglia storicamente
|
||||
# sicura, non il cap di config (che puo' essere stato alzato in previsione di un deposito).
|
||||
CAP_UNKNOWN_USD = 300.0
|
||||
|
||||
|
||||
def _read_watermark() -> float | None:
|
||||
"""Ultima equity REALE osservata sul conto. None se mai vista o file illeggibile."""
|
||||
try:
|
||||
v = float(json.loads(EQUITY_WATERMARK.read_text())["real_equity"])
|
||||
return v if v > 0 else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def write_equity_watermark(real_equity: float | None) -> None:
|
||||
"""Registra l'equity reale ogni volta che e' leggibile. Chiamata da `book_report`."""
|
||||
if real_equity is None or real_equity <= 0:
|
||||
return
|
||||
try:
|
||||
EQUITY_WATERMARK.parent.mkdir(parents=True, exist_ok=True)
|
||||
EQUITY_WATERMARK.write_text(json.dumps(
|
||||
{"real_equity": float(real_equity), "ts": pd.Timestamp.now(tz="UTC").isoformat()}))
|
||||
except Exception:
|
||||
pass # il watermark e' un'ottimizzazione di sicurezza, non un requisito
|
||||
|
||||
|
||||
def _cap(equity: float | None = None, real_equity: float | None = None,
|
||||
eq_fallback: str | None = None) -> float:
|
||||
"""Cap notional per-asset ($). Se in config c'e' `max_notional_per_asset_frac` (opt-in), il cap
|
||||
diventa DINAMICO = equity * frac (es. equity/2) -> cresce col capitale, cosi' un deposito non
|
||||
resta strozzato dal $300 fisso (vedi frontiera 2026-07-03). GUARDRAIL: il cap dinamico si usa
|
||||
SOLO con equity reale fidata (letta dal conto); se l'equity reale non e' leggibile
|
||||
(eq_fallback/offline, real_equity=None) si torna al cap FISSO `max_notional_per_asset_usd`, che
|
||||
protegge il downside quando l'equity e' ignota. Senza `..._frac` in config -> sempre cap fisso."""
|
||||
resta strozzato (vedi frontiera 2026-07-03). GUARDRAIL: il cap dinamico si usa SOLO con equity
|
||||
reale fidata (letta dal conto).
|
||||
|
||||
⚠️ FALLBACK (2026-07-26). Quando l'equity reale NON e' leggibile il sizing ripiega su
|
||||
`paper_cap` (=$2.000 nominali), che NON e' il conto vero: il cap fisso e' li' a impedire che
|
||||
quel nominale diventi leva reale. Un cap fisso mantenuto A MANO e' pero' sbagliato in entrambi
|
||||
i versi — troppo basso dopo un deposito (a $6k strozzava il book al ~10% del target), troppo
|
||||
alto prima (a $597 un cap da $3.000 avrebbe permesso $2.000 di nozionale lordo = **3.35x di
|
||||
leva** proprio nel momento in cui non si sa quanto c'e' sul conto).
|
||||
Quindi il fallback e' legato all'**ultima equity reale osservata**:
|
||||
cap_fallback = min(cap_fisso_di_config, watermark * frac)
|
||||
Cosi' il cap di config resta un TETTO dichiarato e il watermark impedisce di superare il conto
|
||||
vero, in ogni ordine di eventi. Senza watermark (mai visto il conto) -> `CAP_UNKNOWN_USD`.
|
||||
Rende inutile l'azione manuale "al deposito alzare il cap", pre-registrata il 2026-07-02 e
|
||||
rimasta ineseguita per 24 giorni."""
|
||||
cfg = json.loads(CONFIG.read_text()) if CONFIG.exists() else {}
|
||||
fixed = float(cfg.get("max_notional_per_asset_usd", 300.0))
|
||||
frac = cfg.get("max_notional_per_asset_frac")
|
||||
if frac is None:
|
||||
return fixed
|
||||
trusted = (real_equity is not None) and (not eq_fallback) and (equity is not None) and (equity > 0)
|
||||
return float(equity) * float(frac) if trusted else fixed
|
||||
if trusted:
|
||||
return float(equity) * float(frac)
|
||||
wm = _read_watermark()
|
||||
return min(fixed, wm * float(frac)) if wm is not None else min(fixed, CAP_UNKNOWN_USD)
|
||||
|
||||
|
||||
def book_net_target(tp_frac: float, skh_sign: int, equity: float, cap: float,
|
||||
@@ -126,6 +169,9 @@ def book_report(offline: bool = False, equity_override: float | None = None,
|
||||
deterministico: dashboard/test). TP01 resta sul certificato (è giornaliero)."""
|
||||
sh = shadow_report(offline=offline, equity_override=equity_override)
|
||||
equity = sh["equity"]
|
||||
# registra l'equity reale quando e' leggibile: e' cio' che rende sicuro il cap di
|
||||
# fallback in OGNI ordine di eventi (vedi _cap).
|
||||
write_equity_watermark(sh.get("real_equity"))
|
||||
cap = _cap(equity=equity, real_equity=sh.get("real_equity"), eq_fallback=sh.get("eq_fallback"))
|
||||
load5m = None
|
||||
feed_ages: dict[str, float | None] = {}
|
||||
|
||||
Reference in New Issue
Block a user