feat(portfolio): contenitore di strategie ESTENSIBILE — TP01 primo sleeve
src/portfolio/: Sleeve (serie rendimenti netti per-barra, causale/fee-aware) + StrategyPortfolio (combina N sleeve per peso su griglia giornaliera comune, metriche FULL/HOLD-OUT/per-anno + standalone per-sleeve, vs buy&hold). Registry sleeve attivi in sleeves.py: per ora SOLO TP01 (peso 100%); aggiungere = una riga (dopo validazione col gauntlet). Report (run_portfolio.py): TP01 FULL Sh 1.30 / DD 14.3% / ~€1.52/g, HOLD-OUT 0.31 / +3.5% (buy&hold -0.32 / -39%). Posizione corrente flat (difensivo). tests/test_portfolio.py (6 test). CLAUDE.md aggiornato (struttura + comando + come aggiungere uno sleeve). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"""Test del contenitore portafoglio estensibile."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from src.portfolio.portfolio import Sleeve, StrategyPortfolio, to_daily, metrics
|
||||
|
||||
|
||||
def _const_sleeve(name, weight, val, n=400):
|
||||
idx = pd.date_range("2020-01-01", periods=n, freq="1D", tz="UTC")
|
||||
return Sleeve(name, weight, lambda: pd.Series(val, index=idx))
|
||||
|
||||
|
||||
def test_single_sleeve_equals_itself():
|
||||
s = _const_sleeve("A", 1.0, 0.001)
|
||||
pf = StrategyPortfolio([s])
|
||||
combo = pf.combined_daily()
|
||||
assert np.allclose(combo.values, s.daily().values)
|
||||
assert pf.weights() == {"A": 1.0}
|
||||
|
||||
|
||||
def test_weights_normalize():
|
||||
pf = StrategyPortfolio([_const_sleeve("A", 3.0, 0.001), _const_sleeve("B", 1.0, 0.002)])
|
||||
w = pf.weights()
|
||||
assert abs(sum(w.values()) - 1.0) < 1e-12
|
||||
assert abs(w["A"] - 0.75) < 1e-12 and abs(w["B"] - 0.25) < 1e-12
|
||||
|
||||
|
||||
def test_equal_weight_combine():
|
||||
a, b = _const_sleeve("A", 1.0, 0.001), _const_sleeve("B", 1.0, 0.003)
|
||||
pf = StrategyPortfolio([a, b])
|
||||
combo = pf.combined_daily()
|
||||
assert np.allclose(combo.values, 0.5 * 0.001 + 0.5 * 0.003) # 0.002
|
||||
|
||||
|
||||
def test_to_daily_compounds_intraday():
|
||||
# due barre da +1% nello stesso giorno -> +2.01% giornaliero
|
||||
idx = pd.to_datetime(["2020-01-01T00:00", "2020-01-01T12:00"], utc=True)
|
||||
d = to_daily(pd.Series([0.01, 0.01], index=idx))
|
||||
assert len(d) == 1 and abs(d.iloc[0] - (1.01 * 1.01 - 1)) < 1e-12
|
||||
|
||||
|
||||
def test_metrics_basic():
|
||||
idx = pd.date_range("2020-01-01", periods=730, freq="1D", tz="UTC")
|
||||
m = metrics(pd.Series(0.0005, index=idx)) # ritorno costante positivo
|
||||
assert m["ret"] > 0 and m["maxdd"] == 0.0 and m["n"] == 730
|
||||
|
||||
|
||||
def test_empty_portfolio_raises():
|
||||
with pytest.raises(ValueError):
|
||||
StrategyPortfolio([])
|
||||
Reference in New Issue
Block a user