diff --git a/src/live/dashboard.py b/src/live/dashboard.py index 01e005e..385fb30 100644 --- a/src/live/dashboard.py +++ b/src/live/dashboard.py @@ -96,8 +96,12 @@ def html(): pos = ", ".join(f"{a['asset']} ${a['position_usd']:,.0f}" for a in sh["assets"]) ordtxt = ("; ".join(f"{o['side'].upper()} {o['amount']:.0f} {o['instrument']}" for o in sh["orders"]) if sh.get("orders") else "nessuno (target flat / gia' a target)") + dsl = sh.get("disaster_sls") or [] + dsl_txt = (" · ".join(f"{x['asset']} stop ${x['stop']:,.0f} ({x['amount']:.4f})" for x in dsl) + if dsl else "nessuno (flat)") shadow_html = (f"mainnet · sola lettura · conto reale {eq} · pos {pos} · dato {sh['last_data']}
" - f"TP01 target: {bits}
ordini-che-invierebbe (NON inviati): {ordtxt}") + f"TP01 target: {bits}
ordini-che-invierebbe (NON inviati): {ordtxt}
" + f"🛡️ disaster-SL attivi (−30%): {dsl_txt}") else: shadow_html = (f"conto reale non leggibile dal container (token solo su host) · dato {sh['last_data']}
" f"TP01 target: {bits}
→ per gli ordini reali: uv run python scripts/live/live_trend.py (host)") diff --git a/src/live/deribit.py b/src/live/deribit.py index 088edb3..0af29a8 100644 --- a/src/live/deribit.py +++ b/src/live/deribit.py @@ -32,6 +32,7 @@ _CONTRACT = { } # Il conto reale e' USDC -> mappiamo gli asset sui perp LINEARI USDC (gli unici eseguibili qui). INSTRUMENT = {"BTC": "BTC_USDC-PERPETUAL", "ETH": "ETH_USDC-PERPETUAL"} +DISASTER_LABEL = "tp01-disaster" # label dei bracket disaster-SL (per ritrovarli/gestirli/mostrarli) # ----------------------------- costruzione ordini (pura, testabile, NIENTE rete) ----------------------------- diff --git a/src/live/execution.py b/src/live/execution.py index 9df643d..acaa013 100644 --- a/src/live/execution.py +++ b/src/live/execution.py @@ -15,7 +15,7 @@ from __future__ import annotations from dataclasses import dataclass -from src.live.deribit import DeribitRead, disaster_stop_price, notional_to_amount, quantize_price +from src.live.deribit import DISASTER_LABEL, DeribitRead, disaster_stop_price, notional_to_amount, quantize_price # Conto USDC -> perp LINEARE USDC (amount in base-coin). MAX_AMOUNT = TETTO HARD anti-fat-finger # (~$630/$430 su un conto ~$600): backstop sopra il sizing di TP01, non il sizing operativo (quello @@ -152,15 +152,13 @@ class DeribitTrader(DeribitRead): def cancel_order(self, order_id: str) -> dict: return self._unwrap(self._post("/mcp-deribit/tools/cancel_order", {"order_id": order_id})) or {} - DISASTER_LABEL = "tp01-disaster" - def ensure_disaster_sl(self, instrument: str, sl_pct: float) -> dict: """Garantisce UN disaster-SL coerente con la posizione (lifecycle completo, idempotente): - flat -> cancella eventuali bracket orfani; - long -> assicura UN solo STOP_MARKET reduce_only a ~-sl_pct, size = posizione; - gia' coerente (1 bracket, amount~=, stop entro 5%) -> lascia com'e' (niente churn/gap).""" pos = self.position_usd(instrument) - brackets = [o for o in self.open_orders(instrument) if (o.get("label") or "") == self.DISASTER_LABEL] + brackets = [o for o in self.open_orders(instrument) if (o.get("label") or "") == DISASTER_LABEL] if abs(pos) < FLAT_USD: for o in brackets: self.cancel_order(o.get("order_id")) @@ -179,7 +177,7 @@ class DeribitTrader(DeribitRead): for o in brackets: # incoerente o multipli -> ricostruisci UN bracket self.cancel_order(o.get("order_id")) f = self.place_disaster_sl(instrument, "buy" if long else "sell", want_amount, want_stop, - label=self.DISASTER_LABEL) + label=DISASTER_LABEL) return {"state": "placed" if f.verified else "place-failed", "stop": want_stop, "amount": want_amount, "notes": f.notes} # trade_history / open_orders ereditati da DeribitRead (read-only) diff --git a/src/live/shadow.py b/src/live/shadow.py index a4bd867..a8e7159 100644 --- a/src/live/shadow.py +++ b/src/live/shadow.py @@ -11,7 +11,7 @@ import numpy as np import pandas as pd from src.backtest.harness import load -from src.live.deribit import INSTRUMENT, DeribitRead, build_rebalance_order +from src.live.deribit import DISASTER_LABEL, INSTRUMENT, DeribitRead, build_rebalance_order from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio, resample_1d PROJECT_ROOT = Path(__file__).resolve().parents[2] @@ -155,11 +155,25 @@ def shadow_report(offline: bool = False, equity_override: float | None = None) - live_trades.sort(key=lambda r: r["ts"], reverse=True) live_trades = live_trades[:12] + disaster_sls = [] + if client is not None: + for a in ASSETS: + try: + for o in client.open_orders(INSTRUMENT[a]): + if (o.get("label") or "") == DISASTER_LABEL: + disaster_sls.append(dict( + asset=a, instrument=INSTRUMENT[a], + amount=float(o.get("amount") or 0), + stop=float(o.get("trigger_price") or o.get("stop_price") or o.get("price") or 0))) + except Exception: + pass + return dict( last_data=str(pd.Timestamp(last_ts, unit="ms", tz="UTC").date()), online=(client is not None and marks_src.get("BTC") == "mainnet"), real_equity=real_eq, equity=equity, eq_basis=eq_basis, pos_src=pos_src, assets=assets, orders=orders, live_trades=live_trades, + disaster_sls=disaster_sls, flat=all(abs(targets[a]) < 1e-9 for a in ASSETS), paper_aligned=(paper_ts == last_ts), )