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,54 @@
|
||||
"""SLEEVE del portafoglio + REGISTRY degli sleeve attivi.
|
||||
|
||||
Per AGGIUNGERE una strategia al portafoglio:
|
||||
1. Validala col gauntlet onesto (scripts/analysis/research_lab.py + hold-out + cross-asset).
|
||||
2. Scrivi una funzione `_<nome>_returns() -> pd.Series` che ritorna i suoi rendimenti netti
|
||||
per-barra (datetime-indexed, CAUSALE, netto fee). Deve passare il guard di causalità.
|
||||
3. Avvolgila in uno Sleeve(nome, peso, fn[, pos_fn]) e aggiungila a active_sleeves().
|
||||
Niente sleeve non validati: il portafoglio è solo per edge che reggono il gauntlet.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from src.data.downloader import load_data
|
||||
from src.strategies.trend_portfolio import TrendPortfolio, CANONICAL, resample_1d, simple_returns
|
||||
from src.portfolio.portfolio import Sleeve
|
||||
|
||||
ASSETS = ("BTC", "ETH")
|
||||
|
||||
|
||||
# ----------------------------- TP01 (PORT LF1d) -----------------------------
|
||||
def _tp01_returns() -> pd.Series:
|
||||
"""TP01: TSMOM vol-target long-flat, 50/50 BTC+ETH, a 1d (>=12h: vedi nota look-ahead nel modulo).
|
||||
Rendimenti netti per-barra del portafoglio (causale: posizione decisa a close[i-1], tenuta in i)."""
|
||||
tp = TrendPortfolio(**CANONICAL)
|
||||
series = {}
|
||||
for a in ASSETS:
|
||||
df = resample_1d(load_data(a, "1h"))
|
||||
r = simple_returns(df["close"].values.astype(float))
|
||||
tgt = tp.target_series(df)
|
||||
held = np.zeros(len(tgt)); held[1:] = tgt[:-1]
|
||||
net = held * r - tp.fee_side * np.abs(np.diff(held, prepend=0.0)); net[0] = 0.0
|
||||
series[a] = pd.Series(np.clip(net, -0.99, None), index=pd.to_datetime(df["datetime"]))
|
||||
J = pd.concat(series, axis=1, join="inner").fillna(0.0)
|
||||
return pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index)
|
||||
|
||||
|
||||
def _tp01_positions() -> dict:
|
||||
tp = TrendPortfolio(**CANONICAL)
|
||||
return {a: round(tp.current_target(resample_1d(load_data(a, "1h"))), 4) for a in ASSETS}
|
||||
|
||||
|
||||
def tp01_sleeve(weight: float = 1.0) -> Sleeve:
|
||||
return Sleeve("TP01_trend_1d", weight, _tp01_returns, pos_fn=_tp01_positions)
|
||||
|
||||
|
||||
# ----------------------------- REGISTRY -----------------------------
|
||||
def active_sleeves() -> list[Sleeve]:
|
||||
"""Sleeve ATTIVI nel portafoglio. Per ora solo TP01. Aggiungere qui le strategie validate."""
|
||||
return [
|
||||
tp01_sleeve(weight=1.0),
|
||||
# --- TEMPLATE per il prossimo sleeve (dopo validazione col gauntlet) ---
|
||||
# mystrat_sleeve(weight=1.0),
|
||||
]
|
||||
Reference in New Issue
Block a user