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:
@@ -0,0 +1,147 @@
|
||||
"""tradesdb.arricchisci_da_jsonl (#40, TradingBook#11): le righe `ts_source='cron_log'` dal
|
||||
24/08 (`CUTOFF_TS_VERO`) prendono l'ora vera, il prezzo pieno e `pos_after` dal jsonl, SOLO
|
||||
quando l'abbinamento (asset, side, qty, finestra di 15 minuti) e' univoco. `fill_id` e' la
|
||||
chiave della riga: l'arricchimento aggiorna le colonne con un UPDATE per `fill_id`, mai la
|
||||
chiave stessa.
|
||||
|
||||
Nessun test tocca la rete o il libro vero: DB e righe jsonl sono finti, in tmp_path.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live import tradesdb as T # noqa: E402
|
||||
|
||||
# ts di riferimento: subito dopo la soglia, cosi' i fill sono sempre candidati.
|
||||
T0 = "2026-08-24T02:00:00+00:00"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def db(tmp_path):
|
||||
con = T.connect(tmp_path / "t.db")
|
||||
yield con
|
||||
con.close()
|
||||
|
||||
|
||||
def _f(ts, asset="BTC", side="buy", qty=0.005, price=70000.0, fee=0.02):
|
||||
return T.Fill(ts_utc=ts, asset=asset, side=side, qty=qty, price=price, fee=fee)
|
||||
|
||||
|
||||
def _riga_jsonl(ts, asset="BTC", side="buy", filled=0.005, price=70123.4, pos_after=None):
|
||||
return dict(ts_utc=ts, asset=asset, side=side, filled=filled, price=price, pos_after=pos_after)
|
||||
|
||||
|
||||
def test_abbinamento_unico_aggiorna_e_non_cambia_fill_id(db):
|
||||
fill = _f(T0)
|
||||
T.upsert_fills(db, [fill])
|
||||
fill_id_prima = fill.fill_id
|
||||
|
||||
righe = [_riga_jsonl("2026-08-24T02:05:00+00:00", pos_after=555.0)] # +5 min, in finestra
|
||||
aggiornate, ambigue = T.arricchisci_da_jsonl(db, righe)
|
||||
assert (aggiornate, ambigue) == (1, 0)
|
||||
|
||||
riga = db.execute("SELECT * FROM fills WHERE fill_id=?", (fill_id_prima,)).fetchone()
|
||||
assert riga is not None, "il fill_id originale resta la chiave: l'UPDATE non lo cambia"
|
||||
assert riga["ts_source"] == "jsonl"
|
||||
assert riga["ts_utc"] == "2026-08-24T02:05:00+00:00"
|
||||
assert riga["price"] == 70123.4
|
||||
assert riga["pos_after"] == 555.0
|
||||
# nessuna riga in piu' o in meno: l'arricchimento aggiorna, non inserisce.
|
||||
assert db.execute("SELECT COUNT(*) c FROM fills").fetchone()["c"] == 1
|
||||
|
||||
|
||||
def test_pos_after_none_nel_jsonl_non_sovrascrive_quello_gia_nel_db(db):
|
||||
"""(#40, punto 1) `pos_after=None` nella riga jsonl non e' un valore: la colonna resta
|
||||
quella che aveva (qui: None dal cron_log, ma la regola e' 'non toccare', non 'azzerare')."""
|
||||
fill = _f(T0)
|
||||
T.upsert_fills(db, [fill])
|
||||
|
||||
righe = [_riga_jsonl("2026-08-24T02:05:00+00:00", pos_after=None)]
|
||||
aggiornate, ambigue = T.arricchisci_da_jsonl(db, righe)
|
||||
assert (aggiornate, ambigue) == (1, 0)
|
||||
|
||||
riga = db.execute("SELECT * FROM fills").fetchone()
|
||||
assert riga["ts_source"] == "jsonl" and riga["price"] == 70123.4
|
||||
assert riga["pos_after"] is None
|
||||
|
||||
|
||||
def test_due_jsonl_compatibili_e_ambiguo_non_si_tocca(db):
|
||||
fill = _f(T0)
|
||||
T.upsert_fills(db, [fill])
|
||||
|
||||
righe = [_riga_jsonl("2026-08-24T02:03:00+00:00", price=70100.0),
|
||||
_riga_jsonl("2026-08-24T02:07:00+00:00", price=70200.0)] # entrambe in finestra
|
||||
aggiornate, ambigue = T.arricchisci_da_jsonl(db, righe)
|
||||
assert (aggiornate, ambigue) == (0, 1)
|
||||
|
||||
riga = db.execute("SELECT * FROM fills").fetchone()
|
||||
assert riga["ts_source"] == "cron_log" # non toccata
|
||||
assert riga["ts_utc"] == T0
|
||||
assert riga["price"] == 70000.0
|
||||
|
||||
|
||||
def test_secondo_giro_zero_aggiornati_idempotente(db):
|
||||
fill = _f(T0)
|
||||
T.upsert_fills(db, [fill])
|
||||
righe = [_riga_jsonl("2026-08-24T02:05:00+00:00", pos_after=555.0)]
|
||||
|
||||
assert T.arricchisci_da_jsonl(db, righe) == (1, 0)
|
||||
# la riga e' gia' ts_source='jsonl': non e' piu' candidata, anche con le stesse righe jsonl.
|
||||
assert T.arricchisci_da_jsonl(db, righe) == (0, 0)
|
||||
assert db.execute("SELECT COUNT(*) c FROM fills WHERE ts_source='jsonl'").fetchone()["c"] == 1
|
||||
|
||||
|
||||
def test_riga_prima_del_cutoff_non_e_candidata(db):
|
||||
ts_prima = "2026-08-10T00:00:00+00:00" # prima di CUTOFF_TS_VERO (24/08)
|
||||
fill = _f(ts_prima)
|
||||
T.upsert_fills(db, [fill])
|
||||
|
||||
righe = [_riga_jsonl("2026-08-10T00:05:00+00:00", pos_after=1.0)] # abbinamento perfetto
|
||||
aggiornate, ambigue = T.arricchisci_da_jsonl(db, righe)
|
||||
assert (aggiornate, ambigue) == (0, 0) # non e' ambigua: non e' nemmeno candidata
|
||||
|
||||
riga = db.execute("SELECT * FROM fills").fetchone()
|
||||
assert riga["ts_source"] == "cron_log" and riga["ts_utc"] == ts_prima and riga["price"] == 70000.0
|
||||
|
||||
|
||||
def test_fuori_dalla_finestra_dei_15_minuti_non_abbina(db):
|
||||
fill = _f(T0)
|
||||
T.upsert_fills(db, [fill])
|
||||
|
||||
righe = [_riga_jsonl("2026-08-24T02:16:00+00:00", pos_after=1.0)] # +16 min: fuori finestra
|
||||
assert T.arricchisci_da_jsonl(db, righe) == (0, 1)
|
||||
|
||||
riga = db.execute("SELECT * FROM fills").fetchone()
|
||||
assert riga["ts_source"] == "cron_log"
|
||||
|
||||
|
||||
def test_qty_diversa_non_abbina(db):
|
||||
fill = _f(T0, qty=0.005)
|
||||
T.upsert_fills(db, [fill])
|
||||
|
||||
righe = [_riga_jsonl("2026-08-24T02:05:00+00:00", filled=0.006, pos_after=1.0)]
|
||||
assert T.arricchisci_da_jsonl(db, righe) == (0, 1)
|
||||
|
||||
|
||||
def test_record_jsonl_usato_una_volta_non_si_riusa(db):
|
||||
"""Due fill IDENTICI nello stesso giro (stesso asset/side/qty, ts diversi) non devono
|
||||
finire abbinati alla stessa riga jsonl: la prima riga jsonl la prende un solo fill_id."""
|
||||
f1 = _f("2026-08-24T02:00:00+00:00")
|
||||
f2 = _f("2026-08-24T02:01:00+00:00") # ts diverso -> fill_id diverso, stesso asset/side/qty
|
||||
T.upsert_fills(db, [f1, f2])
|
||||
assert db.execute("SELECT COUNT(*) c FROM fills").fetchone()["c"] == 2
|
||||
|
||||
righe = [_riga_jsonl("2026-08-24T02:05:00+00:00", pos_after=1.0)] # UNA sola riga jsonl
|
||||
aggiornate, ambigue = T.arricchisci_da_jsonl(db, righe)
|
||||
# un solo candidato jsonl per due righe DB candidate: la prima (ordinata per ts_utc) lo
|
||||
# prende, la seconda resta senza candidati liberi -> ambigua (zero disponibili).
|
||||
assert aggiornate == 1
|
||||
assert ambigue == 1
|
||||
presi = db.execute("SELECT COUNT(*) c FROM fills WHERE ts_source='jsonl'").fetchone()["c"]
|
||||
assert presi == 1
|
||||
Reference in New Issue
Block a user