"""Smoke della catena SHADOW dentro lo StrategyWorker (testnet, ordini reali minimi). Apre e chiude la quota di UN worker fade come farebbe il runner, esercitando i DUE percorsi del LIMIT reduce-only al TP (fix divergenza sim/reale 2026-06-04): A) TP lontano → REAL_TP_RESTING in book; exit sim non-TP → cancel + market reduce-only di fallback (REAL_CLOSE con tp_filled_amount=0); B) TP gia' oltre il prezzo → il limit crossa e filla SUBITO; la chiusura riconcilia il fill dal trade history (order_id) SENZA ordine market (REAL_CLOSE con market_amount=0); C) SHORT con TP lontano sotto → resting BUY in book + exit non-TP (il path short non era MAI stato esercitato: tutti i REAL_TP_RESTING storici sono side=sell — improvement-sweep 2026-06-06); D) SHORT con TP sopra il prezzo → il buy limit crossa subito → riconciliazione. In TUTTI gli scenari e' attivo il disaster-bracket (~-30%): verifica che lo STOP_MARKET reduce-only venga piazzato all'open e cancellato alla chiusura. Non tocca lo stato di produzione (data_dir temporanea). Costo testnet = €0. uv run python scripts/analysis/live_shadow_smoke.py """ from __future__ import annotations import tempfile from pathlib import Path from src.live.cerbero_client import CerberoClient from src.live.execution import ExecutionClient from src.live.strategy_loader import load_strategy from src.live.strategy_worker import StrategyWorker from src.strategies.base import Signal def main() -> None: client = CerberoClient() acct = client.get_account_summary(currency="USDC") print(f"Account testnet equity={acct.get('equity')} USDC testnet={acct.get('testnet')}") if not acct.get("testnet"): raise SystemExit("ABORT: non testnet") ex = ExecutionClient(client=client) ex.disaster_sl_pct = 0.30 # come in produzione (portfolios.yml) instrument = "BTC_USDC-PERPETUAL" price = ex._mark_price(instrument) print(f"{instrument} mark={price}") with tempfile.TemporaryDirectory() as tmp: w = StrategyWorker( strategy=load_strategy("MR01_bollinger_fade"), asset="BTC", tf="1h", capital=100.0, position_size=0.15, leverage=2.0, data_dir=Path(tmp), executor=ex, exec_instrument=instrument, ) print(f"execution_enabled={w.execution_enabled} notional atteso=${100*0.15*2:.0f}") # --- Scenario A: TP lontano → resting in book, exit non-TP → cancel + market --- print("\n[A] TP lontano (resting) → exit time_limit → cancel + market fallback") sig = Signal(idx=0, direction=1, entry_price=price, metadata={"tp": price * 1.05, "sl": price * 0.50, "max_bars": 6}) w._open_position(sig, price) print(f" real_in_position={w.real_in_position} side={w.real_side} " f"amount={w.real_amount} entry={w.real_entry_price} " f"entry_fee=${w.real_entry_fee_usd:.5f} notional=${w.real_entry_notional:.2f}") assert w.real_in_position, "OPEN reale non verificato" print(f" TP resting order_id={w.real_tp_order_id!r} " f"DSL order_id={w.real_dsl_order_id!r}") assert w.real_tp_order_id, "LIMIT reduce-only al TP non piazzato" assert w.real_dsl_order_id, "disaster-SL STOP_MARKET non piazzato" cap_before = w.real_capital w._close_position((w.entry_price or price) * 1.001, "time_limit") print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} " f"(Δ {w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}") assert not w.real_in_position, "posizione reale non chiusa" assert not w.real_tp_order_id, "order_id TP non resettato dopo la chiusura" assert not w.real_dsl_order_id, "order_id DSL non resettato dopo la chiusura" # --- Scenario B: TP gia' oltre il prezzo → il limit crossa e filla subito --- print("\n[B] TP gia' crossato (fill immediato del limit) → close riconcilia da history") price = ex._mark_price(instrument) or price sig = Signal(idx=0, direction=1, entry_price=price, metadata={"tp": price * 0.995, "sl": price * 0.50, "max_bars": 6}) w._open_position(sig, price) assert w.real_in_position, "OPEN reale non verificato (B)" assert w.real_tp_order_id, "LIMIT TP non piazzato (B)" cap_before = w.real_capital w._close_position(w.tp, "take_profit") print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} " f"(Δ {w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}") assert not w.real_in_position, "posizione reale non chiusa (B)" # --- Scenario C: SHORT, TP lontano sotto → resting BUY, exit non-TP --- print("\n[C] SHORT, TP lontano sotto (resting BUY) → exit time_limit → cancel + market") price = ex._mark_price(instrument) or price sig = Signal(idx=0, direction=-1, entry_price=price, metadata={"tp": price * 0.95, "sl": price * 1.50, "max_bars": 6}) w._open_position(sig, price) print(f" side={w.real_side} amount={w.real_amount} " f"TP={w.real_tp_order_id!r} DSL={w.real_dsl_order_id!r}") assert w.real_in_position and w.real_side == "sell", "OPEN short non verificato (C)" assert w.real_tp_order_id, "LIMIT BUY reduce-only al TP non piazzato (C)" assert w.real_dsl_order_id, "disaster-SL short non piazzato (C)" cap_before = w.real_capital w._close_position((w.entry_price or price) * 0.999, "time_limit") print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} " f"(Δ {w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}") assert not w.real_in_position, "posizione short non chiusa (C)" # --- Scenario D: SHORT, TP sopra il prezzo → il buy limit crossa subito --- print("\n[D] SHORT, TP gia' crossato (buy limit marketable) → riconciliazione da history") price = ex._mark_price(instrument) or price sig = Signal(idx=0, direction=-1, entry_price=price, metadata={"tp": price * 1.005, "sl": price * 1.50, "max_bars": 6}) w._open_position(sig, price) assert w.real_in_position and w.real_side == "sell", "OPEN short non verificato (D)" cap_before = w.real_capital w._close_position(w.tp, "take_profit") print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} " f"(Δ {w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}") assert not w.real_in_position, "posizione short non chiusa (D)" # verifica finale: il conto e' flat sullo strumento (nessuna quota residua del worker) pos = ex._position_size(instrument) print(f"\n posizione netta {instrument}: {pos}") print("✓ catena shadow OK — open reale long+short, LIMIT TP resting due lati " "(cancel+market e fill immediato riconciliato), disaster-SL on-book " "piazzato/cancellato, fee reali nel ledger reale") if __name__ == "__main__": main()