feat(live): SH01 in esecuzione reale shadow (diversificatore decorrelato)
L'infrastruttura no-TP esisteva gia': _place_real_tp ritorna subito senza TP, _real_close chiude tutta la quota a market reduce-only (exit a orizzonte H=12). Bastava: (1) _exec_for accetta kind 'ml', (2) SH01 in execution.sleeves. Disaster-bracket on-book = unica protezione di coda di SH01 (esce a H=12 ben prima del -30%). Motivo: SH01 e' il diversificatore piu' decorrelato (corr 0.07) — senza i 5 sleeve PAPER il DD del portafoglio sale 3.96->5.35%, OOS Sharpe 8.58->8.27. Test: SH01 open/close reale senza TP + disaster bracket (93/93). Copertura reale ora ~81% (resta paper solo TR01/ROT02/TSM01, bloccati dal capitale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,53 @@ class FakeExec:
|
||||
"oid-close", "filled", True)
|
||||
|
||||
|
||||
class FakeFullExec(FakeExec):
|
||||
"""Aggiunge open/place_tp_limit/place_disaster_sl per testare il ciclo SH01 completo."""
|
||||
def __init__(self):
|
||||
super().__init__([{"state": "untriggered"}])
|
||||
self.tp_calls = 0
|
||||
self.dsl_calls = 0
|
||||
self.close_amounts = []
|
||||
|
||||
def open(self, instrument, side, notional, label=None):
|
||||
amt = round(notional / 100.0, 6)
|
||||
return Fill(instrument, side, notional, amt, 100.0, 0.0, 0.05, "oid-open", "filled", True)
|
||||
|
||||
def place_tp_limit(self, *a, **k):
|
||||
self.tp_calls += 1 # NON deve essere chiamato per SH01 (no TP)
|
||||
return Fill("x", "sell", 0.0, 0.0, None, 0.0, 0.0, None, None, False)
|
||||
|
||||
def place_disaster_sl(self, instrument, side, amount, stop, label=None):
|
||||
self.dsl_calls += 1
|
||||
return Fill(instrument, "sell", 0.0, amount, None, 0.0, 0.0, "dsl-1", "untriggered", True)
|
||||
|
||||
def close_amount(self, instrument, side, amount, label=None):
|
||||
self.close_amounts.append(amount)
|
||||
return Fill(instrument, "sell", 0.0, amount, 105.0, 0.0, 0.05, "oid-close", "filled", True)
|
||||
|
||||
|
||||
def test_sh01_real_open_close_no_tp(tmp_path, monkeypatch):
|
||||
"""SH01 (no TP/SL, exit a orizzonte): apre reale, NON piazza TP, piazza disaster,
|
||||
chiude TUTTA la quota a market reduce-only."""
|
||||
from src.live.strategy_worker import StrategyWorker
|
||||
from src.strategies.base import Signal
|
||||
notified = []
|
||||
monkeypatch.setattr("src.live.strategy_worker.notify_event",
|
||||
lambda ev, data=None: notified.append(ev))
|
||||
fake = FakeFullExec()
|
||||
w = StrategyWorker(strategy=SimpleNamespace(name="SH01_shape_ml", fee_rt=0.001),
|
||||
asset="BTC", tf="1h", capital=100.0, position_size=0.5, leverage=2.0,
|
||||
data_dir=tmp_path, executor=fake, exec_instrument="BTC_USDC-PERPETUAL")
|
||||
w._open_position(Signal(idx=0, direction=1, entry_price=100.0,
|
||||
metadata={"max_bars": 12}), 100.0) # niente tp/sl
|
||||
assert w.real_in_position and fake.tp_calls == 0 # nessun TP per SH01
|
||||
assert fake.dsl_calls == 1 and w.real_dsl_order_id # disaster bracket sì
|
||||
amt = w.real_amount
|
||||
w._close_position(105.0, "time_limit")
|
||||
assert not w.real_in_position
|
||||
assert fake.close_amounts == [amt] # chiude TUTTA la quota a market
|
||||
|
||||
|
||||
def _worker(tmp_path, fake, monkeypatch, notified):
|
||||
monkeypatch.setattr("src.live.strategy_worker.notify_event",
|
||||
lambda ev, data=None: notified.append(ev))
|
||||
|
||||
Reference in New Issue
Block a user