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:
@@ -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