debito 14: il report stampa il TWR (+10,61%), non il bonifico (+243%)

`trades_db.py --report` calcolava e1/e0-1 sulla serie grezza di equity: il 96,3% del
numero era il versamento di $1.399,39 del 25/08. La riparazione (journal.movimenti_capitale)
esisteva e non aveva attraversato il confine fra i due lettori della stessa serie (P1).

- src/live/journal.py: `rendimento_twr` — funzione unica, spezza la serie sui movimenti
  CERTI e moltiplica i segmenti; gli ambigui restano dentro, dichiarati (P12); tre stati.
- scripts/live/trades_db.py: report() la chiama; stampa TWR con segmenti datati, movimenti
  elencati, trading al netto, delta $ etichettato "movimenti INCLUSI"; il % grezzo sparisce.
- test: +5 in test_journal.py (incl. riproduzione del +10,80% del diario 01/09, M23),
  +2 in test_trades_report.py sul testo stampato con connect() deviato in tmp. 832 verdi.
- docs: CLAUDE.md §5.14 chiuso, §2 e §13 aggiornati; memoria 40; diario 02/09.

Limite ereditato e dichiarato (D5): +10% di trading fra due letture consecutive tocca la
soglia del rilevatore e a mercato fermo verrebbe classificato movimento.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RziUCB336YPUUyDJ29x4Ke
This commit is contained in:
Adriano Dal Pastro
2026-09-02 14:22:50 +00:00
parent bc43218a12
commit 2f701b8469
7 changed files with 282 additions and 21 deletions
+44
View File
@@ -178,6 +178,50 @@ def movimenti_capitale(con, fino_ts: str) -> dict:
soglia=soglia, leva_tetto=leva_tetto)
def rendimento_twr(con, fino_ts: str) -> dict:
"""Rendimento pesato per il tempo (TWR) della serie di equity, spezzata sui movimenti CERTI.
PERCHE' ESISTE (debito #14, trovato il 2026-09-01). `e1/e0 - 1` sulla serie grezza era il
numero che `trades_db.py --report` stampava come performance: «+243%», per il 96,3% un
bonifico ($1.399 il 25/08 su $667). La riparazione — `movimenti_capitale` — viveva gia' in
questo modulo e NON aveva attraversato il confine fra i due lettori della stessa serie:
variante di P1. Da oggi il secondo lettore CHIAMA il primo invece di rifare il conto.
COME. Ogni evento di classe `movimento` chiude un segmento all'equity PRIMA del salto e ne
apre uno all'equity DOPO; il TWR e' il prodotto dei rendimenti di segmento, meno uno. Gli
eventi `ambiguo` NON spezzano niente (P12: restano nel rendimento, dichiarati accanto).
TRE STATI: `twr` e' un numero, oppure None con `motivo` (serie vuota, base zero).
"""
rows = con.execute("SELECT ts_utc, equity FROM equity WHERE ts_utc <= ? ORDER BY ts_utc",
(fino_ts,)).fetchall()
if not rows:
return dict(twr=None, motivo="nessuna lettura di equity", segmenti=[], eventi=[],
certi=0.0, ambigui=0.0, delta_equity=None, trading=None, letture=0)
mv = movimenti_capitale(con, fino_ts)
certi = [e for e in mv["eventi"] if e["classe"] == "movimento"]
e0, e1 = float(rows[0]["equity"]), float(rows[-1]["equity"])
segmenti, da_ts, da_eq = [], rows[0]["ts_utc"], e0
for ev in certi: # gia' in ordine di tempo
if da_eq:
segmenti.append(dict(da=da_ts, a=ev["ts_prima"], e_da=da_eq, e_a=ev["prima"],
ret=ev["prima"] / da_eq - 1.0))
da_ts, da_eq = ev["ts_dopo"], ev["dopo"]
if da_eq:
segmenti.append(dict(da=da_ts, a=rows[-1]["ts_utc"], e_da=da_eq, e_a=e1,
ret=e1 / da_eq - 1.0))
if not segmenti or e0 <= 0:
return dict(twr=None, motivo="base di equity nulla", segmenti=segmenti, eventi=mv["eventi"],
certi=mv["certi"], ambigui=mv["ambigui"], delta_equity=e1 - e0,
trading=e1 - e0 - mv["certi"], letture=len(rows))
twr = 1.0
for sg in segmenti:
twr *= 1.0 + sg["ret"]
return dict(twr=twr - 1.0, motivo=None, segmenti=segmenti, eventi=mv["eventi"],
certi=mv["certi"], ambigui=mv["ambigui"], delta_equity=e1 - e0,
trading=e1 - e0 - mv["certi"], letture=len(rows))
def pnl_giorno(con, giorno: date) -> dict:
"""P&L del giorno. `equity` e' l'autorita' (venue); il resto e' scomposizione locale.