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>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from src.portfolio.runner import build_worker_for
|
||||
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
|
||||
@@ -20,6 +20,26 @@ def test_build_pairs_worker(tmp_path):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user