feat(xsec): XS01 reversione cross-sectional (8 asset) -> PORT06 PAPER

Famiglia NUOVA trovata in sessione (dopo aver scartato trend/breakout/seasonal/
opzioni/funding come rumore): ogni 12h long i perdenti relativi / short i vincenti
su 8 asset, market-neutral. Scorrelata (~0) da pairs e fade -> diversificatore.

- engine canonico scripts/strategies/XS01_cross_sectional.py (no look-ahead, plateau
  OOS Sharpe 2-3.9, 5/5 anni+, edge concentrato 2025, cost-sensitive ~0.35% RT).
- src/live/xsec_worker.py CrossSectionalWorker: validate_xsec_worker == backtest ESATTO
  (4993/1427 trade). Mirror della cadenza engine (entry-to-entry = hold+1).
- gate PORT06: +XS01 -> OOS Sharpe 9.66->10.07, FULL DD 3.68->3.46 (OOS DD +0.17pp,
  risk-contrib 2.2%). xsec_port06_gate.py.
- wiring: _defs XSEC in PORT06 (19 sleeve, family XSEC), build_everything, runner
  kind=xsec, asset_days da supported (fix fetch alt anche per paper sleeves), paper.
- 8 gambe -> niente exec reale -> gira PAPER. Regression-lock 18->19, FULL 7.20->7.34,
  OOS 9.66->10.07. 93 test verdi. Diario 2026-06-09-xs01-cross-sectional.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-09 21:38:05 +00:00
parent d3dab57532
commit a85289d7c7
12 changed files with 508 additions and 13 deletions
+11 -2
View File
@@ -22,6 +22,7 @@ from src.live.pairs_worker import PairsWorker
from src.live.basket_trend_worker import BasketTrendWorker
from src.live.rotation_worker import RotationWorker
from src.live.tsmom_worker import TsmomWorker
from src.live.xsec_worker import CrossSectionalWorker
from src.live.strategy_loader import load_strategy
# Codice-breve sleeve -> nome modulo Strategy in scripts/strategies/ (worker single/ml)
@@ -30,7 +31,7 @@ _STRAT_MODULE = {
"MR07": "MR07_return_reversal", "SH01": "SH01_shape_ml",
"DIP01": "DIP01_dip_buy",
}
_MULTI_KINDS = ("basket", "rotation", "tsmom")
_MULTI_KINDS = ("basket", "rotation", "tsmom", "xsec")
DATA_DIR = Path("data/portfolio_paper")
# giorni di storia da fetchare per timeframe (TSM01 1d usa 252 barre -> ~440 giorni col buffer)
@@ -88,6 +89,14 @@ def build_worker_for(spec: SleeveSpec, alloc_capital: float, leverage: float,
thr=pr.get("thr", 1.0), gross=pr.get("gross", 0.30),
tf=pr.get("tf", "1d"), capital=alloc_capital, data_dir=data_dir,
)
if spec.kind == "xsec":
pr = spec.params
return CrossSectionalWorker(
universe=pr["universe"], tf=pr.get("tf", "1h"),
params={"lb": pr.get("lb", 48), "hold": pr.get("hold", 12)},
capital=alloc_capital, position_size=position_size, leverage=leverage,
data_dir=data_dir,
)
module = _STRAT_MODULE.get(spec.name)
if module is None:
raise ValueError(f"sleeve live non supportato: {spec.name} (kind={spec.kind})")
@@ -338,7 +347,7 @@ def run(config_path: str = "portfolios.yml"):
# lookback (giorni) richiesto per ogni asset = max sui worker che lo usano
asset_days: dict[str, int] = {}
for s in live_specs:
for s in supported: # live + PAPER (anche XS01/TR01/ROT02/TSM01)
assets, tf = _spec_assets_tf(s)
days = _LOOKBACK_DAYS.get(tf, 90)
if s.kind == "ml": # SH01 ha bisogno di molta storia 1h