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:
@@ -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',
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user