test(book): i due test della formula del report avevano potenza ZERO a libro flat

I due confronti `abs(net_target - formula) < 1e-6` erano insoddisfacibili per
costruzione: `book_report` PUBBLICA valori arrotondati (`net_target` a 2 decimali,
`tp_frac` a 4) mentre l'ordine viene costruito sul valore non arrotondato
(`build_book_order(inst, net, ...)`), quindi un test che ricalcola la formula dal
`tp_frac` pubblicato eredita DUE arrotondamenti. Nessun ordine e' mai stato sbagliato:
lo scarto e' 0.5-1.5 centesimi su target da $114 e $954, con min_order a $5.

⚠️ Il difetto vero non e' la tolleranza, e' la POTENZA: con `tp_frac=0` e `skh_sign=0`
il target e' esattamente `0.0`, i due arrotondamenti sono esatti e l'invariante passa
senza essere mai esercitata. Dal 2026-06-23 al 2026-08-18 il book e' stato flat quasi
ininterrottamente -> per due mesi questi test non hanno verificato nulla su una formula
di produzione, e il difetto e' emerso solo quando TP01 e SKH01 sono andati long insieme.
Stessa lezione del 26/07 (test_skh_partial_entry): un self-check su eventi rari si
campiona sugli EVENTI, non sulla popolazione.

- `_budget_arrotondamento(equity)`: tolleranza DERIVATA (0.005 del round del net +
  WEIGHT*equity*W_TP01*0.00005 del round del tp_frac propagato), non tarata sul risultato.
- `test_la_formula_del_report_ha_potenza_anche_a_libro_flat`: segnale FORZATO su 4
  frazioni scomode (long+SKH long, long+SKH short, flat+SKH short, cap) con contatore
  che verifica che tutti i casi diano target != 0 -> la copertura non dipende dal mercato.
- `test_il_budget_di_arrotondamento_non_copre_un_errore_di_formula`: controllo POSITIVO,
  i modi reali di rompere la formula (pesi scambiati, cap non applicato, segno invertito)
  devono stare oltre 100x il budget. Una tolleranza che assolve tutto non e' una tolleranza.

