Lesson 3.1.3 · 65 min

Roll management, margins, SPAN and implied leverage

Holding a future forces two things a stock does not: switching contracts every few months and always having cash for the margin. This lesson measures in the synthetic data when liquidity migrates and how much rolling costs, builds a scenario-based margin calculation like the clearing houses', shows what happens to margin in a crisis and calculates the leverage of a diversified portfolio, which is much higher than its margin suggests.

Fig. — Notional and initial margin by sector of a 23-future synthetic portfolio with equal risk per market (USD 5 million, 20% risk target, 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)

When to roll

A future expires; a futures position does not have to. Keeping it forces you to roll: close the contract held and open the next one before the first stops being useful. Lesson 1.4.6 dealt with the roll from the data side (how to splice a continuous series). Here it is dealt with from the trading side: when, at what cost and with what risk. The rules of the synthetic data set the roll date of each contract from a reference in its specifications:

IN [01]python
import numpy as np
import pandas as pd

curvas = pd.read_parquet("futuros3_curvas.parquet")
ctr = pd.read_csv("futuros3_contratos.csv",
                  parse_dates=["vencimiento", "primer_aviso",
                               "fecha_alta", "fecha_roll"])
esp = pd.read_csv("futuros3_especificaciones.csv",
                  index_col="mercado")
MERCADOS = list(esp.index[esp["mercado_base"].isna()])
reglas = esp.loc[MERCADOS, ["meses", "referencia_roll", "dias_roll"]]
reglas["rolls_ano"] = reglas["meses"].str.len()
reglas.loc[["RVA", "RVH", "TPC", "PEC", "GNA", "MTO", "GRM", "DQE"]]
OUT [01]Data and roll rules
                meses referencia_roll  dias_roll  rolls_ano
mercado
RVA              HMUZ     vencimiento          8          4
RVH      FGHJKMNQUVXZ     vencimiento          2         12
TPC              HMUZ    primer_aviso          3          4
PEC      FGHJKMNQUVXZ     vencimiento          6         12
GNA      FGHJKMNQUVXZ     vencimiento          4         12
MTO            GJMQVZ    primer_aviso          3          6
GRM             HKNUZ    primer_aviso         10          5
DQE              HMUZ     vencimiento          5          4

Physically delivered contracts with notice (bonds, metals, agriculturals) roll a number of sessions before the first notice: 3 in the 10-year note and gold, 10 in corn. The rest, before expiry: 8 sessions in RVA, 2 in the Spanish index (which has monthly contracts), 6 in crude oil. The number of rolls a year depends on the months that are held: 4 in the quarterly ones, 12 in energy, 5 in corn, 6 in gold.

yesnoyesnoCurrent contractPhysical with notice?Roll before noticeLiquidity migrated?Roll on migrationWaitSpread order
Mermaid source
flowchart TB
  A[Current contract] --> B{Physical with notice?}
  B -- yes --> C[Roll before notice]
  B -- no --> D{Liquidity migrated?}
  D -- yes --> E[Roll on migration]
  D -- no --> F[Wait]
  C --> G[Spread order]
  E --> G

A system's rule does not have to be the data's, but it has to meet two conditions: exit before the first notice when there is one, and be in the contract that has the liquidity. The second one can be measured:

IN [02]python
sesiones = pd.DatetimeIndex(sorted(curvas["fecha"].unique()))
oi = curvas.pivot_table(index="fecha", columns="contrato",
                        values="interes_abierto")

def migracion(mercado, desfases=range(-12, 4)):
    """Share of open interest of the contract being left (against
    the next one), by sessions relative to its roll date."""
    cm = ctr[(ctr["mercado"] == mercado)
             & (ctr["fecha_roll"].between("2011-01-01",
                                          "2024-12-31"))]
    sig = ctr[ctr["mercado"] == mercado]["contrato"].to_numpy()
    siguiente = dict(zip(sig[:-1], sig[1:]))
    filas = []
    for _, c in cm.iterrows():
        p = sesiones.searchsorted(c["fecha_roll"])
        for d in desfases:
            f = sesiones[p + d]
            a = oi.at[f, c["contrato"]]
            b = oi.at[f, siguiente[c["contrato"]]]
            filas.append((d, a / (a + b)))
    return pd.DataFrame(filas, columns=["d", "cuota"]).groupby(
        "d")["cuota"].mean()

