Lección 2.2.3 · 65 min

Frecuencias: intradía, swing y posicional, y la capacidad de una estrategia

Cuanto más corto es el horizonte de una estrategia, más operaciones hace, antes se puede comprobar si funciona y más le cuestan los costes y el impacto. Esta lección mide las tres cosas con barras de 5 minutos y con datos diarios, y estima cuánto capital admite cada horizonte antes de que el impacto se coma la ventaja.

Fig. — Tendencia en los 24 futuros del nivel 2 con ventanas de 2 a 252 sesiones, y la estrategia de huecos de KAPA: Sharpe bruto y neto de costes frente a la rotación anual
Lecciones del nivel 2
  1. 2.1 · Estadística y econometría para trading

    1. 2.1.1 Contrastes de hipótesis, p-valores, bootstrap y regresión con errores robustos
    2. 2.1.2 Estacionariedad: ADF, KPSS, diferenciación y diferenciación fraccional
    3. 2.1.3 Modelos ARMA y ARIMA: autocorrelación simple (ACF) y parcial (PACF)
    4. 2.1.4 Modelos de volatilidad: ARCH, GARCH, EGARCH y GJR con la librería arch
    5. 2.1.5 Cointegración: Engle-Granger, Johansen y vida media de la reversión
    6. 2.1.6 Filtro de Kalman para hedge ratios dinámicos
    7. 2.1.7 Detección de regímenes: modelos de Markov ocultos y cambios estructurales
    8. 2.1.8 El problema de las pruebas múltiples: Bonferroni, FDR y el «factor zoo»
  2. 2.2 · Anatomía de un sistema de trading

    1. 2.2.1 Hipótesis de ventaja: riesgo, comportamiento, estructura e información
    2. 2.2.2 Universo, señal, filtro, entrada, salida, sizing y gestión del riesgo
    3. 2.2.3 Frecuencias: intradía, swing y posicional, y la capacidad de una estrategia
    4. 2.2.4 Diario de investigación y registro de experimentos contra el data snooping
  3. 2.3 · Familias de estrategias

    1. 2.3.1 Seguimiento de tendencia y momentum
    2. 2.3.2 Reversión a la media: RSI y Bollinger, reversión intradía y huecos
    3. 2.3.3 Pairs trading y arbitraje estadístico: distancia, cointegración y Kalman
    4. 2.3.4 Carry en divisas, futuros y bonos; estacionalidad y efectos calendario
    5. 2.3.5 Factores (value, calidad, baja volatilidad, tamaño) y estrategias de eventos
    6. 2.3.6 Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov)
    7. 2.3.7 Estrategias específicas de cripto: momentum, arbitraje de funding y basis
  4. 2.4 · Backtesting: motores y paradigmas

    1. 2.4.1 Backtest vectorizado desde cero: matrices, desfase, costes y límites
    2. 2.4.2 Backtest por eventos desde cero: bucle, cartera, órdenes y fills
    3. 2.4.3 vectorbt: barridos masivos de parámetros y simulación de carteras
    4. 2.4.4 backtesting.py y backtrader: dos motores didácticos y su estado
    5. 2.4.5 NautilusTrader: arquitectura, datos, estrategias y el mismo código en backtest y en real
    6. 2.4.6 LEAN/QuantConnect y Zipline Reloaded; bt para asignación de activos
    7. 2.4.7 Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos
    8. 2.4.8 Modelado de fills: órdenes limitadas, cola, fills parciales, huecos y stops
  5. 2.5 · Sesgos y validación

    1. 2.5.1 Sesgos de backtest: anticipación, supervivencia, selección y narrativa
    2. 2.5.2 In-sample y out-of-sample: walk-forward anclado y móvil
    3. 2.5.3 Validación cruzada combinatoria purgada para estrategias
    4. 2.5.4 Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)
    5. 2.5.5 Monte Carlo: remuestreo de trades, bootstrap de bloques y permutaciones de señales
    6. 2.5.6 Sensibilidad de parámetros: mesetas frente a picos, robustez entre activos y periodos
    7. 2.5.7 Datos sintéticos para pruebas de estrés: GBM, GARCH y bootstrapping
    8. 2.5.8 Checklist de «backtest honesto» antes de pasar a paper trading
  6. 2.6 · Métricas de rendimiento

    1. 2.6.1 CAGR, Sharpe, Sortino, Calmar, Omega, drawdown y Ulcer Index
    2. 2.6.2 Win rate, payoff, expectativa, alpha, beta, information ratio, turnover y capacidad
    3. 2.6.3 Informes con QuantStats o pyfolio-reloaded y tearsheets propios

Tres horizontes

El horizonte de una estrategia es el tiempo típico que mantiene una posición. Se suelen distinguir tres grupos, sin fronteras exactas:

