diff --git a/src/portfolio/runner.py b/src/portfolio/runner.py new file mode 100644 index 0000000..75020a9 --- /dev/null +++ b/src/portfolio/runner.py @@ -0,0 +1,43 @@ +"""PortfolioRunner: faccia live del portafoglio (capitale pool, sizing, ribilancio, ledger). +Riusa i worker esistenti come esecutori e il data layer Cerbero v2.""" +from __future__ import annotations + +from pathlib import Path + +from src.portfolio.base import SleeveSpec, Portfolio +from src.portfolio.ledger import PortfolioLedger +from src.live.strategy_worker import StrategyWorker +from src.live.pairs_worker import PairsWorker +from src.live.multi_runner import MLWorkerWrapper +from src.live.strategy_loader import load_strategy + +# Codice-breve sleeve -> nome modulo Strategy in scripts/strategies/ +_STRAT_MODULE = { + "MR01": "MR01_bollinger_fade", "MR02": "MR02_donchian_fade", + "MR07": "MR07_return_reversal", "SH01": "SH01_shape_ml", + # DIP01/TR01/ROT02 sono honest a sé: vedi nota nel design (worker dedicati in fase 2) +} +DATA_DIR = Path("data/paper_trades") + + +def build_worker_for(spec: SleeveSpec, alloc_capital: float, leverage: float, + data_dir: Path = DATA_DIR, position_size: float = 0.15): + """Costruisce il worker esecutore per uno sleeve con capitale = quota allocata.""" + if spec.kind == "pairs": + return PairsWorker( + asset_a=spec.a, asset_b=spec.b, tf=spec.tf, params=spec.params, + capital=alloc_capital, position_size=position_size, leverage=leverage, + fee_rt=0.001, name="PR01_pairs_reversion", data_dir=data_dir, + ) + module = _STRAT_MODULE.get(spec.name) + if module is None: + raise ValueError(f"sleeve live non ancora supportato: {spec.name} " + f"(honest DIP01/TR01/ROT02 richiedono worker dedicati, fase 2)") + strategy = load_strategy(module) + worker = StrategyWorker( + strategy=strategy, asset=spec.asset, tf=spec.tf, capital=alloc_capital, + position_size=position_size, leverage=leverage, params=spec.params, data_dir=data_dir, + ) + if spec.kind == "ml": # SH01: retraining periodico + return MLWorkerWrapper(worker, {"retrain_hours": 24}) + return worker diff --git a/tests/portfolio/test_runner_build.py b/tests/portfolio/test_runner_build.py new file mode 100644 index 0000000..3cfc7ab --- /dev/null +++ b/tests/portfolio/test_runner_build.py @@ -0,0 +1,20 @@ +from src.portfolio.runner import build_worker_for +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