mig = pd.DataFrame({m: migracion(m)
                    for m in ["RVA", "TPC", "PEC", "GRM"]})
(100 * mig.loc[[-12, -8, -5, -3, -1, 0, 1, 3]]).round(1)
OUT [02]Open interest of the expiring contract around the roll
      RVA   TPC   PEC   GRM
d
-12  92.6  92.6  64.5  68.0
-8   84.4  92.6  62.5  44.0
-5   41.2  89.9  33.3  14.2
-3   12.7  48.4   9.1   4.1
-1    6.0   3.0   2.1   2.0
 0    5.7   2.0   2.0   2.0
 1    3.5   1.2   1.2   1.2
 3    1.3   0.4   0.4   0.4
Fig. — Open interest migration around the roll: share of the expiring contract against the next one, average of the 2011-2024 rolls

The table and the figure average all the rolls from 2011 to 2024. In corn, 12 sessions before the roll the expiring contract still has 68.0% of the open interest, 5 sessions before only 14.2%, and the migration is over before the roll; in crude oil, from 64.5% to 33.3% over the same sessions. In the 10-year note, the old contract keeps 92.6% until 8 sessions before and the switch is concentrated in three sessions (from 89.9% at −5 to 3.0% at −1): it is the pattern of real bonds, which almost all roll on the same days before the first notice. On the roll date, the expiring contract has between 2.0% and 5.7% of the open interest left. Rolling later means trading in a contract that is emptying.

The roll as a calendar spread

Rolling is selling one contract and buying the next at the same time: a calendar spread. Exchanges quote them as an instrument of their own, with their own book, and a spread order is executed as a single trade. The data has the close of the spread book of six markets together with the legs:

IN [03]python
sp = pd.read_parquet("spreads3_cotizados.parquet")
sp["tick"] = esp["tick"].reindex(sp["mercado"]).to_numpy()
sp["libro"] = (sp["ask"] - sp["bid"]) / sp["tick"]
sp["patas"] = ((sp["ask_cerca"] - sp["bid_lejos"])
               - (sp["bid_cerca"] - sp["ask_lejos"])) / sp["tick"]
sp.groupby("mercado")[["libro", "patas"]].mean().round(2)
OUT [03]The spread book against the two legs
         libro  patas
mercado
GNA       1.25   2.81
GRM       1.25   2.79
MTO       1.24   2.76
PEC       1.25   2.77
RVA       1.22   2.83
TPC       1.25   2.78

The spread book trades with an average bid-ask spread of 1.22 to 1.25 ticks; the spread built with the two legs (selling at the bid of one and buying at the ask of the other), 2.76 to 2.83. It is the known answer of the data: the spread book quotes at 1 tick 75% of the time and each leg at 1-3 ticks. Rolling by legs costs more than twice as much and also exposes you to legging risk: if you sell the old one and the price moves before you buy the new one, the roll comes out worse. Lesson 3.2.5 measures that risk with minute data. In annual cost:

IN [04]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 precio_que_se_tiene(mercado, dia):
    cm = ctr[ctr["mercado"] == mercado].reset_index(drop=True)
    k = cm["contrato"].iloc[cm["fecha_roll"].searchsorted(
        pd.Timestamp(dia), side="right")]
    fila = curvas[(curvas["fecha"] == dia)
                  & (curvas["contrato"] == k)]
    return fila["liquidacion"].iloc[0]

