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.

Fig. — Notional and annual risk of one contract of each of the course's 23 futures and its six micros and minis (26 November 2025)
Level 3 lessons
  1. 3.1 · Futures in depth

    1. 3.1.1 Main futures contracts
    2. 3.1.2 Futures curve: basis, cost of carry, contango and roll yield
    3. 3.1.3 Roll management, margins, SPAN and implied leverage
    4. 3.1.4 Diversified futures systems: Carver's approach
  2. 3.2 · Futures spreads

    1. 3.2.1 Calendar spreads, butterflies and condors
    2. 3.2.2 Intercommodity spreads: crack, spark and crush
    3. 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
    4. 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
    5. 3.2.5 Spread data and backtesting: listed, synthetic and legged
    6. 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
  3. 3.3 · Advanced forex

    1. 3.3.1 FX microstructure: OTC, ECN, last look and sessions
    2. 3.3.2 Carry and crash risk, momentum and value in currencies; baskets
    3. 3.3.3 Currency risk in multi-currency portfolios; execution via OANDA, IBKR or MT5
  4. 3.4 · Option theory and pricing

    1. 3.4.1 Payoffs, put-call parity and arbitrage bounds
    2. 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
    3. 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
    4. 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
    5. 3.4.5 Smile, skew and term structure; volatility surface with SVI
    6. 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
    7. 3.4.7 Advanced models: Heston, SABR and local volatility
  5. 3.5 · Option structures in Python

    1. 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
    2. 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
    3. 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
    4. 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
    5. 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
  6. 3.6 · Systematic volatility and options strategies

    1. 3.6.1 Systematic premium selling: put-write, strangles and their tail risk
    2. 3.6.2 Delta hedging, gamma scalping and dispersion
    3. 3.6.3 VIX and VSTOXX strategies: volatility futures and ETPs
    4. 3.6.4 Options backtesting: data, fills and event adjustments
  7. 3.7 · Crypto derivatives and specific strategies

    1. 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
    2. 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
    3. 3.7.3 Crypto options, on-chain data and DeFi
    4. 3.7.4 ccxt for trading on multiple exchanges
    5. 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.

IN [01]python
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))
OUT [01]The specifications of the 23 markets
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: str

Six 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.

FuturesFinancialsCommoditiesIndicesRatesCurrenciesEnergyMetalsAgriculturalsCashPhysical with noticePhysical
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.

IN [02]python
cols = ["divisa", "multiplicador", "unidad_cotizacion", "tick",
        "valor_tick"]
base.loc[["RVA", "RVH", "TPC", "TPE", "PEC", "GNA", "MTO", "GRM",
          "DQE"], cols]
OUT [02]Multiplier, tick and tick value
        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.250

The 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 contractMultiplierTickExpiriesExpiry and settlement
E-mini S&P 500 (CME)50 USD × index0.25 points = 12.50 USDQuarterlyThird Friday; cash, at the special opening quotation
EURO STOXX 50 (Eurex, FESX)10 EUR × index1 point = 10 EURThe nearest twelve quarterlyThird Friday (close at 12:00 CET); cash
IBEX 35 (MEFF)10 EUR × indexWhole points, set by circularTen quarterly, two monthly and semi-annualThird Friday; index average from 16:15 to 16:45; cash
Mini IBEX 35 (MEFF)1 EUR × index5 points = 5 EURSame as the IBEX 35Same as the IBEX 35
Euro-Bund (Eurex, FGBL)100,000 EUR, 6% coupon0.01% = 10 EURThe nearest three quarterlyDelivery 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:

IN [03]python
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"]]
OUT [03]Month codes and listed contracts
('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                True

The 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:

IN [04]python
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"]
OUT [04]The contract held in each market
           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.000816

On 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:

IN [05]python
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})
OUT [05]USD notional and annual risk of a contract
        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.10

The 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.

IN [06]python
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]])
OUT [06]Initial margin: percentage of notional and leverage
         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.36

The 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:

IN [07]python
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})
OUT [07]Contracts for 20% risk with 100,000 USD
        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.0

With 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:

IN [08]python
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)
OUT [08]What a micro costs in basis points
      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.58

With 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):

liquidityfecha_rollfirst noticecashlong still inclosedListedFrontRollNoticeExpiredDelivery
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 --> [*]
IN [09]python
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"]),
})
OUT [09]Calendar of four contracts
                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           29
  • RVAZ25 (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.
Fig. — The last 140 days of three contracts: when they have the largest open interest, when they are held, the roll, the first notice and the expiry

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

IN [10]python
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]]
OUT [10]Volume and open interest by market in 2025
         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               113

The 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.

IN [11]python
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)
OUT [11]Distribution of volume along the curve
            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.6

In 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:

IN [12]python
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))
OUT [12]Correlations within each sector and across sectors
           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.0
Fig. — Mean correlation of daily returns within each sector and with the other markets, 23 synthetic futures (2011-2025)

Within 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

  1. Calculate the notional of one RVE contract and one RVH contract 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
IN [13]python
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}")
OUT [13]Risk of the indices in evros and in dollars
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.230

A 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.

  1. With the ficha table, 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
IN [14]python
n = 50_000 / ficha.loc[base.index, "riesgo_usd"]
print(n.round(2).sort_values().to_string())
OUT [14]Contracts for the same risk
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.23

The 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).

  1. Explain why PECF26 expires 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

  1. 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.

  2. What does the code GRMH26 mean?

    • 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.

  3. 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).

  4. 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.

  5. 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

Data