c52e0ab3f8
Forward-monitor del LEAD dello sweep 2026-06-29 (relative-value ETH/BTC, dollar-neutral 2 gambe), il primo stream insieme ORTOGONALE (corr->book 0.027, beta-mkt 0.013) ED eseguibile a $600. - scripts/live/paper_statarb.py: forward-only, doppio libro MODELED($2000)/REAL-$600 (haircut fill), riusa il segnale ESATTO di orthogonal_signals.py (niente reimplementazione). Config CONGELATA W=45 sgn=+1. - Cablato in scripts/cron_daily.sh accanto a paper_prevday. Stato runtime in data/paper_statarb/ (gitignored). - test tests/test_paper_statarb.py (frozen config + advance forward/idempotente + haircut $600 basso). Correzione di etichetta (verificata): la cella vincente e' sgn=+1 -> NON mean-reversion ma relative-MOMENTUM sul residuo (dislocazioni ETH-vs-BTC continuano a 1d; sgn=-1 perde -1.4 IS). Diario + CLAUDE.md aggiornati. Test 146/146. Nessun deploy, forward-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
2.5 KiB
Python
61 lines
2.5 KiB
Python
"""Lock del forward-monitor STATARB-RESID (scripts/live/paper_statarb.py):
|
|
* config CONGELATA (W=45, sgn=+1) — non deve cambiare silenziosamente;
|
|
* advance() processa SOLO le barre forward ed è IDEMPOTENTE alla punta;
|
|
* a 1d il libro REAL-$600 traccia il MODELED (haircut di fill piccolo).
|
|
Il segnale è quello esatto dello sweep (orthogonal_signals) → niente reimplementazione.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "scripts" / "live"))
|
|
import paper_statarb as ps # noqa: E402
|
|
|
|
|
|
def _fresh_state(j, back):
|
|
ts, _, pos, _ = ps._signal(j)
|
|
start = int(ts[-back - 1])
|
|
return dict(start_ts=start, last_ts=start, n_bars=0,
|
|
pos_modeled=float(pos[-back - 1]), pos_real=float(pos[-back - 1]),
|
|
cap_modeled=ps.MODELED_CAPITAL, cap_real=ps.REAL_CAPITAL,
|
|
peak_modeled=ps.MODELED_CAPITAL, peak_real=ps.REAL_CAPITAL,
|
|
dd_modeled=0.0, dd_real=0.0, n_trades=0)
|
|
|
|
|
|
def _redirect(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(ps, "STATE_DIR", tmp_path)
|
|
monkeypatch.setattr(ps, "STATE_FILE", tmp_path / "state.json")
|
|
monkeypatch.setattr(ps, "RETURNS_FILE", tmp_path / "returns.jsonl")
|
|
monkeypatch.setattr(ps, "TRADES_FILE", tmp_path / "trades.jsonl")
|
|
|
|
|
|
def test_frozen_config():
|
|
"""La cella vincente in-sample è W=45 sgn=+1 (relative-momentum, non mean-reversion)."""
|
|
assert ps.W_FROZEN == 45
|
|
assert ps.SGN_FROZEN == +1
|
|
|
|
|
|
def test_advance_processes_forward_then_idempotent(tmp_path, monkeypatch):
|
|
_redirect(tmp_path, monkeypatch)
|
|
j = ps.build_joint("1d")
|
|
ts, *_ = ps._signal(j)
|
|
st = _fresh_state(j, back=60)
|
|
st = ps.advance(st, j)
|
|
assert st["n_bars"] == 60 # ha processato le 60 barre forward
|
|
assert st["last_ts"] == int(ts[-1])
|
|
assert st["cap_modeled"] != ps.MODELED_CAPITAL # l'equity si è mossa
|
|
# idempotente: ri-avanzare alla punta non aggiunge barre né muove l'equity
|
|
eq_before = st["cap_modeled"]
|
|
st2 = ps.advance(dict(st), j)
|
|
assert st2["n_bars"] == 60
|
|
assert st2["cap_modeled"] == eq_before
|
|
|
|
|
|
def test_real600_tracks_modeled_low_turnover(tmp_path, monkeypatch):
|
|
_redirect(tmp_path, monkeypatch)
|
|
j = ps.build_joint("1d")
|
|
st = ps.advance(_fresh_state(j, back=250), j)
|
|
rm = st["cap_modeled"] / ps.MODELED_CAPITAL - 1
|
|
rr = st["cap_real"] / ps.REAL_CAPITAL - 1
|
|
assert abs(rm - rr) < 0.05 # haircut di fill piccolo a 1d ($600)
|