debito 7: la cadenza del libro e' ORARIA — docstring riscritto, tre fonti tenute d'accordo da un test

Il docstring di book_execute.py prescriveva «ogni ~230 minuti» mentre il cron gira ogni ora
(47 * * * *). Era il docstring a sbagliare: sulla riga 4h BTC raddoppia gli scatti del
disaster-SL rotolante (r0823_sl_anchor.py), e chi avesse "corretto" il cron verso il
docstring avrebbe spostato il libro sulla riga peggiore.

- scripts/live/book_execute.py: CADENZA: ORARIA, la riga di crontab, la ragione (giro
  idempotente, latenza SKH01 <=1h, ri-ancoraggio orario) e il divieto esplicito con la data.
- tests/test_book_cadenza.py (10): deriva e confronta docstring, intestazione di
  cron_book.sh e crontab installata (crontab -l; SALTATO se illeggibile, non verde);
  parser a 5 campi solo per cadenze regolari; 60 < 230 < 240; minuto != :00.
- docs: CLAUDE.md §5.7 chiuso, §13 conteggio 842; memoria 40; diario 02/09b.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RziUCB336YPUUyDJ29x4Ke
This commit is contained in:
Adriano Dal Pastro
2026-09-02 14:38:53 +00:00
parent 2f701b8469
commit 861cc7fc27
5 changed files with 204 additions and 7 deletions
+11 -4
View File
@@ -282,9 +282,16 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
6. ⚠️ **TLT ha 13,5 anni di storia in meno** (parte 2016-02 invece del 2002) → **GTAA01 gira su
CINQUE gambe prima del 2016**, e l'assente e' quella obbligazionaria. Non e' un fetch da rifare (IB
ritorna 0 barre). ⇒ in-sample e hold-out di GTAA01 **non sono la stessa strategia**.
7. ⚠️ **Il docstring di `book_execute.py` prescrive "ogni ~230 minuti", il cron gira OGNI ORA.**
**Il docstring e' sbagliato e la configurazione che gira e' quella giusta** — chi lo "correggesse"
sposterebbe il libro sulla riga 4h, dove BTC **raddoppia** gli scatti del disaster-SL.
7. **RIPARATO (2026-09-02).** Il docstring di `book_execute.py` prescriveva "ogni ~230 minuti"
mentre il cron gira **ogni ora** (`47 * * * *`): era il docstring a essere sbagliato, e chi lo
avesse "corretto" nel verso del cron avrebbe spostato il libro sulla riga 4h, dove BTC
**raddoppia** gli scatti del disaster-SL (`r0823_sl_anchor.py`). Ora il docstring dichiara
**CADENZA: ORARIA** con la ragione (giro idempotente: latenza SKH01 ≤1h, ri-ancoraggio del
rotolante ogni ora) e il divieto esplicito. **`tests/test_book_cadenza.py` (10 test)** tiene
d'accordo le tre dichiarazioni — docstring, riga in `cron_book.sh`, **crontab installata**
(letta con `crontab -l`; dove non e' leggibile il test si dichiara SALTATO, non verde) — e
verifica che la cadenza dichiarata sia 60 min, sotto la griglia 230m e lontana dai 240 della
riga peggiore. Diario `2026-09-02b-debito-7-cadenza-docstring.md`.
8. ⚠️ **Il Rulebook Deribit** (ADL, perdita socializzata, *emergency powers*, conti dormienti) **non
lo sorveglia nessuno**; `MAINT_GRACE_HOURS`=2 **presume** gli annunci invece di leggerli.
*Ridotto in parte il 2026-08-25:* `src/live/venue_probe.py` interroga l'API **pubblica**
@@ -560,7 +567,7 @@ uv run python scripts/live/trades_db.py --report # stato libro d
uv run python scripts/live/trades_db.py --reconcile # incrocio delle 3 fonti sui fill
uv run python scripts/live/journal.py # voce del giorno (numeri + lettura)
uv run python scripts/live/analista.py --secco # analisi del giorno, senza salvare
uv run pytest # test (832, tutti verdi al 02/09)
uv run pytest # test (842, tutti verdi al 02/09)
```
```python
@@ -0,0 +1,45 @@
# 2026-09-02 — Debito 7: il docstring diceva 230 minuti, il cron gira ogni ora
*Scritto il 2026-09-02 alle 14:45Z. Cadenza misurata dal log (`r0823_sl_anchor.py`: 1.443 giri in
1.442 ore) e dalla crontab installata, letta dal test.*
## Il difetto
| fonte | cosa diceva | dal |
|---|---|---|
| `scripts/live/book_execute.py`, docstring | «va lanciato ogni ~230 minuti» | 20/06 |
| `scripts/cron_book.sh`, intestazione | cadenza ORARIA, `47 * * * *` | 25/08 (prima `:07`, sempre orario) |
| crontab della VPS | `47 * * * *` | 25/08 |
Era il **docstring** a sbagliare. E non era una svista innocua: sulla riga 4h (la più vicina ai 230
minuti) BTC **raddoppia** gli scatti del disaster-SL rotolante (2 contro 1; a 24h ETH arriva a 5, con
2 in 30 giorni). Il rischio era un lettore diligente che "allineasse" il cron al docstring.
## La riparazione
1. **Docstring riscritto**: `CADENZA: ORARIA`, la riga di crontab citata, la ragione (giro
idempotente → girare più fitto della griglia non costa ordini; latenza ≤1h sugli ingressi/uscite
software di SKH01; ri-ancoraggio orario del rotolante) e il divieto esplicito, con la data.
2. **`tests/test_book_cadenza.py`, 10 test.** P1: non ridichiara «60 minuti», **deriva** le tre
fonti e le confronta fra loro:
- il docstring non contiene più «ogni ~230» e dichiara ORARIA;
- la riga di crontab citata nel docstring è quella dichiarata in `cron_book.sh`;
- la riga dichiarata è oraria (parser a 5 campi, solo casi regolari, rifiuta il resto),
sotto i 230 della griglia e lontana dai 240 della riga peggiore, e fuori dal minuto `:00`;
- **la crontab installata** (`crontab -l`) coincide con la dichiarata. Dove la crontab non è
leggibile il test è **saltato**, non verde (P5). Sulla VPS oggi gira e passa.
## Numeri
| | |
|---|---|
| test nuovi | 10, tutti verdi; suite dei moduli che toccano `book_execute`: 231 verdi |
| cadenza dichiarata / installata | 60 min / 60 min |
| griglia SKH01 | 230 min (la riga da non usare) |
| riga peggiore misurata | 240 min (BTC 2 scatti contro 1) |
## Cosa NON ho fatto
- Non ho toccato il cron né `cron_book.sh`: la configurazione che gira era quella giusta.
- Non ho aggiunto un sorvegliante a runtime sulla cadenza (contare i giri per ora nel log): il
giornale già stampa «giri mancanti» ogni giorno, e il test copre la deriva di configurazione.
+17
View File
@@ -915,6 +915,23 @@ Sulle 4 finestre osservate 3 sono rientrate entro l'ora → il `:47` ne avrebbe
**Se al prossimo martedì il `:47` becca comunque la manutenzione, la previsione è sbagliata** e lo
slot non è quello descritto in `venue_probe.RELEASE_*`: rileggerlo prima di spostare ancora.
### La cadenza è ORARIA, e ora lo dicono tutte e tre le fonti (debito §5.7, chiuso il 2026-09-02)
Il docstring di `book_execute.py` prescriveva «ogni ~230 minuti» (la griglia di SKH01) mentre il cron
gira ogni ora. Non era cosmetico: `r0823_sl_anchor.py` misura che l'accumulo di scatti del disaster-SL
rotolante è funzione della **cadenza del cron** (1h: BTC 0 / ETH 1 in 7-8 anni · 4h: BTC 2 · 24h: ETH 5
con 2 in 30 giorni), quindi chi avesse "corretto" il cron verso il docstring avrebbe spostato il libro
sulla riga peggiore. La riparazione è il docstring stesso — cadenza ORARIA, la ragione (giro
idempotente: girare più fitto della griglia non costa ordini, e compra latenza ≤1h per gli
ingressi/uscite software di SKH01 e un ri-ancoraggio orario del rotolante) e il divieto scritto —
più `tests/test_book_cadenza.py`, che **deriva** (P1) le tre dichiarazioni e le confronta: la riga
citata nel docstring, quella dichiarata nell'intestazione di `cron_book.sh` (`47 * * * *`) e la
crontab **installata** (`crontab -l`; se non è leggibile il test è SALTATO, non verde — P5). Il
parser accetta solo espressioni regolari e rifiuta il resto. Vincolo del minuto tondo verificato
(≠ `:00`). La regola generale è la stessa del `:47`: **una cadenza si difende nella sua ragione, e
un commento che dichiara una cadenza diversa da quella che gira si paga quando qualcuno lo "corregge"
nel verso sbagliato.**
### Cosa resta scoperto
1. **La sonda dice di chi è il guasto, non lo aggira.** Col gateway giù il libro continua ad
+10 -3
View File
@@ -10,9 +10,16 @@ DOPPIO GATE DI SICUREZZA (entrambi necessari per inviare ordini reali):
Senza entrambi e' un DRY-RUN (stampa il piano, NON invia). Reconciliation dopo ogni ordine; log in
data/live/book_executions.jsonl.
⚠️ CADENZA: SKH01 decide su griglia 230m -> questo script va lanciato ogni ~230 minuti con la feed
fresca all'ultima barra chiusa (NON il cron giornaliero, che mancherebbe gli ingressi). Gli exit di
SKH sono SOFTWARE (latenza fino a fine barra 230m); solo il disaster-SL (-30%) e' on-book.
⚠️ CADENZA: ORARIA — `47 * * * *` in `scripts/cron_book.sh` (il minuto e' spiegato li'). SKH01
decide su griglia 230m, ma il giro e' IDEMPOTENTE (riconcilia al target netto corrente: se non
cambia nulla -> HOLD), quindi girare piu' fitto della griglia non costa ordini e compra due cose
misurate: (1) gli ingressi/uscite SOFTWARE di SKH01 arrivano con latenza <=1h invece che fino a
230m; (2) il disaster-SL rotolante (-30%, l'unico on-book) si ri-ancora ogni ora — a cadenza 1h in
7-8 anni BTC 0 scatti / ETH 1, sulla riga 4h BTC ne fa 2 (`r0823_sl_anchor.py`).
🚨 NON "correggere" la cadenza a ~230 minuti: fino al 2026-09-02 questo docstring lo prescriveva,
ed era il docstring a essere sbagliato, non il cron (debito §5.7). `tests/test_book_cadenza.py`
tiene d'accordo docstring, `cron_book.sh` e crontab installata. NON il cron giornaliero: le entrate
di SKH01 le mancherebbe.
uv run python scripts/live/book_execute.py # DRY-RUN (piano, nessun ordine)
uv run python scripts/live/book_execute.py --execute # esegue SOLO se execution_enabled=true
+121
View File
@@ -0,0 +1,121 @@
"""La cadenza del libro live e' ORARIA, e le tre dichiarazioni devono restare d'accordo.
Debito §5.7 (chiuso il 2026-09-02): il docstring di `book_execute.py` prescriveva «ogni ~230
minuti» mentre il cron gira ogni ora. Non era cosmetico: `r0823_sl_anchor.py` misura che sulla
riga 4h BTC RADDOPPIA gli scatti del disaster-SL rotolante (2 contro 1; a 24h ETH arriva a 5).
Chi avesse "corretto" il cron verso il docstring avrebbe spostato il libro sulla riga peggiore.
P1: il bersaglio si DERIVA da cio' che gira. Qui le fonti sono tre — il docstring (cio' che un
umano legge), `cron_book.sh` (cio' che dichiara di essere installato) e la crontab (cio' che
gira davvero) — e il test le confronta fra loro invece di ridichiarare «60 minuti».
"""
from __future__ import annotations
import ast
import re
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
BOOK_EXECUTE = ROOT / "scripts" / "live" / "book_execute.py"
CRON_BOOK = ROOT / "scripts" / "cron_book.sh"
GRIGLIA_SKH_MIN = 230 # data1 di SKH01 (src/strategies/skyhook.py): la riga da NON usare
RIGA_PEGGIORE_MIN = 240 # 4h: dove BTC raddoppia gli scatti (r0823_sl_anchor.py)
def cadenza_minuti(expr: str) -> int:
"""Minuti fra due esecuzioni di un'espressione crontab a 5 campi, per i soli casi regolari."""
campi = expr.split()
assert len(campi) == 5, expr
m, h, dom, mon, dow = campi
assert dom == mon == dow == "*", f"cadenza non regolare: {expr}"
if h == "*" and m.isdigit():
return 60
if h == "*" and m.startswith("*/"):
return int(m[2:])
if h.startswith("*/") and m.isdigit():
return 60 * int(h[2:])
raise ValueError(f"espressione non riconosciuta: {expr}")
def _docstring() -> str:
return ast.get_docstring(ast.parse(BOOK_EXECUTE.read_text())) or ""
def _cron_dichiarato() -> str:
"""L'espressione crontab che `cron_book.sh` dichiara nel suo intestazione, fra backtick."""
m = re.search(r"`(\d+ \* \* \* \*)`", CRON_BOOK.read_text())
assert m, "cron_book.sh non dichiara piu' la sua riga di crontab"
return m.group(1)
def _cron_installato() -> str | None:
if not shutil.which("crontab"):
return None
try:
out = subprocess.run(["crontab", "-l"], capture_output=True, text=True, timeout=10)
except Exception:
return None
if out.returncode != 0:
return None
for riga in out.stdout.splitlines():
if "cron_book.sh" in riga and not riga.lstrip().startswith("#"):
return " ".join(riga.split()[:5])
return None
# ---------------------------------------------------------------- regressione sul docstring
def test_il_docstring_non_prescrive_piu_i_230_minuti():
d = _docstring()
assert "ogni ~230" not in d and "ogni 230" not in d
assert re.search(r"CADENZA:\s*ORARIA", d), "il docstring deve dichiarare la cadenza ORARIA"
def test_il_docstring_dichiara_la_stessa_riga_del_cron():
# la riga di crontab citata nel docstring e' quella di cron_book.sh, non un'altra
d = _docstring()
assert _cron_dichiarato() in d
# ---------------------------------------------------------------- cron_book.sh
def test_cron_book_e_orario_e_sta_lontano_dalla_riga_peggiore():
expr = _cron_dichiarato()
c = cadenza_minuti(expr)
assert c == 60
assert c < GRIGLIA_SKH_MIN < RIGA_PEGGIORE_MIN
assert "ORARIA" in CRON_BOOK.read_text()
def test_cron_book_evita_il_minuto_tondo():
# vincolo 1 del commento di cron_book.sh: fuori dai ~26s del :00 (rate-limit per-IP)
minuto = int(_cron_dichiarato().split()[0])
assert minuto != 0
@pytest.mark.parametrize("expr,atteso", [("47 * * * *", 60), ("*/30 * * * *", 30),
("7 */4 * * *", 240), ("0 */24 * * *", 1440)])
def test_il_parser_della_cadenza(expr, atteso):
assert cadenza_minuti(expr) == atteso
def test_il_parser_rifiuta_cio_che_non_sa_leggere():
with pytest.raises((ValueError, AssertionError)):
cadenza_minuti("47 9 * * 2")
# ---------------------------------------------------------------- la crontab che GIRA
def test_la_crontab_installata_e_quella_dichiarata():
"""P16: il cron gira dalla macchina, non dal repo. Dove la crontab e' leggibile (la VPS),
deve coincidere con cio' che cron_book.sh dichiara; altrove il test si dichiara saltato,
non verde (P5: 'non vedo' non e' 'va tutto bene')."""
vivo = _cron_installato()
if vivo is None:
pytest.skip("crontab non leggibile o senza cron_book.sh: non verificabile qui")
assert vivo == _cron_dichiarato()
assert cadenza_minuti(vivo) == 60