filas = {}
for m in ["RVA", "TPC", "PEC", "GNA", "MTO", "GRM"]:
    e = esp.loc[m]
    nominal = precio_que_se_tiene(m, DIA) * e["multiplicador"]
    medio_libro = sp.loc[sp["mercado"] == m, "libro"].mean() / 2
    medio_patas = sp.loc[sp["mercado"] == m, "patas"].mean() / 2
    por_roll = lambda t: t * e["valor_tick"] + 2 * e["comision"]
    filas[m] = {"rolls": len(e["meses"]),
                "libro_pb_ano": 1e4 * len(e["meses"])
                * por_roll(medio_libro) / nominal,
                "patas_pb_ano": 1e4 * len(e["meses"])
                * por_roll(medio_patas) / nominal}
pd.DataFrame(filas).T.round(2)
OUT [04]What rolling costs over a year
     rolls  libro_pb_ano  patas_pb_ano
RVA    4.0          1.21          2.23
TPC    4.0          4.51          8.74
PEC   12.0         16.85         28.74
GNA   12.0         17.79         30.71
MTO    6.0          2.96          5.06
GRM    5.0         36.70         65.32

Per roll you pay half a bid-ask spread and two commissions (one per leg). Per year, with each market's rolls and the notional of November 26, 2025: 1.21 basis points in RVA with the book (2.23 by legs), 16.85 in crude oil (28.74), 17.79 in gas (30.71) and 36.70 in corn (65.32). In indices it is irrelevant; in a market with small contracts, an expensive tick and twelve rolls a year, the roll is a fixed cost of holding the position that a backtest without roll costs does not see (lesson 2.4.7).

Rolling earlier or later

With the roll you also choose where on the curve to be. Rolling earlier means holding a farther contract:

IN [05]python
def serie_con_roll(mercado, antes):
    """Returns of the contract held if you roll `antes` sessions
    before the first notice (or the expiry)."""
    c = curvas[curvas["mercado"] == mercado]
    P = c.pivot(index="fecha", columns="contrato",
                values="liquidacion")
    cm = ctr[ctr["mercado"] == mercado].reset_index(drop=True)
    ref = cm["primer_aviso"].fillna(cm["vencimiento"])
    p = P.index.searchsorted(ref.to_numpy()) - antes
    roll = P.index[np.clip(p, 0, len(P) - 1)].to_numpy()
    pos = np.searchsorted(roll, P.index.to_numpy(), side="right")
    pos = np.minimum(pos, len(cm) - 1)
    i = P.columns.get_indexer(cm["contrato"].to_numpy()[pos])
    f, Pv = np.arange(len(P)), P.to_numpy()
    ayer = np.r_[i[0], i[:-1]]
    r = np.log(Pv[f[1:], ayer[1:]] / Pv[f[1:] - 1, ayer[1:]])
    return pd.Series(r, index=P.index[1:]).loc["2011":]

vol = {m: {a: serie_con_roll(m, a).std() * np.sqrt(252)
           for a in (1, 5, 20, 40)} for m in ["RVA", "PEC", "GNA",
                                               "GRM"]}
(100 * pd.DataFrame(vol)).round(1)
OUT [05]Rolling earlier: volatility of the contract held
     RVA   PEC   GNA   GRM
1   19.0  36.1  43.4  25.4
5   19.0  35.4  41.5  25.0
20  19.0  32.7  35.2  22.8
40  19.0  29.7  29.5  20.5

In the index, volatility is the same (19.0%) whenever you roll: all expiries move the same. In markets with a Samuelson effect (lesson 3.1.2), rolling 40 sessions earlier lowers the volatility of the contract held: from 43.4% to 29.5% in gas, from 36.1% to 29.7% in crude oil, from 25.4% to 20.5% in corn. It is not a free improvement: the farther contract responds less to what moves the first one (a different bet), its carry is different and its liquidity is lower (in energy, the next contract has volume; in other markets, it does not). What the table does give is a warning: the volatility of a series depends on its roll rule, and a system that sizes by volatility has to estimate it with the same rule it trades with.

Initial and maintenance margin

