research(gtaa): il numero di ordini e' un parametro — il costo smette di essere binding

Correzione dell'operatore: "io ho gia' Revolut e Degiro e li uso da anni, IB sono solo
iscritto". La raccomandazione di ieri (restare su IB) poggiava su "il conto esiste
gia'", che era falso. Il gateway IB serve solo per i DATI del segnale (basta il paper),
quindi il broker di esecuzione e' libero e la scelta si gioca solo sul costo per ordine.

(a) I 77-149 ordini/anno sono la conseguenza di REBAL_EVERY=5 / REBAL_BAND_USD=50,
scelti il 25/07 per il listino IB su azioni USA e a una taglia sola. A cadenza
settimanale allargare la banda NON costa: Sharpe a costo zero 1.27 ($50) -> 1.27 ($400)
con ordini 84 -> 23. Rallentare la cadenza invece costa (1.27 -> 1.12 mensile). La banda
filtra la deriva del vol-target, non il segnale di trend. Controllo fuori finestra: sui
veicoli USA su 10 anni lo Sharpe a costo zero perde 0.02 mentre gli ordini calano del
74% -> non e' un artefatto della finestra UCITS corta.

(b) MA la banda in dollari assoluti e' la parametrizzazione sbagliata: a $3.000 una
banda da $400 e' l'80% della gamba -> 3 ordini/anno, a mercato il 45% del tempo invece
del 66%, con Sharpe 0.71 ancora "accettabile". Null de-levering in veste nuova: non
travestito da meno drawdown ma da meno costi. Il controllo non e' lo Sharpe ma la quota
di tempo a mercato.

(c) Configurazione proposta: banda = 25% della gamba -> 21 ordini/anno a OGNI capitale,
esposizione 66% ovunque, soglia $5.65/ordine a $3k (contro $2.08 del canonico). Proposta,
non cambio di produzione: non passata per study_family_honest ne' deflated-Sharpe, e
GTAA01 non e' deployabile prima dei $20k.

(d) ISIN da contract details IB per la ricerca sul conto reale (VUAA/XNAS/R2US/IDTL/
IGLN/IHYU, tutti IE, Londra in USD). La valuta della linea conta piu' del broker: una
linea in EUR aggiunge ~25bps per ordine = ~0.15 di Sharpe.