HorizonteTenencia típicaDatos que necesitaContra quién compiteSu problema principal
IntradíaMinutos a horas; sin posición por la nocheBarras de minutos, ticks, libroCreadores de mercado, HFT, algoritmos de ejecuciónCostes e impacto: cada punto básico cuenta
SwingDías a semanasBarras diarias, a veces horariasFondos cuantitativos, traders discrecionalesRuido: las señales de días son débiles
PosicionalSemanas a mesesDiarias y semanales, fundamentalesCTA, gestorasPocas apuestas: hacen falta muchos años para comprobarla

El horizonte no es una preferencia de estilo: decide casi todo lo demás. Una cadena de consecuencias que esta lección mide eslabón a eslabón:

Horizonte cortoMás operacionesMás rotaciónMás apuestasMás coste anualMenos capacidadMenos años de test
Fuente Mermaid
flowchart TB
  H[Horizonte corto] --> N[Más operaciones]
  N --> T[Más rotación]
  N --> A[Más apuestas]
  T --> C[Más coste anual]
  T --> K[Menos capacidad]
  A --> E[Menos años de test]

Dos eslabones van en contra y uno a favor. Más rotación significa más costes y menos capital admisible; más apuestas significa que el azar se promedia antes y la ventaja se distingue con menos años de datos. Las tres cosas dependen del mismo número: cuántas veces al año la estrategia negocia su capital.

Un efecto intradía: el hueco de apertura de KAPA

Los datos de este apartado son barras de 5 minutos de KAPA, un ETF ficticio sobre un índice, en 1.003 sesiones de la NYSE de 2022 a 2025. Llevan un efecto plantado pequeño (README del nivel 2, sección e): en la primera media hora el precio recupera, de media, un 10 % del hueco de apertura (la diferencia entre la apertura y el cierre anterior). Fuera de la primera media hora, los rendimientos no tienen autocorrelación: es el control.

IN [01]python
import numpy as np
import pandas as pd

b = pd.read_parquet("indice2_5min.parquet")
ny = b["ts"].dt.tz_convert("America/New_York")
b["dia"] = ny.dt.date
b["k"] = b.groupby("dia").cumcount()        # barra del día, desde 0
g = b.groupby("dia")
d = pd.DataFrame({
    "apertura": g["apertura"].first(),
    "cierre": g["cierre"].last(),
    "volumen": g["volumen"].sum(),
    "vol_30m": b[b["k"] < 6].groupby("dia")["volumen"].sum(),
    "c_10h": b[b["k"] == 5].set_index("dia")["cierre"],
    "c_12h": b[b["k"] == 29].set_index("dia")["cierre"],
    "c_1230": b[b["k"] == 35].set_index("dia")["cierre"],
    "c_1130": b[b["k"] == 23].set_index("dia")["cierre"],
})
d["hueco"] = np.log(d["apertura"] / d["cierre"].shift())
d["r_30m"] = np.log(d["c_10h"] / d["apertura"])
d = d.dropna()
print(len(d), "sesiones con hueco")
d[["hueco", "r_30m"]].describe().loc[["mean", "std"]].mul(1e4).round(1)
OUT [01]Hueco de apertura y primera media hora
1002 sesiones con hueco
      hueco  r_30m
mean    3.8   -0.3
std    60.8   47.9

Las cifras están en puntos básicos (pb, centésimas de punto porcentual). El hueco medio es casi cero, pero su desviación típica es de unos 60 pb: KAPA abre cada día lejos del cierre anterior. Las medias sesiones (barras 0 a 41) no tienen barra de las 12:30, por eso caen del control más abajo.

IN [02]python
import statsmodels.api as sm

def pendiente(y, x):
    m = sm.OLS(y, sm.add_constant(x)).fit(cov_type="HC1")
    return round(float(m.params.iloc[1]), 3), round(
        float(m.tvalues.iloc[1]), 2)

print("primera media hora sobre el hueco:",
      pendiente(d["r_30m"], d["hueco"]))
ctrl = d.dropna(subset=["c_1230"])
r_antes = np.log(ctrl["c_12h"] / ctrl["c_1130"])
r_despues = np.log(ctrl["c_1230"] / ctrl["c_12h"])
print("12:00-12:30 sobre 11:30-12:00 (control):",
      pendiente(r_despues, r_antes))

# estrategia: contra el hueco desde la apertura hasta las 10:00
contra = -np.sign(d["hueco"]) * d["r_30m"]
sr_bruto = contra.mean() / contra.std() * np.sqrt(252)
print(f"por operación: {1e4 * contra.mean():.2f} pb "
      f"(desv. típica {1e4 * contra.std():.1f} pb)")
