Lección 3.2.2 · 65 min
Spreads intercommodity: crack, spark y crush
Una refinería compra crudo y vende gasolina; una central compra gas y vende electricidad; una molturadora compra soja y vende harina y aceite. Sus márgenes son spreads entre dos o tres futuros distintos, con unidades distintas y un vínculo físico que los ata. Esta lección los construye con el mismo mes de entrega en todas las patas, mide su riesgo y comprueba si su reversión es la que se plantó en los datos.
- Módulo
- 3.2 · Spreads de futuros
- Duración
- 65 min
Lecciones del nivel 3
3.1 · Futuros en profundidad
3.2 · Spreads de futuros
- 3.2.1 Spreads de calendario, butterflies y condors
- 3.2.2 Spreads intercommodity: crack, spark y crush
- 3.2.3 Spreads de tipos y de índices: 2s10s, NOB, NQ/ES y Europa frente a EE. UU.
- 3.2.4 Ratios de cobertura: por nominal, por DV01 y por volatilidad
- 3.2.5 Datos y backtesting de spreads: cotizados, sintéticos y por patas
- 3.2.6 Estacionalidad de spreads: análisis multi-año, patrones y trampas
3.3 · Forex avanzado
3.4 · Teoría y valoración de opciones
- 3.4.1 Payoffs, paridad put-call y límites de arbitraje
- 3.4.2 Árboles binomiales, Black-Scholes-Merton, Black-76 y Monte Carlo
- 3.4.3 Griegas: delta, gamma, vega, theta, rho, vanna, volga y charm
- 3.4.4 Volatilidad implícita: Newton-Raphson, Brent, py_vollib y QuantLib
- 3.4.5 Sonrisa, skew y estructura temporal; superficie de volatilidad con SVI
- 3.4.6 Volatilidad realizada frente a implícita; prima de riesgo de volatilidad; VIX y VSTOXX
- 3.4.7 Modelos avanzados: Heston, SABR y volatilidad local
3.5 · Estructuras de opciones en Python
- 3.5.1 Estructuras direccionales y verticales: call, put, covered call, collar y spreads
- 3.5.2 Estructuras de volatilidad y rango: straddles, strangles, cóndores y mariposas
- 3.5.3 Calendarios, diagonales, ratios, backspreads, risk reversals y jade lizard
- 3.5.4 Herramienta propia: payoff, griegas agregadas y escenarios
- 3.5.5 Gestión: ajustes, roll, cierre anticipado, asignación temprana y pin risk
3.6 · Estrategias sistemáticas de volatilidad y opciones
3.7 · Cripto derivados y estrategias específicas
Márgenes de proceso y valor relativo
Un spread de calendario (lección 3.2.1) enfrenta dos meses del mismo producto. Un spread intercommodity enfrenta productos distintos. Los más operados son márgenes de una industria: lo que gana quien transforma un producto en otro. Una refinería compra crudo y vende gasolina y gasóleo (el crack); una central de ciclo combinado compra gas y vende electricidad (el spark); una molturadora compra soja y vende harina y aceite (el crush).
Fuente Mermaid
flowchart LR C[Crudo] --> R[Refinería] R --> G[Gasolina] R --> D[Gasóleo] S[Soja] --> M[Molturadora] M --> H[Harina] M --> A[Aceite] N[Gas] --> E[Central] E --> L[Electricidad]
El vínculo físico es lo que hace interesantes estos spreads. Si el margen de refino sube mucho, las refinerías procesan más crudo y venden más gasolina, y el margen baja; si cae por debajo del coste de operar, paran unidades y el margen se recupera. El spread no puede alejarse sin límite de lo que cuesta transformar el producto, al menos a plazos de meses. Otros spreads intercommodity no tienen ese ancla: el ratio oro/plata o el trigo frente al maíz son apuestas de valor relativo entre productos que se sustituyen en parte o que se han movido juntos en el pasado.
Fuente Mermaid
flowchart TB
P[Dos mercados] --> V{¿Vínculo físico?}
V -- sí --> T[Margen de proceso]
V -- no --> R[Valor relativo]
T --> RV[Reversión posible]
R --> SA[Sin ancla]Los datos del nivel 3 tienen los tres márgenes de proceso y dos spreads de valor relativo, con la respuesta
conocida en spreads3_verdad.csv: qué componente del modelo mueve cada spread, a qué velocidad revierte y cuáles
son controles. Son datos sintéticos: los mercados imitan al WTI, la gasolina RBOB, el gasóleo de Nueva York, el gas
Henry Hub, la electricidad de punta de PJM, el complejo de la soja, el trigo, el maíz, el oro y la plata, pero los
precios no son de mercado.
Unidades y ratios de contratos
Antes de restar dos precios hay que ponerlos en la misma unidad. Cada contrato cotiza en la suya:
import numpy as np
import pandas as pd
cur = pd.read_parquet("futuros3_curvas.parquet")
ctr = pd.read_csv("futuros3_contratos.csv",
parse_dates=["vencimiento", "fecha_roll"])
esp = pd.read_csv("futuros3_especificaciones.csv",
index_col="mercado")
mercados = ["PEC", "PEG", "PEH", "GNA", "ELB", "GRS", "GRH",
"GRA", "GRT", "GRM", "MTO", "MTP"]
esp.loc[mercados, ["imita", "multiplicador", "unidad_cotizacion",
"meses"]] imita multiplicador unidad_cotizacion meses
mercado
PEC WTI Crude Oil (CL) 1000.0 USD/barril FGHJKMNQUVXZ
PEG RBOB Gasoline (RB) 42000.0 USD/galón FGHJKMNQUVXZ
PEH NY Harbor ULSD (HO) 42000.0 USD/galón FGHJKMNQUVXZ
GNA Henry Hub Natural Gas (NG) 10000.0 USD/MMBtu FGHJKMNQUVXZ
ELB PJM Western Hub Peak mensual 800.0 USD/MWh FGHJKMNQUVXZ
GRS Soybeans (ZS) 50.0 centavos/bushel FHKNQUX
GRH Soybean Meal (ZM) 100.0 USD/tonelada corta FHKNQUVZ
GRA Soybean Oil (ZL) 600.0 centavos/libra FHKNQUVZ
GRT Chicago SRW Wheat (ZW) 50.0 centavos/bushel HKNUZ
GRM Corn (ZC) 50.0 centavos/bushel HKNUZ
MTO Gold (GC) 100.0 USD/onza GJMQVZ
MTP Silver (SI) 5000.0 USD/onza HKNUZLas conversiones que definen cada spread:
| Spread | Fórmula | Unidad | Conversión |
|---|---|---|---|
| Crack de la gasolina 1:1 | USD/barril | 42 galones por barril | |
| Crack del gasóleo 1:1 | USD/barril | 42 galones por barril | |
| Crack 3:2:1 | USD/barril | dos de gasolina por uno de gasóleo | |
| Spark | USD/MWh | heat rate de 7 MMBtu por MWh | |
| Crush | USD/bushel | 0,022 t de harina y 11 lb de aceite por bushel | |
| Oro/plata | cociente | onzas de plata por onza de oro | |
| Trigo/maíz | cociente | mismos bushels |
En el crack, el contrato de gasolina o gasóleo tiene 42.000 galones, que son 1.000 barriles: el mismo volumen que el de crudo. Un contrato de producto contra uno de crudo es el crack 1:1, y la bolsa lo trata así en sus ejemplos. El 3:2:1 (tres de crudo, dos de gasolina, uno de gasóleo) imita el rendimiento de una refinería típica de EE. UU., que produce aproximadamente el doble de gasolina que de destilados.
En el spark, el heat rate es la eficiencia de la central: los MMBtu de gas que quema para producir un MWh. La EIA publica el spark spread con un heat rate de referencia de 7.000 Btu/kWh (7 MMBtu/MWh), el de una central de ciclo combinado moderna y eficiente; y el spark no cuenta otros costes de la central (transporte del gas, mantenimiento, emisiones), así que un spark positivo no es un beneficio.
En el crush, la guía de CME parte de los rendimientos físicos: un bushel de 60 libras da unas 44 libras de harina y 11 de aceite (más cascarilla y merma). Con 44/2.000 = 0,022 toneladas cortas de harina por bushel y los tamaños de los contratos, la proporción en contratos es:
bushels = 10 * 5000 # 10 contratos de soja
harina_t = 44 / 2000 * bushels # toneladas cortas
aceite_lb = 11 * bushels # libras
print(f"Harina: {harina_t:,.0f} t = {harina_t / 100:.2f} contratos")
print(f"Aceite: {aceite_lb:,.0f} lb = "
f"{aceite_lb / 60000:.2f} contratos")Harina: 1,100 t = 11.00 contratos
Aceite: 550,000 lb = 9.17 contratosLa figura hace la cuenta con los contratos de julio de 2025 el 4 de junio: la harina de un bushel vale 7,48 USD, el aceite 5,52 y la soja cuesta 10,89, así que el margen bruto del crush es de 2,12 USD por bushel.
Diez contratos de soja dan exactamente once de harina y 9,17 de aceite. CME facilita el board crush como 30 contratos: 10 de soja, 11 de harina y 9 de aceite. El aceite queda un 2 % corto: con números enteros pequeños, el ratio nunca es exacto. El segundo ejercicio hace la misma búsqueda para el spark spread.
El mismo mes de entrega en todas las patas
Un crack de julio es gasolina de julio contra crudo de julio. Mezclar meses añade a la posición un spread de calendario que nadie ha pedido. Las patas tienen además calendarios distintos: la soja lista siete meses al año, la harina y el aceite ocho, y solo seis son comunes (enero, marzo, mayo, julio, agosto y septiembre). El código busca, para cada sesión, el primer mes de entrega listado en todas las patas cuya fecha de roll no haya llegado en ninguna, y calcula el cambio diario con los contratos del día anterior, como en la lección 3.2.1:
cm = cur.merge(ctr[["contrato", "mes_entrega"]], on="contrato")
px = cm.pivot_table(index="fecha", columns=["mercado", "mes_entrega"],
values="liquidacion")
def mes_comun(mercados, meses=None):
"""Mes de entrega que se tiene a la vez en todas las patas: el
primero listado en todas cuya primera fecha de roll no ha
llegado."""
c = ctr[ctr["mercado"].isin(mercados)]
r = c.groupby("mes_entrega").agg(
n=("mercado", "nunique"), roll=("fecha_roll", "min"))
r = r[r["n"] == len(mercados)]
if meses:
r = r[[int(m[5:]) in meses for m in r.index]]
roll = r["roll"][::-1].cummin()[::-1]
pos = np.searchsorted(roll.to_numpy(), px.index.to_numpy(),
side="right")
pos = np.minimum(pos, len(r) - 1)
return pd.Series(r.index.to_numpy()[pos], index=px.index)
def patas_mes(mercados, meses=None):
"""Precio de hoy de cada pata en el mes común de hoy y cambio
de hoy de cada pata en el mes común de ayer (sin saltos)."""
mes = mes_comun(mercados, meses)
dif = px.diff()
def toma(tabla, m, meses_):
idx = pd.MultiIndex.from_arrays([mes.index, meses_.to_numpy()])
return tabla[m].stack().reindex(idx).to_numpy()
nivel = pd.DataFrame({m: toma(px, m, mes) for m in mercados},
index=mes.index)
cambio = pd.DataFrame({m: toma(dif, m, mes.shift(1))
for m in mercados}, index=mes.index)
return nivel, cambio, mes
niv, dif, mes = patas_mes(["PEG", "PEC"])
mes.loc["2025-06-02":"2025-06-06"]fecha
2025-06-02 2025-07
2025-06-03 2025-07
2025-06-04 2025-07
2025-06-05 2025-07
2025-06-06 2025-07
dtype: strEl 4 de junio de 2025, el crack que se tiene es el de julio en las dos patas. Con esa función se construyen los seis spreads en sus unidades:
SPREADS = {
"crack_gasolina": (["PEG", "PEC"], None,
lambda p: 42 * p.PEG - p.PEC),
"crack_gasoleo": (["PEH", "PEC"], None,
lambda p: 42 * p.PEH - p.PEC),
"spark": (["ELB", "GNA"], None, lambda p: p.ELB - 7 * p.GNA),
"crush": (["GRS", "GRH", "GRA"], [1, 3, 5, 7, 8, 9],
lambda p: 0.022 * p.GRH + 0.11 * p.GRA - p.GRS / 100),
"trigo_maiz": (["GRT", "GRM"], [3, 5, 7, 9, 12],
lambda p: p.GRT / p.GRM),
"aceite_harina": (["GRA", "GRH"], [1, 3, 5, 7, 8, 9, 10, 12],
lambda p: p.GRA / p.GRH),
}
UNIDAD = {"crack_gasolina": "USD/barril", "crack_gasoleo": "USD/barril",
"spark": "USD/MWh", "crush": "USD/bushel",
"trigo_maiz": "cociente", "aceite_harina": "cociente"}
niveles = {}
for nombre, (ms, meses, f) in SPREADS.items():
niveles[nombre] = f(patas_mes(ms, meses)[0])
tabla = pd.DataFrame(niveles).describe().T
tabla["unidad"] = pd.Series(UNIDAD)
tabla[["unidad", "mean", "min", "max"]].round(3) unidad mean min max
crack_gasolina USD/barril 21.318 -8.886 65.059
crack_gasoleo USD/barril 31.515 9.177 78.124
spark USD/MWh 15.711 -2.250 74.091
crush USD/bushel 1.034 -0.289 3.611
trigo_maiz cociente 1.355 1.019 1.779
aceite_harina cociente 0.132 0.087 0.251El crack medio de la gasolina sintética es de 21,3 USD por barril y llega a ser negativo (−8,89); el del gasóleo, más alto y más estable, nunca baja de 9,18. El spark también llega a ser negativo (−2,25 USD/MWh): hay días en que quemar gas para producir electricidad de punta no cubre ni el combustible. El crush medio es de 1,03 USD por bushel. La figura de la cabecera muestra los dos cracks: el de la gasolina oscila mucho más, con un ciclo anual muy marcado. Antes de interpretarlo, conviene mirar el nivel medio por mes de entrega:
mes_gas = patas_mes(["PEG", "PEC"])[2]
nivel_mes = niveles["crack_gasolina"].groupby(mes_gas.str[5:]).mean()
nivel_mes.round(1).to_frame("usd_barril").T 01 02 03 04 05 06 07 08 09 10 11 12
usd_barril 13.6 14.9 23.1 31.1 33.8 32.8 29.5 26.7 19.2 11.0 9.3 11.1El crack de la gasolina de entrega en mayo vale de media 33,8 USD; el de noviembre, 9,3. La gasolina de verano es otro producto (más cara de producir) y la de invierno se vende menos, así que la curva de la gasolina tiene una forma estacional fija. El ciclo de la cabecera es, sobre todo, el paso de un mes de entrega al siguiente.
Riesgo: depende de la correlación de las patas
Un spread de dos patas con volatilidades y y correlación , con pesos iguales en logaritmos, tiene una volatilidad de
Cuanto más correladas están las patas, menos riesgo queda. Medido con los mismos contratos de un día a otro:
LOG = {
"crack_gasolina": lambda p: np.log(42 * p.PEG / p.PEC),
"crack_gasoleo": lambda p: np.log(42 * p.PEH / p.PEC),
"spark": lambda p: np.log(p.ELB / (7 * p.GNA)),
"crush": lambda p: np.log((0.022 * p.GRH + 0.11 * p.GRA)
/ (p.GRS / 100)),
"trigo_maiz": lambda p: np.log(p.GRT / p.GRM),
"aceite_harina": lambda p: np.log(p.GRA / p.GRH),
}
filas = {}
for nombre, (ms, meses, _) in SPREADS.items():
nivel, cambio, mes = patas_mes(ms, meses)
antes = nivel.shift(1) # ayer, contrato de ayer
r = np.log((antes + cambio) / antes).dropna() # mismo contrato
s = LOG[nombre](antes + cambio) - LOG[nombre](antes)
a, b = ms[0], ms[-1]
filas[nombre] = {"pata_a": a, "vol_a": r[a].std() * 252 ** 0.5,
"pata_b": b, "vol_b": r[b].std() * 252 ** 0.5,
"correlacion": r[a].corr(r[b]),
"vol_spread": s.std() * 252 ** 0.5}
riesgo = pd.DataFrame(filas).T
num = ["vol_a", "vol_b", "correlacion", "vol_spread"]
riesgo[num] = riesgo[num].astype(float).round(2)
riesgo pata_a vol_a pata_b vol_b correlacion vol_spread
crack_gasolina PEG 0.39 PEC 0.36 0.91 0.16
crack_gasoleo PEH 0.38 PEC 0.36 0.93 0.14
spark ELB 0.48 GNA 0.42 0.81 0.29
crush GRS 0.19 GRA 0.27 0.71 0.10
trigo_maiz GRT 0.29 GRM 0.24 0.83 0.17
aceite_harina GRA 0.28 GRH 0.25 0.49 0.27Las patas del crack se mueven con una correlación de 0,91 (gasolina) y 0,93 (gasóleo): el spread conserva un 14-16 % de volatilidad anual frente al 36-39 % de cada pata. El spark, con 0,81, conserva un 29 %; el aceite frente a la harina, con 0,49, un 27 %: casi tanto como cada pata. En el crush la tabla enseña la soja y el aceite (0,71), pero el spread incluye las tres patas, y su volatilidad es la menor de todas, un 10 %. Un spread intercommodity tiene más riesgo que uno de calendario del mismo mercado (lección 3.2.1), porque dos productos distintos se parecen menos que dos meses del mismo.
Ese riesgo menor también se refleja en el margen, aunque menos que en los calendarios. CME explica que, por la compensación entre las patas, un crack puede requerir hasta un 30 % menos de capital que una posición simple en gasolina o gasóleo, y en su ejemplo con micros el crack pide 480 USD frente a una pata de gasóleo que, sola, pide 599: un 19 % menos. Un calendario de crudo de los datos pide el 12 % del margen simple, no el 70-80 %.
Reversión: lo plantado frente a lo medido
El vínculo físico sugiere que los márgenes de proceso revierten. En los datos lo hacen por construcción: el componente propio de cada spread es un proceso de Ornstein-Uhlenbeck con velocidad , y su vida media en sesiones es . La pregunta útil es si un método estándar la recupera. El método es el de la lección 2.1.5: un AR(1) del nivel del log-spread, al que antes se le quita su media por mes de entrega (la forma estacional de la curva del apartado anterior), y un contraste ADF de raíz unitaria:
from statsmodels.tsa.stattools import adfuller
ver = pd.read_csv("spreads3_verdad.csv", index_col="spread")
def adf_p(x):
"""p-valor del ADF; result_object=False conserva la tupla de
siempre y evita el FutureWarning de statsmodels 0.15."""
return adfuller(x, result_object=False)[1]
def vida_media(nivel, clave):
"""AR(1) del nivel menos su media por clave (mes de entrega)."""
x = (nivel - nivel.groupby(clave).transform("mean")).dropna()
b = np.polyfit(x.shift(1).dropna(), x.diff().dropna(), 1)[0]
return np.log(2) / -b if b < 0 else np.inf
res = {}
for nombre, (ms, meses, _) in SPREADS.items():
nivel, _, mes = patas_mes(ms, meses)
s = LOG[nombre](nivel)
clave = mes.str[5:]
x = (s - s.groupby(clave).transform("mean")).dropna()
res[nombre] = {"plantada": ver.loc[nombre, "vida_media_sesiones"],
"medida": vida_media(s, clave),
"p_adf": adf_p(x),
"control": ver.loc[nombre, "control"]}
pd.DataFrame(res).T.astype({"plantada": float, "medida": float,
"p_adf": float}).round(
{"plantada": 1, "medida": 1, "p_adf": 4}) plantada medida p_adf control
crack_gasolina 43.7 36.6 0.0000 False
crack_gasoleo 58.2 40.5 0.0000 True
spark 29.1 17.5 0.0000 False
crush 69.9 54.6 0.0000 False
trigo_maiz 174.7 71.4 0.0003 False
aceite_harina 218.3 157.6 0.0522 TrueEl ADF rechaza la raíz unitaria en cinco de los seis spreads y el AR(1) recupera el orden de magnitud de la reversión, pero con un sesgo sistemático: todas las vidas medias medidas son más cortas que las plantadas. En el spark, 17,5 sesiones frente a 29,1; en el crack de la gasolina, 36,6 frente a 43,7; en el trigo frente al maíz, 71,4 frente a 174,7, menos de la mitad. Es el sesgo del estimador autorregresivo en muestras finitas que la lección 2.1.5 midió con simulaciones, al que se suma que cada spread mezcla su componente propio con restos de los factores comunes de las patas.
El aceite frente a la harina es el caso difícil: revierte (vida media plantada de 218 sesiones, casi un año), pero tan despacio que el ADF se queda en p = 0,052 con 16 años de datos. Un contraste al 5 % lo daría por no estacionario.
Un ratio sin ancla: oro frente a plata
El ratio oro/plata es el ejemplo clásico de valor relativo: cuántas onzas de plata compra una onza de oro. En los datos es, por construcción, un paseo aleatorio con episodios de deriva: no hay nada que lo devuelva a una media.
def simple(mercado):
c = cur[cur["mercado"] == mercado].merge(ctr, on=["mercado",
"contrato"])
c = c[c["fecha"] < c["fecha_roll"]].sort_values(
["fecha", "vencimiento"])
return c.groupby("fecha")["liquidacion"].first()
ratio = simple("MTO") / simple("MTP")
lr = np.log(ratio)
uno = pd.Series("", index=lr.index)
print(f"Ratio oro/plata: de {ratio.min():.1f} a {ratio.max():.1f}")
print(f"Toda la muestra: vida media {vida_media(lr, uno):.0f} "
f"sesiones, p ADF {adf_p(lr):.2f}")
for ini in range(2010, 2023, 3):
t = lr.loc[str(ini):str(ini + 2)]
print(f"{ini}-{ini + 2}: vida media "
f"{vida_media(t, uno.loc[t.index]):4.0f}, "
f"p ADF {adf_p(t):.2f}")Ratio oro/plata: de 39.5 a 108.2
Toda la muestra: vida media 698 sesiones, p ADF 0.58
2010-2012: vida media 183, p ADF 0.37
2013-2015: vida media 61, p ADF 0.11
2016-2018: vida media 153, p ADF 0.84
2019-2021: vida media 136, p ADF 0.66
2022-2024: vida media 111, p ADF 0.51El ratio va de 39,5 a 108,2 en 16 años. El AR(1) de toda la muestra da una vida media de 698 sesiones (casi tres años), que suena a «reversión lenta»; en ventanas de tres años da entre 61 y 183 sesiones, que suena a reversión operable. Pero el ADF no rechaza la raíz unitaria ni en toda la muestra (p = 0,58) ni en ninguna ventana (de 0,11 a 0,84). La regresión del cambio sobre el nivel casi siempre da un coeficiente negativo en una muestra finita, aunque el proceso no revierta: la vida media sale finita porque el estimador está sesgado, no porque haya reversión. Es el punto destacado de la figura anterior.
Lo que esto cambia en un sistema
- Unidades, primero. Cada pata en su unidad de cotización, convertida antes de restar; un error de unidades da otro spread.
- El mismo mes en todas las patas. Si no, el spread lleva dentro un calendario. Con calendarios de entrega distintos, solo valen los meses comunes.
- Ratios enteros. La fórmula da fracciones; los contratos, no. 10:11:9 en el crush, 25:14 en el spark: con pocos contratos, el redondeo deja exposición a una pata. Las coberturas por nominal, DV01 o volatilidad son el tema de la lección 3.2.4.
- Reversión contrastada. La vida media medida es más corta que la real, y un ratio sin ancla también tiene una vida media medida. La reversión se contrasta y se explica.
- Forma estacional no es deriva. El nivel por mes de entrega está descontado; la deriva, si existe, se estudia aparte (lección 3.2.6). El backtest por patas, con sus costes y el riesgo de ejecutar una pata sin la otra, es el de la lección 3.2.5.
Ejercicios
- Construye el P&L diario en USD del crack 3:2:1 (dos contratos de gasolina y uno de gasóleo contra tres de crudo) y comprueba que es la suma de dos cracks de gasolina y uno de gasóleo. Compara su riesgo en 2025 con la suma de los riesgos de los tres cracks por separado.
Solución
nivel, cambio, mes = patas_mes(["PEG", "PEH", "PEC"])
usd = 1000 * (2 * 42 * cambio.PEG + 42 * cambio.PEH
- 3 * cambio.PEC) # 2 PEG + 1 PEH − 3 PEC
gas = 1000 * (42 * cambio.PEG - cambio.PEC) # crack 1:1
die = 1000 * (42 * cambio.PEH - cambio.PEC)
print("Igual a 2 cracks de gasolina + 1 de gasóleo:",
np.allclose(usd.dropna(), (2 * gas + die).dropna()))
r25 = pd.DataFrame({"usd": usd, "gas": gas, "die": die}).loc["2025"]
print(f"Riesgo diario del 3:2:1: {r25['usd'].std():,.0f} USD")
print(f"Suma de los riesgos por separado: "
f"{2 * r25['gas'].std() + r25['die'].std():,.0f} USD")
print(f"Correlación de los dos cracks: {gas.corr(die):.2f}")Igual a 2 cracks de gasolina + 1 de gasóleo: True
Riesgo diario del 3:2:1: 1,982 USD
Suma de los riesgos por separado: 2,529 USD
Correlación de los dos cracks: 0.18El 3:2:1 es, exactamente, dos cracks de gasolina más uno de gasóleo. Su riesgo diario en 2025 (1.982 USD) es menor que la suma de los riesgos por separado (2.529 USD) porque los dos cracks están poco correlados entre sí (0,18): sus componentes propios son independientes en el modelo de los datos, y solo comparten el crudo, que se cancela en los dos. Combinar márgenes de productos distintos diversifica.
- Con los tamaños de los contratos de electricidad (800 MWh) y gas (10.000 MMBtu) de la ficha y un heat rate de 7, busca el ratio entero de contratos que replica el spark spread con menos error, con hasta 25 contratos de electricidad.
Solución
mwh = esp.loc["ELB", "multiplicador"] # MWh por contrato
mmbtu = esp.loc["GNA", "multiplicador"] # MMBtu por contrato
exacto = 7 * mwh / mmbtu # GNA por ELB
print(f"Contratos de gas por contrato de electricidad: {exacto:.2f}")
mejor = []
for n_elb in range(1, 26):
n_gna = round(n_elb * exacto)
if n_gna:
error = n_gna / n_elb / exacto - 1
mejor.append((abs(error), n_elb, n_gna, error))
for _, a, b, e in sorted(mejor)[:4]:
print(f"{a:2d} ELB : {b:2d} GNA error {e:+.1%}")Contratos de gas por contrato de electricidad: 0.56
25 ELB : 14 GNA error +0.0%
16 ELB : 9 GNA error +0.4%
9 ELB : 5 GNA error -0.8%
18 ELB : 10 GNA error -0.8%Un contrato de electricidad de 800 MWh quema 5.600 MMBtu de gas con un heat rate de 7: 0,56 contratos de gas. El ratio exacto con números pequeños es 25 contratos de electricidad por 14 de gas; 16:9 deja un error del 0,4 % y 9:5, del 0,8 %. Con menos contratos el error crece: con 2:1, el gas se queda un 11 % corto. En una posición pequeña, el spark spread «exacto» no existe, y lo que sobra es una posición simple en gas.
- Una molturadora cubre su margen vendiendo el board crush (vende harina y aceite, compra soja). Explica qué riesgo cubre, qué riesgo no cubre y por qué el resultado depende del mes de entrega que elija.
Solución
Vender el crush fija hoy el margen de los meses que elija: si después la harina y el aceite bajan respecto a la soja, lo que pierde en el negocio físico lo gana en los futuros. Cubre el precio de los tres productos en Chicago, no su propio rendimiento (si sus plantas sacan menos harina por bushel que 0,022 toneladas, el margen real es otro), ni la base local (la diferencia entre el precio de su zona y el del contrato), ni los costes de operar, que no están en la fórmula. El mes importa porque solo seis meses son comunes a las tres patas y porque cada mes de entrega tiene su propio nivel de crush: la forma de la curva de cada producto (cosecha vieja frente a nueva) está descontada en cada contrato. La cobertura del mes de julio no fija el margen de octubre.
Respuesta plegada en cada pregunta
Autoevaluación
¿Por qué un crack de gasolina 1:1 se hace con un contrato de gasolina por cada contrato de crudo?
- APorque los dos cotizan en USD
- BPorque el contrato de gasolina (42.000 galones) equivale a 1.000 barriles, el mismo volumen que el de crudo
- CPorque la refinería produce la misma cantidad de gasolina que de crudo
- DPorque la bolsa obliga a operar las patas en la misma cantidad
Ver respuesta
Respuesta B. Porque el contrato de gasolina (42.000 galones) equivale a 1.000 barriles, el mismo volumen que el de crudo
Un barril tiene 42 galones: 42.000 galones son 1.000 barriles, el tamaño del contrato de crudo. Por eso el crack en USD por barril es 42 × precio del producto − precio del crudo, y un contrato de cada lado lo replica sin redondeos.
El board crush de CME se negocia como 10 contratos de soja, 11 de harina y 9 de aceite. ¿De dónde sale esa proporción?
- ADe la liquidez de cada contrato
- BDe los rendimientos físicos: 50.000 bushels dan 1.100 toneladas cortas de harina (11 contratos de 100) y 550.000 libras de aceite (9,17 contratos de 60.000, redondeado a 9)
- CDe igualar el nominal de las tres patas
- DEs una convención sin relación con el proceso
Ver respuesta
Respuesta B. De los rendimientos físicos: 50.000 bushels dan 1.100 toneladas cortas de harina (11 contratos de 100) y 550.000 libras de aceite (9,17 contratos de 60.000, redondeado a 9)
Un bushel de 60 libras da unas 44 libras de harina y 11 de aceite (guía del crush de CME). Con contratos de 5.000 bushels, 100 toneladas cortas y 60.000 libras, la proporción entera más cercana con pocos contratos es 10:11:9; el aceite queda un 2 % corto.
En los datos, la vida media medida de todos los spreads con reversión es menor que la plantada (por ejemplo, 17,5 frente a 29,1 sesiones en el spark). ¿Por qué?
- APorque los datos tienen errores
- BPor el sesgo del estimador AR(1) en muestras finitas: el coeficiente sale menor de lo real y la reversión parece más rápida (lección 2.1.5)
- CPorque la reversión se acelera con el tiempo
- DPorque las vidas medias plantadas están en días naturales
Ver respuesta
Respuesta B. Por el sesgo del estimador AR(1) en muestras finitas: el coeficiente sale menor de lo real y la reversión parece más rápida (lección 2.1.5)
El estimador por MCO del coeficiente autorregresivo está sesgado hacia abajo, más cuanto más lenta es la reversión respecto a la muestra. Además, la vida media medida mezcla el componente que revierte con otros factores del spread. Lo importante es el signo del error: un backtest calibrado con la vida media medida espera una reversión más rápida que la real.
El ratio oro/plata de los datos da una vida media de 698 sesiones en toda la muestra y de 61 a 183 en ventanas de tres años. ¿Qué se concluye?
- AQue revierte lentamente y se puede operar a plazos largos
- BQue revierte deprisa en ventanas cortas
- CNada a favor de la reversión: es un paseo aleatorio por construcción, el ADF no rechaza (p = 0,58) y un AR(1) estimado siempre da una vida media finita
- DQue hay que usar ventanas más cortas
Ver respuesta
Respuesta C. Nada a favor de la reversión: es un paseo aleatorio por construcción, el ADF no rechaza (p = 0,58) y un AR(1) estimado siempre da una vida media finita
La regresión del cambio sobre el nivel casi siempre da un coeficiente negativo en una muestra finita, aunque el proceso no revierta: la vida media sale finita por construcción del estimador. El contraste (ADF) es el que dice si hay reversión; aquí, en ninguna ventana rechaza al 5 %.
El crack de la gasolina de los datos vale de media 33,8 USD por barril en los contratos de mayo y 9,3 en los de noviembre. ¿Es una oportunidad estacional?
- ASí: comprar el crack en noviembre y venderlo en mayo
- BNo necesariamente: es la forma estacional de la curva (la gasolina de verano es otro producto y cotiza más cara), y el mercado ya la descuenta en cada contrato
- CSí, porque la diferencia es de más de 20 USD
- DNo, porque el crack de la gasolina no tiene estacionalidad
Ver respuesta
Respuesta B. No necesariamente: es la forma estacional de la curva (la gasolina de verano es otro producto y cotiza más cara), y el mercado ya la descuenta en cada contrato
El nivel medio por mes de entrega es una propiedad de cada contrato, conocida desde que se lista. Ganar con ella exigiría que el contrato de mayo subiera mientras se mantiene, y eso es una deriva, otra cosa. Si existe y si es operable lo estudia la lección 3.2.6.
Ideas clave
- Un spread intercommodity enfrenta mercados distintos: los de proceso (crack, spark, crush) tienen un vínculo físico; los de valor relativo (oro/plata, trigo/maíz), solo de sustitución o de historia.
- Antes de restar hay que convertir unidades (42 galones por barril, heat rate de 7 MMBtu por MWh, 0,022 toneladas de harina y 11 libras de aceite por bushel) y usar el mismo mes de entrega en todas las patas.
- El ratio de contratos sale de los tamaños: 1:1 en el crack, 10:11:9 en el crush, 25:14 en el spark de los datos; los ratios pequeños redondean.
- El riesgo del spread depende de la correlación de sus patas: un 16 % anual en el crack de la gasolina (patas con 0,91 de correlación) frente a un 27 % en aceite/harina (0,49).
- La vida media medida es menor que la plantada en todos los spreads que revierten; el control oro/plata, un paseo aleatorio, da una vida media finita y un ADF que no rechaza.
Recursos
Documentación
- CME Group: Trading Crack Spreads (crack 1:1 y 3:2:1, conversión a barriles, compensación de márgenes)
- CME Group: An Introduction to Crack Spreads (ratios 1:1, 3:2:1, 5:3:2 y 2:1:1)
- CME Group: Soybean Crush Reference Guide (rendimientos por bushel y board crush)
- CME Group: Soybean Crush Spreads (10 soja, 11 harina, 9 aceite)
- U.S. EIA: An introduction to spark spreads (2013)
- U.S. EIA: Today in Energy, Daily Prices (spark spread con heat rate de referencia de 7.000 Btu/kWh)