Lesson 1.2.3 simulated a margin account with margin calls. In the data, each market has three margins:

IN [06]python
m = esp.loc[MERCADOS, ["margen_inicial", "margen_mantenimiento",
                       "margen_spread_calendario"]]
m["inicial_sobre_mant"] = m["margen_inicial"] / m[
    "margen_mantenimiento"]
m["spread_sobre_inicial_pct"] = 100 * m[
    "margen_spread_calendario"] / m["margen_inicial"]
m.loc[["RVA", "TPC", "PEC", "GNA", "ELB", "MTO", "GRM"],
      ["margen_inicial", "inicial_sobre_mant",
       "margen_spread_calendario", "spread_sobre_inicial_pct"]
      ].round(2)
OUT [06]Initial margin, maintenance and the deviations they cover
         margen_inicial  inicial_sobre_mant  margen_spread_calendario  spread_sobre_inicial_pct
mercado
RVA             20200.0                1.10                     610.0                      3.02
TPC              2700.0                1.08                     140.0                      5.19
PEC              5600.0                1.10                     670.0                     11.96
GNA              8500.0                1.10                    3300.0                     38.82
ELB              7300.0                1.11                    3200.0                     43.84
MTO              9600.0                1.10                     290.0                      3.02
GRM               840.0                1.11                     150.0                     17.86
  • Initial: what is deposited when opening. In the data it is 1.1 times the maintenance margin (rounded to the hundred), a ratio that exists at some clearing houses for certain types of account; other clearing houses ask the same for initial as for maintenance and it is the broker that adds its own cushion.
  • Maintenance: the minimum below which the margin call arrives.
  • Calendar spread: the margin of a position long one month and short another of the same market. It is a fraction of the initial margin: 3% in RVA and in gold, where the two months move almost the same; 38.8% and 43.8% in gas and electricity, where the seasonal shape makes two different months move differently.

SPAN: margin as the worst scenario

Clearing houses do not add up margins of individual contracts: they calculate the margin of the whole portfolio with scenarios. The classic method is CME's SPAN, and other clearing houses use variants or their own methods. BME Clearing describes its method for MEFF's financial derivatives (MEFFCOM2) as “similar to an SPAN model”: it evaluates the worst possible loss of the portfolio across several theoretical scenarios and only offsets between instruments with an average correlation of 70% or more. Eurex Clearing uses Prisma, which calculates margin at portfolio level by grouping related products. The structure of SPAN is that of this diagram:

ScenariosScanning risk+ spread charge− creditPortfolio marginShort option minimum
Mermaid source
flowchart TB
  E[Scenarios] --> R[Scanning risk]
  R --> S[+ spread charge]
  S --> C[− credit]
  C --> M[Portfolio margin]
  O[Short option minimum] --> M

Scanning risk is the worst loss of the portfolio over a grid of price moves (and, for options, of volatility), with a range that the clearing house sets per product. Since it moves all the months of the same product at once, a calendar spread appears to have no risk: the spread charge corrects that. The inter-product credit recognizes that a position long one product and short another correlated one risks less than the two separately. A simplified calculation for futures, with the initial margin in the specifications as the range and a grid of moves of 0, ±1/3, ±2/3 and ±1 ranges (the parameters are this lesson's, not a real clearing house's):

IN [07]python
FRACCIONES = np.array([0, 1 / 3, -1 / 3, 2 / 3, -2 / 3, 1, -1])