print(f"Sharpe bruto anual: {sr_bruto:.2f}")
OUT [02]¿Se cierra el hueco? Efecto y control
primera media hora sobre el hueco: (-0.132, -3.52)
12:00-12:30 sobre 11:30-12:00 (control): (-0.01, -0.31)
por operación: 4.69 pb (desv. típica 47.6 pb)
Sharpe bruto anual: 1.56

La pendiente de la regresión del rendimiento de la primera media hora sobre el hueco es de −0,132 con t de −3,52 (con errores robustos a la heterocedasticidad, lección 2.1.1): un error típico de 0,04, así que el −0,10 plantado está a menos de uno. La del control, con los mismos datos y otra hora del día, es de −0,01 con t de −0,31: no se distingue de cero. La estrategia más simple que explota el efecto (vender en la apertura si el hueco es al alza, comprar si es a la baja, cerrar a las 10:00) gana unos 4,7 pb por operación de media con una desviación típica diez veces mayor, y un Sharpe bruto anual de 1,56: la cifra de una estrategia excelente. Falta pagar.

Fig. — KAPA, 2022–2025: rendimiento medio de la primera media hora por deciles del hueco de apertura (izquierda) y, como control, el de 12:00 a 12:30 por deciles del de 11:30 a 12:00 (derecha), con su pendiente de regresión

Lo que cuesta operar cada día

Los costes de una operación son la mitad de la horquilla (se compra en el ask y se vende en el bid) más la comisión. KAPA cotiza en torno a 370 USD; con una horquilla de un céntimo (un supuesto: los datos no traen cotizaciones) y la tarifa escalonada del broker ficticio Alcor de costes2_brokers.csv:

IN [03]python
brokers = pd.read_csv("costes2_brokers.csv")
comision = brokers.query("plan == 'escalonado'")["tarifa"].iloc[1]
precio = d["apertura"].median()
coste_lado = (0.01 / 2 + comision) / precio      # en tanto por uno

def neto(r_op, coste, lados=2):
    x = r_op - lados * coste
    return x.mean() / x.std() * np.sqrt(252)

print(f"comisión {comision} USD/acción; precio mediano {precio:.0f}")
print(f"coste por lado: {1e4 * coste_lado:.2f} pb")
for mult in (1, 5, 10):
    print(f"coste x{mult:<2}: Sharpe neto "
          f"{neto(contra, mult * coste_lado):.2f}")
print(f"coste por lado que anula la ventaja: "
      f"{1e4 * contra.mean() / 2:.2f} pb")
OUT [03]Coste por lado y Sharpe neto de la estrategia de huecos
comisión 0.002 USD/acción; precio mediano 370
coste por lado: 0.19 pb
coste x1 : Sharpe neto 1.44
coste x5 : Sharpe neto 0.93
coste x10: Sharpe neto 0.30
coste por lado que anula la ventaja: 2.34 pb

Con los costes del supuesto, apenas 0,2 pb por lado, el Sharpe neto sigue alto. Pero el margen es estrecho: con 2,3 pb por lado la ventaja desaparece. Una horquilla de dos o tres céntimos, un deslizamiento de un tick al abrir (cuando la horquilla de la apertura es más ancha que la del resto de la sesión, lección 1.1.3) o una comisión minorista bastan para llegar ahí. La estrategia vive en el último decimal de los costes.

La misma idea a distintas velocidades

Para ver el efecto del horizonte sobre una sola familia de estrategias, se toma la tendencia de la lección 2.2.2 en su versión más simple: signo de la rentabilidad de L sesiones, con el mismo riesgo por mercado, en los 24 futuros. Al bajar L, la estrategia cambia de opinión más a menudo. El coste de cada mercado es el de su fichero de especificaciones (deslizamiento típico más comisión por contrato) dividido por el nocional del contrato:

IN [04]python
fut = pd.read_parquet("futuros2_continuos.parquet")
spec = pd.read_csv("futuros2_especificaciones.csv",
                   index_col="mercado")
aj = fut.pivot(index="fecha", columns="mercado",
               values="cierre_ajustado")
ci = fut.pivot(index="fecha", columns="mercado", values="cierre")
spec = spec.loc[aj.columns]
r = aj.pct_change()
c_contrato = (spec["coste_tipico_ticks"] * spec["valor_tick"]
              + spec["comision_usd"])
c_frac = (c_contrato / (ci * spec["multiplicador"])).shift(1)
vol = r.ewm(span=60, min_periods=60).std() * np.sqrt(252)

def tendencia(L, mult_coste=1.0):
    s = np.sign(np.log(aj / aj.shift(L)))
    w = (s * 0.10 / np.sqrt(aj.shape[1]) / vol).shift(1)
    bruto = (w * r).sum(axis=1).loc["2001":]
    dw = w.diff().abs()
    coste = (dw * c_frac).sum(axis=1).loc["2001":] * mult_coste
    return bruto, coste, dw.sum(axis=1).loc["2001":]

