6097205694
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>
53 lines
2.6 KiB
Python
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"
|