feat(live): disaster-bracket on-book sui fade reali + alert FEED_OUTAGE + osservabilita' multi-asset
Punti 5-6 dell'improvement-sweep 2026-06-06 (protezione capitale + osservabilita'): Punto 5 — disaster bracket: - ExecutionClient.place_disaster_sl: STOP_MARKET reduce-only a ~-30% dall'ingresso (trigger mark price), piazzato a ogni REAL_OPEN (MR01/MR02/MR07/DIP01) e cancellato in _real_close. Assicurazione outage: il poll-loop in except lascia le posizioni reali senza valutazione exit (ETH gap max storico 33%/1h). In operativita' normale non scatta mai -> 0 costo Sharpe. real_dsl_order_id persistito (resume-safe). Config overrides.execution.disaster_sl_pct (0.30). - NB: set_stop_loss di cerbero-mcp e' un private/edit Deribit (solo ordini APERTI) -> non usabile su market fillati; il bracket e' un trigger order autonomo via place_order(type=stop_market). Cancel di un trigger order risponde 'untriggered' (= successo, verificato testnet: re-cancel -> order_not_found). - Runner: alert Telegram FEED_OUTAGE dopo 5 poll falliti consecutivi (elenco posizioni reali aperte) + notifica RIPRESO con durata. Punto 6 — osservabilita': - in_position nei _save() di TR01/ROT02/TSM01; hourly_report: sezione MULTI-ASSET (book | ultimo flip | freschezza status) — prima i 3 worker erano invisibili (collect() filtra su event/in_position che non emettevano); esclusi dalla tabella IN CORSO (assume entry/bars single-leg). - live_shadow_smoke esteso: scenari C/D SHORT (TP-resting BUY mai esercitato prima) + disaster bracket in tutti gli scenari. Verifiche: 72/72 test; smoke testnet 4 scenari verdi (DSL piazzato/cancellato due lati, zero ordini orfani sul book, conto flat); multi_asset_section renderizza sui dati live. Diario docs/diary/2026-06-07-sweep-fixes.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,8 @@ def collect():
|
||||
pnl = ev.get("pnl", 0.0)
|
||||
realized += pnl
|
||||
closed.append((_short(wid), ev.get("reason", "?"), nr, pnl, nr > 0))
|
||||
if "positions" in st or "weights" in st:
|
||||
continue # multi-asset (TR01/ROT02/TSM01): sezione dedicata
|
||||
if st.get("in_position"):
|
||||
open_pos.append({
|
||||
"sleeve": _short(wid),
|
||||
@@ -120,6 +122,43 @@ def collect():
|
||||
return closed, open_pos, realized
|
||||
|
||||
|
||||
def multi_asset_section() -> str:
|
||||
"""Worker multi-asset (TR01/ROT02/TSM01): book corrente + eta' dell'ultimo flip
|
||||
+ freschezza dello status. Prima erano INVISIBILI nel report (collect() filtra
|
||||
su event/in_position che non emettevano): impossibile distinguere 'flat in
|
||||
chop / risk-off by-design' da 'wiring rotto' (improvement-sweep 2026-06-06)."""
|
||||
now = datetime.now(timezone.utc)
|
||||
rows = []
|
||||
for sp in sorted(glob.glob(str(PAPER / "*" / "status.json"))):
|
||||
d = Path(sp).parent
|
||||
st = json.loads(Path(sp).read_text())
|
||||
book = st.get("positions") if "positions" in st else st.get("weights")
|
||||
if book is None:
|
||||
continue # single-leg/pairs: gia' coperti da collect()
|
||||
held = {a: v for a, v in book.items() if v > 0}
|
||||
flip = "mai"
|
||||
tp = d / "trades.jsonl"
|
||||
if tp.exists():
|
||||
lines = [ln for ln in tp.read_text().splitlines() if ln.strip()]
|
||||
if lines:
|
||||
ts = json.loads(lines[-1]).get("ts", "")
|
||||
if ts:
|
||||
days = (now - datetime.fromisoformat(ts)).days
|
||||
flip = f"{days}g fa"
|
||||
fresh = "?"
|
||||
lu = st.get("ts") or st.get("last_update")
|
||||
if lu:
|
||||
h = (now - datetime.fromisoformat(lu)).total_seconds() / 3600
|
||||
fresh = "OK" if h < 2 else f"STALE {h:.0f}h"
|
||||
code = d.name.split("__")[0].split("_")[0] # TR01_basket__... -> TR01
|
||||
hb = ",".join(f"{a}:{v:.2f}" for a, v in sorted(held.items())) if held else "flat"
|
||||
rows.append(f"{code:<7}{hb:<26}{flip:>8} {fresh}")
|
||||
if not rows:
|
||||
return ""
|
||||
return ("📈 <b>MULTI-ASSET</b> (book | ultimo flip | status)\n<pre>"
|
||||
+ "\n".join(rows) + "</pre>")
|
||||
|
||||
|
||||
def build_report() -> str:
|
||||
closed, open_pos, realized = collect()
|
||||
pos = sum(1 for c in closed if c[4])
|
||||
@@ -166,6 +205,11 @@ def build_report() -> str:
|
||||
rows.append(f"{p['sleeve']:<14}{d:<2}{p['bars']:>6} {es}")
|
||||
L.append("<pre>" + "\n".join(rows) + "</pre>")
|
||||
|
||||
# 2a) worker multi-asset (TR01/ROT02/TSM01)
|
||||
mas = multi_asset_section()
|
||||
if mas:
|
||||
L.append(mas)
|
||||
|
||||
# 2b) monitor loss-guard
|
||||
L.append(lossguard_section())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user