14522262e6
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>
135 lines
7.0 KiB
Python
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()
|