Book, pesi, cron, config INVARIATI. 444 test verdi (+9).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBbYmiXqbUNuGpK9sfbsGp
This commit is contained in:
Adriano Dal Pastro
2026-07-27 06:01:01 +00:00
parent be4690375f
commit 842b0e865b
4 changed files with 473 additions and 8 deletions
+42 -8
View File
@@ -1078,14 +1078,48 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
⚠️ Letto sulla colonna **gambe vive + vol**, non su Sharpe: il vincolo intero *alza* lo Sharpe a ⚠️ Letto sulla colonna **gambe vive + vol**, non su Sharpe: il vincolo intero *alza* lo Sharpe a
capitale piccolo perche' arrotonda in giu' e paga meno commissioni = **null de-levering, 4ª capitale piccolo perche' arrotonda in giu' e paga meno commissioni = **null de-levering, 4ª
occorrenza** (VRP-DD, TP01×DVOL, MAT01). occorrenza** (VRP-DD, TP01×DVOL, MAT01).
(3) **Cosa resta da verificare: UNA cosa sola.** Lo sleeve fa **77 ordini/anno a $3k, 102 a $10k, (3) **Il costo per ordine.** Col canonico lo sleeve fa **77 ordini/anno a $3k, 102 a $10k, 149 a
149 a $50k**; soglia oltre cui non vale la pena (Sharpe<0.45) = **$0.90/ordine a $3k, $2.23 a $50k**; soglia oltre cui non vale la pena (Sharpe<0.45) = **$0.90/ordine a $3k, $2.23 a $10k,
$10k, $7.62 a $50k**. Listino **proporzionale** ≈ indifferente al capitale (Sh 0.75-0.89 a ogni $7.62 a $50k**. Listino **proporzionale** ≈ indifferente al capitale, listino **fisso** = tassa
taglia), listino **fisso** = tassa regressiva (regola 25/07 riconfermata su altro venue). regressiva (regola 25/07 riconfermata su altro venue). ⚠️ **Revolut/Degiro NON sbloccano gli ETF
**Raccomandazione: restare su IB** — conto gia' aperto, stesso gateway dei dati del segnale, USA**: il PRIIPs vale per ogni intermediario UE, IB e' anzi fra i piu' permissivi.
forma di costo gia' modellata. ⚠️ **Revolut/Degiro NON sbloccano gli ETF USA**: il PRIIPs vale - ✅ **ADDENDUM 2026-07-27 — il numero di ordini e' un PARAMETRO, e con la banda giusta il costo
per ogni intermediario UE, IB e' anzi fra i piu' permissivi; un terzo conto aggiungerebbe rischio smette di essere binding su qualunque broker UE.** Nato dalla correzione dell'operatore *"io ho
di venue senza guadagno. Nessuna azione oggi (decisione venue: 100% Deribit fino a $20k). gia' Revolut e Degiro e li uso da anni, IB sono solo iscritto"*: la raccomandazione del 26/07
("restare su IB") poggiava su **"il conto esiste gia'"**, che era falso. Il gateway IB serve solo
per i **dati** del segnale (basta il paper), quindi il broker di esecuzione e' libero.
Script `r0727_gtaa_broker.py`, test `tests/test_gtaa_broker.py` (9). **Book/pesi/cron/config
INVARIATI.**
(a) I 77-149 ordini/anno sono la conseguenza di `REBAL_EVERY=5`/`REBAL_BAND_USD=50`, scelti il
25/07 **per il listino IB su azioni USA e a una taglia sola**. A cadenza settimanale allargare la
banda **non costa**: Sharpe a **costo zero** 1.27 ($50) → **1.27** ($400) mentre gli ordini vanno
84 → 23. Rallentare la CADENZA invece costa (1.27 → 1.12 mensile). Meccanismo: `_exposure` e' la
media di 4 indicatori binari, si muove a scatti di 0.25 ≈ $417 su una gamba da $1.667 → **la
banda filtra la deriva del vol-target, non il segnale di trend**. ✅ Controllo fuori finestra: sui
**veicoli USA su 10 anni** lo Sharpe a costo zero passa 0.98 → **0.96** mentre gli ordini calano
del **74%** (133 → 35) — non e' un artefatto della finestra corta UCITS (3.2a).
(b) ⚠️ **MA la banda in dollari ASSOLUTI e' la parametrizzazione sbagliata.** A $3.000 una banda
da $400 e' l'**80% della gamba**: **3 ordini/anno**, a mercato il **45%** del tempo invece del
66%. Lo Sharpe resta 0.71 — accettabile — **su una strategia che ha smesso di seguire il proprio
target**. E' il **null de-levering in veste nuova**: non travestito da "meno drawdown" ma da
"meno costi". **REGOLA: quando un parametro di esecuzione e' espresso in valuta assoluta il suo
effetto dipende dal capitale, e il controllo non e' lo Sharpe ma la QUOTA DI TEMPO A MERCATO.**
(c) **Configurazione proposta: banda = 25% della gamba****21 ordini/anno a OGNI capitale**
(invariante), esposizione 66% ovunque; soglia per ordine **$5.65 a $3k / $18.90 a $10k / $47 a
$25k** contro i **$2.08 a $3k** del canonico. E' la differenza fra "serve un broker economico" e
"va bene qualunque broker europeo". ⚠️ **Proposta, non cambio di produzione**: tarata su questa
finestra, non passata per `study_family_honest` ne' per un deflated-Sharpe — e GTAA01 non e'
deployabile prima dei $20k, quindi c'e' tempo per validarla.
(d) **Cosa cercare sul proprio conto — per ISIN, non per ticker** (da contract details IB; tutti
domiciliati IE, quotati a **Londra in USD**): **VUAA** `IE00BFMXXD54` ($143.80) · **XNAS**
`IE00BMFKG444` ($65.74) · **R2US** `IE00BJ38QD84` ($86.35) · **IDTL** `IE00BSKRJZ44` ($3.09) ·
**IGLN** `IE00B4ND3602` ($79.06) · **IHYU** `IE00B4PY7Y77` ($94.12). ⚠️ Le stesse ISIN esistono
in EUR su altre borse: la conversione per ordine a 25bps costa **~0.15 di Sharpe**, piu' del
costo per ordine di qualunque broker a questa cadenza → **la valuta della linea conta piu' del
broker**. Nessuna azione oggi (decisione venue: 100% Deribit fino a $20k).
**LEZIONE: una raccomandazione poggiata su un fatto non verificato sul conto reale vale quanto
quel fatto** — due volte in due giorni un'assunzione sul conto ha cambiato la conclusione (prima
la negoziabilita' PRIIPs, poi quale conto e' davvero operativo).
- ⚠️ **IL FEED EQUITY NON AVEVA UN CROSS-CHECK — buco trovato e chiuso (2026-07-26).** - ⚠️ **IL FEED EQUITY NON AVEVA UN CROSS-CHECK — buco trovato e chiuso (2026-07-26).**
`src/data/eq_crosscheck.py`. Nel crypto la certificazione incrocia sempre piu' venue `src/data/eq_crosscheck.py`. Nel crypto la certificazione incrocia sempre piu' venue
(`certify_feed.py` vs Coinbase USD); il feed equity aveva **solo controlli locali** (integrita', (`certify_feed.py` vs Coinbase USD); il feed equity aveva **solo controlli locali** (integrita',
+118
View File
@@ -276,3 +276,121 @@ contro $2.23 (a $10.000) o $0.90 (a $3.000).
6. **Il grado d'accordo si conta, non si dichiara.** La prima stesura stampava "stesso segno su 6/6 6. **Il grado d'accordo si conta, non si dichiara.** La prima stesura stampava "stesso segno su 6/6
gambe"; i dati dicevano 4/6. Scriverlo a mano nel `print` è il modo più facile di pubblicare una gambe"; i dati dicevano 4/6. Scriverlo a mano nel `print` è il modo più facile di pubblicare una
conclusione che i propri dati smentiscono — 3ª occorrenza in questa sessione. conclusione che i propri dati smentiscono — 3ª occorrenza in questa sessione.
---
# Addendum 2026-07-27 — "io ho già Revolut e Degiro, IB sono solo iscritto"
L'informazione ribalta la raccomandazione di ieri. L'argomento principale per IB era **"il conto
esiste già"**, e non è vero: i conti reali sono Revolut e Degiro, IB è una registrazione.
Cosa **non** cambia: il gateway IB serve per i **dati** del segnale (ADJUSTED_LAST sui 6 ETF USA),
e per quello basta il paper — non serve eseguire lì. La scelta del broker di **esecuzione** è
quindi libera, e si gioca su una sola dimensione misurabile: il **costo per ordine**.
Script: `scripts/research/r0727_gtaa_broker.py`. Test: `tests/test_gtaa_broker.py` (9).
**Book, pesi, cron, config: INVARIATI.**
## Il numero di ordini è un parametro, non un dato
I 77-149 ordini/anno del 26/07 sono la conseguenza di `REBAL_EVERY=5` e `REBAL_BAND_USD=50`,
scelti il 25/07 sul plateau **per il listino IB su azioni USA e a una taglia sola**.
| cadenza | banda | ord/anno | Sh @ $0 | Sh @ $1 | Sh @ $2 |
|---|---|---|---|---|---|
| settimanale | $25 | 102 | 1.28 | 1.08 | 0.87 |
| settimanale | **$50** (canonico) | 84 | 1.27 | 1.10 | 0.94 |
| settimanale | $400 | **23** | **1.27** | 1.22 | 1.18 |
| mensile | $50 | 33 | **1.12** | 1.06 | 1.00 |
| bimestrale | $400 | 10 | 1.10 | 1.08 | 1.06 |
**Si legge `Sh @ $0` per prima**: è il prezzo della banda *in assenza di commissioni*. Allargare la
banda a cadenza settimanale **non lo tocca** (1.27 → 1.27), rallentare la cadenza sì (1.27 → 1.12).
Il meccanismo è interpretabile: `_exposure` è la media di 4 indicatori binari, quindi si muove a
scatti di 0.25 ≈ $417 su una gamba da $1.667. **La banda filtra la deriva del vol-target, non il
segnale di trend.**
### Controllo su finestra lunga e veicoli diversi
Sui **veicoli USA**, finestra comune ai 6 (10 anni, limitata dalla cache TLT dal 2016):
| banda | ord/anno | Sh @ $0 | Sh @ $1 |
|---|---|---|---|
| $25 | 133 | 0.98 | 0.72 |
| $400 | **35** | **0.96** | **0.89** |
✅ Lo Sharpe a costo zero perde **0.02** mentre gli ordini calano del **74%**. Non è un artefatto
della finestra corta.
## ⚠️ Ma la banda in dollari assoluti è la parametrizzazione sbagliata
| capitale | banda | % della gamba | ord/anno | **esposta** | Sharpe | |
|---|---|---|---|---|---|---|
| $3.000 | $400 | **80%** | **3** | **45%** | 0.71 | ❌ congelato |
| $10.000 | $400 | 24% | 23 | 66% | 1.22 | OK |
| $50.000 | $400 | 5% | 75 | 66% | 1.25 | OK |
A $3.000 la banda da $400 è l'80% della gamba: **3 ordini l'anno**, a mercato il **45%** del tempo
invece del 66%. Lo Sharpe resta 0.71 — accettabile — **su una strategia che ha smesso di seguire il
proprio target**. È il null de-levering in una veste nuova: qui non travestito da "meno drawdown"
ma da "meno costi".
> **Regola: quando un parametro di esecuzione è espresso in valuta assoluta, il suo effetto dipende
> dal capitale — e il controllo non è lo Sharpe ma la QUOTA DI TEMPO A MERCATO.**
## La configurazione: banda = 25% della gamba
| capitale | banda | ord/anno | esposta | soglia $/ordine | budget/anno |
|---|---|---|---|---|---|
| $3.000 | $125 | **21** | 66% | **$5.65** | $120 |
| $6.000 | $250 | **21** | 66% | $11.34 | $240 |
| $10.000 | $417 | **21** | 66% | $18.90 | $400 |
| $25.000 | $1.042 | **21** | 66% | $47.26 | $1.000 |
| $50.000 | $2.083 | **21** | 66% | >$60 | — |
**21 ordini l'anno a ogni capitale**, esposizione 66% ovunque. Contro il canonico: a $3.000 la
soglia passa da **$2.08 a $5.65 per ordine** — cioè dalla differenza fra *"serve un broker
economico"* e *"va bene qualunque broker europeo"*.
⚠️ **Non è un cambio di produzione.** È una configurazione **proposta**, tarata su questa finestra,
non passata per `study_family_honest` né per un deflated-Sharpe. Regge il controllo su 10 anni e
veicoli diversi, ma GTAA01 non è deployabile prima dei $20k (decisione venue 26/07): c'è tempo per
validarla come si deve.
## Cosa cercare sul proprio conto — per ISIN, non per ticker
| gamba | ticker | ISIN | prezzo | descrizione |
|---|---|---|---|---|
| SPY | VUAA | `IE00BFMXXD54` | $143.80 | Vanguard S&P 500 UCITS (Acc, USD) |
| QQQ | XNAS | `IE00BMFKG444` | $65.74 | Xtrackers Nasdaq 100 UCITS 1C |
| IWM | R2US | `IE00BJ38QD84` | $86.35 | SPDR Russell 2000 US Small Cap UCITS |
| TLT | IDTL | `IE00BSKRJZ44` | $3.09 | iShares $ Treasury Bond 20+yr UCITS |
| GLD | IGLN | `IE00B4ND3602` | $79.06 | iShares Physical Gold ETC |
| HYG | IHYU | `IE00B4PY7Y77` | $94.12 | iShares $ High Yield Corp Bond UCITS |
*(ISIN da contract details IB: i ticker cambiano da borsa a borsa, le ISIN no. Tutti domiciliati in
Irlanda — è il motivo per cui sono acquistabili nell'UE.)*
Tutti quotati a **Londra in USD**. ⚠️ Le stesse ISIN esistono su altre borse e in altre valute: una
linea in **EUR** aggiunge la conversione per ordine, che a 25bps costa **~0.15 di Sharpe** — più del
costo per ordine di qualunque broker a questa cadenza. **La valuta della linea conta più del
broker.**
## Verdetto
Con 21 ordini l'anno e una tolleranza di $5.65/ordine già a $3.000, **il vincolo di costo smette di
essere binding su qualunque broker retail europeo**. La domanda "Revolut o Degiro" perde quindi la
sua parte quantitativa e resta solo la parte di disponibilità: quale dei due quota le sei ISIN sopra
sulla linea in USD.
Restano due criteri non misurabili da qui, entrambi a favore di Degiro su Revolut per questo uso
(universo ETF europeo tipicamente più ampio, e accesso diretto a LSE), **ma vanno verificati
guardando i due conti** — non sono cose che posso misurare da questo lato.
## Lezione
**Una raccomandazione poggiata su un fatto non verificato sul conto reale vale quanto quel fatto.**
Ieri ho consigliato IB in parte perché "il conto esiste già": era un'assunzione, e per la seconda
volta in due giorni un'assunzione sul conto reale ha cambiato la conclusione. La prima volta era la
negoziabilità (PRIIPs), questa volta quale conto è davvero operativo.
+193
View File
@@ -0,0 +1,193 @@
"""GTAA01 su un broker a COSTO FISSO — il numero di ordini e' un PARAMETRO, non un dato.
CONTESTO. Il 26/07 la via UCITS e' risultata praticabile (drag del veicolo ~0, biglietto minimo
risolto scegliendo linee a prezzo unitario basso) e il solo vincolo residuo e' il **costo per
ordine**: 77-149 ordini l'anno con banda e cadenza canoniche, contro soglie di $0.90-$7.62.
Ma quei 77-149 ordini sono la conseguenza di `REBAL_EVERY=5` e `REBAL_BAND_USD=50`, scelti il
25/07 sul plateau **per il listino IB su azioni USA e a una taglia sola**. La domanda giusta non
e' "quale broker costa meno" ma "quanti ordini servono davvero".
IL COMPROMESSO HA DUE LATI. Allargare la banda risparmia commissioni MA aumenta il tracking
error. Guardare solo lo Sharpe netto confonde le due cose: qui si riporta anche lo Sharpe a
**costo zero**, che isola il prezzo della banda. Se lo Sharpe a costo zero tiene, il risparmio e'
reale; se crolla, si sta solo smettendo di seguire il target.
E LA BANDA IN DOLLARI ASSOLUTI NON SCALA. A $10.000 allocati una banda da $400 e' il 24% della
gamba (sana, 23 ordini/anno); a $3.000 e' l'**80%** e lo sleeve si congela 3 ordini l'anno, a
mercato il 45% del tempo invece del 66%. Uno Sharpe accettabile su una strategia congelata non e'
un successo: e' il null de-levering. La banda va espressa come FRAZIONE della gamba.
uv run python scripts/research/r0727_gtaa_broker.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
import r0726_gtaa_ucits as U # noqa: E402
CADENZE = [(5, "settimanale"), (10, "bisettimanale"), (21, "mensile"), (42, "bimestrale")]
BANDE = [25.0, 50.0, 100.0, 200.0, 400.0]
BAND_FRAC = 0.25 # limite superiore della regione sana, misurato in (3)
ESPOSTA_SANA = 0.60 # sotto questa quota di tempo a mercato lo sleeve e' congelato, non frugale
# ISIN da contract details IB (fonte autorevole: i ticker cambiano da borsa a borsa, le ISIN no).
ISIN = {"VUAA": ("IE00BFMXXD54", "Vanguard S&P 500 UCITS (Acc, USD)"),
"XNAS": ("IE00BMFKG444", "Xtrackers Nasdaq 100 UCITS 1C"),
"R2US": ("IE00BJ38QD84", "SPDR Russell 2000 US Small Cap UCITS"),
"IDTL": ("IE00BSKRJZ44", "iShares $ Treasury Bond 20+yr UCITS"),
"IGLN": ("IE00B4ND3602", "iShares Physical Gold ETC"),
"IHYU": ("IE00B4PY7Y77", "iShares $ High Yield Corp Bond UCITS")}
def ordini_anno(veic, cal, capital, band, every) -> float:
n = 0
for k in veic:
_, pos = U.leg_returns(veic[k], veic[k], cal, capital / 6.0, band, every,
U.cost_form(fixed=1.0))
n += int((pos.diff().abs() > 1e-12).sum())
return n / (len(cal) / 252.0)
def soglia_per_ordine(veic, cal, capital, band, every, target=0.45) -> str:
def sh(f):
return U.met(U.sleeve(veic, veic, cal, capital, band, every,
U.cost_form(fixed=f)))["sharpe"]
lo, hi = 0.0, 60.0
if sh(hi) > target:
return ">$60"
for _ in range(10):
mid = (lo + hi) / 2
lo, hi = (mid, hi) if sh(mid) > target else (lo, mid)
return f"${(lo + hi) / 2:.2f}"
def main() -> None:
us = {s: U.close_us(s) for s in U.US}
dep = {s: U.close_uc(u, us[s])[0] for s, u in U.SET_DEPLOY.items()}
cal = U.common_cal(list(us.values()) + list(dep.values()))
start = max(U._norm(x).index[0] for x in dep.values())
cal = cal[cal >= start + np.timedelta64(380, "D")]
cal_us = U.common_cal(list(us.values()))
print("=" * 100)
print(" GTAA01 SU UN BROKER A COSTO FISSO — quanti ordini servono davvero")
print("=" * 100)
print(f" veicoli DEPLOY {list(U.SET_DEPLOY.values())}")
print(f" finestra UCITS {cal[0].date()} -> {cal[-1].date()} ({len(cal)/252:.1f} anni)")
print(" ⚠️ Finestra corta e tutta di mercato favorevole: gli Sharpe assoluti qui (1.1-1.3) NON")
print(" sono l'aspettativa dello sleeve (~0.6 sulla storia lunga). Servono a confrontare")
print(" configurazioni FRA LORO sulla stessa finestra, non a stimare il rendimento.")
CAP = 10_000.0
can_every, can_band = 5, 50.0
# ---------- (1) griglia ----------
print("\n" + "=" * 100)
print(" (1) GRIGLIA cadenza x banda — capitale allocato $10.000")
print("=" * 100)
print(f"\n {'cadenza':15s}{'banda':>8s}{'ord/anno':>10s}{'Sh @ $0':>10s}{'Sh @ $1':>10s}"
f"{'Sh @ $2':>10s}{'Sh @ $5':>10s}")
griglia = []
for every, nome in CADENZE:
for band in BANDE:
n = ordini_anno(dep, cal, CAP, band, every)
sh = [U.met(U.sleeve(dep, dep, cal, CAP, band, every,
U.cost_form(fixed=f)))["sharpe"] for f in (0.0, 1.0, 2.0, 5.0)]
griglia.append((every, nome, band, n, sh))
print(f" {nome:15s}{'$'+format(band,'.0f'):>8s}{n:>10.0f}"
+ "".join(f"{x:>10.2f}" for x in sh))
base_z = next(sh[0] for e, _, b, _, sh in griglia if (e, b) == (can_every, can_band))
print(f"\n Sharpe a COSTO ZERO del canonico (settimanale/$50): {base_z:.2f}")
print(" Allargare la banda a cadenza settimanale non lo tocca (il segnale si muove a scatti")
print(" di 0.25 di esposizione = ~$417 su una gamba da $1.667: la banda filtra la deriva del")
print(" vol-target, non il segnale di trend). Rallentare la CADENZA invece costa davvero.")
# ---------- (2) il plateau regge sulla finestra lunga? ----------
print("\n" + "=" * 100)
print(" (2) CONTROLLO — la banda larga regge fuori dalla finestra corta?")
print("=" * 100)
print(f" Stessa griglia di banda sui VEICOLI USA, finestra comune ai 6: "
f"{cal_us[0].date()} -> {cal_us[-1].date()} ({len(cal_us)/252:.0f} anni).")
print(" (e' l'intersezione delle 6 serie, limitata dalla cache TLT che parte dal 2016; lo")
print(" sleeve in produzione gira da prima con meno gambe, qui serve un campione appaiato)")
print(f"\n {'banda':>8s}{'ord/anno':>10s}{'Sh @ $0':>10s}{'Sh @ $1':>10s}{'CAGR @ $0':>12s}")
for band in BANDE:
n = ordini_anno(us, cal_us, CAP, band, can_every)
m0 = U.met(U.sleeve(us, us, cal_us, CAP, band, can_every, U.cost_form(fixed=0.0)))
m1 = U.met(U.sleeve(us, us, cal_us, CAP, band, can_every, U.cost_form(fixed=1.0)))
print(f" {'$'+format(band,'.0f'):>8s}{n:>10.0f}{m0['sharpe']:>10.2f}"
f"{m1['sharpe']:>10.2f}{m0['cagr']:>11.2%}")
print("\n ✅ Il plateau si estende a $400 anche su 10 anni e su veicoli diversi: lo Sharpe a")
print(" costo zero perde 0.02 mentre gli ordini calano del 74%. Non e' un artefatto della")
print(" finestra corta.")
# ---------- (3) la degenerazione ----------
print("\n" + "=" * 100)
print(" (3) PERCHE' LA BANDA IN DOLLARI ASSOLUTI E' SBAGLIATA")
print("=" * 100)
print(f"\n {'capitale':>9s}{'banda':>8s}{'% gamba':>10s}{'ord/anno':>10s}{'esposta':>9s}"
f"{'Sharpe':>9s} verdetto")
for cap in (3_000.0, 10_000.0, 50_000.0):
for band in (50.0, 400.0):
n = ordini_anno(dep, cal, cap, band, can_every)
r, P = U.sleeve(dep, dep, cal, cap, band, can_every, U.cost_form(fixed=1.0),
want_pos=True)
esp = float((P > 0).mean().mean())
v = "OK" if esp >= ESPOSTA_SANA else "❌ CONGELATO"
print(f" {'$'+format(int(cap),',d'):>9s}{'$'+format(band,'.0f'):>8s}"
f"{band/(cap/6):>9.0%}{n:>10.0f}{esp:>9.0%}"
f"{U.met(r)['sharpe']:>9.2f} {v}")
print("\n ⚠️ A $3.000 la banda da $400 e' l'80% della gamba: 3 ordini l'anno e a mercato il 45%")
print(" del tempo invece del 66%. Lo Sharpe resta 0.71 — accettabile — ma su una strategia")
print(" che ha smesso di seguire il proprio target. SI LEGGE 'esposta', NON 'Sharpe'.")
# ---------- (4) la configurazione capital-aware ----------
print("\n" + "=" * 100)
print(f" (4) BANDA = {BAND_FRAC:.0%} DELLA GAMBA — e il numero di ordini diventa invariante")
print("=" * 100)
print(f"\n {'capitale':>9s}{'banda':>9s}{'ord/anno':>10s}{'esposta':>9s}{'Sharpe':>8s}"
f"{'soglia $/ordine':>17s}{'= budget/anno':>15s}")
for cap in (3_000.0, 6_000.0, 10_000.0, 25_000.0, 50_000.0):
band = BAND_FRAC * cap / 6.0
n = ordini_anno(dep, cal, cap, band, can_every)
r, P = U.sleeve(dep, dep, cal, cap, band, can_every, U.cost_form(fixed=1.0), want_pos=True)
thr = soglia_per_ordine(dep, cal, cap, band, can_every)
budget = "-" if thr.startswith(">") else f"${n*float(thr[1:]):,.0f}"
print(f" {'$'+format(int(cap),',d'):>9s}{'$'+format(band,'.0f'):>9s}{n:>10.0f}"
f"{float((P > 0).mean().mean()):>9.0%}{U.met(r)['sharpe']:>8.2f}{thr:>17s}{budget:>15s}")
n_can = ordini_anno(dep, cal, CAP, can_band, can_every)
print(f"\n Contro il canonico ($50 fissi): {n_can:.0f} ordini/anno a $10.000 e "
f"{ordini_anno(dep, cal, 3_000.0, can_band, can_every):.0f} a $3.000, con soglia "
f"{soglia_per_ordine(dep, cal, 3_000.0, can_band, can_every)}/ordine a $3.000.")
print(" La banda proporzionale porta la soglia a $3.000 da ~$2 a ~$5.7 per ordine: e' la")
print(" differenza fra 'serve un broker economico' e 'va bene qualunque broker europeo'.")
print("\n ⚠️ NON e' un cambio di produzione. E' una configurazione PROPOSTA, tarata su questa")
print(" finestra e non passata per study_family_honest ne' per un deflated-Sharpe. Regge il")
print(" controllo (2) su 10 anni e veicoli diversi, ma GTAA01 non e' deployabile prima dei")
print(" $20k (decisione venue 26/07): c'e' tempo per validarla come si deve.")
# ---------- (5) cosa cercare sul proprio conto ----------
print("\n" + "=" * 100)
print(" (5) COSA CERCARE SUL PROPRIO BROKER — per ISIN, non per ticker")
print("=" * 100)
print(f"\n {'gamba':7s}{'ticker':8s}{'ISIN':16s}{'prezzo':>9s} descrizione")
for s in U.US:
t = U.SET_DEPLOY[s]
isin, d = ISIN[t]
print(f" {s:7s}{t:8s}{isin:16s}{float(U._norm(dep[s]).iloc[-1]):>9.2f} {d}")
print("\n Tutti su LONDRA in USD. Le stesse ISIN esistono su altre borse e in altre valute:")
print(" una linea in EUR aggiunge la conversione per ordine, che a 25bps costa ~0.15 di")
print(" Sharpe — piu' del costo per ordine di qualunque broker a questa cadenza.")
print("\n" + "=" * 100)
if __name__ == "__main__":
main()
+120
View File
@@ -0,0 +1,120 @@
"""Test della configurazione di esecuzione di GTAA01 su un broker a costo fisso
(scripts/research/r0727_gtaa_broker.py).
Il finding da bloccare e' UNO: una banda in dollari ASSOLUTI non scala col capitale, e a capitale
piccolo congela lo sleeve invece di renderlo frugale. Lo Sharpe non lo dice resta accettabile
lo dice la QUOTA DI TEMPO A MERCATO. E' la stessa firma del null de-levering che il progetto ha
gia' incontrato quattro volte, in una veste nuova.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
import numpy as np
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
import r0726_gtaa_ucits as U # noqa: E402
import r0727_gtaa_broker as B # noqa: E402
RAW = ROOT / "data" / "raw"
@pytest.fixture(scope="module")
def ctx():
for s in list(U.US) + list(U.SET_DEPLOY.values()):
pre = "eq" if s in U.US else "eqx"
if not (RAW / f"{pre}_{s.lower()}_1d.parquet").exists():
pytest.skip("data/raw incompleto (gitignored)")
us = {s: U.close_us(s) for s in U.US}
dep = {s: U.close_uc(u, us[s])[0] for s, u in U.SET_DEPLOY.items()}
cal = U.common_cal(list(us.values()) + list(dep.values()))
start = max(U._norm(x).index[0] for x in dep.values())
return dep, cal[cal >= start + np.timedelta64(380, "D")]
def _esposta(dep, cal, cap, band, every=5) -> float:
_, P = U.sleeve(dep, dep, cal, cap, band, every, U.cost_form(fixed=1.0), want_pos=True)
return float((P > 0).mean().mean())
# ===========================================================================
# il finding
# ===========================================================================
def test_banda_assoluta_larga_congela_lo_sleeve_a_capitale_piccolo(ctx):
"""IL test. A $3.000 una banda da $400 e' l'80% della gamba: lo sleeve smette di seguire il
target. Lo Sharpe resta accettabile, quindi il controllo NON puo' essere sullo Sharpe."""
dep, cal = ctx
assert _esposta(dep, cal, 3_000.0, 400.0) < B.ESPOSTA_SANA
assert B.ordini_anno(dep, cal, 3_000.0, 400.0, 5) < 10
def test_la_stessa_banda_a_capitale_grande_e_sana(ctx):
"""Il confronto che rende il test precedente informativo: non e' la banda a essere sbagliata,
e' il fatto che sia espressa in dollari assoluti."""
dep, cal = ctx
assert _esposta(dep, cal, 10_000.0, 400.0) >= B.ESPOSTA_SANA
def test_la_banda_proporzionale_resta_sana_a_ogni_capitale(ctx):
dep, cal = ctx
for cap in (3_000.0, 10_000.0, 50_000.0):
assert _esposta(dep, cal, cap, B.BAND_FRAC * cap / 6.0) >= B.ESPOSTA_SANA, \
f"congelato a ${cap:,.0f}"
def test_la_banda_proporzionale_rende_gli_ordini_invarianti_al_capitale(ctx):
"""E' la proprieta' che serve: il costo annuo scala col capitale invece che restare fisso."""
dep, cal = ctx
n = [B.ordini_anno(dep, cal, c, B.BAND_FRAC * c / 6.0, 5) for c in (3_000., 10_000., 50_000.)]
assert max(n) - min(n) <= 2, f"ordini/anno non invarianti: {n}"
def test_una_banda_piu_larga_non_puo_generare_piu_ordini(ctx):
dep, cal = ctx
n = [B.ordini_anno(dep, cal, 10_000.0, b, 5) for b in (25.0, 100.0, 400.0)]
assert n == sorted(n, reverse=True), f"monotonia violata: {n}"
# ===========================================================================
# la soglia per il broker
# ===========================================================================
def test_la_soglia_per_ordine_cresce_col_capitale(ctx):
"""Con banda proporzionale gli ordini sono costanti ma il capitale cresce: la tariffa
tollerabile deve crescere in proporzione."""
dep, cal = ctx
v = []
for cap in (3_000.0, 10_000.0):
s = B.soglia_per_ordine(dep, cal, cap, B.BAND_FRAC * cap / 6.0, 5)
v.append(60.0 if s.startswith(">") else float(s[1:]))
assert v[1] > 2 * v[0], f"la soglia non scala col capitale: {v}"
def test_la_banda_proporzionale_alza_la_soglia_rispetto_al_canonico(ctx):
"""La ragione operativa di tutto il filone: a $3.000 si passa da ~$2 a ~$5 per ordine."""
dep, cal = ctx
can = B.soglia_per_ordine(dep, cal, 3_000.0, 50.0, 5)
prop = B.soglia_per_ordine(dep, cal, 3_000.0, B.BAND_FRAC * 3_000.0 / 6.0, 5)
assert float(prop[1:]) > 2 * float(can[1:])
# ===========================================================================
# cio' che l'operatore copia e incolla
# ===========================================================================
def test_le_isin_sono_ben_formate_e_coprono_le_sei_gambe():
"""Le ISIN finiscono in un campo di ricerca su un conto reale: un refuso qui compra un'altra
cosa. Formato ISO 6166 e domicilio irlandese (e' il motivo per cui sono acquistabili nell'UE)."""
assert set(B.ISIN) == set(U.SET_DEPLOY.values())
for t, (isin, desc) in B.ISIN.items():
assert re.fullmatch(r"IE00[A-Z0-9]{8}", isin), f"{t}: ISIN malformata {isin}"
assert desc.strip()
def test_le_soglie_di_lettura_sono_dichiarate():
assert B.BAND_FRAC == 0.25
assert B.ESPOSTA_SANA == 0.60