feat(live): portafoglio REALE-only — i 2000EUR ai soli 14 sleeve eseguiti, paper fuori dal pool
Decisione utente: il conto reale deve mostrare il risultato puro. I 2000 si dividono SOLO tra i 14 sleeve reali (6 fade + DIP01 + 5 pairs + SH01). I 3 multi-asset (TR01/ROT02/TSM01), non eseguibili in reale, escono da pool/pesi/ledger e girano solo per statistica. - portfolios.yml: paper_sleeves [TR01,ROT02,TSM01]. - runner: live_specs (pool/ledger) vs paper_specs (capitale nozionale fisso = total/N, data/portfolio_paper_stats/, ticcati ma MAI in update_equity). - hourly_report: sezione PAPER da portfolio_paper_stats, etichettata 'fuori dal conto reale'. Pesi reale-only (14, cap): non-cappati 0.087, PAIRS 0.33, SHAPE 0.0588. Costo misurato e accettato: FULL DD 3.96->5.34%, OOS DD 1.36->1.70% (perde i diversificatori PAPER, corr 0.07); Sharpe ~invariato. Test 93/93. Diario docs/diary/2026-06-08-real-only-portfolio.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
# 2026-06-08 — Portafoglio live REALE-only: i €2000 ai soli sleeve eseguiti
|
||||||
|
|
||||||
|
Decisione utente: il portafoglio live deve mostrare il **risultato reale puro**. I €2000
|
||||||
|
si dividono SOLO tra i 14 sleeve che eseguono davvero (6 fade + DIP01 + 5 pairs + SH01);
|
||||||
|
i 3 multi-asset (TR01/ROT02/TSM01), non eseguibili in reale (bloccati dal capitale),
|
||||||
|
escono dal pool e girano **solo per statistica**.
|
||||||
|
|
||||||
|
## Implementazione (runner-level, non rompe le definizioni)
|
||||||
|
|
||||||
|
- `portfolios.yml`: `paper_sleeves: [TR01, ROT02, TSM01]`.
|
||||||
|
- `runner`: separa `live_specs` (pool/pesi/ledger) da `paper_specs`. I paper:
|
||||||
|
- capitale NOZIONALE fisso = `total_capital / N_sleeve_totali` (la fetta equal che
|
||||||
|
avrebbero avuto), NON dal pool;
|
||||||
|
- girano in `data/portfolio_paper_stats/` (binario separato);
|
||||||
|
- ticcati per statistica ma MAI in `ledger.update_equity` → non toccano l'equity reale.
|
||||||
|
- `hourly_report`: la sezione multi-asset ora legge `portfolio_paper_stats/` ed è
|
||||||
|
etichettata "PAPER — solo statistica, FUORI dal conto reale".
|
||||||
|
|
||||||
|
## Pesi del portafoglio reale-only (14 sleeve, cap)
|
||||||
|
|
||||||
|
sum=1.0; non-cappati 0.0873 (erano 0.0647 su 17), PAIRS 0.33 (cap), SHAPE 0.0588 (cap,
|
||||||
|
SH ciascuno 0.0294). Il cap SHAPE resta valido e protettivo anche su 14.
|
||||||
|
|
||||||
|
## Il costo, misurato e accettato
|
||||||
|
|
||||||
|
| | FULL Sh | FULL DD | OOS Sh | OOS DD |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| PORT06 completo (17, validato) | 6.43 | 3.96% | 8.58 | 1.36% |
|
||||||
|
| **REALE-only (14, live ora)** | 6.49 | **5.34%** | 8.54 | **1.70%** |
|
||||||
|
|
||||||
|
Perdendo i 3 diversificatori PAPER (corr 0.07), il DD del portafoglio reale sale
|
||||||
|
~3.96→5.34% FULL e 1.36→1.70% OOS, Sharpe sostanzialmente invariato. È il **prezzo di
|
||||||
|
vedere il risultato reale puro** invece di una curva mista reale+paper: scelta
|
||||||
|
consapevole dell'utente. I PAPER restano misurati per ri-integrarli quando saranno
|
||||||
|
eseguibili (SH01 è già stato integrato oggi; i multi-asset attendono capitale ~€20k).
|
||||||
|
|
||||||
|
## Note di transizione
|
||||||
|
|
||||||
|
- Il ledger `PORT06` (code invariato) ora rappresenta i 14 reali: la curva equity ha
|
||||||
|
una discontinuità di composizione da questo deploy (atteso).
|
||||||
|
- I 3 paper ripartono puliti in `portfolio_paper_stats/` con capitale nozionale uniforme
|
||||||
|
(statistica comparabile); i vecchi status in `portfolio_paper/` restano come storico.
|
||||||
@@ -11,6 +11,13 @@ overrides:
|
|||||||
leverage: 2 # sobrio per il live reale
|
leverage: 2 # sobrio per il live reale
|
||||||
rebalance: 1D
|
rebalance: 1D
|
||||||
poll_seconds: 60
|
poll_seconds: 60
|
||||||
|
# SLEEVE PAPER (2026-06-08): fuori dal pool/pesi/ledger — i €2000 si dividono SOLO
|
||||||
|
# tra i 14 sleeve REALI (fade+DIP01+pairs+SH01). I multi-asset (no esecuzione reale,
|
||||||
|
# bloccati dal capitale) girano in data/portfolio_paper_stats/ con capitale nozionale
|
||||||
|
# fisso, SOLO per statistica in vista di future implementazioni reali. NB: il portafoglio
|
||||||
|
# live diverge ora dal PORT06 canonico (17 sleeve) -> DD reale ~5.35% vs 3.96% validato:
|
||||||
|
# il prezzo di vedere il risultato reale puro (scelta utente).
|
||||||
|
paper_sleeves: [TR01, ROT02, TSM01]
|
||||||
# Frazione di capitale-sleeve per posizione (canonico backtest = 0.15).
|
# Frazione di capitale-sleeve per posizione (canonico backtest = 0.15).
|
||||||
# 0.5 con leva 2x = 100% della fetta impegnata quando in posizione (max impiego
|
# 0.5 con leva 2x = 100% della fetta impegnata quando in posizione (max impiego
|
||||||
# dei 2K senza debito di margine). NB: il DD scala ~lineare (~×3.3 vs validato).
|
# dei 2K senza debito di margine). NB: il DD scala ~lineare (~×3.3 vs validato).
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
PAPER = ROOT / "data" / "portfolio_paper"
|
PAPER = ROOT / "data" / "portfolio_paper"
|
||||||
|
PAPER_STATS = ROOT / "data" / "portfolio_paper_stats" # sleeve PAPER fuori dal conto reale
|
||||||
PORT_STATUS = ROOT / "data" / "portfolios" / "PORT06" / "status.json"
|
PORT_STATUS = ROOT / "data" / "portfolios" / "PORT06" / "status.json"
|
||||||
|
|
||||||
|
|
||||||
@@ -130,13 +131,13 @@ def collect():
|
|||||||
|
|
||||||
|
|
||||||
def multi_asset_section() -> str:
|
def multi_asset_section() -> str:
|
||||||
"""Worker multi-asset (TR01/ROT02/TSM01): book corrente + eta' dell'ultimo flip
|
"""Sleeve PAPER (TR01/ROT02/TSM01): SOLO statistica, FUORI dal conto reale dei
|
||||||
+ freschezza dello status. Prima erano INVISIBILI nel report (collect() filtra
|
€2000 (2026-06-08). Vivono in data/portfolio_paper_stats/ con capitale nozionale
|
||||||
su event/in_position che non emettevano): impossibile distinguere 'flat in
|
fisso, raccolti per eventuali future implementazioni reali. Book corrente + eta'
|
||||||
chop / risk-off by-design' da 'wiring rotto' (improvement-sweep 2026-06-06)."""
|
ultimo flip + freschezza."""
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
rows = []
|
rows = []
|
||||||
for sp in sorted(glob.glob(str(PAPER / "*" / "status.json"))):
|
for sp in sorted(glob.glob(str(PAPER_STATS / "*" / "status.json"))):
|
||||||
d = Path(sp).parent
|
d = Path(sp).parent
|
||||||
st = json.loads(Path(sp).read_text())
|
st = json.loads(Path(sp).read_text())
|
||||||
book = st.get("positions") if "positions" in st else st.get("weights")
|
book = st.get("positions") if "positions" in st else st.get("weights")
|
||||||
@@ -162,7 +163,7 @@ def multi_asset_section() -> str:
|
|||||||
rows.append(f"{code:<7}{hb:<26}{flip:>8} {fresh}")
|
rows.append(f"{code:<7}{hb:<26}{flip:>8} {fresh}")
|
||||||
if not rows:
|
if not rows:
|
||||||
return ""
|
return ""
|
||||||
return ("📈 <b>MULTI-ASSET</b> (book | ultimo flip | status)\n<pre>"
|
return ("📈 <b>PAPER — solo statistica, FUORI dal conto reale</b> (book | ultimo flip | status)\n<pre>"
|
||||||
+ "\n".join(rows) + "</pre>")
|
+ "\n".join(rows) + "</pre>")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+30
-4
@@ -232,12 +232,25 @@ def run(config_path: str = "portfolios.yml"):
|
|||||||
|
|
||||||
def _supported(s):
|
def _supported(s):
|
||||||
return s.kind in ("pairs",) + _MULTI_KINDS or s.name in _STRAT_MODULE
|
return s.kind in ("pairs",) + _MULTI_KINDS or s.name in _STRAT_MODULE
|
||||||
live_specs = [s for s in p.sleeves if _supported(s)]
|
supported = [s for s in p.sleeves if _supported(s)]
|
||||||
skipped = [s.sid for s in p.sleeves if not _supported(s)]
|
skipped = [s.sid for s in p.sleeves if not _supported(s)]
|
||||||
if skipped:
|
if skipped:
|
||||||
print(f"[runner] sleeve saltati nel live (worker non disponibili): {skipped}")
|
print(f"[runner] sleeve saltati nel live (worker non disponibili): {skipped}")
|
||||||
|
|
||||||
|
# SLEEVE "PAPER" (solo statistica, 2026-06-08): NON entrano nel pool/pesi/ledger del
|
||||||
|
# portafoglio — i €total_capital si dividono SOLO tra gli sleeve reali. I paper girano
|
||||||
|
# con un capitale nozionale fisso (la fetta equal che avrebbero avuto) per raccogliere
|
||||||
|
# statistica in vista di future implementazioni reali. Default: TR01/ROT02/TSM01
|
||||||
|
# (multi-asset, esecuzione reale bloccata dal capitale).
|
||||||
|
paper_codes = {str(c).upper() for c in (_ov.get("paper_sleeves") or [])}
|
||||||
|
live_specs = [s for s in supported if s.name.upper() not in paper_codes]
|
||||||
|
paper_specs = [s for s in supported if s.name.upper() in paper_codes]
|
||||||
|
if paper_specs:
|
||||||
|
print(f"[runner] sleeve PAPER (solo statistica, fuori dal pool): "
|
||||||
|
f"{[s.sid for s in paper_specs]}")
|
||||||
live_ids = [s.sid for s in live_specs]
|
live_ids = [s.sid for s in live_specs]
|
||||||
clusters = {s.sid: (s.cluster or s.sid) for s in live_specs}
|
clusters = {s.sid: (s.cluster or s.sid) for s in live_specs}
|
||||||
|
paper_notional = p.total_capital / max(len(supported), 1) # fetta equal nozionale
|
||||||
|
|
||||||
ledger = PortfolioLedger(p.code, total_capital=p.total_capital)
|
ledger = PortfolioLedger(p.code, total_capital=p.total_capital)
|
||||||
client = CerberoClient()
|
client = CerberoClient()
|
||||||
@@ -294,6 +307,14 @@ def run(config_path: str = "portfolios.yml"):
|
|||||||
if ps_family:
|
if ps_family:
|
||||||
print(f"[runner] position_size globale={position_size} override famiglia={ps_family}")
|
print(f"[runner] position_size globale={position_size} override famiglia={ps_family}")
|
||||||
|
|
||||||
|
# worker PAPER (solo statistica): capitale nozionale fisso, NESSUNA esecuzione reale,
|
||||||
|
# NON nel ledger del portafoglio. Salvano in data/portfolio_paper_stats/.
|
||||||
|
paper_dir = DATA_DIR.parent / "portfolio_paper_stats"
|
||||||
|
paper_workers = {s.sid: build_worker_for(s, paper_notional, p.leverage,
|
||||||
|
data_dir=paper_dir,
|
||||||
|
position_size=pos_for_spec(s.sid, position_size, ps_family))
|
||||||
|
for s in paper_specs}
|
||||||
|
|
||||||
# bootstrap storia full per gli sleeve ML (SH01): parquet locale + feed live.
|
# bootstrap storia full per gli sleeve ML (SH01): parquet locale + feed live.
|
||||||
# L'edge SH01 richiede train expanding full-history (sh01_trainwindow_validate);
|
# L'edge SH01 richiede train expanding full-history (sh01_trainwindow_validate);
|
||||||
# il path live fitta solo l'ultimo blocco (last_block_only nei params SHAPE).
|
# il path live fitta solo l'ultimo blocco (last_block_only nei params SHAPE).
|
||||||
@@ -374,11 +395,10 @@ def run(config_path: str = "portfolios.yml"):
|
|||||||
_check_stale_feed(asset, raw1h[asset], stale_alerted)
|
_check_stale_feed(asset, raw1h[asset], stale_alerted)
|
||||||
|
|
||||||
# tick di ogni worker col suo timeframe (resample dal 1h)
|
# tick di ogni worker col suo timeframe (resample dal 1h)
|
||||||
for s in live_specs:
|
def _tick(s, w):
|
||||||
w = workers[s.sid]
|
|
||||||
assets, tf = _spec_assets_tf(s)
|
assets, tf = _spec_assets_tf(s)
|
||||||
if any(a not in raw1h for a in assets):
|
if any(a not in raw1h for a in assets):
|
||||||
continue
|
return
|
||||||
res = {a: _resample(raw1h[a], tf) for a in assets}
|
res = {a: _resample(raw1h[a], tf) for a in assets}
|
||||||
if s.kind == "pairs":
|
if s.kind == "pairs":
|
||||||
w.tick(res[s.a], res[s.b])
|
w.tick(res[s.a], res[s.b])
|
||||||
@@ -393,6 +413,12 @@ def run(config_path: str = "portfolios.yml"):
|
|||||||
# single (fade/dip): StrategyWorker su feed live.
|
# single (fade/dip): StrategyWorker su feed live.
|
||||||
w.tick(res[s.asset])
|
w.tick(res[s.asset])
|
||||||
|
|
||||||
|
for s in live_specs:
|
||||||
|
_tick(s, workers[s.sid])
|
||||||
|
# PAPER: ticcati per statistica, MAI nel ledger del portafoglio
|
||||||
|
for s in paper_specs:
|
||||||
|
_tick(s, paper_workers[s.sid])
|
||||||
|
|
||||||
ledger.update_equity({sid: _worker_equity(wk) for sid, wk in workers.items()})
|
ledger.update_equity({sid: _worker_equity(wk) for sid, wk in workers.items()})
|
||||||
|
|
||||||
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||||
|
|||||||
Reference in New Issue
Block a user