tradesdb: docstring corretto (jsonl ha l'ora vera dal 24/08, non piu' la barra); trades_db sync confronta i conteggi jsonl/DB e notifica una volta per episodio (#37 C11, #35 A9)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""scripts/live/trades_db.py:sync() — controllo di conteggio jsonl-vs-DB (#37 C11, #35 A9).
|
||||
|
||||
Dal 2026-08-24T01:07Z (`tradesdb.CUTOFF_TS_VERO`) `book_executions.jsonl` porta l'ora VERA del
|
||||
fill: un conteggio diverso da quello del DB in quella finestra segnala che una delle due fonti
|
||||
sta perdendo fill. `sync()` non ripara (come `reconcile()`): riporta e notifica UNA volta per
|
||||
episodio, con lo stesso marcatore-solo-su-invio-riuscito di fee_watch (#37 B9).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "live"))
|
||||
|
||||
from src.live import tradesdb as T # noqa: E402
|
||||
|
||||
import trades_db as TD # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def redirige(monkeypatch, tmp_path):
|
||||
"""DB, cron_book.log e jsonl finti in tmp_path: non si tocca mai il libro vero."""
|
||||
reale = T.connect
|
||||
monkeypatch.setattr(T, "connect", lambda *a, **k: reale(tmp_path / "t.db"))
|
||||
monkeypatch.setattr(T, "CRON_LOG", tmp_path / "cron_book.log") # assente -> nessun fill dal log
|
||||
monkeypatch.setattr(T, "EXEC_JSONL", tmp_path / "book_executions.jsonl")
|
||||
return tmp_path
|
||||
|
||||
|
||||
def _semina_db(tmp_path, n, ts=None):
|
||||
ts = ts or T.CUTOFF_TS_VERO
|
||||
con = T.connect(tmp_path / "t.db")
|
||||
fills = [T.Fill(ts_utc=ts, asset="BTC", side="buy", qty=0.001 * (i + 1), price=70000.0, fee=0.01)
|
||||
for i in range(n)]
|
||||
T.upsert_fills(con, fills)
|
||||
con.close()
|
||||
|
||||
|
||||
def _scrivi_jsonl(tmp_path, n, ts=None):
|
||||
ts = ts or T.CUTOFF_TS_VERO
|
||||
righe = [dict(ts_utc=ts, asset="BTC", side="buy", filled=0.001 * (i + 1), price=70000.0, fee=0.01)
|
||||
for i in range(n)]
|
||||
(tmp_path / "book_executions.jsonl").write_text(
|
||||
"\n".join(json.dumps(r) for r in righe) + ("\n" if righe else ""))
|
||||
|
||||
|
||||
def test_conteggi_uguali_non_notifica(monkeypatch, redirige):
|
||||
tmp_path = redirige
|
||||
_semina_db(tmp_path, 2)
|
||||
_scrivi_jsonl(tmp_path, 2)
|
||||
inviato = []
|
||||
monkeypatch.setattr(TD, "notify", lambda *a, **k: inviato.append((a, k)) or True)
|
||||
r = TD.sync(verbose=False)
|
||||
assert inviato == []
|
||||
assert r["jsonl_dal_24_08"] == r["db_dal_24_08"] == 2
|
||||
|
||||
|
||||
def test_conteggi_diversi_notifica_una_volta_per_episodio(monkeypatch, redirige):
|
||||
tmp_path = redirige
|
||||
_semina_db(tmp_path, 2)
|
||||
_scrivi_jsonl(tmp_path, 3)
|
||||
inviato = []
|
||||
monkeypatch.setattr(TD, "notify", lambda *a, **k: inviato.append((a, k)) or True)
|
||||
r = TD.sync(verbose=False)
|
||||
assert len(inviato) == 1, "conteggi diversi: un invio"
|
||||
assert r["jsonl_dal_24_08"] == 3 and r["db_dal_24_08"] == 2
|
||||
|
||||
# secondo giro, stessa divergenza -> nessun invio aggiuntivo (marcatore gia' scritto)
|
||||
r2 = TD.sync(verbose=False)
|
||||
assert len(inviato) == 1, "stesso episodio: nessun secondo invio"
|
||||
assert r2["jsonl_dal_24_08"] == 3 and r2["db_dal_24_08"] == 2
|
||||
|
||||
|
||||
def test_invio_fallito_non_marca_ritenta_al_giro_dopo(monkeypatch, redirige):
|
||||
tmp_path = redirige
|
||||
_semina_db(tmp_path, 2)
|
||||
_scrivi_jsonl(tmp_path, 3)
|
||||
inviato = []
|
||||
monkeypatch.setattr(TD, "notify", lambda *a, **k: inviato.append((a, k)) or False)
|
||||
TD.sync(verbose=False)
|
||||
TD.sync(verbose=False)
|
||||
assert len(inviato) == 2, "invio fallito -> marcatore non scritto, si ritenta ogni giro"
|
||||
|
||||
|
||||
def test_conteggio_rientrato_azzera_il_marcatore(monkeypatch, redirige):
|
||||
tmp_path = redirige
|
||||
_semina_db(tmp_path, 2)
|
||||
_scrivi_jsonl(tmp_path, 3)
|
||||
monkeypatch.setattr(TD, "notify", lambda *a, **k: True)
|
||||
TD.sync(verbose=False) # divergenza detta
|
||||
|
||||
# il jsonl si allinea (es. arrivato il fill mancante) -> episodio chiuso
|
||||
_scrivi_jsonl(tmp_path, 2)
|
||||
inviato = []
|
||||
monkeypatch.setattr(TD, "notify", lambda *a, **k: inviato.append((a, k)) or True)
|
||||
TD.sync(verbose=False)
|
||||
assert inviato == [], "conteggi tornati uguali: nessun invio da fare"
|
||||
|
||||
# una NUOVA divergenza deve poter notificare di nuovo (il marcatore era stato azzerato)
|
||||
_scrivi_jsonl(tmp_path, 4)
|
||||
TD.sync(verbose=False)
|
||||
assert len(inviato) == 1
|
||||
|
||||
|
||||
def test_fill_prima_del_24_08_non_entra_nel_conteggio(monkeypatch, redirige):
|
||||
"""Prima di CUTOFF_TS_VERO il ts_utc del jsonl e' la barra di segnale, non l'ora vera: non
|
||||
e' comparabile col DB, e non deve contare come divergenza."""
|
||||
tmp_path = redirige
|
||||
_semina_db(tmp_path, 0)
|
||||
_scrivi_jsonl(tmp_path, 2, ts="2026-08-10T00:00:00+00:00") # prima della soglia
|
||||
inviato = []
|
||||
monkeypatch.setattr(TD, "notify", lambda *a, **k: inviato.append((a, k)) or True)
|
||||
r = TD.sync(verbose=False)
|
||||
assert inviato == []
|
||||
assert r["jsonl_dal_24_08"] == 0 and r["db_dal_24_08"] == 0
|
||||
Reference in New Issue
Block a user