def fila(bruto, coste, giro):
    sr = lambda x: x.mean() / x.std() * np.sqrt(252)
    v = bruto.std() * np.sqrt(252)
    return {"rotacion": 252 * giro.mean(), "vol": v,
            "sr_bruto": sr(bruto), "coste_anual": 252 * coste.mean(),
            "sr_neto": sr(bruto - coste),
            "aprox": sr(bruto) - 252 * coste.mean() / v}

ventanas = [2, 5, 10, 21, 63, 126, 252]
tabla = pd.DataFrame({L: fila(*tendencia(L)) for L in ventanas}).T
tabla.index.name = "L"
tabla.round(3)
OUT [04]Tendencia con distintas ventanas: rotación y costes
     rotacion    vol  sr_bruto  coste_anual  sr_neto  aprox
L
2     751.828  0.149     0.362        0.195   -0.952 -0.952
5     475.550  0.150     0.139        0.122   -0.673 -0.673
10    337.859  0.149     0.119        0.087   -0.466 -0.466
21    224.479  0.149     0.402        0.059    0.010  0.010
63    138.463  0.149     0.640        0.035    0.402  0.402
126   100.789  0.146     0.705        0.025    0.535  0.535
252    66.946  0.147     0.999        0.017    0.884  0.884

La rotación es la suma anual de los cambios de peso, en veces el capital. Baja de forma continua con la ventana: de 752 veces al año con L = 2 a 67 con L = 252 (incluye el reajuste diario a la volatilidad, que también negocia). La columna aprox es la regla rápida:

SRneto≈SRbruto−T⋅cσSR_{neto} \approx SR_{bruto} - \frac{T \cdot c}{\sigma}

con TT la rotación anual, cc el coste por unidad negociada y σ\sigma la volatilidad anual de la estrategia: el coste anual en rentabilidad, dividido por la volatilidad, se resta del Sharpe. Coincide con el Sharpe neto calculado en centésimas. Es la cuenta que conviene hacer antes del backtest: con la rotación esperada y un coste realista se sabe qué Sharpe bruto hace falta para que quede algo.

Las ventanas cortas pierden por los dos lados. Su señal bruta es peor: el Sharpe bruto sube de 0,40 con L = 21 a 1,00 con L = 252, porque el término plantado usa el signo de 252 sesiones y una ventana más corta lo recoge peor; por debajo de 21 sesiones queda entre 0,12 y 0,36, una señal débil y ruidosa. Y cuestan más: con L = 2 el coste anual es del 19,5 % del capital y el Sharpe neto, de −0,95. La figura de la cabecera pone las siete ventanas y la estrategia de huecos en un plano de Sharpe frente a rotación: la línea neta se separa de la bruta cuanto más rota la estrategia, y la de huecos, con 504 vueltas al año, solo sobrevive porque sus costes por operación son diminutos.

Sensibilidad a los costes

Los costes de referencia son una estimación. La pregunta útil es cuánto peor pueden ser antes de que la estrategia deje de valer, y cuántos años hacen falta para distinguir su ventaja del azar:

IN [05]python
sens = {}
for L in ventanas:
    bruto, coste, _ = tendencia(L)
    sr = lambda x: x.mean() / x.std() * np.sqrt(252)
    sens[f"L={L}"] = {f"x{m}": sr(bruto - m * coste)
                      for m in (0, 1, 2, 3)}
sens["huecos KAPA"] = {f"x{m}": neto(contra, m * coste_lado)
                       for m in (0, 1, 2, 3)}
sens = pd.DataFrame(sens).T
sens["anos_t2"] = (2 / sens["x0"].clip(lower=0.05)) ** 2
sens.round(2)
OUT [05]Sharpe neto con costes multiplicados y años para una t de 2
               x0    x1    x2    x3  anos_t2
L=2          0.36 -0.95 -2.26 -3.56    30.60
L=5          0.14 -0.67 -1.48 -2.29   206.57
L=10         0.12 -0.47 -1.05 -1.63   284.39
L=21         0.40  0.01 -0.38 -0.77    24.71
L=63         0.64  0.40  0.16 -0.07     9.77
L=126        0.71  0.53  0.36  0.19     8.04
L=252        1.00  0.88  0.77  0.65     4.01
huecos KAPA  1.56  1.44  1.31  1.18     1.64
Fig. — Sharpe neto con los costes de referencia multiplicados por 0, 1, 2 y 3: tendencia en futuros con ventanas de 21, 63 y 252 sesiones y estrategia de huecos de KAPA

