de83909db9
MISURATO oggi durante la revisione: `trades_db.py --help` non stampava l'uso, cadeva in sync() e riscriveva meta.ultimo_sync. Nessuno dei 18 script di scripts/live/ usava argparse: un flag sbagliato era il ramo else. Su journal.py avrebbe scritto pagina e riga di DB, su analista.py avrebbe speso una chiamata al modello e mandato un Telegram. - src/live/cli.valida: prima istruzione di ogni __main__, prima di connect()/sync/rete. --help -> 0 con l'uso; flag ignoto, valore mancante o posizionale -> 2 con l'elenco dei previsti (P4). NIENTE argparse: cambierebbe messaggi, codici d'uscita e --help di script che il cron gia' chiama. - 18 script cablati (i 3 che scrivono + 14 + cc01), flag invariati. - tests/test_cli_flag.py (30): elenco DERIVATO dalla cartella (P1), valida come prima istruzione, uso che documenta i flag, e i flag che il CRON usa davvero restano accettati (P15/P16); end-to-end su --help e flag ignoto con trades.db non toccato (M15). Verificato a mano: monitor_health --quiet, trades_db --sync --quiet, book_execute dry-run. Debito §5.15, primo passo: balance_watch (orario) registra `usde_usdc` a ogni campione — None con la ragione se illeggibile, mai 1,0. Il cablaggio nel bound quando la serie ha storia. Pulizia dalla revisione: tests/helpers.carica_script al posto della 15a copia del loader importlib (5 file del libro live); il fill di prova via upsert_fills invece di un INSERT che lasciava verified NULL; asserzioni non ancorate al padding; movimenti_capitale accetta le righe gia' lette (una SELECT invece di due ai due lati di una scrittura del cron). Test 910 verdi (+52). Diario 2026-09-02c. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDJsH3iDSaBns3ccpPBwiu
102 lines
4.0 KiB
Python
102 lines
4.0 KiB
Python
"""PAPER PORTFOLIO — forward-only del portafoglio attivo (TP01 + XS01), simulato.
|
|
|
|
Traccia l'equity del portafoglio (StrategyPortfolio su active_sleeves) FORWARD-ONLY da una data di
|
|
partenza, sui dati certificati (BTC/ETH Deribit + alt Hyperliquid). Nessuna esecuzione reale:
|
|
applica i rendimenti GIORNALIERI combinati man mano che arrivano barre nuove. Stato persistente.
|
|
Il dashboard (src/live/dashboard.py) legge questo stato + ricalcola il backtest a colpo d'occhio.
|
|
|
|
uv run python scripts/live/paper_portfolio.py # avanza (init al 1o run)
|
|
uv run python scripts/live/paper_portfolio.py --status # solo stato
|
|
uv run python scripts/live/paper_portfolio.py --reset # azzera (riparte da ora)
|
|
"""
|
|
from __future__ import annotations
|
|
import sys, json
|
|
from pathlib import Path
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
import numpy as np, pandas as pd
|
|
from src.portfolio.portfolio import StrategyPortfolio
|
|
from src.portfolio.sleeves import active_sleeves
|
|
from src.live import paper_guard as PG
|
|
|
|
STATE_DIR = PROJECT_ROOT / "data" / "paper_portfolio"
|
|
STATE = STATE_DIR / "state.json"
|
|
EQ = STATE_DIR / "equity.csv"
|
|
INITIAL = 2000.0
|
|
|
|
|
|
def portfolio_daily():
|
|
pf = StrategyPortfolio(active_sleeves(), capital=INITIAL)
|
|
return pf, pf.combined_daily()
|
|
|
|
|
|
def load():
|
|
return json.loads(STATE.read_text()) if STATE.exists() else None
|
|
|
|
|
|
def save(st):
|
|
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
STATE.write_text(json.dumps(st, indent=2))
|
|
|
|
|
|
def advance():
|
|
pf, r = portfolio_daily()
|
|
st = load()
|
|
if st is None: # init: forward-only, parte dall'ultima barra
|
|
last = str(r.index[-1])
|
|
st = dict(start=last, last=last, equity=INITIAL, initial=INITIAL,
|
|
peak=INITIAL, max_dd=0.0, n_days=0)
|
|
save(st)
|
|
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
EQ.write_text("date,equity\n" + f"{last},{INITIAL}\n")
|
|
return st
|
|
last = pd.Timestamp(st["last"])
|
|
new = r[r.index > last]
|
|
# SOLO giorni CHIUSI: l'ultima riga dei rendimenti giornalieri e' il giorno in corso
|
|
# (~19 min/giorno registrati, r0822_monitor_audit). E' un monitor da dashboard, ma la
|
|
# regola D2 vale anche qui.
|
|
new = new[PG.indice_chiuso(new.index, PG.MS_1D)]
|
|
if len(new):
|
|
eq = st["equity"]; peak = st["peak"]; dd = st["max_dd"]
|
|
lines = []
|
|
for d, ret in new.items():
|
|
eq *= (1.0 + float(ret)); peak = max(peak, eq); dd = max(dd, (peak - eq) / peak if peak > 0 else 0)
|
|
lines.append(f"{d},{eq:.4f}")
|
|
st.update(equity=eq, last=str(new.index[-1]), peak=peak, max_dd=dd, n_days=st["n_days"] + len(new))
|
|
save(st)
|
|
with open(EQ, "a") as f:
|
|
f.write("\n".join(lines) + "\n")
|
|
return st
|
|
|
|
|
|
def main():
|
|
a = sys.argv[1:]
|
|
if "--reset" in a:
|
|
for f in (STATE, EQ):
|
|
f.unlink(missing_ok=True)
|
|
print("paper portfolio azzerato.")
|
|
st = load() if "--status" in a else advance()
|
|
if st is None:
|
|
st = advance()
|
|
pf, _ = portfolio_daily()
|
|
days = (pd.Timestamp(st["last"]) - pd.Timestamp(st["start"])).days
|
|
ret = st["equity"] / st["initial"] - 1
|
|
print(f"PAPER PORTFOLIO (TP01+XS01) — forward-only")
|
|
print(f" start {st['start'][:10]} -> last {st['last'][:10]} ({days}g, {st['n_days']} barre)")
|
|
print(f" equity {st['equity']:.2f} (start {st['initial']:.0f}) ret {ret*100:+.2f}% maxDD {st['max_dd']*100:.1f}%")
|
|
print(f" posizioni correnti: {pf.current_positions()}")
|
|
|
|
|
|
USO = """uso: paper_portfolio.py [--reset] [--status]
|
|
|
|
uv run python scripts/live/paper_portfolio.py # avanza (init al 1o run)
|
|
uv run python scripts/live/paper_portfolio.py --status # solo stato
|
|
uv run python scripts/live/paper_portfolio.py --reset # azzera (riparte da ora)
|
|
|
|
Dettaglio nel docstring in testa al file."""
|
|
|
|
if __name__ == "__main__":
|
|
from src.live.cli import valida
|
|
valida("paper_portfolio.py", USO, flag=("--status", "--reset"), con_valore=())
|
|
main()
|