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:
@@ -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.
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user