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