diff --git a/docs/research/RESULTS-0822.md b/docs/research/RESULTS-0822.md index d2ec17c..2fa4a62 100644 --- a/docs/research/RESULTS-0822.md +++ b/docs/research/RESULTS-0822.md @@ -697,7 +697,7 @@ stessi hanno nominato senza poterli eseguire**. Stesso contratto di consegna, st | 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` | | 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente | | 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata | -| 25 | BIN-FREQ | il binario di frequenza su SKH01 e' un candidato o un sottoprodotto? | _in corso_ | +| 25 | BIN-FREQ | il binario di frequenza su SKH01 e' un candidato o un sottoprodotto? | **LEAD** — direzione reale, taglia = selezione | | 26 | TP01-SINISTRO | quanto pesa TP01 se l'obiettivo include un crash che nel campione non c'e'? | **allarme SCARTATO** — il rischio e' XS01, non TP01 | | 27 | SURFACE-RV | la superficie e' internamente incoerente, e l'incoerenza vale piu' del costo di attraversarla? | **SCARTATO** — l'incoerenza al mid **e'** la larghezza del mercato | @@ -1157,3 +1157,75 @@ chiusure**, e i close giornalieri non lo vedrebbero. ⚠️ *Nota di attribuzione: il file risultava `M` in git perche' **io** (coordinatore) l'avevo incluso in un commit mentre l'agente ci stava ancora lavorando — non un altro agente. Un `git add -A` durante un'ondata cattura il lavoro in corso.* + +--- + +## 25 — BIN-FREQ (il binario di frequenza su SKH01, giudicato come candidato) + +`scripts/research/r0822b_bin_freq.py` (~400 s, **23 ancore piene**, due esecuzioni indipendenti con +output **numericamente identici**). **144 celle** dichiarate nel docstring **prima di guardare**, piu' +i **71 trial gia' spesi sullo stesso oggetto** (28 VOL-SIZE + 43 scettico) → **conto onesto = 215**: +*riaprire un parametro riapre la sua famiglia.* + +**Repliche bit-exact** (`max|dif| = 0`) contro `sleeves._skyhook_returns()` **e** contro +`r0702_anchor_skh01.run_asset`; **titolo dello scettico riprodotto esattamente** chiamando la sua +funzione (`BIN L>=med` +0,080 in 20/23, inverso −0,159 in 0/23). + +| | ShFULL | ShHOLD | maxDD | CAGR | +|---|---|---|---|---| +| BASE (libro live 75/25) | 1,626 | 1,037 | 10,4% | 18,6% | +| BIN, peso effettivo lasciato cadere | 1,690 | 1,031 | 10,3% | 17,8% | +| **BIN, controllo ISO-PESO** | **1,589** | 0,956 | 10,0% | 16,8% | + +📌 **La statistica che decide non e' l'argmax, e' la MEDIANA DI FAMIGLIA.** Argmax +(`q0,50 w60 h0,10 s+`) ISO **+0,112 in 23/23**; **stima onesta di famiglia +0,007**, con **39/72 +celle positive = 54% ≈ monetina**. **L'argmax vale 16× la mediana → punta, non plateau.** +**Controllo di segno: 0/72 celle positive** sulla meta' inversa → la *direzione* e' solidissima. + +**Gate:** `marginal_vs_tp01` **ADDS**; selezione **in-sample-only** sceglie la stessa cella mentre +scegliendo **sull'hold-out** ne esce un'altra (−0,017) → **il gate ha potenza**; +**`weights_tilt_null` FAIL** (pctl 36-38) **e per giunta inerte** (il binario *abbassa* il peso +effettivo 0,250→0,19, e nella forma di deploy naturale il vettore proposto e' identico a quello +corrente); causalita' verificata con **troncamento diretto** su 52 decisioni, 0 divergenti; +**trasferisce a SKH01_V1** (geometria d'uscita diversa): +0,087 in 20-21/23. + +❌ **Il deflated-Sharpe NON e' vacuo — refuta l'ipotesi a-priori che l'agente aveva registrato:** +**0,976** (N=144) / **0,977** (N=215, conto onesto) / 0,948 (N=500), baseline 0,828/0,834. E il +**motivo e' riusabile**: il DSR e' vacuo quando i trial si somigliano (var→0 ⇒ sr0→0 ⇒ DSR≈1 *per +chiunque*); una famiglia **binaria** contiene celle che scartano insiemi di trade molto diversi, +quindi **riacquista potenza**. *Non e' il binario a essere piu' solido: e' la famiglia a essere piu' +larga.* ✅ Terza misura convergente della stessa giornata sul difetto del DSR (con BOCPD e CRITICO). + +⚠️ **Il null "con potenza" ha una mediana che NON e' zero, ed e' un fatto di misura:** scartare a caso +il 40% dei trade **danneggia**, perche' lo Sharpe annualizzato va con √(scommesse/anno) — misurato: +gamba 1,107 → **0,883** permutata contro **0,861** previsti da √0,60. Il null resta valido e **piu' +severo** (reale +0,112 vs massimo del null −0,031, p<0,01), **ma risponde a "batte una selezione +casuale?", non a "batte il non filtrare?"**. + +🚨 **Due premesse del MIO briefing misurate e REFUTATE:** +(i) *"il binario dimezza gli ordini → migliora l'eseguibilita' e riduce le fee"* — gli ordini +**nettati** calano del **17%**, non del 50%; i sotto-min-order **non si muovono** perche' sono di +TP01; e l'**haircut a $635 e' 0,001 in entrambe le braccia**: non c'era niente da guadagnare. +(ii) *"converti il risparmio di fee in Sharpe con la curva del 26/07"* — **a fee ZERO l'effetto resta +l'87-110%** → non e' risparmio di costo, e' **informazione sul rendimento lordo**, e convertirlo +sarebbe stata un'attribuzione sbagliata. + +**VERDETTO: LEAD** (non candidato, non scartato). La **direzione** e' reale e sopravvive a tutto; la +**taglia di una singola cella e' selezione**. +📌 **E la domanda che avevo posto — "+0,080 contro ±0,196 di banda d'ancora, merita?" — era il +confronto SBAGLIATO** (la banda del *livello* si cancella in un disegno appaiato), **ma la risposta +corretta e' PEGGIORE per il candidato**. Criterio dichiarato, 4 gambe: (1) mediana di famiglia ≥ meta' +della cella e >65% celle positive → **FAIL (6%, 54%)**; (2) `weights_tilt_null` → **FAIL/vacuo**; +(3) marginale + null con potenza → PASS; (4) taglia ≥ €0,10/giorno per pagare un modo di fallire in +piu' → **FAIL: €0,0113/giorno** ($4,79/anno a $635). +Due prove ausiliarie che chiudono il caso: **due implementazioni difendibili della stessa regola** +(d'accordo sul 97,6% dei trade) danno **+0,066 contro +0,080** — *il 21% dell'effetto sta nel come si +riempie il warm-up*; e **versare €500/mese vale ~1456× l'effetto misurato**. +**Libro, pesi, cron, config INVARIATI.** + +📌 **Riapertura pre-registrata: non una data, una SOGLIA DI CAPITALE** — cio' che fallisce e' il +rapporto fra un costo operativo **fisso** e un beneficio **proporzionale**, e nessun dato in piu' lo +cambia (con SE(Sharpe)≈1,4 su 6 mesi nessun forward misura 0,07). €0,10/giorno richiede **~$5.600** +alla stima della cella e **~$53.000** a quella onesta di famiglia → si rivaluta alla revisione dei +**$20k** gia' in calendario, **e solo se la cella e' giustificata da un MECCANISMO** invece che +dall'argmax di una griglia. diff --git a/scripts/research/r0822b_bin_freq.py b/scripts/research/r0822b_bin_freq.py index a33d18b..9130f92 100644 --- a/scripts/research/r0822b_bin_freq.py +++ b/scripts/research/r0822b_bin_freq.py @@ -56,8 +56,12 @@ Nessuna riscrittura: `extract / equity_daily / leg_daily / sizes_from_L / book / sono IMPORTATE da r0822_vol_size, e il titolo dello scettico e' riprodotto chiamando la SUA funzione. Replica bit-exact contro `sleeves._skyhook_returns()` prima di ogni delta (§0). -USO: nice -n 19 timeout 900 uv run python scripts/research/r0822b_bin_freq.py [--every K] - --every 8 -> 3 ancore (pilota). default: tutte e 23. +USO: nice -n 19 timeout 900 uv run python scripts/research/r0822b_bin_freq.py + [--every K] 1 ancora ogni K delle 23 (K=8 -> 3 ancore, pilota ~80 s) + [--ndraw N] estrazioni del null di permutazione (default 100) + Con i default gira in ~400 s e ~500 MB di RSS su questa VPS (2 core). Tutti i numeri + riportati qui vengono da questa invocazione, e due esecuzioni indipendenti hanno dato + output numericamente IDENTICI. """ from __future__ import annotations @@ -192,9 +196,12 @@ def iso_pair(TP: pd.Series, leg: pd.Series, leg_base: pd.Series): def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--every", type=int, default=1) - ap.add_argument("--ndraw", type=int, default=120) + ap.add_argument("--ndraw", type=int, default=100) args = ap.parse_args() offs = OFFSETS_FULL[:: max(1, args.every)] + if len(offs) < 3: + raise SystemExit(f"--every {args.every} lascia {len(offs)} ancore: " + "`A.anchor_luck_delta` ne vuole almeno 3 (usa --every <= 8).") t0 = time.time() print("=" * L) @@ -772,11 +779,18 @@ def main() -> None: print(f" (RSS {rss_mb():.0f} MB)") f_mod = FEEDEC[(f"MODELLATA {keep_fee*1e4:.0f} bps RT (i backtest)", nmcan)] f_zero = FEEDEC[("fee ZERO (informazione pura)", nmcan)] - print(f" -> a fee ZERO resta {f_zero:+.3f} di {f_mod:+.3f} = " - f"{100*f_zero/f_mod if f_mod else float('nan'):.0f}% dell'effetto: la parte restante") - print(" e' fee risparmiata. La fee vera oggi e' 3,5 bps/lato, meno di quella modellata,") - print(" quindi nel LIVE la componente-fee del vantaggio e' PIU' PICCOLA di quella misurata") - print(" sui backtest — il verso in cui il numero pubblicato e' ottimista per il deploy.") + quota_info = 100.0 * f_zero / f_mod if f_mod else float("nan") + print(f" -> a fee ZERO resta {f_zero:+.3f} di {f_mod:+.3f} = {quota_info:.0f}% dell'effetto.") + if quota_info >= 85.0: + print(" ❌ IPOTESI REFUTATA (era la mia, ed era anche quella del compito): il vantaggio") + print(" NON e' fee risparmiata. A fee zero l'effetto c'e' tutto, quindi scartare i") + print(" trade in alta vol e' INFORMAZIONE sul rendimento lordo, non un risparmio di") + print(" costo. Conseguenza pratica: NON si puo' replicare abbassando il turnover, e") + print(" NON va convertito con la curva fee del 26/07 (sarebbe doppio conteggio in una") + print(" direzione e attribuzione sbagliata nell'altra).") + else: + print(" Una parte e' fee risparmiata; la fee vera (3,5 bps/lato) e' piu' bassa di quella") + print(" modellata, quindi nel LIVE quella componente e' PIU' PICCOLA.") # ---------------------------------------------------------- §10 trasferimento su V1 print("\n" + "-" * L) @@ -863,21 +877,29 @@ def main() -> None: f"= ${(max(cag_band)-min(cag_band))*CAPITAL:.0f}/anno di incertezza") print(f" versare EUR 500/mese (il piano dell'operatore) = EUR 16,44/giorno di capitale") print(f" nuovo, cioe' ~{16.44/max(1e-9, abs(dC*CAPITAL/365*0.86)):.0f}x l'effetto misurato qui.") - print(f" (v) IL CRITERIO APPLICATO, riga per riga:") - med_pos = float(np.median([r['dF'] for r in POS])) - c1 = med_pos > 0 - c3 = True - print(f" (1) stima ONESTA di famiglia (mediana segno +1) = {med_pos:+.3f} " - f"contro argmax {argmax['dF']:+.3f} e cella pubblicata {RES[nmcan]['dF']:+.3f}" - f" -> {'PASS' if c1 else 'FAIL'}") + print(f" (v) IL CRITERIO APPLICATO, riga per riga. La forma quantitativa di (1) e'") + print(f" dichiarata qui: 'sopravvive alla stima di famiglia' = la MEDIANA della meta'") + print(f" d'ipotesi vale almeno la META' della cella selezionata (altrimenti non e' un") + print(f" plateau, e' una punta), E la quota di celle positive e' chiaramente sopra il") + print(f" 50% (altrimenti la famiglia e' una monetina). Soglie scelte per essere") + print(f" verificabili, non per far passare o bocciare un numero gia' visto.") + med_pos = float(np.median([r["dF"] for r in POS])) + fpos = sum(r["dF"] > 0 for r in POS) / len(POS) + c1 = (med_pos >= 0.5 * CHOSEN["dF"]) and (fpos >= 0.65) + eurday = dC * CAPITAL / 365 * 0.86 + c4 = abs(eurday) >= 0.10 + print(f" (1) mediana di famiglia (segno +1) {med_pos:+.3f} = " + f"{100*med_pos/CHOSEN['dF'] if CHOSEN['dF'] else float('nan'):.0f}% della cella scelta " + f"({CHOSEN['dF']:+.3f}); celle positive {fpos*100:.0f}% -> {'PASS' if c1 else 'FAIL'}") print(f" (2) `weights_tilt_null`: gate_pass=False nella forma nuda, INERTE nella forma") - print(f" compensata -> il gate non lo sostiene in nessuna delle due -> FAIL/vacuo") - print(f" (3) marginale ADDS + null di permutazione con potenza (§6) -> vedi sopra") - print(f" (4) taglia contro il modo di fallire in piu': " - f"EUR {dC*CAPITAL/365*0.86:+.4f}/giorno a ${CAPITAL:.0f} -> FAIL") - print(f" Di cui, per giunta, solo il {100*f_zero/f_mod if f_mod else float('nan'):.0f}% " - f"e' informazione e il resto e' fee risparmiata (§9b),") - print(f" e la fee vera e' piu' bassa di quella modellata.") + print(f" compensata -> non lo sostiene in nessuna delle due -> FAIL/vacuo") + print(f" (3) marginale ADDS su entrambe le celle + null di permutazione battuto a") + print(f" p<0.01 con la penale meccanica dentro (§6) -> PASS") + print(f" (4) taglia contro un modo di fallire in piu': EUR {eurday:+.4f}/giorno a " + f"${CAPITAL:.0f} -> {'PASS' if c4 else 'FAIL'} (soglia dichiarata: EUR 0,10/giorno)") + print(f" VERDETTO: {'CANDIDATO' if (c1 and c4) else 'LEAD / NESSUN CAMBIO'} — servono") + print(f" tutte e quattro; (1) e (4) falliscono, (2) non e' utilizzabile. Cio' che") + print(f" REGGE e va conservato e' il SEGNO: 0/72 celle positive sul controllo inverso.") print("\n" + "=" * L) print(f" fatto in {time.time()-t0:.0f}s")