Dos lecturas de la tabla:

  • Las ventanas largas aguantan los errores de coste; las cortas, no. La de 252 sesiones sigue con Sharpe de 0,65 con el triple de coste. La de 21 ya está en 0,01 con los costes de referencia y pasa a negativa con el doble. Una estrategia cuyo resultado cambia de signo cuando el coste se dobla depende sobre todo de un número, el coste, que el backtest no puede medir bien.
  • Las ventanas cortas se comprueban antes. La columna anos_t2 es (2/SR)2(2 / SR)^2: los años de datos que hacen falta para que una ventaja con ese Sharpe bruto dé una t de 2. Con el Sharpe de la estrategia de huecos bastan 1,6 años; con la tendencia de 252 sesiones, 4; con las de 5 y 10, que casi no tienen señal bruta, más de 200. Por eso las mesas de alta frecuencia pueden validar y descartar ideas en semanas, y los gestores de tendencia necesitan décadas de historia (y sufren más el riesgo de sobreajuste, lección 2.5.4).

Capacidad: el impacto de mercado

Los costes de la sección anterior son proporcionales: el mismo porcentaje para 1.000 USD que para 100 millones. El impacto de mercado no: una orden grande mueve el precio contra quien la ejecuta mientras la ejecuta, y cuanto mayor, más lo mueve. La regularidad empírica más estable es la ley de la raíz cuadrada1Nota 1 · Tóth y otros (2011) proponen una explicación de la ley de la raíz cuadrada del impacto a partir de la liquidez latente del libro y aportan evidencia empírica; Bouchaud, Bonart, Donier y Gould (2018) tratan el impacto y la ejecución óptima con datos de Nasdaq. Ambos, en los recursos.: el impacto medio de una orden (en tanto por uno del precio) es

I(Q)=Y⋅σD⋅QVDI(Q) = Y \cdot \sigma_D \cdot \sqrt{\frac{Q}{V_D}}

con QQ el tamaño de la orden, VDV_D el volumen diario del activo (en las mismas unidades), σD\sigma_D su volatilidad diaria e YY un factor empírico. En esta lección se toma Y=1Y = 1 como supuesto: el valor real depende del activo, del periodo y de cómo se ejecute la orden, y hay que estimarlo con las ejecuciones propias (lección 2.4.7). El coste total de la orden es Q⋅I(Q)Q \cdot I(Q), que crece como Q1,5Q^{1,5}.

Con impacto, la rentabilidad neta deja de ser constante y cae con el capital AA. Para una estrategia con ventaja bruta anual α\alpha que negocia cada año TT veces su capital, repartido en órdenes diarias de tamaño Q=A T/252Q = A\,T/252:

αneto(A)=α−T c−T⋅YσDA T252 VD\alpha_{neto}(A) = \alpha - T\,c - T \cdot Y \sigma_D \sqrt{\frac{A\,T}{252\,V_D}}

El capital que anula la ventaja, la capacidad en sentido estricto, sale de igualar a cero:

A0=252 VDT(α−T cT Y σD)2A_0 = \frac{252\,V_D}{T} \left(\frac{\alpha - T\,c}{T\,Y\,\sigma_D}\right)^2

La rotación aparece al cubo en el denominador: doblar la rotación con la misma ventaja anual divide la capacidad entre ocho. Y el beneficio en dólares, A⋅αneto(A)A \cdot \alpha_{neto}(A), es máximo en A∗=49A0A^* = \frac{4}{9} A_0: más allá de ese capital, cada dólar añadido reduce el beneficio total.

Para la estrategia de huecos, las cantidades salen de los datos de KAPA. Cada día hay dos órdenes del tamaño del capital (entrada y salida), así que T=504T = 504 y Q=AQ = A:

IN [06]python
V_usd = (d["volumen"] * d["cierre"]).median()
sigma_d = np.log(d["cierre"] / d["cierre"].shift()).std()
Y = 1.0                                     # supuesto
alfa_op = contra.mean()                     # por operación

def neto_op(A):
    """Rentabilidad neta por operación (ida y vuelta)."""
    impacto = Y * sigma_d * np.sqrt(A / V_usd)
    return alfa_op - 2 * coste_lado - 2 * impacto

A0 = V_usd * ((alfa_op - 2 * coste_lado) / (2 * Y * sigma_d)) ** 2
print(f"volumen diario mediano: {V_usd / 1e9:.1f} mil M USD")
print(f"sigma diaria: {sigma_d:.4f}")
print(f"capacidad A0: {A0 / 1e6:.1f} M USD; óptimo: "
      f"{4 / 9 * A0 / 1e6:.1f} M USD")
part = A0 / (d["vol_30m"] * d["cierre"]).median()
print(f"participación en la primera media hora con A0: {part:.2%}")
for A in (1e6, 3e6, 1e7):
    print(f"A = {A / 1e6:>4.0f} M: {1e4 * neto_op(A):+.2f} pb "
          f"por operación, {252 * A * neto_op(A) / 1e3:,.0f} mil "
          "USD al año")
