Saltar a contenido

Q09 · Carry de futuros por estructura temporal

Campo Valor
Familia Carry: posicionarse según la pendiente de la curva de futuros (backwardation / contango)
Universo / panel Familias de futuros con varios vencimientos por familia
Datos Curvas por vencimiento, precios de contratos negociables, calendarios y multiplicadores, margen y moneda
Features curve_alignment, annualized_carry, roll_schedule, volatility
Parámetros y defaults rebalance_bars = 5, roll_sessions = 5, vol_window = 63
Rejilla rebalance_bars ∈ {1, 5, 21}, roll_sessions ∈ {5, 10, 20}, vol_window ∈ {20, 63} → 18 candidatos
Coste esperado O(B·S) tras los joins de curvas
Estado Especificada, no implementada (specified_not_implemented, hito H4); la matriz de capacidades da MISSING_DATA: no individual futures contracts
Motores implicados Ninguno cubre curvas de futuros ni rolls. Reutilizaría el sizing por volatilidad de Q01 y el ledger, que necesitaría una extensión de contabilidad de futuros (margen, multiplicador, roll)

Estado

Q09 no está implementada y, además, no hay datos para ella: la plataforma no tiene contratos de futuros individuales por vencimiento (solo series de acciones, índices y divisas). La matriz de capacidades de la API de backtesting la declara UNSUPPORTED con MISSING_DATA: no individual futures contracts. El ejemplo numérico es didáctico, calculado a mano con precios inventados.

Fuentes: catalog/strategy_catalog.json, finazbench/api/routers/capabilities.py, docs/API.md.

Qué vas a aprender

  • Qué es la estructura temporal de un futuro y qué significan backwardation y contango.
  • Cómo se calcula un carry anualizado con dos vencimientos consecutivos.
  • Qué es un roll y por qué hay que pagar costes en las dos patas.
  • Por qué no se puede construir la curva a partir de un único "futuro continuo".
  • Qué le falta a la plataforma (datos y contabilidad) para ejecutarla.

La idea en una frase

Comprar los futuros cuya curva baja con el vencimiento (el precio lejano es menor que el cercano) y vender aquellos cuya curva sube, dimensionando por volatilidad y renovando la posición antes de cada vencimiento.

Intuición y evidencia académica

Intuición. Si el futuro a tres meses cotiza por debajo del de un mes, y la curva no cambia, el precio del contrato largo sube hacia el spot a medida que se acerca su vencimiento: quien está largo cobra esa convergencia. Es el "rendimiento de mantener" (carry). La literatura de carry en múltiples clases de activos (materias primas, divisas, bonos, índices) documenta una prima asociada a esta pendiente, con riesgo de caídas bruscas en crisis.

El catálogo es tajante: el carry no es un beneficio garantizado. La curva se mueve y la convergencia puede no ocurrir.

Cuándo falla.

  • Cambios de curva: un contango que se profundiza borra el carry esperado.
  • Crisis de liquidez: las posiciones de carry tienden a deshacerse a la vez.
  • Rolls caros: en contratos poco líquidos, el coste de cambiar de vencimiento se come el carry.

El modelo matemático

Por familia, con dos vencimientos consecutivos elegibles (near y far), tiempos a vencimiento en años:

carry = ( ln F_near − ln F_far ) / ( tau_far − tau_near )
objetivo direccional = sign(carry)
peso = sign(carry) · min(cap_individual, vol_target / (S · max(vol, vol_floor)))   (sizing de Q01)
se negocia el contrato near
  • carry > 0 (backwardation: el lejano es más barato) → largo.
  • carry < 0 (contango) → corto.
  • F <= 0 hace la fórmula no evaluable: resultado no_evaluable_para_esta_formula, sin borrar la fila en silencio.

Roll: si el contrato near entra en la ventana de roll_sessions sesiones antes del vencimiento, se cierra y se abre el siguiente según el calendario fijado, pagando costes en las dos patas.

Pseudocódigo causal

para cada sesión t (tras el watermark de todas las curvas):
    para cada familia:
        elegir near y far elegibles con precios PUBLICADOS en t (timestamps de settlement)
        carry, vol del contrato negociado
        si near está a <= roll_sessions del vencimiento: programar roll near → siguiente
    si t es rebalanceo: pesos por sizing de Q01; cantidades con multiplicador
    ejecutar deltas y rolls en el siguiente evento negociable; margen y moneda en el ledger

Cómo lo haría la plataforma

Pieza Quién la cubriría Estado
Curvas por vencimiento Nueva fuente de datos con contratos individuales No existe (MISSING_DATA)
Calendario de vencimientos y rolls finaz_calendars + tabla de vencimientos por familia Calendarios de sesión existen; vencimientos, no
Instrumentos con multiplicador finaz_instruments / catalog/instruments.json (tick, lote, multiplicador, moneda) Existe para contado; no para futuros por vencimiento
Sizing por volatilidad Fórmula de Q01 (finazbench/policy/q01_tsmom.py) Existe
Volatilidad de cartera finaz_risk.volatilidad_cartera, anualizar_volatilidad Existe
Lotes enteros de contratos finaz_discrete.proyectar_cpsat (tamaños de lote arbitrarios) Existe
Contabilidad de futuros (margen, variación diaria, roll) Extensión del ledger en ambos adaptadores No existe
flowchart LR
    D["Curvas por vencimiento<br/>(sin datos: MISSING_DATA)"] --> A["curve_alignment<br/>near / far elegibles"]
    A --> C["annualized_carry<br/>(ln F_near − ln F_far)/(tau_far − tau_near)"]
    C --> S["sign(carry)"]
    V["Volatilidad del contrato<br/>vol_window 63"] --> W
    S --> W["Sizing de Q01<br/>vol_target · caps"]
    W --> X["finaz_discrete<br/>contratos enteros"]
    R["roll_schedule<br/>5 sesiones"] --> L
    X --> L["Ledger con contabilidad de futuros<br/>(no implementada)"]

