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>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user