OUT [06]Capacidad de la estrategia de huecos de KAPA
volumen diario mediano: 20.8 mil M USD
sigma diaria: 0.0109
capacidad A0: 8.2 M USD; óptimo: 3.6 M USD
participación en la primera media hora con A0: 0.27%
A =    1 M: +2.80 pb por operación, 71 mil USD al año
A =    3 M: +1.70 pb por operación, 129 mil USD al año
A =   10 M: -0.45 pb por operación, -114 mil USD al año

Es la lección de la sección: una estrategia con Sharpe bruto de 1,56 sobre el ETF más líquido de los datos, con 20.800 millones de USD negociados en una sesión mediana, deja de ganar dinero con 8,2 millones de capital. No por falta de volumen (con A0A_0 se negocia el 0,27 % del volumen de la primera media hora), sino porque su ventaja por operación es de 4,7 pb y el impacto de la raíz cuadrada se la come antes. El beneficio en dólares es máximo con 3,6 millones: 129.000 USD al año con 3 millones, y pérdidas con 10.

Curvas de capacidad por horizonte

Para comparar horizontes con la misma vara, la última celda aplica la fórmula del impacto a las estrategias de tendencia de 21 y de 252 sesiones, mercado a mercado: con la rotación medida de cada uno, su volatilidad diaria y su volumen diario mediano en dólares, que sale de la columna volumen del fichero de futuros (contratos negociados en el contrato que se tiene) por el precio y el multiplicador:

IN [07]python
# volumen diario en USD y volatilidad diaria de cada futuro
fut["usd"] = (fut["volumen"] * fut["cierre"]
              * fut["mercado"].map(spec["multiplicador"]))
V_m = fut.groupby("mercado")["usd"].median()
sigma_m = r.std()

def curva_tendencia(L, capitales):
    bruto, coste, _ = tendencia(L)
    s = np.sign(np.log(aj / aj.shift(L)))
    w = (s * 0.10 / np.sqrt(aj.shape[1]) / vol).shift(1)
    T_m = 252 * w.diff().abs().loc["2001":].mean()   # por mercado
    alfa = 252 * (bruto - coste).mean()
    out = []
    for A in capitales:
        q = A * T_m / 252                   # orden diaria por mercado
        imp = (T_m * Y * sigma_m * np.sqrt(q / V_m)).sum()
        out.append(alfa - imp)
    return np.array(out)

print("volumen diario mediano (mil M USD):",
      (V_m / 1e9).round(1).agg(["min", "median", "max"]).to_dict())

capitales = np.logspace(6, 11, 11)          # de 1 M a 100.000 M USD
curvas = pd.DataFrame({
    "huecos": [252 * neto_op(A) for A in capitales],
    "L=21": curva_tendencia(21, capitales),
    "L=252": curva_tendencia(252, capitales),
}, index=[f"{A:.0e}" for A in capitales])
(100 * curvas).round(2)
OUT [07]Rentabilidad neta frente al capital
volumen diario mediano (mil M USD): {'min': 0.3, 'median': 6.3, 'max': 72.8}
        huecos    L=21  L=252
1e+06     7.06   -0.25  12.95
3e+06     4.11   -0.56  12.90
1e+07    -1.14   -1.11  12.82
3e+07   -10.48   -2.08  12.68
1e+08   -27.08   -3.81  12.42
3e+08   -56.61   -6.88  11.97
1e+09  -109.13  -12.34  11.17
3e+09  -202.51  -22.06   9.75
1e+10  -368.57  -39.34   7.21
3e+10  -663.87  -70.08   2.71
1e+11 -1188.99 -124.72  -5.30
Fig. — Curvas de capacidad con la ley de la raíz cuadrada (Y = 1, supuesto): rentabilidad neta anual frente al capital para la estrategia de huecos de KAPA y la tendencia de 21 y 252 sesiones, con el volumen y la volatilidad de cada futuro

El volumen de los futuros va de 0,3 a 72,8 mil millones de USD al día según el mercado (mediana de 6,3): los más pequeños son los primeros en notar el impacto. Las tres curvas caen con el capital y cruzan el cero en puntos muy distintos. La de huecos rinde un 7,1 % anual con un millón (con una volatilidad del 7,6 %) y pasa a negativa antes de los 10 millones. La de tendencia de 21 sesiones es negativa desde el primer millón (−0,25 %): sus costes proporcionales ya se comen toda la ventaja (Sharpe neto de 0,01) y el impacto solo resta. La de 252 sesiones, con la menor rotación, rinde un 12,95 % con un millón (con un 15 % de volatilidad), todavía un 7,21 % con 10.000 millones y un 2,71 % con 30.000, y cruza el cero antes de los 100.000 millones con estos supuestos. Es la razón de que los fondos más grandes del mundo sean posicionales y de que las estrategias de alta frecuencia, con Sharpe muy altos, no admitan apenas capital externo.

