Files
PythagorasGoal/tests/portfolio/test_runner_build.py
T
Adriano Dal Pastro 6097205694 feat(pairs): position_size per-famiglia, PAIRS 0.5->0.20 (gate PORT06)
Punto 8 roadmap sweep. La famiglia pairs (SENZA stop, validata a pos 0.15 lev 3 =
esposizione 0.45) girava col position_size globale 0.5 a leva 2 = esposizione 1.0,
~2.2x il validato (ETH/BTC DD grezzo 78% a quella taglia; live: ADA/ETH -4.26%
sleeve in un trade il 2026-06-05).

Gate pairspos_port06_impact.py (pairs_sim alla leva live 2x, parita' canonica
1.00000 5/5, PORT06 pesi cap): griglia pos {0.5, 0.25, 0.20, 0.15}. Il gate
meccanico boccia ogni riduzione (costa OOS Sharpe — il compounding in-sample a 0.5
e' fantasia: +2.6e9% ETH/BTC), ma 0.20 e' il ginocchio del trade-off: OOS DD
3.40->1.26% (meglio anche di 0.15), famiglia DD 14->5.8%, costo OOS Sharpe
9.05->8.43. Decisione assicurativa (utente), come il precedente cap SHAPE.

- runner.pos_for_spec(sid, global, family_overrides): override per-famiglia
  (chiave weighting.family_of), plumbato in build_worker_for.
- portfolios.yml: position_size_family {PAIRS: 0.20} (globale 0.5 invariato).
- Test: pos_for_spec mapping + PairsWorker.position_size (74/74 verdi).

Nota transizione: la posizione LTC/ETH aperta verra' chiusa col pos nuovo
(one-time). Diario docs/diary/2026-06-07-pairspos-gate.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:44:15 +00:00

53 lines
2.6 KiB
Python

from src.portfolio.runner import build_worker_for, pos_for_spec
from src.portfolio.base import SleeveSpec
from src.live.strategy_worker import StrategyWorker
from src.live.pairs_worker import PairsWorker
def test_build_single_worker_capital_from_alloc(tmp_path):
spec = SleeveSpec(kind="single", name="MR01", sid="MR01_BTC", asset="BTC",
params={"bb_window": 50, "k": 2.5, "sl_atr": 2.0, "max_bars": 24})
w = build_worker_for(spec, alloc_capital=300.0, leverage=2.0, data_dir=tmp_path)
assert isinstance(w, StrategyWorker)
assert w.capital == 300.0 and w.leverage == 2.0
def test_build_pairs_worker(tmp_path):
spec = SleeveSpec(kind="pairs", name="PR01", sid="PR_ETHBTC", a="ETH", b="BTC",
params={"n": 50, "z_in": 2.0, "z_exit": 0.75, "max_bars": 72})
w = build_worker_for(spec, alloc_capital=200.0, leverage=2.0, data_dir=tmp_path)
assert isinstance(w, PairsWorker)
assert w.capital == 200.0
def test_pos_for_spec_family_override():
"""position_size per-famiglia (improvement-sweep punto 8): l'override PAIRS si
applica ai soli PR_*, gli altri sleeve restano sul globale."""
fam = {"PAIRS": 0.20}
assert pos_for_spec("PR_ETHBTC", 0.5, fam) == 0.20
assert pos_for_spec("PR_ADAETH", 0.5, fam) == 0.20
assert pos_for_spec("MR01_BTC", 0.5, fam) == 0.5 # FADE -> globale
assert pos_for_spec("SH_BTC", 0.5, fam) == 0.5 # SHAPE -> globale
assert pos_for_spec("TSM01", 0.5, fam) == 0.5
assert pos_for_spec("PR_ETHBTC", 0.5, {}) == 0.5 # nessun override
def test_build_pairs_worker_position_size(tmp_path):
spec = SleeveSpec(kind="pairs", name="PR01", sid="PR_ETHBTC", a="ETH", b="BTC",
params={"n": 50, "z_in": 2.0, "z_exit": 0.75, "max_bars": 72})
w = build_worker_for(spec, alloc_capital=200.0, leverage=2.0,
position_size=0.20, data_dir=tmp_path)
assert w.position_size == 0.20
def test_build_ml_sh01_is_plain_strategyworker(tmp_path):
"""SH01 (kind=ml) deve costruirsi come StrategyWorker che esegue SH01_shape_ml — NON il
vecchio MLWorkerWrapper (SignalEngine squeeze scartato, che ignorava la strategia ed usciva
a hold_bars=3). Regressione del bug di wiring scoperto il 2026-06-01."""
spec = SleeveSpec(kind="ml", name="SH01", sid="SH_BTC", asset="BTC")
w = build_worker_for(spec, alloc_capital=58.82, leverage=2.0, data_dir=tmp_path)
assert isinstance(w, StrategyWorker)
assert w.strategy.name == "SH01_shape_ml" # esegue davvero shape-ML
assert not hasattr(w, "engine") # niente SignalEngine squeeze
assert w.tf == "1h"