Ejemplo numérico

Ejemplo didáctico

Precios, vencimientos y volatilidad inventados. No hay datos de futuros en la plataforma.

Una familia, dos vencimientos.

F_near = 80.00   tau_near = 0.10 años  (≈ 5 semanas)
F_far  = 78.00   tau_far  = 0.35 años  (≈ 4 meses)

ln F_near = 4.382027     ln F_far = 4.356709
carry = (4.382027 − 4.356709) / (0.35 − 0.10) = 0.025318 / 0.25 = 0.1013   (10,1 % anual)
carry > 0  → backwardation → objetivo +1 (largo el near)

Sizing con los parámetros de Q01 (vol_target = 0.10, cap_individual = 0.25, vol_floor = 0.02), una sola familia (S = 1) y volatilidad anual del contrato del 30 %:

peso = +1 · min(0.25, 0.10 / (1 · max(0.30, 0.02))) = min(0.25, 0.333) = 0.25

Cantidad con capital 1.000.000 y multiplicador 1.000 (nocional por contrato 80 · 1000 = 80.000):

contratos = trunc(0.25 · 1.000.000 / 80.000) = trunc(3.125) = 3

Roll. Cinco sesiones antes del vencimiento del near se venden los 3 contratos near y se compran 3 del siguiente vencimiento. Si cada pata paga 2 ticks de 0,01 con multiplicador 1.000, el roll cuesta 3 · 2 · 2 · 0,01 · 1.000 = 120 por cada roll, que hay que restar del carry cobrado.

Contango (contraejemplo): con F_near = 78 y F_far = 80, el carry sería −0,1013 y el objetivo −1 (corto el near).

Cómo lanzarla

Hoy no se puede lanzar. Lo que devuelve la API de backtesting:

GET /v1/capabilities
  { "template_id": "Q09", "VTA_CPU_LEDGER": "UNSUPPORTED", "NT_FEATURES": "UNSUPPORTED",
    "NT_INTENT_REPLAY": "UNSUPPORTED", "NT_ONLINE": "UNSUPPORTED",
    "unsupported_reason": "MISSING_DATA: no individual futures contracts." }

POST /v1/backtest con una instancia de Q09  → 501 STRATEGY_UNSUPPORTED_BY_ADAPTER

Tampoco hay un job S7 que cubra su pieza central (la curva). Las piezas reutilizables (sizing, volatilidad, discretización) se pueden ejercitar con los jobs de las imágenes opt y con la propia Q01, como se explica en Q01 y Q06.

Qué haría falta para implementarla

  1. Datos: contratos individuales por vencimiento con timestamps de publicación y de settlement, multiplicador, tick, moneda y fecha de vencimiento. La receta prohíbe derivar la curva de un único futuro continuo.
  2. Calendario de rolls fijado y versionado por familia.
  3. Contabilidad de futuros en el ledger de cartera y en los carriles Nautilus: margen, liquidación diaria, conversión de moneda, roll con dos patas.
  4. Política con sizing de Q01 y paridad.
  5. Declarar que datos sintéticos validan el software, no la rentabilidad.

Riesgos, límites y qué no hace

  • Sin datos en la plataforma: es la receta más lejana a la ejecución junto con Q10.
  • No suma el carry como beneficio garantizado: el resultado depende de cómo se mueva la curva.
  • Timestamps: usar un precio de settlement antes de su publicación es una fuga.
  • Precio no positivo: ln no está definido; se marca no_evaluable_para_esta_formula.
  • Margen y apalancamiento: un futuro requiere solo una fracción del nocional como margen; el sizing es sobre el nocional, no sobre el margen.

Resumen

  • Carry anualizado (ln F_near − ln F_far) / (tau_far − tau_near); objetivo sign(carry) con sizing de Q01.
  • Se negocia el near y se rueda roll_sessions sesiones antes del vencimiento, con costes en ambas patas.
  • Especificada, no implementada y sin datos (MISSING_DATA).
  • La plataforma aporta el sizing, la volatilidad y la discretización; faltan curvas y contabilidad de futuros.

Para practicar

  1. Calcula el carry con F_near = 100, F_far = 97, tau_near = 0.05, tau_far = 0.30. ¿Largo o corto?
  2. ¿Por qué no se puede calcular el carry con una serie de "futuro continuo" ajustada por rolls?
  3. Si el carry anual es 10 % y cada roll cuesta el 0,3 % del nocional, ¿cuántos rolls al año se pueden permitir antes de que el carry neto sea negativo?
  4. Explica la diferencia entre el peso sobre nocional y el margen exigido.