Ejercicios

  1. La estrategia de huecos entra en la apertura. Si entrara al cierre de la primera barra (09:35), ¿cuánto de la ventaja quedaría? Mídelo con los datos.
Solución
IN [08]python
c_0935 = b[b["k"] == 0].set_index("dia")["cierre"].reindex(d.index)
tarde = -np.sign(d["hueco"]) * np.log(d["c_10h"] / c_0935)
print(f"desde la apertura: {1e4 * contra.mean():.2f} pb; "
      f"desde las 09:35: {1e4 * tarde.mean():.2f} pb")
print(f"Sharpe neto desde las 09:35: {neto(tarde, coste_lado):.2f}")
OUT [08]Entrada a las 09:35
desde la apertura: 4.69 pb; desde las 09:35: 3.99 pb
Sharpe neto desde las 09:35: 1.44

Entrar cinco minutos tarde deja 3,99 pb de los 4,69: la primera barra ya recoge una parte de la vuelta del hueco. Con estos costes el Sharpe neto apenas cambia (1,44 en los dos casos, porque el rendimiento de la ventana más corta también es menos volátil), pero el margen sobre el coste que anula la ventaja se estrecha. Entrar en la apertura exige una orden en la subasta de apertura (lección 1.1.2), con su propia horquilla y su propio riesgo de ejecución.

  1. Repite el cálculo de la capacidad de la estrategia de huecos con Y=0,5Y = 0,5 y con Y=2Y = 2. ¿Cómo escala A0A_0 con YY? Comprueba la respuesta con la fórmula.
Solución
IN [09]python
for y in (0.5, 1.0, 2.0):
    a0 = V_usd * ((alfa_op - 2 * coste_lado)
                  / (2 * y * sigma_d)) ** 2
    print(f"Y = {y}: A0 = {a0 / 1e6:6.1f} M USD")
OUT [09]Capacidad según Y
Y = 0.5: A0 =   32.8 M USD
Y = 1.0: A0 =    8.2 M USD
Y = 2.0: A0 =    2.0 M USD

La capacidad escala como 1/Y21/Y^2: con la mitad de impacto, cuatro veces más capital; con el doble, la cuarta parte. Como YY es incierto, la capacidad estimada lo es mucho más: un error de un factor 2 en YY es un error de un factor 4 en el capital admisible. Por eso los gestores estiman su propio impacto con sus ejecuciones reales antes de crecer.

  1. Una estrategia swing gana un 6 % anual bruto con una rotación de 40 veces el capital. Otra, posicional, gana un 4 % con una rotación de 4. Con la fórmula de A0A_0 y el mismo activo, ¿cuántas veces más capacidad tiene la segunda? (Ignora los costes proporcionales.)
Solución

Con c=0c = 0, A0∝1T(αT)2=α2T3A_0 \propto \frac{1}{T}\left(\frac{\alpha}{T}\right)^2 = \frac{\alpha^2}{T^3}. Para la swing, 0,062/403=5,6×10−80,06^2 / 40^3 = 5,6 \times 10^{-8}; para la posicional, 0,042/43=2,5×10−50,04^2 / 4^3 = 2,5 \times 10^{-5}. El cociente es de unas 444 veces: con una ventaja anual menor, la estrategia posicional admite más de cuatrocientas veces más capital, porque su rotación es diez veces menor.

Footnotes

  1. Tóth y otros (2011) proponen una explicación de la ley de la raíz cuadrada del impacto a partir de la liquidez latente del libro y aportan evidencia empírica; Bouchaud, Bonart, Donier y Gould (2018) tratan el impacto y la ejecución óptima con datos de Nasdaq. Ambos, en los recursos. ↩

Respuesta plegada en cada pregunta

