---
title: "3.2.5 Datos y backtesting de spreads por patas"
description: "Spreads cotizados frente a sintéticos, datos de CME con Databento, backtest pata a pata con roll y costes, legging risk y órdenes combinadas."
url: "https://pythonparatrading.com/curso-python-trading/nivel-3/datos-y-backtesting-de-spreads-por-patas/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-3/datos-y-backtesting-de-spreads-por-patas.png"
---

# 3.2.5 · Datos y backtesting de spreads: cotizados, sintéticos y por patas

> Un spread de futuros se puede operar como un instrumento propio, con su libro, o montar pata a pata. Las dos cosas dejan huellas distintas en los datos y en el resultado. Esta lección mide la diferencia entre el libro del spread y el que se construye con sus patas, escribe un backtest que lleva cada pata con sus contratos y sus costes, cuantifica el riesgo de ejecutar una pata sin la otra y enseña a pedir los datos reales a Databento con un simulador local de su API.

- Curso: [Nivel 3: Derivados: futuros, spreads, forex y opciones](https://pythonparatrading.com/curso-python-trading/nivel-3/)
- Módulo: 3.2 · Spreads de futuros
- Duración: 70 min
- Requisitos: [1.3.4 · Proveedores de datos profesionales](https://pythonparatrading.com/curso-python-trading/nivel-1/proveedores-de-datos-profesionales/), [1.4.1 · Descarga por REST y websockets: paginación, límites y reintentos](https://pythonparatrading.com/curso-python-trading/nivel-1/descarga-rest-websockets-y-reintentos/), [2.4.7 · Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos](https://pythonparatrading.com/curso-python-trading/nivel-2/modelado-de-costes/), [3.2.1 · Spreads de calendario, butterflies y condors](https://pythonparatrading.com/curso-python-trading/nivel-3/spreads-de-calendario-butterflies-y-condors/)

## Al terminar esta lección

- Distinguir el spread cotizado (un instrumento con su libro) del sintético (las patas por separado) y medir su diferencia de ancho y de coste.
- Escribir un backtest de un spread de calendario que lleve las patas con sus contratos, rolee cuando cambia el par y cobre los costes de cada roll.
- Cuantificar el legging risk con datos de un minuto: cuánto cuesta ejecutar una pata y esperar para la otra.
- Pedir definiciones, mejores precios y liquidaciones de spreads de CME a la API histórica de Databento, y probar el cliente contra un simulador local con autenticación y límites.
- Elegir entre una orden combinada garantizada, una no garantizada y la ejecución pata a pata, y saber qué hace la bolsa con las órdenes implícitas.

## Cotizado o sintético

Un spread de calendario (comprar el vencimiento cercano y vender el siguiente, lección 3.2.1) se puede ejecutar de
dos maneras. La primera, como **spread cotizado**: las bolsas de futuros listan los spreads más usados como
instrumentos propios, con su libro de órdenes, su precio (cercano menos lejano) y su volumen; una orden compra el
spread entero, las dos patas a la vez, a un solo precio. La segunda, como **spread sintético**: comprar una pata en su
libro y vender la otra en el suyo, dos operaciones.

```mermaid
flowchart TB
  O[Orden de spread] --> C{¿Cómo?}
  C -- cotizado --> L[Libro del spread]
  C -- sintético --> P1[Pata cercana]
  C -- sintético --> P2[Pata lejana]
  L --> E[Una ejecución]
  P1 --> D[Dos ejecuciones]
  P2 --> D
```

La diferencia se ve en los precios. Con el mejor bid y ask de cada pata, el spread sintético se compra pagando el
ask de la cercana y cobrando el bid de la lejana, y se vende al revés:

$$
\text{bid}_{\text{sint}} = \text{bid}_{\text{cerca}} - \text{ask}_{\text{lejos}}, \qquad
\text{ask}_{\text{sint}} = \text{ask}_{\text{cerca}} - \text{bid}_{\text{lejos}}
$$

Su ancho es la **suma** de los anchos de las dos patas. El libro del spread, en cambio, lo cotizan quienes quieren
el spread entero, y su ancho no tiene por qué ser la suma de nada.

`spreads3_cotizados.parquet` tiene, para cada sesión de 2010 a 2025, el cierre del libro del spread de calendario
(contrato que se tiene menos el siguiente) de seis mercados del nivel, y el mejor bid y ask de cada pata. Son datos
**sintéticos** con una respuesta conocida: el libro del spread cotiza a 1 tick el 75 % de los días y a 2 el resto; la
pata cercana, a 1 o 2 ticks, y la lejana, a 1, 2 o 3. En `RVA` (índice) y `TPC` (bono) el spread solo tiene filas en
las 10 sesiones anteriores al roll, que es cuando se negocia en esos mercados.

```python
import numpy as np
import pandas as pd

cot = pd.read_parquet("spreads3_cotizados.parquet")
esp = pd.read_csv("futuros3_especificaciones.csv",
                  index_col="mercado")
tick = cot["mercado"].map(esp["tick"])
cot["ancho_libro"] = (cot["ask"] - cot["bid"]) / tick
sint_bid = cot["bid_cerca"] - cot["ask_lejos"]
sint_ask = cot["ask_cerca"] - cot["bid_lejos"]
cot["ancho_sint"] = (sint_ask - sint_bid) / tick
ultimo = cot[cot["mercado"] == "PEC"].iloc[-1]
print(ultimo[["fecha", "spread", "liquidacion", "bid", "ask"]]
      .to_string())
print(f"Sintético: bid {sint_bid[ultimo.name]:.2f}, "
      f"ask {sint_ask[ultimo.name]:.2f}")
```
```output
fecha          2025-12-31 00:00:00
spread               PECG26-PECH26
liquidacion                   0.78
bid                           0.77
ask                           0.78
Sintético: bid 0.77, ask 0.80
```

El último día de los datos, el spread febrero-marzo del crudo ficticio cotiza 0,77-0,78 en su libro: un tick (0,01 USD
por barril, 10 USD por contrato de 1.000 barriles). El sintético del mismo cierre, montado con las patas, cotiza
0,77-0,80: tres ticks. La liquidación del spread (0,78) es la diferencia de las liquidaciones de las patas y, en todas
las filas del archivo, cae dentro del libro del spread.

```python
mas_ancho = cot["ancho_sint"] > cot["ancho_libro"]
anchos = pd.DataFrame({
    "filas": cot.groupby("mercado").size(),
    "libro": cot.groupby("mercado")["ancho_libro"].mean(),
    "sintetico": cot.groupby("mercado")["ancho_sint"].mean(),
    "sint_mas_ancho": mas_ancho.groupby(cot["mercado"]).mean(),
})
print(f"Media: libro {cot['ancho_libro'].mean():.2f} ticks, "
      f"sintético {cot['ancho_sint'].mean():.2f}")
anchos.round(2)
```
```output
Media: libro 1.25 ticks, sintético 2.78
         filas  libro  sintetico  sint_mas_ancho
mercado
GNA       4024   1.25       2.81            0.94
GRM       4024   1.25       2.79            0.89
MTO       4024   1.24       2.76            0.96
PEC       4024   1.25       2.77            0.93
RVA        650   1.22       2.83            0.91
TPC        650   1.25       2.78            0.90
```

En los seis mercados, el libro del spread tiene un ancho medio de 1,25 ticks y el sintético, de 2,78: más del doble,
como dice la respuesta conocida. El sintético es más ancho entre el 89 % y el 96 % de las sesiones y en el resto
empata; nunca es más estrecho. En un mercado real la relación depende de la liquidez de cada libro, pero el mecanismo
de las órdenes implícitas de la bolsa (más abajo) empuja en la misma dirección: los precios de las patas se trasladan
al libro del spread.

```python
mk = esp.loc[["RVA", "TPC", "PEC", "GNA", "MTO", "GRM"]]
media = cot.groupby("mercado")[["ancho_libro", "ancho_sint"]].mean()
coste = pd.DataFrame({
    "medio_libro": media["ancho_libro"] / 2 * mk["valor_tick"],
    "medio_sint": media["ancho_sint"] / 2 * mk["valor_tick"],
    "comision_2_patas": 2 * mk["comision"],
    "margen_spread": mk["margen_spread_calendario"],
    "margen_2_simples": 2 * mk["margen_inicial"],
})
coste.round(1)
```
```output
         medio_libro  medio_sint  comision_2_patas  margen_spread  margen_2_simples
mercado
GNA              6.2        14.0               4.5         3300.0           17000.0
GRM              7.8        17.4               4.5          150.0            1680.0
MTO              6.2        13.8               4.5          290.0           19200.0
PEC              6.3        13.9               4.5          670.0           11200.0
RVA              7.7        17.7               4.2          610.0           40400.0
TPC              9.8        21.7               3.0          140.0            5400.0
```

Medio ancho es lo que cuesta cruzar una vez: por el libro, de 6,2 a 9,8 USD por spread; pata a pata, de 13,8 a 21,7,
algo más del doble. A eso se suman las comisiones de las dos patas, que se pagan igual por las dos vías. El margen
añade otra diferencia, esta vez a favor de cualquier spread: la cámara reconoce que las dos patas se compensan y pide
670 USD por un calendario de crudo frente a 11.200 por dos posiciones simples, un 6 %; en el índice, 610 frente a
40.400 (lección 3.1.3).

## El backtest lleva las patas

La forma tentadora de hacer el backtest de un spread es tomar la serie de su precio (la `liquidacion` del libro del
spread, o la diferencia de las dos primeras posiciones de la curva) y tratarla como un activo más. Falla por la misma
razón que una serie continua de futuros sin ajustar (lección 1.4.6): en cada roll la serie pasa de un par de contratos
al siguiente, y la diferencia de ese día no es lo que ganó nadie. La lección 3.2.1 enseña el salto; aquí se mide su
efecto en un backtest completo.

El backtest correcto es **por patas**: la posición son contratos concretos, y el P&L de cada día es el cambio de
precio de los contratos que se tenían al cierre anterior. Para un spread largo (cerca − lejos), con $m$ el
multiplicador:

$$
\mathrm{P\&L}_t = m\left[\left(F^{\,c}_{t} - F^{\,c}_{t-1}\right) - \left(F^{\,l}_{t} - F^{\,l}_{t-1}\right)\right]
$$

con $c$ y $l$ los contratos del par de $t - 1$, aunque $t$ sea el día del roll.

```python
cur = pd.read_parquet("futuros3_curvas.parquet")
px = cur.set_index(["fecha", "contrato"])["liquidacion"]

def backtest_calendario(mercado):
    """Largo 1 spread (cerca - lejos) todos los días, rolado cuando
    cambia el par. P&L por patas (mismos contratos que ayer) y P&L
    ingenuo (diferencia de la serie de liquidaciones del spread)."""
    s = cot[cot["mercado"] == mercado].set_index("fecha")
    m = esp.at[mercado, "multiplicador"]
    f, f0 = s.index[1:], s.index[:-1]
    cerca = s["pata_cerca"].iloc[:-1]       # el par de ayer
    lejos = s["pata_lejos"].iloc[:-1]

    def cambio(patas):
        hoy = px.reindex(list(zip(f, patas))).to_numpy()
        antes = px.reindex(list(zip(f0, patas))).to_numpy()
        return hoy - antes

    out = pd.DataFrame(index=f)
    out["patas"] = (cambio(cerca) - cambio(lejos)) * m
    out["ingenuo"] = s["liquidacion"].diff().iloc[1:] * m
    out["roll"] = (s["spread"] != s["spread"].shift(1)).iloc[1:]
    return out

bt = {k: backtest_calendario(k) for k in ["PEC", "GNA", "MTO", "GRM"]}
resumen = {}
for k, v in bt.items():
    salto = (v["ingenuo"] - v["patas"])[v["roll"]]
    resumen[k] = {"patas": v["patas"].sum(),
                  "ingenuo": v["ingenuo"].sum(),
                  "saltos_roll": salto.sum(),
                  "rolls": v["roll"].sum()}
pd.DataFrame(resumen).T.round(0)
```
```output
       patas  ingenuo  saltos_roll  rolls
PEC  11850.0    790.0     -11060.0  192.0
GNA   6910.0   1960.0      -4950.0  192.0
MTO  -1230.0  -1450.0       -220.0   96.0
GRM   5400.0   -262.0      -5662.0   80.0
```

*Fig. — Calendario largo del crudo ficticio (cercano menos siguiente, 1 spread, rolado cada mes), 2010-2025, sin costes: P&L acumulado llevando las patas frente a la diferencia de la serie de liquidaciones del spread.* Dos líneas mensuales. La del P&L por patas, destacada, sube con altibajos hasta 11.850 USD a finales de 2025. La de la serie del spread, discontinua, se queda cerca de cero, con un mínimo de −3.490, y termina en 790: la diferencia son los saltos de los 192 rolls.

Llevando las patas, el calendario largo del crudo gana 11.850 USD en 16 años sin costes; con la serie del spread, 790.
La diferencia, −11.060 USD, sale entera de los 192 días de roll: es la suma de los saltos entre el par viejo y el
nuevo, que cotizan a niveles distintos porque están en tramos distintos de la curva. En el gas, 6.910 frente a 1.960;
en el maíz, 5.400 frente a −262: cambia hasta el signo. Solo en el oro, con una curva casi recta (su pendiente es la
financiación), el error es pequeño (−220 USD). La figura enseña cómo se separan las dos curvas: el backtest con la
serie no se equivoca en un día concreto, se equivoca un poco en cada roll, siempre en el mismo sentido.

## Los costes del roll

Un calendario mantenido en el tiempo hace en cada roll dos operaciones de spread: cerrar el par viejo y abrir el
nuevo. Cruzando medio ancho en cada una, el coste es un ancho completo más cuatro comisiones (dos patas por spread),
por el libro del spread o, si se monta pata a pata, con el ancho de las dos patas:

```python
def neto(mercado):
    s = cot[cot["mercado"] == mercado].set_index("fecha").iloc[1:]
    v = bt[mercado]
    vt, t = esp.at[mercado, "valor_tick"], esp.at[mercado, "tick"]
    com = esp.at[mercado, "comision"]
    libro = (s["ask"] - s["bid"]) / t * vt           # USD
    patas = ((s["ask_cerca"] - s["bid_cerca"])
             + (s["ask_lejos"] - s["bid_lejos"])) / t * vt
    c_libro = (libro + 4 * com).where(v["roll"], 0)
    c_patas = (patas + 4 * com).where(v["roll"], 0)
    anos = len(v) / 252
    sr = lambda x: x.mean() / v["patas"].std() * np.sqrt(252)
    return {"bruto_anual": v["patas"].sum() / anos,
            "coste_libro_anual": c_libro.sum() / anos,
            "coste_patas_anual": c_patas.sum() / anos,
            "sharpe_bruto": sr(v["patas"]),
            "sharpe_libro": sr(v["patas"] - c_libro),
            "sharpe_patas": sr(v["patas"] - c_patas)}

pd.DataFrame({k: neto(k) for k in bt}).T.round(2)
```
```output
     bruto_anual  coste_libro_anual  coste_patas_anual  sharpe_bruto  sharpe_libro  sharpe_patas
PEC       742.28             261.08             428.96          0.21          0.14          0.09
GNA       432.84             262.96             432.09          0.06          0.02          0.00
MTO       -77.05             130.54             216.98         -0.35         -0.95         -1.35
GRM       338.26             122.62             222.84          0.24          0.15          0.08
```

En el crudo, el calendario largo gana 742 USD al año en bruto. Rolar por el libro cuesta 261 USD al año (doce rolls de
un ancho completo más cuatro comisiones): un 35 % del bruto, y el Sharpe pasa de 0,21 a 0,14. Pata a pata cuesta 429
USD al año, el 58 %, y el Sharpe baja a 0,09. En el gas, el resultado neto desaparece. En el oro, que pierde en bruto,
los costes multiplican la pérdida: su spread se mueve poco (unos 220 USD de volatilidad anual) y 131 USD al año de
costes le llevan el Sharpe de −0,35 a −0,95. El maíz, con cinco vencimientos al año, rola menos y paga menos: 123 USD.
La lección 2.4.7 trata el modelado de costes en general; aquí lo propio de un spread es que el coste depende de **cómo**
se ejecuta.

<aside class="callout" data-kind="aviso">

Los resultados brutos de estos calendarios son los de datos **sintéticos**: el modelo del crudo tiene una prima de
carry plantada (la curva descuenta una reversión más rápida que la real, lección 3.1.2) y por eso el calendario largo
gana algo en bruto. No es evidencia de que un calendario de crudo real gane dinero. Lo que sí se traslada a los datos
reales es la mecánica: el P&L por patas, el coste de cada roll y la diferencia entre ejecutar por el libro o pata a
pata.

</aside>

## Legging risk

Montar el spread pata a pata tiene un coste además de la horquilla: entre la primera ejecución y la segunda, la
posición no es un spread sino un **futuro solo**. Si el precio se mueve en ese intervalo, la segunda pata se ejecuta
a otro nivel. Es el *legging risk*, y su tamaño depende de cuánto se mueve una pata frente a cuánto se mueve el
spread.

`spreads3_intradia_1min.parquet` tiene cinco sesiones de noviembre de 2025 del crudo ficticio `PEC`, minuto a minuto:
el mejor bid y ask de la pata de diciembre (`PECZ25`), de la de enero (`PECF26`) y del libro del spread entre las dos
(`PECZ25-PECF26`). Cada sesión va de las 23:00 UTC del día anterior a las 22:00 UTC.

```python
im = pd.read_parquet("spreads3_intradia_1min.parquet")
im["medio"] = (im["bid"] + im["ask"]) / 2
med = im.pivot(index="ts", columns="instrumento", values="medio")
d1 = med.diff()
print(f"Correlación de los cambios de 1 minuto de las patas: "
      f"{d1['PECZ25'].corr(d1['PECF26']):.3f}")
d5 = med.diff(5).std()
print(f"Desv. típica del cambio en 5 minutos: pata cercana "
      f"{d5['PECZ25']:.3f} USD, spread {d5['PECZ25-PECF26']:.3f} USD")
im.groupby("instrumento").agg(minimo=("bid", "min"),
                              maximo=("ask", "max"),
                              volumen=("volumen", "sum"))
```
```output
Correlación de los cambios de 1 minuto de las patas: 0.987
Desv. típica del cambio en 5 minutos: pata cercana 0.097 USD, spread 0.011 USD
               minimo  maximo  volumen
instrumento
PECF26          67.09   73.99  1992508
PECZ25          68.74   76.54  1509831
PECZ25-PECF26    1.53    2.59   896527
```

*Fig. — Las dos patas del calendario diciembre-enero del crudo ficticio y el libro del spread, precio medio cada 30 minutos, del 3 al 7 de noviembre de 2025.* Dos paneles con el mismo eje de tiempo. Arriba, las patas de diciembre y enero se mueven juntas de unos 70 a unos 76 USD, con caídas y subidas de más de un dólar en una sesión. Abajo, el spread diciembre menos enero, destacado, se mueve entre 1,5 y 2,6 USD: unos céntimos en cada media hora.

Las dos patas se mueven casi a la vez (correlación de 0,987 entre los cambios de un minuto) y mucho: la cercana, con
una desviación típica de 0,097 USD en 5 minutos, 97 USD por contrato de 1.000 barriles. El spread entre las dos, 0,011
USD: nueve veces menos. La figura lo enseña en las cinco sesiones: las patas suben de unos 70 a unos 76 USD, con
caídas de más de un dólar dentro de una sesión; el spread va de 1,56 a 2,58 sin saltos.

```python
w = im.pivot(index="ts", columns="instrumento", values=["bid", "ask"])
sesion = (w.index + pd.Timedelta(hours=1)).date   # 23:00 a 22:00 UTC
SP, CERCA, LEJOS = "PECZ25-PECF26", "PECZ25", "PECF26"

def legging(k):
    """Comprar el spread pata a pata: cerca al ask en t, lejos al bid
    en t + k (misma sesión). Coste frente al ask del libro del spread
    en t, en USD por spread (1.000 barriles)."""
    lejos_bid = w[("bid", LEJOS)].groupby(sesion).shift(-k)
    pagado = w[("ask", CERCA)] - lejos_bid
    return ((pagado - w[("ask", SP)]) * 1000).dropna()

filas = {}
for k in [0, 1, 2, 5, 10, 15, 30, 60]:
    c = legging(k)
    filas[k] = {"media": c.mean(), "desv": c.std(),
                "p1": c.quantile(0.01), "p99": c.quantile(0.99)}
tab_leg = pd.DataFrame(filas).T.rename_axis("minutos")
tab_leg.round(1)
```
```output
         media   desv      p1    p99
minutos
0         10.9    5.7    -0.0   20.0
1         10.2   43.9  -100.0  120.0
2          9.5   61.3  -150.0  160.0
5          7.3   96.3  -250.0  240.0
10         3.5  135.2  -370.0  320.0
15        -0.4  165.7  -440.0  380.0
30       -12.7  238.8  -675.1  530.0
60       -37.9  350.5 -1080.0  820.0
```

Con k = 0, las dos patas en el mismo minuto, el sintético cuesta 10,9 USD más que el ask del libro del spread (algo
más de un tick), con poca dispersión (5,7). En cuanto se espera, la dispersión crece como la raíz del tiempo, la de una
pata sola: 96,3 USD con 5 minutos, 165,7 con 15 y 350,5 con 60. En el 1 % de los peores casos, esperar 15 minutos
cuesta 380 USD por spread y esperar una hora, 820: más que el resultado bruto de un año del calendario. La media se
vuelve negativa con esperas largas (−37,9 con 60 minutos) porque en estas cinco sesiones el crudo sube dentro de la
sesión y la pata lejana se vende más cara más tarde: es la tendencia de cinco días, no un coste esperado (el ejercicio 1
lo comprueba al revés).

```python
espera = {k: (w[("ask", SP)].groupby(sesion).shift(-k)
              - w[("ask", SP)]).mul(1000).std()
          for k in [1, 5, 15, 30, 60]}
pd.Series(espera, name="desv_usd").rename_axis("minutos").round(1)
```
```output
minutos
1      6.2
5     10.3
15    16.5
30    23.0
60    32.0
Name: desv_usd, dtype: float64
```

Esperar con el spread entero también tiene riesgo, pero el del spread: 10,3 USD de desviación en 5 minutos y 32,0 en
60, entre nueve y once veces menos que pata a pata. La figura de la cabecera pone las dos curvas juntas. De ahí la
regla práctica: si el spread cotiza, se ejecuta por su libro; si hay que montarlo pata a pata, primero la menos líquida
(la que más cuesta llenar) y la otra inmediatamente, con una orden que cruce. Y en el backtest, el coste del legging
(la horquilla de las dos patas y un margen por el retraso) se cobra en cada entrada y en cada roll.

## Órdenes combinadas e implícitas

Hay tres formas de llevar una orden de spread al mercado, con riesgos distintos:

| Forma | Quién cruza | Legging risk | Precio |
|---|---|---|---|
| Spread de la bolsa | El motor de la bolsa, en el libro del spread | No: una sola operación | Uno, el del spread |
| Combinación garantizada | La bolsa o el bróker, todas las patas a la vez | No | El del conjunto |
| Combinación no garantizada | El bróker, pata a pata | Sí | El de cada pata |

En CME Globex, el libro del spread y los de las patas no están aislados. La bolsa calcula **órdenes implícitas**: una
orden en el libro del spread que deduce de órdenes en las patas (*implied IN*) o una orden en una pata que deduce de
órdenes en el spread y en la otra pata (*implied OUT*). El ejemplo de su documentación: una venta de 10 contratos de
septiembre a 9500 y una compra de 5 de diciembre a 9450 crean una venta implícita de 5 spreads septiembre-diciembre a
50.

```mermaid
flowchart LR
  A[Venta sep. 9500] --> I[Venta implícita 50]
  B[Compra dic. 9450] --> I
  I --> S[Libro sep-dic]
```

La misma página fija algunas reglas: hacen falta al menos dos órdenes en mercados relacionados, las implícitas de
compra no se cruzan con las implícitas de venta, en futuros no tienen prioridad temporal y no existen cuando el
mercado no está en negociación (la preapertura, por ejemplo). Para quien hace el backtest, la consecuencia es que el
libro del spread de un contrato líquido no suele ser peor que el sintético: si lo fuera, la propia bolsa lo mejoraría
con las implícitas.

En un bróker, una combinación se define con las patas. Con la API de Interactive Brokers (aquí con `ib_async`, la
continuación mantenida de `ib_insync`: versión 2.1.0, licencia BSD, en PyPI), una combinación es un contrato `BAG`
con una `ComboLeg` por pata, cada una con su `conId`, su proporción y su sentido. La documentación de la API distingue
los spreads intercommodity que lista la bolsa, que IBKR enruta directamente y son siempre garantizados, de las
combinaciones de futuros por `SMART`, que se envían como **no garantizadas** con la etiqueta `NonGuaranteed`: el
bróker no asegura que todas las patas se ejecuten a la vez. El bloque necesita TWS o IB Gateway y no se ejecuta aquí:

```python skip
from ib_async import IB, Bag, ComboLeg, Future, LimitOrder, TagValue

ib = IB()
ib.connect("127.0.0.1", 7497, clientId=7)   # TWS en cuenta de papel

cerca = Future("CL", "202512", "NYMEX")
lejos = Future("CL", "202601", "NYMEX")
ib.qualifyContracts(cerca, lejos)           # rellena los conId

spread = Bag(symbol="CL", exchange="SMART", currency="USD",
             comboLegs=[
                 ComboLeg(conId=cerca.conId, ratio=1, action="BUY",
                          exchange="NYMEX"),
                 ComboLeg(conId=lejos.conId, ratio=1, action="SELL",
                          exchange="NYMEX")])

# Precio límite del spread (cerca - lejos), no de cada pata.
orden = LimitOrder("BUY", 1, 0.55)
orden.smartComboRoutingParams = [TagValue("NonGuaranteed", "1")]
operacion = ib.placeOrder(spread, orden)
ib.sleep(5)
print(operacion.orderStatus.status, operacion.fills)
```

Si la orden se llena solo en parte o una pata se ejecuta antes que la otra, `operacion.fills` lo enseña ejecución a
ejecución. Un sistema que opere así necesita, además, una regla para el caso de una pata colgada: cuánto esperar,
cuándo cruzar la otra a mercado, cuándo deshacer la primera.

## Los datos reales: CME con Databento

Databento distribuye los datos de CME Globex (el conjunto `GLBX.MDP3`, el que presenta la lección 1.3.4) con los
spreads de la bolsa como instrumentos propios. Según su documentación de simbología, el símbolo padre `ES.FUT` incluye
«todos los futuros y spreads de futuros» del E-mini, y en las definiciones el campo `instrument_class` distingue un
futuro (`F`) de un spread de futuros (`S`); los símbolos de los spreads de calendario tienen la forma `ESU3-ESZ3`. Los
mejores precios por minuto están en el esquema `bbo-1m` (con `ts_recv` al **final** de cada minuto) y las
liquidaciones oficiales, en `statistics`, con `stat_type` 3[^1]. Con el cliente oficial (`databento` 0.88.0, licencia
Apache-2.0, en PyPI) y una clave en la variable de entorno `DATABENTO_API_KEY`, la descarga de una semana del
calendario de diciembre-enero del crudo sería:

```python skip
import databento as db

cliente = db.Historical()          # lee DATABENTO_API_KEY

# 1. Qué instrumentos hay: futuros y spreads de toda la familia CL
defs = cliente.timeseries.get_range(
    dataset="GLBX.MDP3", schema="definition", symbols=["CL.FUT"],
    stype_in="parent", start="2025-11-03", end="2025-11-04").to_df()
spreads = defs[defs["instrument_class"] == "S"]
calendarios = spreads[spreads["raw_symbol"].str.fullmatch(
    r"CL[FGHJKMNQUVXZ]\d-CL[FGHJKMNQUVXZ]\d")]

# 2. Lo que costaría antes de pedirlo (USD)
simbolos = ["CLZ5", "CLF6", "CLZ5-CLF6"]
coste = cliente.metadata.get_cost(
    dataset="GLBX.MDP3", schema="bbo-1m", symbols=simbolos,
    start="2025-11-03", end="2025-11-08")

# 3. Mejor bid y ask de las patas y del spread, minuto a minuto
bbo = cliente.timeseries.get_range(
    dataset="GLBX.MDP3", schema="bbo-1m", symbols=simbolos,
    start="2025-11-03", end="2025-11-08").to_df()

# 4. Liquidaciones oficiales
st = cliente.timeseries.get_range(
    dataset="GLBX.MDP3", schema="statistics", symbols=simbolos,
    start="2025-11-03", end="2025-11-08").to_df()
liquidaciones = st[st["stat_type"] == 3]
```

Debajo del cliente hay una API HTTP: `POST https://hist.databento.com/v0/timeseries.get_range`, con la clave como
usuario de autenticación básica y parámetros de formulario (`dataset`, `symbols`, `schema`, `start`, `end`,
`encoding`, `pretty_px`, `pretty_ts`, `map_symbols`, `limit`). Sin `pretty_px`, los precios llegan como enteros en
unidades de $10^{-9}$; sin `pretty_ts`, las marcas de tiempo, en nanosegundos. Su página de límites admite por
dirección IP hasta 100 conexiones simultáneas y 100 peticiones de series por segundo, y responde 429 con una cabecera
`Retry-After` al pasarse. Todo eso se puede probar sin red ni clave con un **simulador local** que imite la API: un
servidor en un hilo, en un puerto libre que elige el sistema, que sirve las cinco sesiones del crudo ficticio en el
formato del esquema `bbo-1m` (con menos columnas que el real) y rechaza la primera petición con un 429.

```python
import base64
import io
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

CLAVE = "db-" + "x" * 29            # 32 caracteres, como una real
IDS = {"PECZ25": 101, "PECF26": 102, "PECZ25-PECF26": 9001}
peticiones = []

def a_csv(d, mapear):
    """Filas de `im` en el formato bbo-1m (simplificado)."""
    fin_minuto = d["ts"] + pd.Timedelta(minutes=1)
    out = pd.DataFrame({
        "ts_recv": fin_minuto.dt.as_unit("ns").astype("int64"),
        "rtype": 196,
        "instrument_id": d["instrumento"].map(IDS),
        "bid_px_00": (d["bid"] * 1e9).round().astype("int64"),
        "ask_px_00": (d["ask"] * 1e9).round().astype("int64"),
        "bid_sz_00": d["tam_bid"], "ask_sz_00": d["tam_ask"]})
    if mapear:
        out["symbol"] = d["instrumento"].to_numpy()
    return out.to_csv(index=False)

class Simulador(BaseHTTPRequestHandler):
    def log_message(self, *args):      # sin registro en stderr
        pass

    def responder(self, codigo, cuerpo, cabeceras=()):
        self.send_response(codigo)
        for k, v in cabeceras:
            self.send_header(k, v)
        self.end_headers()
        self.wfile.write(cuerpo.encode())

    def do_POST(self):
        peticiones.append(self.path)
        clave = base64.b64encode(f"{CLAVE}:".encode()).decode()
        if self.headers.get("Authorization") != f"Basic {clave}":
            return self.responder(401, '{"detail":"Not authenticated"}')
        if len(peticiones) == 1:      # la primera choca con el límite
            return self.responder(429, '{"detail":"Too Many Requests"}',
                                  [("Retry-After", "1")])
        largo = int(self.headers["Content-Length"])
        p = urllib.parse.parse_qs(self.rfile.read(largo).decode())
        if (self.path != "/v0/timeseries.get_range"
                or p["schema"] != ["bbo-1m"]
                or p.get("encoding", ["csv"]) != ["csv"]):
            return self.responder(422, '{"detail":"no simulado"}')
        ini = pd.Timestamp(p["start"][0], tz="UTC")
        fin = pd.Timestamp(p["end"][0], tz="UTC")
        d = im[im["instrumento"].isin(p["symbols"][0].split(","))
               & (im["ts"] >= ini) & (im["ts"] < fin)]
        mapear = p.get("map_symbols", ["false"]) == ["true"]
        self.responder(200, a_csv(d, mapear),
                       [("Content-Type", "text/csv")])

servidor = ThreadingHTTPServer(("127.0.0.1", 0), Simulador)
threading.Thread(target=servidor.serve_forever, daemon=True).start()
BASE = f"http://127.0.0.1:{servidor.server_port}"
print("Simulador en marcha:", BASE.startswith("http://127.0.0.1:"))
```
```output
Simulador en marcha: True
```

El cliente es el de la lección 1.4.1 con lo propio de esta API: autenticación básica con la clave como usuario y la
contraseña vacía, parámetros de formulario y, ante un 429, esperar lo que diga `Retry-After` antes de reintentar.

```python
def get_range(base, clave, intentos=3, **params):
    """POST /v0/timeseries.get_range con encoding=csv. Devuelve un
    DataFrame con precios en USD y marcas de tiempo UTC."""
    datos = urllib.parse.urlencode({"encoding": "csv", **params})
    auth = base64.b64encode(f"{clave}:".encode()).decode()
    for i in range(intentos):
        req = urllib.request.Request(
            f"{base}/v0/timeseries.get_range", data=datos.encode(),
            headers={"Authorization": f"Basic {auth}"})
        try:
            with urllib.request.urlopen(req, timeout=10) as r:
                df = pd.read_csv(io.StringIO(r.read().decode()))
            break
        except urllib.error.HTTPError as e:
            if e.code != 429 or i == intentos - 1:
                raise
            espera = float(e.headers.get("Retry-After", "1"))
            print(f"HTTP 429: espero {espera:.0f} s y reintento")
            time.sleep(espera)
    df["ts_recv"] = pd.to_datetime(df["ts_recv"], unit="ns", utc=True)
    for c in ["bid_px_00", "ask_px_00"]:
        df[c] = df[c] / 1e9
    return df

bbo = get_range(BASE, CLAVE, dataset="GLBX.MDP3", schema="bbo-1m",
                symbols="PECZ25,PECF26,PECZ25-PECF26",
                start="2025-11-04T13:00", end="2025-11-04T13:05",
                map_symbols="true")
print(f"{len(peticiones)} peticiones, {len(bbo)} filas")
bbo.loc[bbo["symbol"] == "PECZ25-PECF26",
        ["ts_recv", "bid_px_00", "ask_px_00", "bid_sz_00", "ask_sz_00"]]
```
```output
HTTP 429: espero 1 s y reintento
2 peticiones, 15 filas
                     ts_recv  bid_px_00  ask_px_00  bid_sz_00  ask_sz_00
2  2025-11-04 13:01:00+00:00       1.69       1.70         42         90
5  2025-11-04 13:02:00+00:00       1.70       1.71         24         33
8  2025-11-04 13:03:00+00:00       1.70       1.71         33        123
11 2025-11-04 13:04:00+00:00       1.70       1.71         51         63
14 2025-11-04 13:05:00+00:00       1.70       1.71        111        165
```

La primera petición recibe un 429; el cliente espera el segundo que pide `Retry-After` y la segunda trae las 15 filas:
cinco minutos de tres instrumentos. Las marcas son el final de cada minuto, como en `bbo-1m`: la fila de las 13:01 es
el minuto que empieza a las 13:00 en el Parquet del curso.

```python raises
get_range(BASE, "db-clave-equivocada", dataset="GLBX.MDP3",
          schema="bbo-1m", symbols="PECZ25",
          start="2025-11-04", end="2025-11-05")
```
```output
urllib.error.HTTPError: HTTP Error 401: Unauthorized
```

Un 401 no se reintenta: es un error de configuración, no de carga. El cliente solo repite ante el 429; ante un 5xx
real convendría repetir también, con espera exponencial (lección 1.4.1).

```python
dia = get_range(BASE, CLAVE, dataset="GLBX.MDP3", schema="bbo-1m",
                symbols="PECZ25,PECF26,PECZ25-PECF26",
                start="2025-11-03T23:00", end="2025-11-04T22:00",
                map_symbols="true")
ref = im[(im["ts"] >= "2025-11-03T23:00Z")
         & (im["ts"] < "2025-11-04T22:00Z")]
iguales = (np.allclose(dia["bid_px_00"], ref["bid"])
           and np.allclose(dia["ask_px_00"], ref["ask"])
           and (dia["ts_recv"] - pd.Timedelta(minutes=1)
                == ref["ts"].dt.as_unit("ns").to_numpy()).all())
print(f"{len(dia)} filas; coinciden con el Parquet: {iguales}")
servidor.shutdown()
```
```output
4140 filas; coinciden con el Parquet: True
```

Una sesión completa (de las 23:00 a las 22:00 UTC, 1.380 minutos por instrumento) llega con 4.140 filas y coincide con
el archivo, desplazada un minuto. Con la URL real y una clave, el mismo cliente pide los datos de verdad; el simulador
sirve para probar autenticación, límites, errores y paginación sin gastar uso (Databento cobra por volumen descargado,
lección 1.3.4) y sin depender de la red en los tests (lección 0.4.5).

## Errores típicos

- **La serie del spread como si fuera un activo.** Su diferencia en el día del roll mezcla dos pares. El P&L se calcula
  con los contratos de ayer; la serie sirve para la señal, no para el resultado.
- **Ejecutar en el backtest a la liquidación del spread.** La liquidación de los datos cae siempre dentro del libro del
  spread, pero no es un precio al que se pueda operar en volumen: el coste mínimo realista es medio ancho del libro.
- **Costes de un spread con el ancho del sintético, o al revés.** Si el sistema opera por el libro, el coste es el del
  libro; si opera pata a pata, el de las dos patas más el legging. Confundirlos cambia el resultado neto en un factor de
  dos en el calendario de crudo.
- **Ignorar los días sin libro.** En índices y bonos, el spread de calendario solo se negocia de verdad alrededor del
  roll (en los datos, las 10 sesiones anteriores). Un backtest que lo opera todo el trimestre supone una liquidez que
  no existe.
- **Marcas de tiempo del proveedor.** En `bbo-1m` de Databento la marca es el final del minuto; en el Parquet del curso,
  el inicio. Alinear mal un minuto entre la señal y el precio es anticipación (lección 1.7.4).
- **Margen.** El margen de un spread de calendario es una fracción del de dos posiciones simples (670 USD frente a
  11.200 en el crudo ficticio): un rendimiento «sobre margen» parece enorme y esconde que la pérdida posible no se
  reduce en la misma proporción (lección 3.1.3).

## Ejercicios

1. Repite el estudio de legging al revés: vender primero la pata lejana al bid y comprar la cercana al ask k minutos
   después. ¿Cambia la desviación? ¿Y la media? Explica por qué la media del legging de la tabla se vuelve negativa con
   esperas largas en estas cinco sesiones.

**Solución.**

```python
def legging_inverso(k):
    cerca_ask = w[("ask", CERCA)].groupby(sesion).shift(-k)
    pagado = cerca_ask - w[("bid", LEJOS)]
    return ((pagado - w[("ask", SP)]) * 1000).dropna()

pd.DataFrame({k: {"media": legging_inverso(k).mean(),
                  "desv": legging_inverso(k).std()}
              for k in [0, 5, 15, 60]}).T.round(1)
```
```output
    media   desv
0    10.9    5.7
5    15.1   96.4
15   23.8  166.3
60   66.8  353.0
```

Empezando por la lejana, la desviación es la misma (96,4 USD con 5 minutos, 353,0 con 60): el riesgo de una pata sola
no depende de cuál se ejecute primero. La media cambia de signo: +15,1 USD con 5 minutos y +66,8 con 60, frente a 7,3 y
−37,9. En estas cinco sesiones el crudo sube dentro de la sesión (el contrato de diciembre pasa de unos 70 a unos 76
USD, como se ve en la figura): quien compra después la pata cercana la paga más cara, y quien vende después la lejana
la vende más cara. Con cinco sesiones, esa media es la tendencia de esos días; la desviación, cinco veces mayor, es lo
que hay que presupuestar.

2. Las sesiones de Asia (de 23:00 a 08:00 UTC) tienen horquillas más anchas en los datos. Mide el ancho medio en ticks
   del libro del spread y del sintético por franja (Asia, Londres de 08:00 a 13:00, Nueva York desde las 13:00) y el
   coste de comprar el spread por cada vía en USD.

**Solución.**

```python
h = w.index.hour
franja = np.select([(h >= 23) | (h < 8), h < 13],
                   ["asia", "londres"], "nueva_york")
libro = (w[("ask", SP)] - w[("bid", SP)]) / 0.01
sint = ((w[("ask", CERCA)] - w[("bid", CERCA)])
        + (w[("ask", LEJOS)] - w[("bid", LEJOS)])) / 0.01
t = pd.DataFrame({"libro": libro, "sintetico": sint})
t = t.groupby(franja).mean()
t["coste_libro_usd"] = t["libro"] / 2 * 10
t["coste_sint_usd"] = t["sintetico"] / 2 * 10
t.round(2)
```
```output
            libro  sintetico  coste_libro_usd  coste_sint_usd
asia         1.43       2.89             7.16           14.44
londres      1.00       2.00             5.00           10.00
nueva_york   1.00       2.00             5.00           10.00
```

En Asia, el libro del spread tiene 1,43 ticks de ancho medio y el sintético, 2,89; en Londres y Nueva York, 1,00 y 2,00
exactos. Comprar por el libro cuesta 7,16 USD en Asia y 5,00 después; pata a pata, 14,44 y 10,00. En los datos el
horario solo ensancha el mercado en Asia (la mitad de los minutos con un tick más en cada libro). Un sistema que ejecuta
al cierre europeo o americano paga lo mínimo; uno que reacciona a noticias de madrugada paga un 43 % más por el libro.

3. Amplía el simulador para que pagine: con un parámetro `limit`, que devuelva como mucho ese número de filas, y que el
   cliente pida el siguiente tramo a partir del último `ts_recv` recibido. ¿Qué problema aparece si varias filas
   comparten el mismo `ts_recv` justo en el corte?

**Solución.**

El parámetro `limit` de la API real limita el número de registros, pero no devuelve un cursor: el cliente tiene que
volver a pedir desde la última marca recibida. Con `start` = último `ts_recv` (incluido), las filas de ese instante se
duplican; con `start` = último `ts_recv` + 1 ns, se pierden las que no cupieron en el tramo anterior y compartían esa
marca (en `bbo-1m`, las tres series de un mismo minuto tienen el mismo `ts_recv`). La solución robusta es pedir desde
la última marca incluida y descartar los duplicados por (`ts_recv`, `instrument_id`), o bien no paginar por tiempo sino
por bloques de tiempo cerrados (un día por petición), que es lo que hace la descarga por lotes de Databento
(`batch.submit_job`). En el simulador basta con añadir `d = d.head(int(p["limit"][0]))` si viene `limit`, y en el
cliente un bucle que avance `start` hasta que una respuesta llegue con menos filas que `limit`.

[^1]: Databento, «Statistics» (esquema `statistics`: `stat_type` 3 = precio de liquidación, 6 = volumen compensado, 9 =
    interés abierto): https://databento.com/docs/schemas-and-data-formats/statistics

## Autoevaluación

1. En los datos, el libro del spread de calendario cotiza con 1,25 ticks de ancho medio y el spread montado con las patas, con 2,78. ¿Por qué el sintético es más ancho?

   - A. Porque los datos tienen errores
   - B. Porque comprar el sintético es comprar la pata cercana al ask y vender la lejana al bid: se pagan los dos anchos, y la suma de dos horquillas es mayor que la horquilla de un instrumento que se negocia entero
   - C. Porque el spread cotizado no se puede operar
   - D. Porque el sintético incluye la comisión

   *Respuesta B: Porque comprar el sintético es comprar la pata cercana al ask y vender la lejana al bid: se pagan los dos anchos, y la suma de dos horquillas es mayor que la horquilla de un instrumento que se negocia entero.* El bid del sintético es bid_cerca − ask_lejos y su ask, ask_cerca − bid_lejos: su ancho es la suma de los anchos de las dos patas. El libro del spread lo cotizan quienes quieren el spread entero, y con las órdenes implícitas de la bolsa nunca es peor que el sintético.

2. Un backtest de un calendario largo y rolado de crudo da 790 USD en 16 años con la diferencia de la serie de liquidaciones del spread, y 11.850 USD llevando las patas. ¿Cuál es el bueno y por qué?

   - A. El de la serie, porque es el precio del spread
   - B. Los dos, según el día
   - C. El de las patas: la serie del spread cambia de par en cada roll y su diferencia de ese día mezcla dos instrumentos distintos; esos saltos suman −11.060 USD que nadie pagó
   - D. Ninguno: hay que usar la serie ajustada por ratio

   *Respuesta C: El de las patas: la serie del spread cambia de par en cada roll y su diferencia de ese día mezcla dos instrumentos distintos; esos saltos suman −11.060 USD que nadie pagó.* El P&L de un día es el cambio del par que se tenía ayer. En el roll, la serie pasa del par viejo al nuevo y su diferencia no es una ganancia ni una pérdida: es el cambio de instrumento. Es el mismo error que el de una serie continua de futuros sin ajustar (lección 1.4.6), multiplicado por 192 rolls.

3. Se compra la pata cercana al ask y se espera 15 minutos para vender la lejana. ¿Qué dice la tabla de legging de los datos?

   - A. Que no cambia nada: el spread apenas se mueve
   - B. Que siempre se gana porque se espera a un precio mejor
   - C. Que cuesta exactamente un tick
   - D. Que el coste medio es casi cero, pero su desviación típica es de 166 USD por spread y el percentil 1, −440 USD: el riesgo es el de una pata sola, no el del spread

   *Respuesta D: Que el coste medio es casi cero, pero su desviación típica es de 166 USD por spread y el percentil 1, −440 USD: el riesgo es el de una pata sola, no el del spread.* Mientras solo hay una pata, la posición es un futuro de crudo de 1.000 barriles, no un spread. Su precio se mueve 0,097 USD en 5 minutos (97 USD por contrato) frente a 0,011 del spread. Esperar con el spread entero cuesta una décima parte de ese riesgo.

4. ¿Qué es una orden implícita «implied IN» en CME Globex?

   - A. Una orden en el libro del spread que la bolsa deduce de órdenes en los libros de las patas
   - B. Una orden que el bróker parte en dos
   - C. Una orden oculta de un creador de mercado
   - D. Una orden que solo existe en la subasta

   *Respuesta A: Una orden en el libro del spread que la bolsa deduce de órdenes en los libros de las patas.* Según la documentación de CME, una venta de 10 en septiembre a 9500 y una compra de 5 en diciembre a 9450 crean una venta implícita de 5 a 50 en el spread septiembre-diciembre. Al revés, de órdenes en el spread y en una pata la bolsa deduce órdenes en la otra pata (implied OUT). Por eso el libro del spread no suele ser peor que el sintético.

5. Una combinación de futuros enviada por SMART como no garantizada a IBKR, ¿qué riesgo tiene?

   - A. Ninguno: el bróker la ejecuta como un solo instrumento
   - B. El de que se ejecuten las patas por separado y no a la vez: legging risk, con el resultado de una pata sola si la otra no se llena
   - C. El de pagar el doble de comisiones
   - D. El de que la bolsa la rechace siempre

   *Respuesta B: El de que se ejecuten las patas por separado y no a la vez: legging risk, con el resultado de una pata sola si la otra no se llena.* La documentación de la API de IBKR distingue los spreads que lista la bolsa (intercommodity, enrutados directamente y siempre garantizados) de las combinaciones por SMART, que se marcan como no garantizadas. No garantizada quiere decir que el bróker no asegura que todas las patas se ejecuten juntas.

## Ideas clave

- El spread cotizado es un instrumento con su libro; el sintético se monta con las patas y paga sus dos horquillas. En los datos, 1,25 ticks frente a 2,78 de ancho medio: un 93 % de las sesiones de crudo, más ancho el sintético.
- El backtest de un spread lleva las patas: el P&L de cada día es el cambio del par que se tenía ayer. La diferencia de la serie del spread mezcla pares en cada roll; en el calendario de crudo, 790 USD frente a 11.850 en 16 años.
- Cada roll de un calendario es cerrar un spread y abrir otro. Por el libro cuesta 261 USD al año en el calendario de crudo, un 35 % del resultado bruto; pata a pata, 429 USD, un 58 %.
- El legging risk es el riesgo de una pata sola: con 5 minutos de espera, 96 USD de desviación por spread de crudo; con 60, 350. Esperar con el spread entero, 10 y 32.
- Databento sirve los spreads de CME como instrumentos propios (`instrument_class` S, símbolos como `CLZ5-CLF6`), con mejores precios por minuto (`bbo-1m`) y liquidaciones (`statistics`, `stat_type` 3); `metadata.get_cost` dice lo que cuesta antes de descargar.
- Orden combinada de bolsa: una sola operación, sin legging. Combinación no garantizada del bróker: pata a pata. Las implícitas de CME conectan los libros de las patas y del spread.

## Recursos

### Documentación

- [Databento: timeseries.get_range (API histórica, parámetros y ejemplo HTTP)](https://databento.com/docs/api-reference-historical/timeseries/timeseries-get-range) · databento.com
- [Databento: simbología (parent, raw_symbol, continuous)](https://databento.com/docs/standards-and-conventions/symbology) · databento.com
- [Databento: definiciones de instrumentos (instrument_class y patas de los spreads)](https://databento.com/docs/schemas-and-data-formats/instrument-definitions) · databento.com
- [Databento: convenciones especiales de los futuros (spreads, liquidaciones en statistics)](https://databento.com/docs/examples/futures/futures-introduction/special-conventions-for-futures-on-databento) · databento.com
- [CME Group Client Systems Wiki: Implied Orders](https://cmegroupclientsite.atlassian.net/wiki/spaces/EPICSANDBOX/pages/457095346/Implied+Orders) · cmegroupclientsite.atlassian.net
- [Interactive Brokers, TWS API: combos y spreads (BAG, ComboLeg, combinaciones no garantizadas)](https://www.interactivebrokers.com/docs/tws-api/contracts/combos-spreads) · interactivebrokers.com

### Datos

- [Datos del nivel 3: spreads de calendario cotizados (Parquet)](https://pythonparatrading.com/curso/datos/nivel-3/spreads3_cotizados.parquet) · Descarga · spreads3_cotizados.parquet

Lección anterior: [3.2.4 · Ratios de cobertura: por nominal, por DV01 y por volatilidad](https://pythonparatrading.com/curso-python-trading/nivel-3/ratios-de-cobertura-nominal-dv01-y-volatilidad/)

Lección siguiente: [3.2.6 · Estacionalidad de spreads: análisis multi-año, patrones y trampas](https://pythonparatrading.com/curso-python-trading/nivel-3/estacionalidad-de-spreads/)
