revisione 02/09, seconda tornata: il classificatore dei movimenti era cieco ~23 ore al giorno
Il feed 1h certificato si ferma alle 00:00: per le letture successive `asof` dava la stessa barra a t0 e t1, mercato "fermo" = 0, e qualunque calo >=10% del giorno sarebbe stato un "movimento" scorporato come prelievo (giornale dal 25/08, report da oggi). Con feed assente tutto era "ambiguo" e il report stampava e1/e0-1 sotto l'etichetta TWR. - journal.movimenti_capitale: stato "mercato non misurabile" (feed fermo/assente/barra mancante) => ambiguo con la ragione; leva_tetto = min(frac x n x scala, LEVA_LORDA_MAX) (P1). - journal.rendimento_twr: salto non classificabile => twr E trading None con motivo. - journal.pnl_giorno CHIAMA rendimento_twr (prima rifaceva cum - certi: "entrambi la chiamano" era falso); la pagina stampa il TWR; analista qualifica il cumulato "di cui versati". - trades_db --report: la classe di ogni salto con la sua misura (mercato max a leva piena). - book_execute docstring: banda dello stop -33,5%/-26,5% (era invertita), "non costa ordini" -> micro-ordini di ri-taglia (22/48, C2), latenza <=1h solo con feed fresca, conteggi di r0823 al posto di "raddoppia" (2 contro 1 e' rotolante vs pavimento sulla riga 4h; la peggiore misurata e' 24h). - test_book_cadenza: LTF_MIN importato da skyhook; tutte le righe attive della crontab (una sola); tre stati dello skip (col progetto ma senza cron_book => ROSSO); slot di release testato con venue_probe (il :07 come controllo positivo); parser regolare, passo > 0. - r0823_sl_anchor: guardia sul :47 (era sul vecchio :07), prosa al passato; cron_chain.sh idem. - CLAUDE.md: §5.7 frase invertita corretta, §5.14 limite vero (non l'artefatto della fixture), §2 ora della LETTURA (13:47Z), nuovo debito §5.15 (USDE fuori dal bound di mercato). - diari: tempi "scritto" corretti coi commit; sezione "Seconda tornata". Test +11 (858 verdi). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XDJsH3iDSaBns3ccpPBwiu
This commit is contained in:
+51
-12
@@ -148,30 +148,48 @@ def movimenti_capitale(con, fino_ts: str) -> dict:
|
||||
return dict(eventi=[], certi=0.0, ambigui=0.0, soglia=soglia)
|
||||
try:
|
||||
cfg = json.loads((PROJECT_ROOT / "config" / "live.json").read_text())
|
||||
leva_tetto = float(cfg["max_notional_per_asset_frac"]) * len(ASSETS)
|
||||
# la leva lorda massima che il libro puo' tenere: frac x n_asset x scala, mai oltre il
|
||||
# tetto di CODICE (P1: derivata da `book`, non ridichiarata — la chiave di scala del
|
||||
# 01/09 entra qui da sola)
|
||||
scala = float(cfg.get("book_scale_k") or 1.0)
|
||||
leva_tetto = min(float(cfg["max_notional_per_asset_frac"]) * len(ASSETS) * scala,
|
||||
float(_book.LEVA_LORDA_MAX))
|
||||
except Exception:
|
||||
leva_tetto = 1.0 # il tetto di progetto: bound conservativo, usato solo qui
|
||||
leva_tetto = float(_book.LEVA_LORDA_MAX) # config illeggibile: il tetto di codice
|
||||
px = {a: _px_1h(a) for a in ASSETS}
|
||||
eventi = []
|
||||
for prev, cur in candidati:
|
||||
t0, t1 = pd.Timestamp(prev["ts_utc"]), pd.Timestamp(cur["ts_utc"])
|
||||
delta = float(cur["equity"] - prev["equity"])
|
||||
pct = delta / prev["equity"]
|
||||
mosse = []
|
||||
# Il mercato e' MISURABILE solo se il feed copre entrambe le letture. Il feed 1h
|
||||
# certificato si ferma alle 00:00 (rebuild alle 00:30): per il resto del giorno
|
||||
# `asof` restituirebbe la STESSA barra per t0 e t1, max_mkt 0, e qualunque calo >=10%
|
||||
# diventerebbe un "movimento" — un crash stampato come prelievo (revisione 02/09).
|
||||
# Non misurabile => `ambiguo` con la ragione (P5: "non vedo" non e' "mercato fermo").
|
||||
mosse, non_mis = [], []
|
||||
for a in ASSETS:
|
||||
s = px.get(a)
|
||||
if s is None or s.empty:
|
||||
continue
|
||||
non_mis.append(f"{a}: feed 1h non leggibile"); continue
|
||||
fine = s.index[-1] + pd.Timedelta(hours=1) # l'ultima barra copre un'ora
|
||||
if t0 < s.index[0] or t1 > fine:
|
||||
non_mis.append(f"{a}: feed 1h fermo a {s.index[-1]:%Y-%m-%dT%H:%M}Z"); continue
|
||||
p0, p1 = s.asof(t0), s.asof(t1)
|
||||
if p0 and p1 and not (pd.isna(p0) or pd.isna(p1)):
|
||||
mosse.append(abs(float(p1) / float(p0) - 1.0))
|
||||
max_mkt = max(mosse) * leva_tetto if mosse else None
|
||||
classe = "movimento" if (max_mkt is not None and abs(pct) > 2.0 * max_mkt) else "ambiguo"
|
||||
if pd.isna(p0) or pd.isna(p1) or not p0 or not p1:
|
||||
non_mis.append(f"{a}: barra assente"); continue
|
||||
mosse.append(abs(float(p1) / float(p0) - 1.0))
|
||||
if non_mis:
|
||||
max_mkt, classe, nota = None, "ambiguo", "mercato non misurabile: " + "; ".join(non_mis)
|
||||
else:
|
||||
max_mkt = max(mosse) * leva_tetto
|
||||
classe = "movimento" if abs(pct) > 2.0 * max_mkt else "ambiguo"
|
||||
nota = None
|
||||
eventi.append(dict(ts_prima=prev["ts_utc"], ts_dopo=cur["ts_utc"],
|
||||
prima=float(prev["equity"]), dopo=float(cur["equity"]),
|
||||
delta=delta, pct=float(pct),
|
||||
mercato_max_pct=(None if max_mkt is None else float(max_mkt)),
|
||||
classe=classe))
|
||||
mercato_nota=nota, classe=classe))
|
||||
return dict(eventi=eventi,
|
||||
certi=float(sum(e["delta"] for e in eventi if e["classe"] == "movimento")),
|
||||
ambigui=float(sum(e["delta"] for e in eventi if e["classe"] == "ambiguo")),
|
||||
@@ -200,7 +218,10 @@ def rendimento_twr(con, fino_ts: str) -> dict:
|
||||
Non si stima e non si corregge (P12): si dichiara. Un intervallo di sola marcatura fra due
|
||||
movimenti consecutivi, o prima del primo, ha lunghezza zero e NON produce un segmento.
|
||||
|
||||
TRE STATI: `twr` e `trading` sono numeri, oppure None con `motivo`. Base di equity zero =>
|
||||
TRE STATI: `twr` e `trading` sono numeri, oppure None con `motivo`. Mercato NON misurabile
|
||||
per almeno un salto (feed 1h fermo, o assente) => entrambi None: non si conosce la classe
|
||||
del salto, quindi non si conosce il rendimento — e `e1/e0-1` non torna sotto un'etichetta
|
||||
piu' autorevole. Base di equity zero =>
|
||||
entrambi None: il salto 0 -> X del primo versamento e' INVISIBILE a `movimenti_capitale`
|
||||
(una base zero non ha percentuale), quindi `certi` lo perderebbe e `trading` varrebbe
|
||||
l'intero conto — un numero etichettato "al netto dei versamenti" che li contiene tutti.
|
||||
@@ -217,6 +238,16 @@ def rendimento_twr(con, fino_ts: str) -> dict:
|
||||
motivo="base di equity nulla: il primo versamento non e' classificabile",
|
||||
segmenti=[], eventi=mv["eventi"], certi=mv["certi"], ambigui=mv["ambigui"],
|
||||
delta_equity=e1 - e0, letture=len(rows))
|
||||
non_mis = [e for e in mv["eventi"] if e.get("mercato_nota")]
|
||||
if non_mis:
|
||||
# un salto oltre soglia che NON si e' potuto confrontare col mercato non e' ne' un
|
||||
# movimento ne' un rendimento: il TWR non si stampa (altrimenti `e1/e0-1` tornerebbe
|
||||
# sotto l'etichetta "TWR", il numero che il debito 14 e' nato per uccidere)
|
||||
return dict(twr=None, trading=None,
|
||||
motivo=(f"{len(non_mis)} salto/i non classificabile/i — "
|
||||
+ non_mis[0]["mercato_nota"]),
|
||||
segmenti=[], eventi=mv["eventi"], certi=mv["certi"], ambigui=mv["ambigui"],
|
||||
delta_equity=e1 - e0, letture=len(rows))
|
||||
certi = [e for e in mv["eventi"] if e["classe"] == "movimento"]
|
||||
segmenti, da_ts, da_eq = [], rows[0]["ts_utc"], e0
|
||||
for ev in certi: # gia' in ordine di tempo
|
||||
@@ -253,7 +284,10 @@ def pnl_giorno(con, giorno: date) -> dict:
|
||||
e_arm = con.execute("SELECT equity FROM equity ORDER BY ts_utc LIMIT 1").fetchone()
|
||||
delta = (e_fin - e_ini) if (e_ini is not None and e_fin is not None) else None
|
||||
cum = (e_fin - e_arm["equity"]) if (e_fin is not None and e_arm) else None
|
||||
mv = movimenti_capitale(con, f"{g}T23:59:59+00:00")
|
||||
# il cumulato dall'arming, i movimenti e il trading al netto vengono da rendimento_twr:
|
||||
# UNA funzione per i due lettori (report e giornale), non due aritmetiche (revisione 02/09)
|
||||
r_twr = rendimento_twr(con, f"{g}T23:59:59+00:00")
|
||||
mv = dict(eventi=r_twr["eventi"], certi=r_twr["certi"], ambigui=r_twr["ambigui"])
|
||||
ev_g = [e for e in mv["eventi"] if e["ts_dopo"][:10] == g]
|
||||
mov_g = sum(e["delta"] for e in ev_g if e["classe"] == "movimento")
|
||||
amb_g = sum(e["delta"] for e in ev_g if e["classe"] == "ambiguo")
|
||||
@@ -267,7 +301,8 @@ def pnl_giorno(con, giorno: date) -> dict:
|
||||
movimenti_giorno=ev_g, mov_giorno=float(mov_g), mov_giorno_ambiguo=float(amb_g),
|
||||
trading_giorno=(delta - mov_g) if delta is not None else None,
|
||||
movimenti_arming=mv["eventi"], mov_arming=mv["certi"], mov_arming_ambiguo=mv["ambigui"],
|
||||
trading_da_arming=(cum - mv["certi"]) if cum is not None else None)
|
||||
trading_da_arming=(r_twr["trading"] if cum is not None else None),
|
||||
twr_da_arming=(r_twr["twr"] if cum is not None else None), twr_motivo=r_twr["motivo"])
|
||||
|
||||
|
||||
def salute(con, giorno: date) -> dict:
|
||||
@@ -639,6 +674,10 @@ def rendi_markdown(con, voce: dict) -> str:
|
||||
if p.get("mov_arming"):
|
||||
riga_c += (f" — di cui ${p['mov_arming']:+,.2f} versati/prelevati -> trading "
|
||||
f"**{_n(p.get('trading_da_arming'),'${:+,.2f}')}**")
|
||||
if p.get("twr_da_arming") is not None:
|
||||
riga_c += f" · TWR **{100 * p['twr_da_arming']:+.2f}%**"
|
||||
elif p.get("twr_motivo"):
|
||||
riga_c += f" · TWR n/d ({p['twr_motivo']})"
|
||||
if p.get("mov_arming_ambiguo"):
|
||||
riga_c += f" — ⚠️ ${p['mov_arming_ambiguo']:+,.2f} ambigui non attribuiti"
|
||||
L += ["", "## P&L", "",
|
||||
|
||||
Reference in New Issue
Block a user