Files
PythagorasGoal/tests/test_paper_statarb.py
T
Adriano Dal Pastro c52e0ab3f8 feat(forward): cabla STATARB-RESID nel forward-monitor PAPER (lead ortogonale ETH/BTC)
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>
2026-06-29 20:58:29 +00:00

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)