tradesdb: fill cron_log arricchiti con ora, prezzo e pos_after dal jsonl (#40)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+88
-5
@@ -13,10 +13,13 @@ il perimetro gia' coperto dal backup rotativo della VPS.
|
||||
19 righe su 19 a `00:00:00`. Dal 2026-08-24 01:07Z `book_execute.py` (`_registra_fill`) scrive
|
||||
l'ora VERA del fill (`datetime.now(timezone.utc)` al momento dell'ordine, non piu' la barra),
|
||||
UNA per fill — con il prezzo a piena precisione (non arrotondato come nella stampa del log) e il
|
||||
campo `pos_after` (oggi sempre `None` qui: la posizione post-rebalance si legge dopo, best-effort,
|
||||
e non viene riscritta nel jsonl — vedi il commento in `_registra_fill`). E' quindi, dal 24/08 in
|
||||
poi, la fonte con l'ora PIU' precisa per-fill — ma resta usata solo da `reconcile()`: arricchire
|
||||
`fills` con ts/prezzo/pos_after dal jsonl e' uno SVILUPPO non fatto qui (TradingBook#11).
|
||||
campo `pos_after` (letto dal reconcile subito dopo il rebalance, #39 — None se quella lettura
|
||||
solleva, o se il passo e' un `RebalanceParziale` a meta'). E' quindi, dal 24/08 in poi, la fonte
|
||||
con l'ora PIU' precisa per-fill. Dal 2026-09-27 (#40) `sync()` la usa anche per ARRICCHIRE il
|
||||
DB: `arricchisci_da_jsonl()` gira dopo l'inserimento e prima dei round-trip, e le righe dal 24/08
|
||||
finiscono con `ts_utc`/`price` dal fill vero e `ts_source='jsonl'` invece che `'cron_log'` (e
|
||||
`pos_after` quando il jsonl lo porta) — SOLO se l'abbinamento e' univoco, altrimenti resta com'e'
|
||||
e conta come ambiguo.
|
||||
|
||||
FONTI, in ordine di autorita' — e nessuna delle tre e' completa da sola
|
||||
----------------------------------------------------------------------
|
||||
@@ -43,7 +46,7 @@ import json
|
||||
import re
|
||||
import sqlite3
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -348,6 +351,86 @@ def upsert_fills(con: sqlite3.Connection, fills: list[Fill]) -> int:
|
||||
return n
|
||||
|
||||
|
||||
def _ts_utc(s) -> datetime | None:
|
||||
"""Parsa un timestamp ISO in un datetime AWARE utc. None se illeggibile.
|
||||
|
||||
Le 19 righe scritte prima del fix #35 A9 (barra di segnale, `pd.Timestamp` in stampa: es.
|
||||
"2026-07-08 00:00:00", spazio invece di 'T', nessun offset) sono NAIVE — senza questa
|
||||
normalizzazione, confrontarle con un ts_utc del DB (sempre aware, +00:00) solleva
|
||||
`TypeError: can't compare offset-naive and offset-aware datetimes` (misurato sui dati veri,
|
||||
27/09/2026). Sono tutte precedenti a CUTOFF_TS_VERO: qui si limita a non farle esplodere,
|
||||
non a farle abbinare — restano fuori dalla finestra di qualunque candidato dal 24/08."""
|
||||
if not s:
|
||||
return None
|
||||
try:
|
||||
ts = datetime.fromisoformat(str(s))
|
||||
except ValueError:
|
||||
return None
|
||||
return ts.replace(tzinfo=timezone.utc) if ts.tzinfo is None else ts
|
||||
|
||||
|
||||
def arricchisci_da_jsonl(con: sqlite3.Connection, righe_jsonl: list[dict]) -> tuple[int, int]:
|
||||
"""Arricchisce le righe `ts_source='cron_log'` con l'ora vera, il prezzo e `pos_after` letti
|
||||
da `book_executions.jsonl` (#40, TradingBook#11: lo sviluppo lasciato non fatto dal
|
||||
commento sopra). NON tocca `fill_id`: e' la chiave della riga, calcolata all'INSERT da
|
||||
ts_utc/asset/side/qty/price ORIGINALI (cron_log) — qui si fa un UPDATE per `fill_id`, che
|
||||
cambia solo le colonne, mai la chiave con cui la riga e' indicizzata.
|
||||
|
||||
Candidate: `ts_source='cron_log'` e `ts_utc >= CUTOFF_TS_VERO` (prima di quella soglia il
|
||||
jsonl portava la barra di segnale, non l'ora vera — non ha una controparte comparabile).
|
||||
Abbinamento a una riga jsonl: stesso asset, stesso side, |qty - filled| < 1e-9, e ts jsonl in
|
||||
[ts_utc DB, ts_utc DB + 15 minuti] (il DB ha l'ora di INIZIO GIRO, il jsonl l'ora del fill,
|
||||
che viene dopo). Esattamente UN candidato -> UPDATE; zero o piu' di uno -> non si tocca (non
|
||||
si indovina quale, o se ce n'e' uno) e conta come ambiguo. Un record jsonl assegnato non si
|
||||
riusa: due fill identici nello stesso giro non finiscono abbinati alla stessa riga.
|
||||
Idempotente: una riga gia' `ts_source='jsonl'` non e' piu' candidata al giro successivo.
|
||||
|
||||
Ritorna (righe_aggiornate, righe_ambigue)."""
|
||||
candidati = con.execute(
|
||||
"SELECT fill_id, ts_utc, asset, side, qty FROM fills "
|
||||
"WHERE ts_source='cron_log' AND ts_utc >= ? ORDER BY ts_utc, asset",
|
||||
(CUTOFF_TS_VERO,)).fetchall()
|
||||
usati: set[int] = set() # indici in righe_jsonl gia' assegnati a un fill_id
|
||||
aggiornate = 0
|
||||
ambigue = 0
|
||||
for riga in candidati:
|
||||
ts_db = _ts_utc(riga["ts_utc"])
|
||||
if ts_db is None:
|
||||
ambigue += 1 # ts illeggibile: non e' un abbinamento, e' un dato rotto
|
||||
continue
|
||||
fine = ts_db + timedelta(minutes=15)
|
||||
trovati = []
|
||||
for i, r in enumerate(righe_jsonl):
|
||||
if i in usati:
|
||||
continue
|
||||
if r.get("asset") != riga["asset"] or r.get("side") != riga["side"]:
|
||||
continue
|
||||
filled = r.get("filled")
|
||||
if filled is None or abs(float(filled) - riga["qty"]) >= 1e-9:
|
||||
continue
|
||||
ts_j = _ts_utc(r.get("ts_utc"))
|
||||
if ts_j is None or not (ts_db <= ts_j <= fine):
|
||||
continue
|
||||
trovati.append(i)
|
||||
if len(trovati) != 1:
|
||||
ambigue += 1
|
||||
continue
|
||||
i = trovati[0]
|
||||
r = righe_jsonl[i]
|
||||
usati.add(i)
|
||||
pos_after = r.get("pos_after")
|
||||
if pos_after is None:
|
||||
con.execute("UPDATE fills SET ts_utc=?, ts_source='jsonl', price=? WHERE fill_id=?",
|
||||
(r["ts_utc"], r["price"], riga["fill_id"]))
|
||||
else:
|
||||
con.execute(
|
||||
"UPDATE fills SET ts_utc=?, ts_source='jsonl', price=?, pos_after=? WHERE fill_id=?",
|
||||
(r["ts_utc"], r["price"], pos_after, riga["fill_id"]))
|
||||
aggiornate += 1
|
||||
con.commit()
|
||||
return aggiornate, ambigue
|
||||
|
||||
|
||||
def upsert_equity(con: sqlite3.Connection, punti: list[tuple[str, float, str]]) -> int:
|
||||
n = 0
|
||||
for ts, v, src in punti:
|
||||
|
||||
Reference in New Issue
Block a user