Files
PythagorasGoal/Old/scripts/analysis/live_shadow_smoke.py
T
Adriano Dal Pastro 14522262e6 chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera
libreria "validata OOS" era artefatto di feed contaminato (print fantasma del
feed Cerbero TESTNET + storico Binance/USDT).

- Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e
  CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia
  (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample
  (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE
  50-82% barre flat; XRP/BNB non certificabili).
- Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni
  portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST
  con segnale residuo, da ri-validare in isolamento.
- Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio,
  runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/
  portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/
  (preservati, non cancellati). Diario consolidato in un unico documento.
- Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal +
  src/backtest/engine + load_data; tool dati certificati (rebuild_history,
  certify_feed, audit_feed, multi_source_check).
- Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico
  (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:20:59 +00:00

135 lines
7.0 KiB
Python

"""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()