def margen_escenarios(cartera, credito=0.0):
    """cartera: {contract: number of contracts}. Each market moves
    by its range (the initial margin of one contract, in USD); all
    the months of a market at once (correlation 1)."""
    por_mercado = {}
    for k, n in cartera.items():
        m = k[:3]
        por_mercado.setdefault(m, []).append(n)
    riesgo, cargo = {}, {}
    for m, ns in por_mercado.items():
        rango = esp.loc[m, "margen_inicial"]
        pyg = np.outer(FRACCIONES, ns).sum(axis=1) * rango
        riesgo[m] = max(0.0, -pyg.min())          # worst scenario
        largos = sum(n for n in ns if n > 0)
        cortos = -sum(n for n in ns if n < 0)
        cargo[m] = min(largos, cortos) * esp.loc[
            m, "margen_spread_calendario"]        # months not identical
    total = sum(riesgo.values()) + sum(cargo.values())
    # inter-market credit: fraction of the smaller risk of each pair
    if credito and len(riesgo) == 2:
        total -= credito * min(riesgo.values())
    return {"escenarios": float(sum(riesgo.values())),
            "cargo_spread": float(sum(cargo.values())),
            "total": float(total)}

for nombre, c in {"long 2 PECF26": {"PECF26": 2},
                  "long F26, short G26": {"PECF26": 1,
                                          "PECG26": -1},
                  "long 2 F26, short 1 G26": {"PECF26": 2,
                                              "PECG26": -1}}.items():
    print(f"{nombre:26s}", margen_escenarios(c))
OUT [07]A scenario-based margin calculation
long 2 PECF26              {'escenarios': 11200.0, 'cargo_spread': 0.0, 'total': 11200.0}
long F26, short G26        {'escenarios': 0.0, 'cargo_spread': 670.0, 'total': 670.0}
long 2 F26, short 1 G26    {'escenarios': 5600.0, 'cargo_spread': 670.0, 'total': 6270.0}

Two long crude oil contracts ask 11,200 USD, two ranges. Long January and short February, the scan sees no risk (the two move the same in every scenario) and the margin is the spread charge, 670 USD: 6% of what an outright position of two contracts would ask. Two longs and one short come down to one net long (5,600) plus one spread (670). That is why calendar spreads are traded with much less margin (lesson 3.2.1), and also why their leverage can be misleading: the spread charge covers normal moves in the slope, not an extreme supply squeeze in the near month.

IN [08]python
cartera = {"RVAH26": 1, "RVBH26": -1}
sin = margen_escenarios(cartera)
con = margen_escenarios(cartera, credito=0.5)
print("no credit:", sin["total"], " with 50%:", con["total"])
OUT [08]Inter-market credit: RVA against RVB
no credit: 44900.0  with 50%: 34800.0

A long in RVA and a short in RVB (two indices with a correlation of 0.91 in the data) ask 44,900 USD if they are added as two independent risks. With a credit of 50% of the smaller one (an example value: in reality the clearing house sets it for each pair of products and reviews it), 34,800. The credit exists because the clearing house recognizes that the two legs offset each other in almost every scenario; the risk that they stop doing so does not go away.

Margins in a crisis

The scanning range is not fixed: clearing houses review it with recent volatility, and in a crisis they raise it. The data has constant margins (calibrated with the last year), so to see the effect you have to simulate a rule. The data README says that the margins cover between 3 and 10 daily standard deviations of the notional, 5.3 in RVA. A rule that keeps those 5.3 with the volatility of the last few weeks:

IN [09]python
def serie(mercado):
    c = curvas[curvas["mercado"] == mercado]
    P = c.pivot(index="fecha", columns="contrato",
                values="liquidacion")
    cm = ctr[ctr["mercado"] == mercado].reset_index(drop=True)
    pos = np.searchsorted(cm["fecha_roll"].to_numpy(),
                          P.index.to_numpy(), side="right")
    i = P.columns.get_indexer(cm["contrato"].to_numpy()[pos])
    f, Pv = np.arange(len(P)), P.to_numpy()
    ayer = np.r_[i[0], i[:-1]]
    r = np.r_[np.nan, np.log(Pv[f[1:], ayer[1:]]
                             / Pv[f[1:] - 1, ayer[1:]])]
    return pd.DataFrame({"precio": Pv[f, i], "ret": r},
                        index=P.index)

