e28176efef
Goal: comprare azioni IB quando sottoquotate, con verifica incrociata dei dati. 1) CHECK DATI DALLA RETE (il pezzo richiesto): IB certificato (eq_*, ADJUSTED) vs Yahoo adjclose (sorgente rete indipendente, tokenless). Tutti CONCORDE <=1.2bps sui rendimenti. Teaching: un confronto naif (adjusted vs grezzo) falso-allarmava 4/6 ticker a 30-52bps = TUTTO stacco dividendo -> ogni divergenza va SPIEGATA prima di gridare 'feed sporco' (v2.0.0). Strumento riutilizzabile (validatore feed / pre-trade price-check). 2) Scalping 'sottoquotate': NON testabile (no dati intraday) ne' eseguibile (PDT rule 5k blocca il day-trading; IB instabile). Versione testabile = swing MR (Connors RSI2<10 + MA200, exit MA5): Sharpe modesto (SPY 0.75/0.70) ma NON batte il buy&hold (B&H hold 0.81), expo 13%, CAGR 2-5%, fee-sensitive. Niente edge schierabile. - scripts/research/eq_meanrev_ib.py - tests/test_eq_meanrev.py (incl. network check) - docs/diary/2026-06-26-equity-meanrev-network-check.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
2.0 KiB
Python
52 lines
2.0 KiB
Python
"""Lock 2026-06-26 (equity MR + check rete): la mean-reversion "sottoquotata" daily NON batte il
|
|
buy&hold, e il check dati dalla rete (IB vs Yahoo) deve concordare adjusted-vs-adjusted.
|
|
Diario docs/diary/2026-06-26-equity-meanrev-network-check.md."""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
|
|
|
from eq_meanrev_ib import rsi, mr_target, backtest, metrics, buyhold, yahoo_daily # type: ignore
|
|
from eqlib import load_eq # type: ignore
|
|
|
|
import pandas as pd
|
|
|
|
|
|
def test_rsi_in_bounds():
|
|
c = np.cumprod(1 + np.linspace(-0.02, 0.02, 300))
|
|
r = rsi(c, 2)
|
|
assert np.nanmin(r) >= 0.0 and np.nanmax(r) <= 100.0
|
|
|
|
|
|
def test_mr_target_binary_and_has_trades():
|
|
"""La posizione è 0/1 ed entra solo da sottoquotata (genera scambi, exposure < 50%)."""
|
|
c = load_eq("SPY")["close"].astype(float).values
|
|
pos = mr_target(c)
|
|
assert set(np.unique(pos)).issubset({0.0, 1.0})
|
|
expo = pos.mean()
|
|
assert 0.02 < expo < 0.5 # mean-reversion = poco investito
|
|
|
|
|
|
def test_meanrev_does_not_beat_buyhold_holdout():
|
|
"""Verdetto onesto: la MR daily NON batte il buy&hold risk-adjusted nel hold-out su SPY."""
|
|
HO = pd.Timestamp("2015-01-01", tz="UTC")
|
|
mr = metrics(backtest("SPY"), lo=HO)["sharpe"]
|
|
bh = buyhold("SPY", lo=HO)["sharpe"]
|
|
assert mr <= bh + 0.05 # non supera il benchmark (entro rumore)
|
|
|
|
|
|
@pytest.mark.network
|
|
def test_ib_feed_concords_with_network_source():
|
|
"""Check dati dalla rete: i rendimenti IB (adjusted) concordano con Yahoo adjclose a pochi bps."""
|
|
ib = load_eq("SPY")["close"].astype(float); ib.index = ib.index.normalize()
|
|
yh = yahoo_daily("SPY")["adjclose"]
|
|
J = pd.concat({"a": ib, "b": yh}, axis=1, join="inner").dropna().tail(120)
|
|
d = (J["a"].pct_change() - J["b"].pct_change()).abs().dropna()
|
|
assert d.max() < 0.001 # <10bps adjusted-vs-adjusted
|
|
assert abs(J["a"].iloc[-1] / J["b"].iloc[-1] - 1) < 0.002
|