Autoevaluación

  1. Una estrategia tiene un Sharpe bruto de 0,8, una volatilidad del 10 % y rota 50 veces su capital al año con un coste de 2 pb por unidad negociada. ¿Qué Sharpe neto aproximado le queda?

    • A0,8
    • B0,7: el coste anual es 50 × 0,0002 = 1 %, y dividido por la volatilidad resta 0,1 al Sharpe
    • C0,4
    • DNegativo
    Ver respuesta

    Respuesta B. 0,7: el coste anual es 50 × 0,0002 = 1 %, y dividido por la volatilidad resta 0,1 al Sharpe

    El coste anual en rentabilidad es rotación × coste por unidad: 50 × 2 pb = 100 pb. Dividido por la volatilidad del 10 %, son 0,1 puntos de Sharpe. La aproximación se cumple en la tabla de la lección con un error de centésimas.

  2. ¿Por qué una estrategia intradía puede comprobarse con menos años de datos que una posicional?

    • APorque los datos intradía son más fiables
    • BPorque hace muchas más apuestas independientes: con el mismo Sharpe anual por operación pequeña, su Sharpe anual es mayor y la t crece antes con la raíz de los años
    • CPorque no tiene costes
    • DPorque no le afecta el sesgo de supervivencia
    Ver respuesta

    Respuesta B. Porque hace muchas más apuestas independientes: con el mismo Sharpe anual por operación pequeña, su Sharpe anual es mayor y la t crece antes con la raíz de los años

    Los años necesarios para una t de 2 son (2 / Sharpe anual)². Con el Sharpe bruto de 1,56 de la estrategia de huecos bastan 1,6 años; con el 1,00 de la tendencia de 252 sesiones, unos 4. La contrapartida es que los costes y la capacidad castigan mucho más al horizonte corto.

  3. Según la ley de la raíz cuadrada, si una orden se multiplica por 4, ¿cómo cambia su impacto medio por acción?

    • ASe multiplica por 4
    • BSe multiplica por 2
    • CNo cambia
    • DSe divide por 2
    Ver respuesta

    Respuesta B. Se multiplica por 2

    El impacto relativo es Y·σ·raíz(Q/V): proporcional a la raíz del tamaño. El coste total en dólares, impacto por tamaño, crece como Q^1,5: cuatro veces más tamaño, ocho veces más coste.

  4. ¿Por qué la capacidad de una estrategia cae tan deprisa con la rotación?

    • APorque los brokers cobran más a quien opera mucho
    • BPorque la rotación multiplica tres veces: reparte la ventaja anual entre más operaciones, hace que cada sesión negocie más y multiplica el número de veces que se paga el impacto
    • CNo cae: la capacidad no depende de la rotación
    • DPorque las estrategias de alta rotación tienen menos Sharpe
    Ver respuesta

    Respuesta B. Porque la rotación multiplica tres veces: reparte la ventaja anual entre más operaciones, hace que cada sesión negocie más y multiplica el número de veces que se paga el impacto

    Con la ley de la raíz cuadrada, el capital en que la ventaja se anula es proporcional al volumen dividido por T y por el cuadrado de T·Y·σ entre la ventaja anual; es decir, a 1/T³ con todo lo demás fijo. Doblar la rotación divide la capacidad entre ocho.

  5. La estrategia de huecos de KAPA tiene un Sharpe bruto de 1,56 en el ETF más líquido de los datos. ¿Qué limita su capacidad?

    • AEl volumen de la primera media hora: no se puede ejecutar
    • BEl impacto: con una ventaja de unos 4,7 pb por operación y dos operaciones al día, el impacto de la raíz cuadrada se la come con unos pocos millones de USD
    • CLos costes de comisión
    • DNada: admite cualquier capital
    Ver respuesta

    Respuesta B. El impacto: con una ventaja de unos 4,7 pb por operación y dos operaciones al día, el impacto de la raíz cuadrada se la come con unos pocos millones de USD

    Con el supuesto Y = 1, el impacto de una orden de 5 millones de USD sobre un volumen diario de unos 20.800 millones es σ·raíz(Q/V) ≈ 1,1 % × 0,016 ≈ 1,7 pb por lado. Dos lados ya son 3,4 pb de los 4,7 disponibles. Poder ejecutar la orden (participación del 10 % de la media hora) no es lo que limita.

Ideas clave

  • El horizonte fija la rotación, y la rotación fija lo que cuestan los costes: SR neto ≈ SR bruto − rotación × coste / volatilidad. Con los costes de referencia de los futuros, la tendencia de 2 sesiones pasa de un Sharpe bruto de 0,36 a −0,95; la de 252, de 1,00 a 0,88.
  • El horizonte corto tiene una ventaja estadística: más apuestas y menos años para distinguir la ventaja del azar ((2 / Sharpe)² años para una t de 2).
  • El impacto de mercado crece con la raíz del tamaño de la orden (Y·σ·raíz(Q/V)); el coste total, con su potencia 1,5. Y es un parámetro empírico: aquí se supone 1 y se dice.
  • La capacidad cae como 1/T³ con la rotación. La estrategia de huecos de KAPA, con Sharpe bruto de 1,56, deja de ganar con unos 8 millones de USD; la tendencia de 252 sesiones admite órdenes de magnitud más.
  • Las cifras salen de datos sintéticos con efectos plantados y de supuestos de coste e impacto declarados; sirven para ordenar los horizontes, no como estimación de ninguna estrategia real.

Recursos