Lesson 3.1.1 · 60 min
Main futures contracts
A diversified futures system trades indices, bonds, energy, metals, agriculturals and currencies at the same time, and each contract is a different object: another size, another tick, another currency, another delivery calendar. This lesson goes through the 23 synthetic futures of the level, which imitate the most traded real contracts, and measures what matters before trading any of them: how much risk a contract brings, how much it costs, when you have to get out and where the liquidity is.
- Module
- 3.1 · Futures in depth
- Duration
- 60 min
- Prerequisites
- 1.2.3 · Futures: specifications, margin, roll and contango, 1.4.6 · Continuous futures series: roll rules, difference adjustment and ratio adjustment, 1.1.5 · Costs, settlement, leverage and margin
- Author
- Jesús Cuesta
- Updated
Level 3 lessons
3.1 · Futures in depth
3.2 · Futures spreads
- 3.2.1 Calendar spreads, butterflies and condors
- 3.2.2 Intercommodity spreads: crack, spark and crush
- 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
- 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
- 3.2.5 Spread data and backtesting: listed, synthetic and legged
- 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
3.3 · Advanced forex
3.4 · Option theory and pricing
- 3.4.1 Payoffs, put-call parity and arbitrage bounds
- 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
- 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
- 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
- 3.4.5 Smile, skew and term structure; volatility surface with SVI
- 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
- 3.4.7 Advanced models: Heston, SABR and local volatility
3.5 · Option structures in Python
- 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
- 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
- 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
- 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
- 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
3.6 · Systematic volatility and options strategies
3.7 · Crypto derivatives and specific strategies
- 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
- 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
- 3.7.3 Crypto options, on-chain data and DeFi
- 3.7.4 ccxt for trading on multiple exchanges
- 3.7.5 MiCA: the EU framework for crypto-assets and their service providers (CASPs)
Twenty-three spec sheets
A diversified futures system does not trade “futures”: it trades a US index quoted in points, a European bond quoted as a percentage of par, crude oil in dollars per barrel and corn in cents per bushel. Each one has its specifications (lesson 1.2.3) and each set of specifications changes what a point is worth, what trading costs and when you have to get out. Before writing the first rule, all of them have to be put in the same unit.
The level data has 23 fictitious markets that imitate the most traded real contracts in each sector, with every listed expiry in each session from 2010 to 2025, and six micros and minis that use the prices of their base market with a tenth of the multiplier. It is synthetic data: the names are made up and the magnitudes (multipliers, ticks, margins, volatilities) are plausible, but it is not market data.
import numpy as np
import pandas as pd
esp = pd.read_csv("futuros3_especificaciones.csv",
index_col="mercado")
base = esp[esp["mercado_base"].isna()]
print(len(base), "markets and", len(esp) - len(base), "micro or mini")
base.groupby("sector")["imita"].apply(
lambda s: ", ".join(x.split(" (")[-1].rstrip(")") for x in s))23 markets and 6 micro or mini
sector
agricolas ZC, ZW, ZS, ZM, ZL
divisas 6E
energia CL, RB, HO, NG, PJM Western Hub Peak mensual
indices ES, NQ, RTY, FESX, MEFF
metales GC, SI
tipos ZT, ZF, ZN, ZB, FGBL
Name: imita, dtype: strSix sectors. Indices: the E-mini S&P 500 (ES), the E-mini Nasdaq-100 (NQ), the E-mini Russell 2000 (RTY), the Euro
Stoxx 50 (FESX) and MEFF's IBEX 35. Rates: the US Treasury 2-, 5- and 10-year notes and 30-year bond (ZT, ZF, ZN, ZB)
and the German Bund (FGBL). Energy: WTI crude oil (CL), gasoline (RB), heating oil (HO), natural gas (NG) and a monthly
electricity contract. Metals: gold (GC) and silver (SI). Agriculturals: corn (ZC), wheat (ZW), soybeans (ZS), soybean
meal and soybean oil (ZM, ZL). And one currency: the euro against the dollar (6E), here the “Vesperian evro”, QEV.
Mermaid source
flowchart LR F[Futures] --> FI[Financials] F --> MP[Commodities] FI --> IX[Indices] FI --> TP[Rates] FI --> DV[Currencies] MP --> EN[Energy] MP --> MT[Metals] MP --> AG[Agriculturals] IX --> EF[Cash] TP --> FA[Physical with notice] DV --> FS[Physical] EN --> FS MT --> FA AG --> FA
The way they settle at expiry splits the markets into three groups, and the group decides when a system that does not want the underlying has to get out: indices are cash-settled; the currency and energy (except the course's electricity, which is also cash-settled) are delivered; bonds, metals and agriculturals are delivered with a first notice day, from which the seller can give notice of delivery. The calendar section comes back to this.
cols = ["divisa", "multiplicador", "unidad_cotizacion", "tick",
"valor_tick"]
base.loc[["RVA", "RVH", "TPC", "TPE", "PEC", "GNA", "MTO", "GRM",
"DQE"], cols] divisa multiplicador unidad_cotizacion tick valor_tick
mercado
RVA USD 50.0 puntos de índice 0.250000 12.500
RVH QEV 10.0 puntos de índice 1.000000 10.000
TPC USD 1000.0 % del nominal 0.015625 15.625
TPE QEV 1000.0 % del nominal 0.010000 10.000
PEC USD 1000.0 USD/barril 0.010000 10.000
GNA USD 10000.0 USD/MMBtu 0.001000 10.000
MTO USD 100.0 USD/onza 0.100000 10.000
GRM USD 50.0 centavos/bushel 0.250000 12.500
DQE USD 125000.0 USD por evro 0.000050 6.250The multiplier turns the quote into money: 50 USD per point in RVA, 1,000 USD per percentage point in the 10-year
note TPC (a notional of 100,000 USD), 1,000 barrels in crude oil, 10,000 MMBtu in gas, 100 ounces in gold, 5,000
bushels in corn (50 USD per cent) and 125,000 evros in DQE. The tick of the 10-year note is half of a thirty-second
of a point (0.015625 = 15.625 USD), the convention of Treasury futures, and RVH and TPE are quoted in evros: what
they gain or lose has to be converted to dollars.
The specifications of the real Eurex and MEFF contracts, read on their official pages, match what the data imitates in the figures that can be compared:
| Real contract | Multiplier | Tick | Expiries | Expiry and settlement |
|---|---|---|---|---|
| E-mini S&P 500 (CME) | 50 USD × index | 0.25 points = 12.50 USD | Quarterly | Third Friday; cash, at the special opening quotation |
| EURO STOXX 50 (Eurex, FESX) | 10 EUR × index | 1 point = 10 EUR | The nearest twelve quarterly | Third Friday (close at 12:00 CET); cash |
| IBEX 35 (MEFF) | 10 EUR × index | Whole points, set by circular | Ten quarterly, two monthly and semi-annual | Third Friday; index average from 16:15 to 16:45; cash |
| Mini IBEX 35 (MEFF) | 1 EUR × index | 5 points = 5 EUR | Same as the IBEX 35 | Same as the IBEX 35 |
| Euro-Bund (Eurex, FGBL) | 100,000 EUR, 6% coupon | 0.01% = 10 EUR | The nearest three quarterly | Delivery on the 10th of the month; last trading two business days before |
The mini in the data (RVHM) has a tick of 1 point and MEFF's real one, of 5: it is one of the simplifications of the
data. Eurex also lists a EURO STOXX 50 micro of 1 EUR per point, and MEFF has delisted its Micro IBEX 35 effective
October 1, 2026 (circular C-EX-DF-2026-07). Specifications change: before trading a real contract you read the
exchange's, not a book's or a course's. The CME Group contracts that the other markets imitate (NQ, RTY, ZN, CL, NG,
GC, ZC, ZS, 6E and the micros) have their specifications in the rulebook of each exchange of the group; here only the
figures of the data are used.
Contracts are named with the market root, one letter per month and two digits for the year:
MESES = "FGHJKMNQUVXZ"
def descifra(contrato):
"""PECZ25 -> ('PEC', 2025, 12)."""
raiz, letra, anio = contrato[:-3], contrato[-3], contrato[-2:]
return raiz, 2000 + int(anio), MESES.index(letra) + 1
print(descifra("PECZ25"), descifra("GRMH26"), descifra("TPEU24"))
base[["meses", "meses_listados", "liquidacion_fisica"]].loc[
["RVA", "RVH", "TPC", "PEC", "GNA", "MTO", "GRM", "GRS"]]('PEC', 2025, 12) ('GRM', 2026, 3) ('TPE', 2024, 9)
meses meses_listados liquidacion_fisica
mercado
RVA HMUZ 13 False
RVH FGHJKMNQUVXZ 13 False
TPC HMUZ 9 True
PEC FGHJKMNQUVXZ 18 True
GNA FGHJKMNQUVXZ 24 True
MTO GJMQVZ 18 True
GRM HKNUZ 18 True
GRS FHKNQUX 16 TrueThe months tell which expiries exist: quarterly (HMUZ) in US indices, bonds and the currency; every month in the
Spanish index and in energy; six even months in gold (GJMQVZ); the harvest months in agriculturals (HKNUZ in corn,
FHKNQUX in soybeans). meses_listados is how far the curve goes: 9 months in bonds (three expiries), 18 in crude oil,
24 in gas. That an expiry is listed does not mean it trades: the liquidity section measures that.
Notional, tick and risk of a contract
Comparing the 23 markets needs one series per market: the contract held each day. The data has the roll rule of each
contract (fecha_roll, the session in which you move to the next one) and with it the series is built as in lesson
1.4.6, with the daily return always calculated with the same contract:
curvas = pd.read_parquet("futuros3_curvas.parquet")
ctr = pd.read_csv("futuros3_contratos.csv",
parse_dates=["vencimiento", "primer_aviso",
"fecha_alta", "fecha_roll"])
def que_se_tiene(fechas, mercado):
"""The first contract whose roll date is after each date (on
the roll date the next one is already held)."""
cm = ctr[ctr["mercado"] == mercado].reset_index(drop=True)
pos = np.searchsorted(cm["fecha_roll"].to_numpy(),
fechas.to_numpy(), side="right")
return pd.Series(cm["contrato"].to_numpy()[pos], index=fechas)
def serie(mercado):
"""Price and daily return (of the same contract) of the one
held: the return on the roll day is that of the old contract."""
c = curvas[curvas["mercado"] == mercado]
P = c.pivot(index="fecha", columns="contrato",
values="liquidacion")
tiene = que_se_tiene(P.index, mercado)
i = P.columns.get_indexer(tiene)
filas = np.arange(len(P))
precio = P.to_numpy()[filas, i]
ayer = np.r_[i[0], i[:-1]]
hoy_viejo = P.to_numpy()[filas, ayer]
antes = np.r_[np.nan, P.to_numpy()[filas[1:] - 1, ayer[1:]]]
return pd.DataFrame({"contrato": tiene.to_numpy(),
"precio": precio,
"ret": np.log(hoy_viejo / antes)},
index=P.index)
S = {m: serie(m) for m in base.index}
S["PEC"].loc["2025-11-07":"2025-11-14"] contrato precio ret
fecha
2025-11-07 PECZ25 76.42 0.015694
2025-11-10 PECZ25 75.86 -0.007355
2025-11-11 PECZ25 77.49 0.021259
2025-11-12 PECF26 75.59 0.012950
2025-11-13 PECF26 73.59 -0.026815
2025-11-14 PECF26 73.53 -0.000816On November 12, 2025, crude oil moves from PECZ25 to PECF26. The visible price jumps (from 77.49 to 75.59), but
that day's return, 1.30%, is the December contract's between the 11th and the 12th: the jump between contracts is
neither a gain nor a loss. With the series of each market, the notional of a contract is price × multiplier (× USD per
evro for those quoted in QEV), and its annual risk is the notional times the annual volatility of the return:
fx = pd.read_parquet("fx3_diario.parquet")
usd_qev = fx[fx["divisa"] == "QEV"].set_index("fecha")[
"usd_por_unidad"]
DIA = "2025-11-26"
def ficha_del_dia(dia, mercados):
filas = {}
for m in mercados:
b = esp.loc[m, "mercado_base"]
s = S[b if isinstance(b, str) else m]
cambio = usd_qev[dia] if esp.loc[m, "divisa"] == "QEV" else 1
precio = s.loc[dia, "precio"]
nominal = precio * esp.loc[m, "multiplicador"] * cambio
vol = s["ret"].loc[:dia].tail(252).std() * np.sqrt(252)
filas[m] = {"contrato": s.loc[dia, "contrato"],
"precio": precio, "nominal_usd": nominal,
"vol_anual": vol, "riesgo_usd": vol * nominal,
"tick_pb": 1e4 * esp.loc[m, "valor_tick"]
/ (precio * esp.loc[m, "multiplicador"])}
return pd.DataFrame(filas).T.infer_objects()
ficha = ficha_del_dia(DIA, esp.index)
ficha.loc[base.index].sort_values("riesgo_usd").round(
{"nominal_usd": 0, "vol_anual": 3, "riesgo_usd": 0,
"tick_pb": 2}) contrato precio nominal_usd vol_anual riesgo_usd tick_pb
mercado
TPB TPBH26 109.164062 109164.0 0.041 4453.0 0.72
TPA TPAH26 104.449219 208898.0 0.023 4730.0 0.37
GRM GRMH26 335.750000 16788.0 0.285 4790.0 7.45
TPC TPCH26 113.078125 113078.0 0.058 6513.0 1.38
GRT GRTH26 461.500000 23075.0 0.318 7349.0 5.42
GRH GRHF26 299.200000 29920.0 0.254 7606.0 3.34
GRA GRAF26 53.110000 31866.0 0.275 8750.0 1.88
TPE TPEH26 128.120000 174586.0 0.058 10112.0 0.78
GRS GRSF26 1095.750000 54788.0 0.204 11168.0 2.28
DQE DQEZ25 1.363950 170494.0 0.075 12826.0 0.37
TPD TPDH26 122.281250 122281.0 0.113 13824.0 2.56
RVE RVEZ25 6946.000000 94651.0 0.186 17629.0 1.44
ELB ELBF26 58.150000 46520.0 0.506 23540.0 8.60
PEC PECF26 76.690000 76690.0 0.307 23550.0 1.30
MTO MTOG26 2170.700000 217070.0 0.130 28326.0 0.46
PEG PEGF26 2.066000 86772.0 0.361 31362.0 0.48
GNA GNAF26 7.239000 72390.0 0.438 31690.0 1.38
MTP MTPH26 28.320000 141600.0 0.241 34115.0 1.77
PEH PEHF26 2.582400 108461.0 0.342 37094.0 0.39
RVC RVCZ25 3232.000000 161600.0 0.257 41496.0 0.31
RVH RVHZ25 19700.000000 268447.0 0.223 59866.0 0.51
RVA RVAZ25 7851.750000 392588.0 0.177 69320.0 0.32
RVB RVBZ25 26222.000000 524440.0 0.221 116098.0 0.10The table sorts the 23 markets by the risk of one contract, and the order looks nothing like that of the notional. The
2-year note (TPA) has a notional of 208,898 USD, almost that of gold, but with a volatility of 2.3% its risk is 4,730
USD a year; corn (GRM), with 16,788 USD of notional, has almost the same risk (4,790) because it moves 28.5% a year.
At the other end, a single RVB risks 116,098 USD a year. The header figure plots both dimensions on logarithmic
scales: each diagonal is a volatility, and the vertical distance between a bond and an index of similar notional is an
order of magnitude.
margen_usd = esp["margen_inicial"] * np.where(
esp["divisa"] == "QEV", usd_qev[DIA], 1)
ficha["margen_pct"] = 100 * margen_usd / ficha["nominal_usd"]
ficha["apalancamiento"] = ficha["nominal_usd"] / margen_usd
# how many daily standard deviations of the notional the margin covers
ficha["desv_diarias"] = (margen_usd * np.sqrt(252)
/ ficha["riesgo_usd"])
(ficha.loc[base.index, ["margen_pct", "apalancamiento",
"desv_diarias"]]
.sort_values("apalancamiento").round(2).iloc[[0, 1, 2, -3, -2, -1]]) margen_pct apalancamiento desv_diarias
mercado
ELB 15.69 6.37 4.92
GNA 11.74 8.52 4.26
PEG 7.95 12.58 3.49
DQE 1.99 50.15 4.21
TPB 1.65 60.65 6.42
TPA 0.48 208.90 3.36The initial margin goes from 15.69% of the notional in electricity to 0.48% in the 2-year note: leverage of 6 to 1 in one case and 209 to 1 in the other. But measured in risk it is almost the same: in every market in the table the margin covers between 3.4 and 6.4 daily standard deviations of the notional. The clearing house sets the margin by risk, so a contract's leverage tells how little it moves, not how risky it is. Lesson 3.1.3 takes apart how that margin is calculated and what happens when volatility changes.
Micros and minis: granularity
A contract is indivisible. With a small account and a reasonable risk target, the problem is not the margin but the size: a single contract can be too much risk. That is what micros and minis are for, which in the data have a tenth of the multiplier of their base market:
capital, riesgo_obj = 100_000, 0.20
micro = esp[esp["mercado_base"].notna()]
pares = pd.DataFrame({
"base": micro["mercado_base"],
"riesgo_base": ficha.loc[micro["mercado_base"],
"riesgo_usd"].to_numpy(),
"riesgo_micro": ficha.loc[micro.index, "riesgo_usd"],
}, index=micro.index)
pares["n_base"] = capital * riesgo_obj / pares["riesgo_base"]
pares["n_micro"] = capital * riesgo_obj / pares["riesgo_micro"]
pares["capital_min_base"] = pares["riesgo_base"] / riesgo_obj
pares.round({"riesgo_base": 0, "riesgo_micro": 0, "n_base": 2,
"n_micro": 1, "capital_min_base": -3}) base riesgo_base riesgo_micro n_base n_micro capital_min_base
mercado
RVAM RVA 69320.0 6932.0 0.29 2.9 347000.0
RVBM RVB 116098.0 11610.0 0.17 1.7 580000.0
RVCM RVC 41496.0 4150.0 0.48 4.8 207000.0
RVHM RVH 59866.0 5987.0 0.33 3.3 299000.0
PECM PEC 23550.0 2355.0 0.85 8.5 118000.0
MTOM MTO 28326.0 2833.0 0.71 7.1 142000.0With 100,000 USD and 20% annual risk (20,000 USD), the ideal position in RVA is 0.29 contracts. Buying one
multiplies the risk by 3.5; buying none leaves the market out. With the micro, the position is 2.9 contracts, which is
rounded to 3 with a small error. capital_min_base is the capital at which a single contract of the base market is
already the whole risk target: 347,000 USD in RVA and 580,000 in RVB. Below that, the market cannot be traded with
its large contract without concentrating the risk in it, which is why lesson 2.2.2 removed from the universe the
contracts that weighed too much relative to capital.
Granularity is not free:
def coste_lado_pb(m, ticks=0.5):
"""Half a tick of bid-ask spread plus the commission, per
contract and side, in basis points of the notional."""
usd = (ticks * esp.loc[m, "valor_tick"] + esp.loc[m, "comision"])
if esp.loc[m, "divisa"] == "QEV":
usd *= usd_qev[DIA]
return 1e4 * usd / ficha.loc[m, "nominal_usd"]
pd.DataFrame({m: {"base": coste_lado_pb(b), "micro": coste_lado_pb(m)}
for m, b in micro["mercado_base"].items()}).T.round(2) base micro
RVAM 0.21 0.34
RVBM 0.09 0.18
RVCM 0.28 0.59
RVHM 0.31 0.46
PECM 0.95 1.63
MTOM 0.33 0.58With half a tick of spread and the commission in the data, one side in RVAM costs 0.34 basis points of the notional
against 0.21 in RVA; in crude oil, 1.63 against 0.95. The micro's commission per contract (0.70 USD in RVAM) is a
third of the large one's (2.10), but it moves a tenth of the notional. For a low-turnover system it is a reasonable
price for being able to trade; for a high-turnover one, it can eat the edge.
Calendar: months, expiry, notice and delivery
Each contract has four dates: the listing (first listed day), the roll (when the system moves to the next one), the first notice day in physically delivered contracts with notice, and the expiry (last trading day):
Mermaid source
stateDiagram-v2 [*] --> Listed Listed --> Front: liquidity Front --> Roll: fecha_roll Roll --> Notice: first notice Roll --> Expired: cash Notice --> Delivery: long still in Notice --> Expired: closed Expired --> [*] Delivery --> [*]
cal = ctr.set_index("contrato").loc[
["RVAZ25", "PECF26", "TPCZ25", "GRMZ25"]]
dias = lambda a, b: (b - a).dt.days
pd.DataFrame({
"alta": cal["fecha_alta"].dt.date,
"roll": cal["fecha_roll"].dt.date,
"primer_aviso": cal["primer_aviso"].dt.date,
"vencimiento": cal["vencimiento"].dt.date,
"vida_dias": dias(cal["fecha_alta"], cal["vencimiento"]),
"roll_a_venc": dias(cal["fecha_roll"], cal["vencimiento"]),
}) alta roll primer_aviso vencimiento vida_dias roll_a_venc
contrato
RVAZ25 2024-11-01 2025-12-09 NaT 2025-12-19 413 10
PECF26 2024-07-01 2025-12-11 NaT 2025-12-19 536 8
TPCZ25 2025-03-01 2025-11-24 2025-11-28 2025-12-19 293 25
GRMZ25 2024-06-01 2025-11-13 2025-11-28 2025-12-12 559 29RVAZ25(index, cash) expires on the third Friday of December and has no notice: the roll, 10 calendar days before, is set by liquidity.PECF26(crude oil, physical delivery) is the January contract, but it expires on December 19: in crude oil, trading ends in the month before delivery. The roll is 8 days before.TPCZ25(10-year note) expires on December 19, but its first notice is November 28, the last business day of the previous month: from then on the seller can give notice of delivery. The roll in the data is November 24, 25 days before expiry.GRMZ25(corn) has its first notice on November 28 and expires on December 12; the roll is November 13, 10 sessions before the notice.
The figure puts three of them on the same axis. In all three, the stretch in which the contract has the largest open interest in its market starts a few days before it is held and ends a few days before the roll: open interest migrates before the system moves. In the 10-year note, the whole useful stretch ends three weeks before expiry. A backtest that keeps the contract “until it expires” because the price still exists is, in reality, trading in a contract without liquidity and with delivery risk.
Where the liquidity is
anio = curvas[curvas["fecha"] >= "2025-01-01"]
por_dia = anio.groupby(["mercado", "fecha"])[
["volumen", "interes_abierto"]].sum()
liq = por_dia.groupby("mercado").median()
liq["nominal_dia_musd"] = (liq["volumen"]
* ficha.loc[liq.index, "nominal_usd"]
/ 1e6)
liq.sort_values("nominal_dia_musd", ascending=False).round(0).astype(
int).iloc[[0, 1, 2, 3, -4, -3, -2, -1]] volumen interes_abierto nominal_dia_musd
mercado
RVA 1817640 2271914 713583
RVB 655680 239321 343865
TPC 1429191 4356144 161610
TPE 689645 1150384 120402
GRA 115254 450506 3673
GRH 118998 426642 3560
GRT 133398 422790 3078
ELB 2422 30075 113The notional traded per day (volume × notional of one contract, in millions of USD) separates the markets by several
orders of magnitude: RVA moves about 714,000 million a day and the 10-year note, 162,000; wheat, soybean meal and
soybean oil, between 3,100 and 3,700; electricity, 113 million, with a median of 2,422 contracts a day. These are
figures from a simulation, but the order (indices and bonds far above commodities, electricity far below) constrains
the capacity of a system (lesson 2.2.3): a large fund can have the same risk in every market only as long as its orders
are small against the volume of the thinnest one.
anio = anio.assign(tiene=False)
for m in base.index:
sel = anio["mercado"] == m
t = que_se_tiene(pd.DatetimeIndex(anio.loc[sel, "fecha"]), m)
anio.loc[sel, "tiene"] = (anio.loc[sel, "contrato"].to_numpy()
== t.to_numpy())
cuota = anio.groupby("mercado").apply(
lambda g: g.loc[g["tiene"], "volumen"].sum() / g["volumen"].sum())
(100 * cuota).groupby(base["sector"]).agg(["min", "max"]).round(1) min max
sector
agricolas 59.2 64.9
divisas 91.3 91.3
energia 34.2 61.7
indices 86.9 91.8
metales 80.2 82.0
tipos 84.8 94.6In indices, bonds and the currency, the contract held concentrates between 85% and 95% of the year's volume; in metals, around 80%; in agriculturals, from 59% to 65%. In energy, much less: between 34.2% in electricity and 61.7% in gasoline. Monthly energy contracts spend little time as the front contract and the volume is spread across several months (and across calendar spreads, lesson 3.2.1). For a system, this means that in energy the next contract is also liquid and you can choose where on the curve to be, a decision that lesson 3.1.2 connects with roll yield.
Twenty-three markets are not twenty-three bets
If the markets moved together, 23 markets would give little diversification. The average correlation of daily returns, within each sector and with the rest:
R = pd.DataFrame({m: S[m]["ret"] for m in base.index}).loc["2011":]
C = R.corr()
sector = base["sector"]
fuera_diag = ~np.eye(len(C), dtype=bool)
resumen = {}
for s in ["indices", "tipos", "energia", "metales", "agricolas"]:
f = (sector == s).to_numpy()
dentro = C.to_numpy()[np.ix_(f, f)][fuera_diag[np.ix_(f, f)]]
fuera = C.to_numpy()[np.ix_(f, ~f)]
resumen[s] = {"dentro": dentro.mean(),
"con_el_resto": fuera.mean()}
print(pd.DataFrame(resumen).T.round(2))
print("currency vs the rest:", round(C.loc["DQE"].drop("DQE").mean(), 2)) dentro con_el_resto
indices 0.82 0.00
tipos 0.76 -0.05
energia 0.41 0.03
metales 0.85 -0.02
agricolas 0.44 0.00
currency vs the rest: -0.0Within each sector the correlations are high: 0.82 among the five indices, 0.76 among the bonds, 0.85 between gold and silver. In energy and agriculturals they are lower (0.41 and 0.44) because they mix markets with their own factors (electricity, gas, wheat). Across sectors, in this data, they are practically zero. Two consequences:
- Five indices with a correlation of 0.82 are, for risk purposes, little more than one. A system that gives the same weight to each market has five times more risk in indices than in currencies without having decided it. Allocating risk first by sector and then within each one corrects it (lesson 3.1.4).
- Real diversification comes from combining sectors. In the data, the correlation across sectors is almost zero by construction of the generator; in real markets it is not, and it changes with the regime (that between equities and bonds, for example, has changed sign several times), so the diversification of a real system has to be estimated and monitored.
What this changes in your systems
- Everything in risk, nothing in contracts. A multi-market system decides how much risk it wants in each market and translates it into contracts with the notional and volatility of each one, in the account currency.
- The calendar is part of the strategy. Roll, first notice and expiry come from the specifications and belong in a table, not in the code: the rules change by contract and by exchange.
- Granularity or cost. With little capital, micros make it possible to trade markets that would otherwise be left out; with a lot, the large contracts are cheaper.
- Liquidity is not evenly spread. The markets at the bottom of the liquidity table limit the size of the whole system.
Exercises
- Calculate the notional of one
RVEcontract and oneRVHcontract on November 26, 2025 in evros and in dollars. Compare their volatility measured in evros with that of the return converted to dollars. Which one is relevant for the risk of a future?
Solution
fx_r = np.log(usd_qev).diff()
for m in ["RVE", "RVH"]:
r_qev = S[m]["ret"].loc[:DIA].tail(252)
r_usd = (r_qev + fx_r.reindex(r_qev.index)).dropna()
nom_qev = S[m].loc[DIA, "precio"] * esp.loc[m, "multiplicador"]
print(m, f"notional {nom_qev:,.0f} QEV = "
f"{nom_qev * usd_qev[DIA]:,.0f} USD;",
f"vol in QEV {r_qev.std() * np.sqrt(252):.3f},",
f"in USD {r_usd.std() * np.sqrt(252):.3f}")RVE notional 69,460 QEV = 94,651 USD; vol in QEV 0.186, in USD 0.199
RVH notional 197,000 QEV = 268,447 USD; vol in QEV 0.223, in USD 0.230A dollar investor who buys a future on a European index does not hold the notional in evros: only the daily result, which is settled in evros and converted to dollars (and the margin deposited). That is why the currency risk of a future is much smaller than that of buying the index in the cash market, and the relevant volatility is that of the return in evros, converted at the day's exchange rate. The one measured by adding the exchange rate return would be that of a cash investment in the European index. Lesson 3.3.3 deals with the currency risk of a multi-currency portfolio.
- With the
fichatable, how many contracts of each market would be needed for all of them to contribute 50,000 USD of annual risk? Which markets need less than one contract and why is it a problem?
Solution
n = 50_000 / ficha.loc[base.index, "riesgo_usd"]
print(n.round(2).sort_values().to_string())mercado
RVB 0.43
RVA 0.72
RVH 0.84
RVC 1.20
PEH 1.35
MTP 1.47
GNA 1.58
PEG 1.59
MTO 1.77
PEC 2.12
ELB 2.12
RVE 2.84
TPD 3.62
DQE 3.90
GRS 4.48
TPE 4.94
GRA 5.71
GRH 6.57
GRT 6.80
TPC 7.68
GRM 10.44
TPA 10.57
TPB 11.23The large indices need less than one contract for 50,000 USD of risk, and the short notes more than ten. Rounding the
RVB fraction to 0 leaves the market out; to 1, it gives it more than twice the requested risk. The solutions are to
raise the capital, use the micro or accept that some markets do not fit (the universe block of lesson 2.2.2).
- Explain why
PECF26expires in December 2025 and what would happen in a backtest that, to keep things simple, assumed that all contracts expire on the third Friday of the month in their code.
Solution
In crude oil, trading in a contract ends in the month before delivery (the rule in the data: three sessions before the 25th of the previous month), because delivery takes place throughout the contract month. A backtest that assumed it expired on the third Friday of January would hold the contract a month longer than possible: it would roll late, use prices of a contract that no longer trades (or make them up) and miscalculate the carry, which depends on the distance between expiries (lesson 3.1.2). The calendar of each contract has to come from its specifications.
Each answer is folded under its question
Self-assessment
On November 26, 2025, one RVA contract (multiplier 50 USD) is worth 392,588 USD and one TPA (2-year note, multiplier 2,000) is worth 208,898 USD. Which one adds more risk to a portfolio?
- ATPA, because its leverage (notional over margin) is ten times higher
- BBoth the same, because the notionals are similar
- CRVA: with an annual volatility of 17.7% against 2.3%, its annual risk is about 69,300 USD per contract against about 4,700
- DIt cannot be known without the initial margin
Show answer
Answer C. RVA: with an annual volatility of 17.7% against 2.3%, its annual risk is about 69,300 USD per contract against about 4,700
The risk of a contract is its notional times the volatility of its return. TPA has more leverage (209 to 1) because the clearing house asks little margin for an asset that moves little; matching the risk of one RVA would take almost fifteen TPA.
What does the code
GRMH26mean?- AThe corn contract (GRM) for delivery in March (H) 2026
- BThe 2026 corn contract that expires in June
- CCorn contract number 26 of the year
- DThe corn micro
Show answer
Answer A. The corn contract (GRM) for delivery in March (H) 2026
Market root, delivery month letter (F January, G February, H March, J April, K May, M June, N July, Q August, U September, V October, X November, Z December) and two digits for the year.
A 100,000 USD account wants an annual risk of 20%. One RVA contract brings about 69,300 USD of risk a year and its micro RVAM, a tenth of that. What is reasonable?
- ABuy one RVA: it is the closest to the target
- BNever trade indices
- CBuy one RVA and reduce the leverage with the margin
- DUse the micro: 2.9 micros get close to the target, while one RVA would take the risk to 69% of capital
Show answer
Answer D. Use the micro: 2.9 micros get close to the target, while one RVA would take the risk to 69% of capital
With 100,000 USD and 20% risk, the target is 20,000 USD a year: 0.29 RVA contracts. Rounding to 1 multiplies the risk by 3.5. The micro lets you get close (3 micros, about 20,800 USD), at the price of paying more commission per unit of notional (0.34 against 0.21 basis points per side in the data).
Why does a system exit a bond future before its first notice day instead of waiting for the last trading day?
- ABecause after the first notice the contract stops trading
- BBecause from the first notice a long can receive a delivery notice, and the liquidity is already in the next contract
- CBecause the margin is multiplied by ten
- DBecause the specifications require it
Show answer
Answer B. Because from the first notice a long can receive a delivery notice, and the liquidity is already in the next contract
In physically delivered contracts with notice, the seller can give notice of delivery from the first notice day: a long still holding can end up receiving the bond. In TPCZ25 the first notice is 21 days before expiry and the roll in the data, 25 days before.
The five indices of the course have an average correlation of 0.82 with each other. What does it imply for a system that gives them the same risk as five weakly correlated markets?
- AThat the risk of the indices adds up almost entirely: they are almost a single bet, and the portfolio has much more risk in that sector than it seems
- BNothing: five markets are five bets
- CThat only indices should be traded
- DThat correlation does not affect risk
Show answer
Answer A. That the risk of the indices adds up almost entirely: they are almost a single bet, and the portfolio has much more risk in that sector than it seems
The variance of a sum depends on the correlations. With 0.82, five equal positions have almost the risk of a single position five times larger. That is why diversified systems allocate risk by sector and not only by market (lesson 3.1.4).
Key points
- The specifications set everything except the price: multiplier, tick, months, last day and settlement method. The same “1 contract” goes from 16,788 USD of notional (corn) to 524,440 (RVB).
- What you compare across markets is the risk of one contract (notional × volatility), not the notional or the margin: in the data, from about 4,500 USD a year (2- and 5-year notes) to 116,098 (RVB).
- The initial margin covers 3 to 6 daily standard deviations of the notional: that is why a 2-year bond has leverage of 209 to 1 and electricity 6 to 1. Leverage does not measure risk.
- Micros and minis give granularity to small accounts (with 100,000 USD, 2.9 RVAM micros against 0.29 RVA) at the price of a higher cost per unit of notional.
- In physically delivered contracts you have to exit before the first notice day; in cash-settled ones, liquidity decides the roll. In energy, less than half the volume is in the contract you hold.
- The data is synthetic: the figures imitate the real contracts, but for trading the official specifications of each exchange are what counts.
Resources
Documentation
- Eurex: EURO STOXX 50 Index Futures (FESX), specifications
- Eurex: Euro-Bund Futures (FGBL), specifications
- MEFF: IBEX 35 futures (in Spanish)
- MEFF: Mini IBEX 35 futures (in Spanish)
- MEFF: circular C-EX-DF-2026-07 (underlyings of index derivatives; delisting of the Micro IBEX 35, in Spanish)
- CME Rulebook, chapter 358: E-mini S&P 500