rva = serie("RVA")
nominal = rva["precio"] * esp.loc["RVA", "multiplicador"]
K = 5.3                      # daily deviations (data README)
dinamico = K * rva["ret"].ewm(span=20).std() * nominal
t = dinamico.loc["2019-12-01":"2020-12-31"]
print(t.resample("ME").agg(["min", "max"]).round(-2).iloc[:7])
print(f"maximum / January: {t.max() / t.loc['2020-01'].mean():.1f}")
OUT [09]A margin that follows volatility, 2020
                min      max
fecha
2019-12-31   4800.0   7200.0
2020-01-31   4100.0   5900.0
2020-02-29   5200.0  11900.0
2020-03-31  10200.0  32600.0
2020-04-30  16300.0  33400.0
2020-05-31  14700.0  23900.0
2020-06-30  13600.0  17300.0
maximum / January: 6.7
Fig. — A margin that tracks volatility: 5.3 daily standard deviations (20-session exponential average) of the notional of one RVA contract, December 2019 to December 2020

In January 2020 the rule asks between 4,100 and 5,900 USD per contract; in March, up to 32,600, and the maximum, on April 1, is 6.7 times the January average. Then it comes down slowly: in June it is still between 13,600 and 17,300, because volatility takes time to be forgotten. The margins of real clearing houses do not follow this exact rule, but they have the same underlying flaw: they are procyclical. They rise when there are losses, and a system that in calm times uses almost all its cash as margin receives at the same time the calls for the losses and the margin increases. If it cannot post more, it cuts positions at the worst moment.

Implied leverage of a diversified portfolio

A diversified system (lesson 3.1.4) gives the same risk to each market. With 5 million USD, a 20% volatility target and the diversification multiplier of 2.5 calculated in that lesson, the positions on November 26, 2025, in notional and in margin:

IN [10]python
CAPITAL, RIESGO = 5_000_000, 0.20
filas = {}
for m in MERCADOS:
    s = serie(m)
    cambio = usd_qev[DIA] if esp.loc[m, "divisa"] == "QEV" else 1
    nominal = s.loc[DIA, "precio"] * esp.loc[m, "multiplicador"] \
        * cambio
    vol = s["ret"].loc[:DIA].tail(252).std() * np.sqrt(252)
    # IDM of 2.5 (lesson 3.1.4) and the same weight for each market
    n = np.round(2.5 / len(MERCADOS) * CAPITAL * RIESGO
                 / (nominal * vol))
    filas[m] = {"sector": esp.loc[m, "sector"], "contratos": n,
                "nominal": n * nominal,
                "margen": n * esp.loc[m, "margen_inicial"] * cambio}
cart = pd.DataFrame(filas).T.infer_objects()
por_sector = cart.groupby("sector")[["nominal", "margen"]].sum()
print((100 * por_sector / CAPITAL).round(1))
print("total:", (100 * por_sector.sum() / CAPITAL).round(1).to_dict())
OUT [10]Notional and margin of a portfolio with the same risk per market
           nominal  margen
sector
agricolas     41.6     2.4
divisas       27.3     0.5
energia       28.4     2.6
indices       58.0     3.3
metales       25.9     1.4
tipos        244.9     4.4
total: {'nominal': 426.0, 'margen': 14.7}

The notional of the portfolio is 426% of capital: leverage of 4.3 times. More than half, 244.9% of capital, is bonds, because a bond moves so little that it takes a lot of notional for it to contribute the same risk as an index. The margin is 14.7% of capital. The header figure shows both numbers by sector.

Neither of them measures the risk of the portfolio. Notional leverage mixes 245% in bonds that move 6% a year with 28% in energy that moves 35%. The margin is what the clearing house asks to cover one or two bad days, and it changes with its rule. The risk is the one set by the size of each position (a 20% annual target) plus what volatility does not capture: that all the bonds fall at once, as in the 2022 carry portfolio of lesson 2.3.4, or that correlations change in a crisis. That is why a futures system is managed in risk, with stress scenarios, and margin is monitored as a liquidity constraint.