Verificato per MUTAZIONE, non a occhio: `round(net, 0)` in book.py -> falliscono tutti e
tre (incluso il nuovo, che e' il punto); `W_TP01 <-> W_SKH` -> falliscono test_net_target_sizing
e il controllo positivo. src/live/book.py ripristinato bit-identico, produzione NON toccata.

38/38 in tests/test_book_live.py; suite 619 passati, 1 fallito (il noto
test_gtaa_band_gate, altro asse, invariato).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-08-21 17:17:27 +00:00
parent c932fab304
commit e69cc5bd81
+93 -2
View File
@@ -22,6 +22,29 @@ from src.live.book import W_SKH, W_TP01, book_net_target, build_book_order
from src.live.execution import Fill from src.live.execution import Fill
# ---------------------------------------------------------------------------
# BUDGET DI ARROTONDAMENTO del report (2026-08-21).
# ---------------------------------------------------------------------------
def _budget_arrotondamento(equity: float) -> float:
"""Tolleranza per confrontare il `net_target` PUBBLICATO con la formula pura.
`book_report` pubblica valori arrotondati — `net_target` a 2 decimali e `tp_frac` a 4 —
mentre l'ordine viene costruito sul valore NON arrotondato (`build_book_order(inst, net, ...)`).
Un test che ricalcola la formula dal `tp_frac` pubblicato eredita quindi DUE arrotondamenti e
non puo' confrontare a 1e-6:
- `round(net, 2)` -> fino a 0.005 di scarto;
- `round(tp_frac, 4)` -> fino a 0.00005 di scarto sulla frazione, che la formula
amplifica per WEIGHT * equity * W_TP01.
⚠️ Questo NON e' un allentamento del test: i modi in cui la formula puo' rompersi davvero
(peso sbagliato, cap non applicato, segno invertito, W_TP01/W_SKH scambiati) valgono decine
di dollari su target da $114, tre ordini di grandezza sopra questo budget.
"""
from src.live.book import WEIGHT
return 0.005 + WEIGHT * equity * W_TP01 * 0.00005
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Formula di netting: 75/25, cap, combinazioni long/short/flat. # Formula di netting: 75/25, cap, combinazioni long/short/flat.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -116,7 +139,7 @@ def test_book_report_dynamic_cap_online(monkeypatch, tmp_path):
assert r["cap_per_asset"] == 2500.0 assert r["cap_per_asset"] == 2500.0
for a in r["assets"]: for a in r["assets"]:
expect = book_net_target(a["tp_frac"], a["skh_sign"], 5000.0, 2500.0) expect = book_net_target(a["tp_frac"], a["skh_sign"], 5000.0, 2500.0)
assert abs(a["net_target"] - expect) < 1e-6 assert abs(a["net_target"] - expect) < _budget_arrotondamento(5000.0)
def test_weights_match_backtest_sleeves(): def test_weights_match_backtest_sleeves():
@@ -307,12 +330,80 @@ def test_book_report_uses_pure_formula_offline():
assert r["equity"] == 600.0 and r["cap_per_asset"] > 0 assert r["equity"] == 600.0 and r["cap_per_asset"] > 0
for a in r["assets"]: for a in r["assets"]:
expect = book_net_target(a["tp_frac"], a["skh_sign"], 600.0, r["cap_per_asset"]) expect = book_net_target(a["tp_frac"], a["skh_sign"], 600.0, r["cap_per_asset"])
assert abs(a["net_target"] - expect) < 1e-6, f"{a['asset']}: net incoerente con la formula" assert abs(a["net_target"] - expect) < _budget_arrotondamento(600.0), \
f"{a['asset']}: net incoerente con la formula"
assert a["skh_sign"] in (-1, 0, 1) assert a["skh_sign"] in (-1, 0, 1)
# offline -> conto assunto flat -> nessuna posizione reale, report deterministico # offline -> conto assunto flat -> nessuna posizione reale, report deterministico
assert all(a["position_usd"] == 0.0 for a in r["assets"]) assert all(a["position_usd"] == 0.0 for a in r["assets"])
def test_la_formula_del_report_ha_potenza_anche_a_libro_flat(monkeypatch):
"""⚠️ I due test qui sopra leggono il segnale REALE del giorno: quando il libro e' flat
(`tp_frac=0`, `skh_sign=0`) il target e' esattamente `0.0`, i due arrotondamenti del report
sono esatti e l'invariante passa **senza essere mai esercitata**.
E' successo davvero: dal 2026-06-23 al 2026-08-18 il book e' stato flat quasi ininterrottamente
e questi test hanno avuto potenza ZERO su una formula di produzione; il difetto di tolleranza
e' emerso solo il 2026-08-21, quando TP01 e SKH01 sono andati long insieme.
E' la lezione gia' codificata il 26/07 (`test_skh_partial_entry`): **un self-check su eventi
rari si campiona sugli EVENTI, non sulla popolazione.**
Qui il segnale e' FORZATO su frazioni scomode, cosi' la copertura non dipende dal mercato.
"""
import src.live.book as book
# frazioni scelte per NON essere clean a 4 decimali e per coprire i rami della formula:
# long parziale + SKH long, long parziale + SKH short (hedge), flat + SKH short, cap.
casi = [
(0.17578123, 1),
(0.29149997, -1),
(0.0, -1),
(1.0, 1), # -> clampato al cap
]
equity, cap = 5000.0, 2500.0
# ⚠️ `base` va preso PRIMA del loop: dalla seconda iterazione `book.shadow_report` e' gia'
# sostituito dal monkeypatch e si leggerebbe il finto della iterazione precedente.
base = book.shadow_report(offline=True, equity_override=equity)
non_banali = 0
for tp_vero, sign in casi:
finto = {**base, "real_equity": equity, "equity": equity, "eq_fallback": None,
"assets": [{**a, "target": tp_vero, "position_usd": 0.0} for a in base["assets"]]}
monkeypatch.setattr(book, "shadow_report", lambda *a, _f=finto, **k: _f)
monkeypatch.setattr(book, "_skyhook_positions",
lambda load5m=None, _s=sign: {a: ("flat" if _s == 0 else
{"dir": "LONG" if _s > 0 else "SHORT"})
for a in book.ASSETS})
r = book.book_report()
atteso_vero = book_net_target(tp_vero, sign, equity, cap)
non_banali += (atteso_vero != 0.0)
for a in r["assets"]:
assert a["skh_sign"] == sign
# (a) il valore pubblicato e' la formula sul tp_frac VERO, a meno del solo round(.,2)
assert abs(a["net_target"] - atteso_vero) <= 0.005, \
f"tp={tp_vero} sign={sign}: net pubblicato incoerente con la formula"
# (b) e ricalcolandola dal tp_frac PUBBLICATO resta dentro il budget dichiarato
da_pubblicato = book_net_target(a["tp_frac"], a["skh_sign"], equity, cap)
assert abs(a["net_target"] - da_pubblicato) < _budget_arrotondamento(equity)
# controllo di POTENZA: ogni caso deve produrre un target != 0, altrimenti il test
# passerebbe per lo stesso motivo per cui passava a libro flat (0.0 arrotondato e' 0.0).
assert non_banali == len(casi), f"solo {non_banali}/{len(casi)} casi esercitano l'invariante"
def test_il_budget_di_arrotondamento_non_copre_un_errore_di_formula():
"""Controllo POSITIVO del budget: deve restare molto piu' stretto dei modi in cui la
formula puo' rompersi davvero, o sarebbe una tolleranza che assolve tutto."""
E, cap = 5000.0, 2500.0
giusto = book_net_target(0.5, 1, E, cap)
budget = _budget_arrotondamento(E)
pesi_scambiati = 0.5 * E * (W_SKH * 0.5 + W_TP01 * 1) # W_TP01/W_SKH invertiti
senza_cap = 0.5 * E * (W_TP01 * 5.0 + W_SKH * 1) # cap non applicato
for sbagliato, nome in ((pesi_scambiati, "pesi scambiati"), (senza_cap, "cap non applicato"),
(-giusto, "segno invertito")):
assert abs(giusto - sbagliato) > 100 * budget, f"{nome}: il budget lo assolverebbe"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# SKH feed fallito: book_report NON crasha (forza flat + flagga skh_error) e # SKH feed fallito: book_report NON crasha (forza flat + flagga skh_error) e
# book_execute DEVE farlo emergere (log + alert), non ingoiarlo silenziosamente. # book_execute DEVE farlo emergere (log + alert), non ingoiarlo silenziosamente.