Files
PythagorasGoal/scripts/live/journal.py
T
Adriano Dal Pastro 10c373075c libro di bordo: DB dei trade allineato col tempo + giornale giornaliero
I trade erano salvati, ma non allineati col tempo: book_execute.py scriveva
ts_utc = pd.Timestamp(r['last_data']), cioe' la data della BARRA DI SEGNALE.
19 righe su 19 a 00:00:00, e un trade (ETH 0.04 @ 1869.74) registrato SEI
GIORNI prima di essere eseguito — fill vero 2026-07-14T14:00, scritto 08/07.
L'ora vera esisteva solo in logs/cron_book.log, che e' gitignored, fuori dal
backup e ruotabile: la cronologia reale del libro live viveva in un file che
una rotazione avrebbe cancellato senza che nessuno se ne accorgesse.

- src/live/tradesdb.py: parser del cron log (ora vera + contesto del segnale),
  FIFO con fee pro-quota, riconciliazione a tre fonti, sqlite in data/live/
  (dentro il perimetro del backup). Le tre fonti si INCROCIANO e non si
  sovrascrivono: reconcile() riporta le divergenze e non ripara niente da solo.
  Il venue e' autorevole ma TRONCA (1 trade su BTC, 0 su ETH): dichiarato.
- scripts/live/trades_db.py: --sync (idempotente, in cron_book ogni ora),
  --report, --reconcile.
- src/live/journal.py + scripts/live/journal.py: una voce al giorno in
  docs/journal/YYYY-MM-DD.md — mercato (ritorni, RV30, TSMOM sugli orizzonti
  di produzione, DVOL con eta'), libro (TP01/SKH01, target, posizione, leva),
  P&L (equity del venue come autorita', scomposizione locale), salute.
  NIENTE narrativa automatica: il campo `nota` e' l'unico posto per il testo
  libero ed e' dell'operatore, mai riscritto da un ricalcolo.
- book_execute.py: ts_utc = ora vera del fill, bar_ts = barra. Test di
  regressione sulla sorgente: se qualcuno rimette last_data, il test lo dice.
- 62 voci di giornale ricostruite dall'arming a oggi.

Tre difetti trovati dai test mentre scrivevo, non a occhio: il renderer cadeva
in KeyError se mancava il blocco mercato (un giornale che non si scrive non e'
un giornale); "24 giri attesi" su un giorno IN CORSO produceva un allarme a
ogni esecuzione; e libro e P&L leggevano due istanti diversi, quindi la stessa
pagina mostrava due equity.

Strategia, pesi, config INVARIATI. Nessun ordine. 659 test passano.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 13:13:02 +00:00

68 lines
2.6 KiB
Python

"""Giornale di bordo: scrive la voce del giorno nel DB e in `docs/journal/YYYY-MM-DD.md`.
uv run python scripts/live/journal.py # voce di oggi
uv run python scripts/live/journal.py --giorno 2026-08-21
uv run python scripts/live/journal.py --backfill 60 # ricostruisce gli ultimi N giorni
uv run python scripts/live/journal.py --nota "testo" # annota il giorno (operatore)
SOLA LETTURA su feed, log e venue: non tocca il libro, non manda ordini.
Il campo `nota` e' l'UNICO posto per il testo libero, e non viene mai riscritto da un
ricalcolo automatico.
"""
from __future__ import annotations
import sys
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from src.live import journal as J # noqa: E402
from src.live import tradesdb as T # noqa: E402
def _arg(flag: str, default=None):
a = sys.argv[1:]
return a[a.index(flag) + 1] if flag in a and a.index(flag) + 1 < len(a) else default
def una(con, g: date, nota: str | None = None, verbose: bool = True) -> Path:
voce = J.costruisci(con, g)
J.salva(con, voce, nota=nota)
f = J.scrivi_file(con, voce)
if verbose:
p = voce["pnl"]
d = p.get("delta_equity")
print(f" {g} equity {p.get('equity_fine') or float('nan'):,.2f} "
f"({'n/d' if d is None else f'{d:+.2f}'}) "
f"fill {p['fill']} · rt {p['roundtrip_chiusi']} · giri {voce['salute']['giri_book']}/24"
f" -> {f.relative_to(ROOT)}")
return f
if __name__ == "__main__":
# sincronizzare PRIMA: altrimenti la sezione 'Libro' (letta dal log) e la sezione 'P&L'
# (letta dal DB) descrivono due istanti diversi e la pagina si contraddice da sola.
sys.path.insert(0, str(ROOT / "scripts" / "live"))
from trades_db import sync as _sync
_sync(verbose=False)
con = T.connect()
nota = _arg("--nota")
if (n := _arg("--backfill")):
oggi = datetime.now(timezone.utc).date()
for i in range(int(n), -1, -1):
g = oggi - timedelta(days=i)
v = J.costruisci(con, g)
# un giorno senza giri di book non e' una voce: e' assenza di dato
if v["salute"]["giri_book"] == 0:
continue
J.salva(con, v); J.scrivi_file(con, v)
tot = con.execute("SELECT COUNT(*) c FROM journal").fetchone()["c"]
print(f" voci nel giornale: {tot}")
else:
g = date.fromisoformat(_arg("--giorno", datetime.now(timezone.utc).date().isoformat()))
una(con, g, nota=nota, verbose="--quiet" not in sys.argv[1:])
con.close()