Exercises

  1. Use margen_escenarios to calculate the margin of a calendar butterfly in crude oil (long 1 PECF26, short 2 PECG26, long 1 PECH26). What spread charge does the function apply and why is it probably too high?
Solution
IN [11]python
print(margen_escenarios({"PECF26": 1, "PECG26": -2, "PECH26": 1}))
OUT [11]Calendar butterfly
{'escenarios': 0.0, 'cargo_spread': 1340.0, 'total': 1340.0}

The net position is zero, so the scan sees no risk. The function counts two spreads (two longs and two shorts) and charges twice, 1,340 USD. But a butterfly is the difference of two spreads with one leg in common, and its risk is lower than that of two independent spreads: real clearing houses have charge tables by curve segment and can treat these combinations differently. Lesson 3.2.1 builds calendar butterflies and condors.

  1. Repeat the 2020 dynamic margin rule with a 60-session exponential average instead of 20. Does it rise less? Does it come down sooner or later?
Solution
IN [12]python
lento = K * rva["ret"].ewm(span=60).std() * nominal
t60 = lento.loc["2019-12-01":"2020-12-31"]
print(t60.resample("ME").agg(["min", "max"]).round(-2).iloc[:7])
print(f"maximum / January: {t60.max() / t60.loc['2020-01'].mean():.1f}")
OUT [12]Dynamic margin with 60 sessions
                min      max
fecha
2019-12-31   7100.0   8200.0
2020-01-31   6200.0   7100.0
2020-02-29   6200.0  10000.0
2020-03-31   9800.0  35600.0
2020-04-30  34700.0  39200.0
2020-05-31  32600.0  38200.0
2020-06-30  26900.0  32700.0
maximum / January: 6.0

With 60 sessions the rule starts from a higher level in January (from 6,200 to 7,100 USD, because its window still remembers the volatility of late 2019) and reacts later, but it does not rise less: its April maximum, 39,200, exceeds that of the 20-session rule, because it accumulates more bad days in the window, and in June it is still between 26,900 and 32,700, when the other one has already come down by half. Relative to January, the maximum is 6.0 times instead of 6.7. It is the dilemma of any risk measure: fast and jumpy or slow and with memory; the slow one does not avoid the rise, it delays and prolongs it, and it ties up the account's cash for longer.

  1. With the cart table, what would happen to the total margin if the clearing house doubled the margin of the five bonds? And if it doubled it for the five indices? What does it tell you about where the portfolio's margin exposure is?
Solution

The margin of the bonds is 4.4% of capital and that of the indices, 3.3%: doubling that of the bonds would take the total margin from 14.7% to 19.1%, and that of the indices, to 18.0%. Bonds have the largest notional and the largest margin in the portfolio, even though each contract asks little, because there are many contracts. A change in the margin of a sector with many positions weighs more than one in a sector with expensive but few contracts. It is another way of seeing that the notional and the margin are concentrated in bonds even though the risk is spread out.

Each answer is folded under its question

