feat(web): simulatore di accumulo nel browser, verificato contro il Python

scripts/web/ — motore in JavaScript (engine.js) sui ritorni VERI del book
esportati da export_series.py, pagine assemblate da build.py. Da "sistema
dinamico dove imposti valore iniziale, mensile e durata, e calcola la best curva
fissando il tempo": la simulazione gira nel browser, quindi il motore va
riscritto e VA PROVATO che dia la stessa risposta.

test_engine.js confronta mediane e probabilita' con r0807_growth_yearly.py su due
configurazioni, esige che il versato (deterministico) coincida al centesimo, e
include un controllo positivo — un motore col fisco spento DEVE risultare fuori
tolleranza, perche' un test che non sa fallire non e' un test. Il risolutore e'
validato contro dep_necessario di r0727_tasse.py: -0.4 / -0.6 / -1.1%.

smoke.js ESEGUE le pagine con un DOM finto. Serve perche' node --check valida solo
la sintassi: ho pubblicato una pagina che lo passava e moriva alla prima riga
utile (chiavi Python "0.0" ricostruite in JS come String(0) = "0"; i pesi
intermedi funzionavano per caso).

Altri due errori che questi strumenti hanno intercettato:
- due anni di dati di un grafico scritti A MEMORIA perche' tail aveva troncato
  l'output -> ora i dati si INIETTANO da JSON (build.py), il passaggio manuale
  non esiste piu';
- una "distorsione sistematica" del motore JS (+0.75%, 8 semi tutti positivi) che
  erano 8 estrazioni contro UN punto Python rumoroso. Misurato bene, 8 semi per
  parte: -0.01%, t = -0.04; ed entrambi i campionatori cadono entro 1.7 SE
  dall'atteso ANALITICO della media di blocco.

Scelta guidata da una misura: la banda del versamento suggerito resta +-1% da
1.200 a 3.000 percorsi -> non domina il Monte Carlo ma la granularita' della
bisezione (~5 EUR). Percorsi tenuti bassi e incertezza dichiarata, invece di
pagare tempo per una precisione che non arriva.

Nessun impatto sulla produzione: non tocca book, pesi, cron o config.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-08-07 18:44:13 +00:00
parent 56141b0ffe
commit ed2532b991
9 changed files with 2444 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python
"""export_series.py — esporta la serie del book live per il motore nel browser.
La pagina interattiva (`pagina_simulatore.html`) simula nel browser, quindi le serve la sequenza
dei ritorni giornalieri VERI del book: gli stessi che usa `r0807_growth_yearly.py`, cioe' TP01 75%
+ SKH01 25% alle ancore canoniche, gia' de-luckati x0.89 sul drift da `r0726_venue_risk`.
Esporta anche le costanti che il motore deve condividere col Python (bersaglio, cambio, aliquote,
lunghezza dei blocchi): se una di queste vivesse in due posti, prima o poi divergerebbe.
uv run python scripts/web/export_series.py --out /tmp/series.json
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
import r0725_capcurve as CC # noqa: E402
import r0726_venue_risk as VR # noqa: E402
import r0727_lumpsum_split as LS # noqa: E402
import r0727_tasse as TX # noqa: E402
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--out", required=True)
a = ap.parse_args()
s = VR.venue_series()["Deribit"]
r = s.values.astype(float)
dati = dict(
n=len(r), start=str(s.index[0].date()), end=str(s.index[-1].date()),
start_usd=LS.START, target=LS.TARGET, eurusd=CC.EURUSD, deluck=VR.DELUCK,
tax=0.33, patrimoniale=TX.PATRIMONIALE, carry=TX.CARRY_ANNI, block=20,
r=[round(x, 8) for x in r],
)
Path(a.out).write_text(json.dumps(dati))
print(f" {len(r)} giorni [{dati['start']} -> {dati['end']}] "
f"drift {r.mean()*365:.1%} vol {r.std()*365**0.5:.1%}")
print(f" bersaglio ${LS.TARGET:,.0f} · de-luck ×{VR.DELUCK} · "
f"scritto {a.out} ({Path(a.out).stat().st_size/1024:.0f} KB)")
if __name__ == "__main__":
main()