research(gtaa): ZPRR e' R2US (stessa ISIN) + correzione sul verso del costo FX
L'operatore ha verificato su Revolut: R2US assente, ZPRR e SPY4 presenti. (1) ZPRR (Xetra EUR) == R2US (Londra USD): stessa ISIN IE00BJ38QD84, stessa classe di quote, stesso NAV. La gamba small cap e' risolta. Regola: si cerca per ISIN, non per ticker — lo stesso fondo ha ticker diversi su borse diverse. (2) SPY4 IE00B4YBJ215 NON e' small cap ne' S&P 500: e' SPDR S&P 400 MID cap (l'S&P 500 e' SPY5). Misurato come ripiego su 6.5 anni: Sharpe 0.86 vs 0.86, corr fra i due sleeve 0.991, peggiore in 3/7 anni = moneta. Sulla finestra corta di 3.2 anni sembrava -0.15: era rumore. Ripiego accettabile per ragione meccanica (small e mid USA correlano ~0.95 giornaliero), NON validato — e' proprio perche' non si distinguono che la scelta non conta. (3) CORREZIONE a un'indicazione data ieri. Avevo scritto "una linea in EUR aggiunge la conversione per ordine". Vero solo per un conto in USD: per un conto in EURO vale l'opposto. L'esposizione economica e' identica (il fondo detiene attivi USD, nessuna delle due linee e' coperta) — la valuta di quotazione non copre nulla, decide solo se serve una conversione. Regola giusta: prendere la linea nella valuta del proprio saldo. Misurato: turnover lordo $13.655/anno su $10k -> 25bps = -0.07 Sharpe = $34/anno = ~$1.6 per ordine, contro una soglia di $18.90. Regola generale: un costo di conversione non e' una proprieta' dello strumento ma della coppia strumento-CONTO. Book, pesi, cron, config INVARIATI. 447 test verdi (+3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBbYmiXqbUNuGpK9sfbsGp
This commit is contained in:
@@ -54,7 +54,10 @@ EXCH, CUR = "LSEETF", "USD"
|
||||
CANDIDATI: dict[str, tuple[str, ...]] = {
|
||||
"SPY": ("CSPX", "VUAA"), # S&P 500: CSPX $802 (storia) · VUAA $144 (deploy)
|
||||
"QQQ": ("CNDX", "EQQQ", "XNAS"), # Nasdaq-100: EQQQ $693 (storia) · XNAS $66 (deploy)
|
||||
"IWM": ("XRSU", "R2US", "IDP6"), # Russell 2000: XRSU $438 · R2US $86 (piu' economico E piu' lungo)
|
||||
"IWM": ("XRSU", "R2US", "IDP6", "SPY4"), # Russell 2000: XRSU $438 · R2US $86 (piu' economico E lungo)
|
||||
# SPY4 = S&P 400 MID cap: indice DIVERSO, tenuto solo
|
||||
# per misurare il costo di una sostituzione forzata
|
||||
# (e' cio' che alcuni broker offrono al posto dello small cap)
|
||||
"TLT": ("IDTL", "DTLA"), # US Treasury 20+ — indice identico, IDTL $3.09
|
||||
"GLD": ("IGLN",), # oro fisico (ETC, non UCITS: ha comunque il KID)
|
||||
"HYG": ("IHYU",), # USD High Yield corp — indice equivalente
|
||||
|
||||
@@ -46,6 +46,15 @@ ISIN = {"VUAA": ("IE00BFMXXD54", "Vanguard S&P 500 UCITS (Acc, USD)"),
|
||||
"IGLN": ("IE00B4ND3602", "iShares Physical Gold ETC"),
|
||||
"IHYU": ("IE00B4PY7Y77", "iShares $ High Yield Corp Bond UCITS")}
|
||||
|
||||
# Stesso fondo, ticker diverso su borse diverse. Verificato su contract details IB: se le ISIN
|
||||
# coincidono e' la STESSA classe di quote, quindi lo stesso NAV — cambia solo dove si negozia e in
|
||||
# che valuta. Serve perche' un broker puo' non quotare la linea di Londra e quotare quella di Xetra.
|
||||
EQUIVALENZE = [("R2US", "ZPRR", "IE00BJ38QD84", "Xetra EUR")]
|
||||
|
||||
# Ripiego misurato: se un broker non ha NESSUNA linea Russell 2000, l'S&P 400 mid cap e' l'unica
|
||||
# alternativa comune. NON e' lo stesso indice (mid, non small) — vedi sezione (6) per il costo.
|
||||
RIPIEGO_SMALLCAP = ("SPY4", "IE00B4YBJ215", "SPDR S&P 400 US Mid Cap UCITS")
|
||||
|
||||
|
||||
def ordini_anno(veic, cal, capital, band, every) -> float:
|
||||
n = 0
|
||||
@@ -238,9 +247,72 @@ def main() -> None:
|
||||
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 ⚠️ SI CERCA PER ISIN, NON PER TICKER — e il motivo non e' la pignoleria: lo STESSO")
|
||||
print(" fondo ha ticker diversi su borse diverse. Verificato su contract details IB:")
|
||||
for a, b, isin, nota in EQUIVALENZE:
|
||||
print(f" {a:6s} (Londra USD) == {b:6s} ({nota:12s}) ISIN {isin}")
|
||||
print(" Sono la stessa classe di quote: NAV identico, cambia solo dove e in che valuta si")
|
||||
print(" negozia. Cercando per ISIN si trova qualunque linea il proprio broker offra.")
|
||||
print("\n ⚠️ CORREZIONE (2026-07-27) A UN'INDICAZIONE DATA IERI. Avevo scritto che 'una linea in")
|
||||
print(" EUR aggiunge la conversione per ordine'. E' vero solo per un conto in USD. Per un conto")
|
||||
print(" in EURO — che e' il caso di un retail italiano su Revolut/Degiro — vale l'OPPOSTO:")
|
||||
print(" comprare la linea in USD costringe a convertire a ogni ordine, la linea in EUR no.")
|
||||
print(" L'esposizione economica e' identica in entrambi i casi (il fondo detiene attivi in USD")
|
||||
print(" e nessuna delle due linee e' coperta): la valuta di quotazione non copre nulla, decide")
|
||||
print(" solo se serve una conversione. **La regola giusta e': prendere la linea nella valuta")
|
||||
print(" del proprio saldo.**")
|
||||
turnover = 0.0
|
||||
for k in dep:
|
||||
_, pos = U.leg_returns(dep[k], dep[k], cal, CAP / 6.0, band_c, can_every,
|
||||
U.cost_form(fixed=1.0))
|
||||
turnover += float(pos.diff().abs().sum()) * CAP / 6.0
|
||||
turnover /= (len(cal) / 252.0)
|
||||
print(f"\n Quanto vale la conversione, misurato: turnover lordo ${turnover:,.0f}/anno su "
|
||||
f"${CAP:,.0f}")
|
||||
print(f" {'bps FX/ordine':>15s}{'Sharpe':>9s}{'dSharpe':>10s}{'costo/anno':>13s}")
|
||||
b0 = None
|
||||
for p in (0.0, 15.0, 25.0, 50.0):
|
||||
m = U.met(U.sleeve(dep, dep, cal, CAP, band_c, can_every, U.cost_form(prop_bps=p)))
|
||||
b0 = m["sharpe"] if b0 is None else b0
|
||||
print(f" {format(p,'.0f')+' bps':>15s}{m['sharpe']:>9.2f}{m['sharpe']-b0:>+10.2f}"
|
||||
f"{'$'+format(turnover*p/1e4,',.0f'):>13s}")
|
||||
print(" Cioe' a 25bps la conversione equivale a ~$1.6 in piu' su ciascuno dei 21 ordini:")
|
||||
print(" reale, ma piccolo rispetto alla soglia (${:.2f}/ordine a ${:,.0f}).".format(
|
||||
float(soglia_per_ordine(dep, cal, CAP, band_c, can_every)[1:]), int(CAP)))
|
||||
|
||||
# ---------- (6) ripiego sulla gamba small cap ----------
|
||||
print("\n" + "=" * 100)
|
||||
print(" (6) SE IL BROKER NON HA NESSUNA LINEA RUSSELL 2000")
|
||||
print("=" * 100)
|
||||
tic, isin_r, desc_r = RIPIEGO_SMALLCAP
|
||||
print(f" Ripiego comune: {tic} {isin_r} — {desc_r}. NON e' lo stesso indice (MID cap, non")
|
||||
print(" small): va misurato, non assunto equivalente.")
|
||||
alt = dict(dep)
|
||||
alt["IWM"], _ = U.close_uc(tic, us["IWM"])
|
||||
# finestra lunga: veicoli STORIA sulle altre 5 gambe, cosi' il campione non e' limitato da XNAS
|
||||
st_set = {s: U.close_uc(u, us[s])[0] for s, u in U.SET_STORIA.items()}
|
||||
small = dict(st_set)
|
||||
small["IWM"], _ = U.close_uc("R2US", us["IWM"])
|
||||
mid = dict(st_set)
|
||||
mid["IWM"] = alt["IWM"]
|
||||
cl = U.common_cal(list(small.values()) + [mid["IWM"]])
|
||||
st = max(U._norm(x).index[0] for x in list(small.values()) + [mid["IWM"]])
|
||||
cl = cl[cl >= st + np.timedelta64(380, "D")]
|
||||
print(f"\n finestra {cl[0].date()} -> {cl[-1].date()} ({len(cl)/252:.1f} anni)")
|
||||
s1 = U.sleeve(small, small, cl, CAP, BAND_FRAC * CAP / 6.0, can_every, U.cost_form(fixed=1.0))
|
||||
s2 = U.sleeve(mid, mid, cl, CAP, BAND_FRAC * CAP / 6.0, can_every, U.cost_form(fixed=1.0))
|
||||
for nome, s in [("R2US small cap", s1), ("SPY4 mid cap ", s2)]:
|
||||
m = U.met(s)
|
||||
print(f" {nome} Sharpe {m['sharpe']:.2f} CAGR {m['cagr']:.2%} maxDD {m['dd']:.1%}")
|
||||
peggio = sum(1 for y, g in s2.groupby(s2.index.year)
|
||||
if len(g) > 100 and U.met(g)["sharpe"] < U.met(s1[s1.index.year == y])["sharpe"])
|
||||
tot = sum(1 for y, g in s2.groupby(s2.index.year) if len(g) > 100)
|
||||
print(f" correlazione dei due sleeve {s1.corr(s2):.4f} · SPY4 peggiore in {peggio}/{tot} anni")
|
||||
print("\n -> RIPIEGO ACCETTABILE, e il motivo e' meccanico: small e mid cap USA hanno")
|
||||
print(" correlazione giornaliera ~0.95, e un segnale TSMOM su due serie cosi' vicine si")
|
||||
print(" accende quasi negli stessi giorni. ⚠️ NON e' 'validato': 6.5 anni non bastano a")
|
||||
print(" distinguere due gambe cosi' simili, ed e' esattamente per questo che la scelta fra")
|
||||
print(" le due non e' importante. Se c'e' una linea Russell 2000, si usa quella.")
|
||||
print("\n" + "=" * 100)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user