Self-assessment

  1. In the data, the open interest of corn (GRM) crosses 50% towards the next contract between 9 and 8 sessions before the roll date, and that of the 10-year note (TPC), around session −3. What does it imply for a system's roll date?

    • AThat each market has its window: rolling later than when liquidity migrates means trading the last stretch in a contract that is emptying, with wider spreads and, in physically delivered ones, close to the first notice
    • BThat it does not matter when you roll
    • CThat you always have to roll on expiry day
    • DThat corn has no roll
    Show answer

    Answer A. That each market has its window: rolling later than when liquidity migrates means trading the last stretch in a contract that is emptying, with wider spreads and, in physically delivered ones, close to the first notice

    The roll date is set per market: before the contract being left loses its liquidity and, in physically delivered ones, before the first notice. On the roll date in the data, the expiring contract has between 2% and 6% of the open interest left.

  2. In the calendar spreads of the data, the spread book trades with an average bid-ask spread of 1.25 ticks and the synthetic spread built from the two legs, 2.78. How should you roll?

    • ASelling the old contract and buying the new one at market separately
    • BWith a calendar spread order: a single bid-ask spread, narrower, and without the risk of the price moving between the execution of one leg and the other
    • CLetting the contract expire
    • DIt does not matter: the cost is the same
    Show answer

    Answer B. With a calendar spread order: a single bid-ask spread, narrower, and without the risk of the price moving between the execution of one leg and the other

    Rolling is selling one contract and buying the next at the same time, which is exactly a calendar spread. Traded as a spread, you pay half the spread book's bid-ask spread; by legs, half the bid-ask spread of each, and you also run the legging risk (lesson 3.2.5).

  3. A position long PECF26 and short PECG26 has zero scanning risk in the lesson's calculation. Why is the margin not zero?

    • ABecause the clearing house charges a fixed fee
    • BBecause shorts always pay more margin
    • CBecause it is a calculation error
    • DBecause the scan moves all the months of a market at once, as if the correlation were 1; the calendar spread charge (670 USD) covers the risk that the two months move differently
    Show answer

    Answer D. Because the scan moves all the months of a market at once, as if the correlation were 1; the calendar spread charge (670 USD) covers the risk that the two months move differently

    Two contracts of the same market do not move exactly the same (the slope changes). The spread charge is small compared with the margin of an outright contract (5,600 USD in PEC) because the spread's risk is small: lesson 3.2.1 takes advantage of it.

  4. A margin rule that covers 5.3 daily standard deviations of the notional goes from an average of 4,967 USD per RVA contract in January 2020 to 33,406 on April 1. What consequence does it have for a system?

    • ANone: margin is not a loss
    • BThat more contracts have to be traded
    • CThat it needs reserve cash: margin rises exactly when there are losses, and whoever cannot post it has to cut positions at the worst moment
    • DThat margin falls in crises
    Show answer

    Answer C. That it needs reserve cash: margin rises exactly when there are losses, and whoever cannot post it has to cut positions at the worst moment

    Scenario-based margins depend on recent volatility: they are procyclical. A system that uses almost all its cash as margin in calm times receives margin calls and margin increases at the same time.

  5. The lesson's portfolio has a notional of 426% of capital and a margin of 14.7%. Which number best describes its risk?

    • AThe notional: the portfolio is leveraged 4.3 times
    • BThe margin: it risks 14.7% of capital
    • CNeither: its risk is the volatility target (20% a year), which depends on the volatility and correlation of the positions, not on their notional or their margin
    • DThe sum of the two
    Show answer

    Answer C. Neither: its risk is the volatility target (20% a year), which depends on the volatility and correlation of the positions, not on their notional or their margin

    57% of the notional is bonds (244.9% of capital), which move little. The margin depends on what the clearing house asks. Risk is measured in volatility (and in stress scenarios), and it is what sets the size of each position.

Key points

  • The roll date is set per market: before the first notice in physically delivered ones and when liquidity migrates in the rest. In the data, open interest crosses 50% between 9 and 3 sessions before the roll, depending on the market.
  • Rolling is a calendar spread. With a spread order you pay half the bid-ask spread of a 1.25-tick book; by legs, half of each (2.78 ticks in total) plus legging risk. In corn, rolling costs 36.7 basis points a year with the book and 65.3 by legs.
  • Rolling earlier reduces the volatility of the contract held in markets with a Samuelson effect (gas: from 43.4% to 29.5% when rolling 40 sessions earlier), at the price of moving away from the most liquid contract.
  • Scenario margin takes the worst loss over a grid of price moves, adds a charge for spreads within the same market and subtracts a credit between correlated markets. A crude oil calendar spread asks 670 USD against 5,600 for an outright contract.
  • Margins are procyclical: a rule that covers 5.3 daily deviations multiplies the RVA margin by 6.7 between January and April 2020.
  • A portfolio with the same risk in 23 markets and a 20% volatility target has a notional of 426% of capital (244.9% in bonds) and a margin of 14.7%: neither leverage nor margin measures its risk.

Resources

Data