Markets: A Rigorous Course

Exercise · m07_mtm.py

Daily mark-to-market, margin calls, and futures versus forward prices

Implement each function, then press Run tests.

Read notes 7.2 and 7.4 first. Use numpy/pandas only.

Run locally

From the repository root, with the exercises environment installed:

cd exercises && .venv/bin/python -m pytest modules/M07-forwards-futures/tests/test_m07_mtm.py

Tests (8)

  • test_daily_mtm_values_and_telescoping
  • test_margin_account_hull_rule
  • test_margin_account_cash_identity_random_path
  • test_indian_regime_calls_equal_losses
  • test_return_on_margin
  • test_tailed_futures_replicate_forward_on_any_path
  • test_stochastic_rates_sign_of_futures_minus_forward
  • test_deterministic_rates_futures_equal_forward

Theory: M07 notes. Run with Ctrl/⌘+Enter.

not run yet

Not saved: sign up to keep your code
"""M07 exercise 3: daily mark-to-market, margin calls, and futures versus forward prices.

Implement each function, then run from the repo root:

    cd exercises && .venv/bin/python -m pytest modules/M07-forwards-futures/tests/test_m07_mtm.py

Read notes §7.2 and §7.4 first. Use numpy/pandas only.
"""

from __future__ import annotations

import math

import numpy as np
import pandas as pd


def daily_mtm(settles, entry: float, qty: float, side: int = 1) -> np.ndarray:
    """Daily variation-margin cash flows of a futures position (eq. 7.6).

    settles: daily settlement prices F_1..F_n (position opened at `entry` on day 1, held through day n).
    Flow on day 1: side*qty*(F_1 - entry); on day t > 1: side*qty*(F_t - F_{t-1}).
    side = +1 long, -1 short. Return a numpy array of length n.
    """
    raise NotImplementedError


def margin_account(settles, entry: float, qty: float, side: int, initial_margin: float,
                   maintenance_margin: float) -> pd.DataFrame:
    """Simulate a margin account with a variation-margin call rule (Hull's convention, §7.2.2).

    Day 0: deposit initial_margin. For each day t = 1..n:
        balance_pre_t = balance_{t-1} + mtm_t
        call_t = initial_margin - balance_pre_t  if balance_pre_t < maintenance_margin, else 0
        balance_t = balance_pre_t + call_t            (excess is never withdrawn)
    Return a DataFrame with index 1..n and columns
    ['settle', 'mtm', 'balance_pre', 'call', 'balance'].
    Indian practice is the special case maintenance_margin == initial_margin.
    """
    raise NotImplementedError


def return_on_margin(entry: float, exit_price: float, qty: float, margin: float, side: int = 1) -> float:
    """Return on margin (eq. 7.12): side * (exit_price - entry) * qty / margin."""
    raise NotImplementedError


def tailed_holdings(times, r: float, T: float) -> np.ndarray:
    """Holdings that make daily-settled futures replicate a forward (Prop. 7.5).

    times: t_0 < t_1 < ... < t_n = T in years. Over (t_i, t_{i+1}] hold
    delta_i = exp(-r (T - t_{i+1})) contracts. Return an array of length n.
    """
    raise NotImplementedError


def futures_terminal_value(fut_path, times, r: float, T: float, holdings) -> float:
    """Value at T of a futures strategy whose daily gains are invested (losses financed) at r until T:

        V_T = sum_{i=0}^{n-1} holdings_i * (F_{i+1} - F_i) * exp(r (T - t_{i+1})).
    fut_path has length n+1 (F_0..F_n), times has length n+1, holdings length n.
    """
    raise NotImplementedError


def _vasicek_B_integrals(kappa: float, T: float) -> tuple[float, float]:
    """Return (I1, I2) = (int_0^T B(s,T) ds, int_0^T B(s,T)^2 ds), B(s,T) = (1 - e^{-kappa (T-s)}) / kappa.

    Closed forms (Problem 7.7): with B0 = B(0,T),
        I1 = (T - B0) / kappa
        I2 = (T - 2 B0 + (1 - e^{-2 kappa T}) / (2 kappa)) / kappa^2
    """
    raise NotImplementedError


def futures_forward_ratio_analytic(sigma: float, kappa: float, eta: float, rho: float, T: float) -> float:
    """F_futures / F_forward under Vasicek rates dr = kappa(theta - r)dt + eta dW_r and
    dS/S = r dt + sigma dW_S under Q, d<W_S, W_r> = rho dt (Prop. 7.6, Problem 7.7):

        ratio = exp(eta^2 * I2 + rho * sigma * eta * I1).
    """
    raise NotImplementedError


def futures_forward_mc(S0: float, sigma: float, r0: float, kappa: float, theta: float, eta: float,
                       rho: float, T: float, n_steps: int = 100, n_paths: int = 40_000,
                       seed: int = 0) -> dict:
    """Monte Carlo futures and forward prices under stochastic (Vasicek) rates (§7.2.4).

    rng = np.random.default_rng(seed); dt = T/n_steps. Per step, draw z1 then an
    independent z, set z2 = rho z1 + sqrt(1-rho^2) z, and update (in this order)
        ln S += (r - sigma^2/2) dt + sigma sqrt(dt) z1
        ln D -= r dt
        r    += kappa (theta - r) dt + eta sqrt(dt) z2
    starting from S0, D = 1, r = r0. Then
        futures = mean(S_T); forward = mean(D_T S_T) / mean(D_T); diff = futures - forward
        se_diff = std(psi, ddof=1)/sqrt(n_paths) with the delta-method influence function
                  psi = S - D S / mean(D) + mean(D S) D / mean(D)^2
        P0T = mean(D_T)
    Return dict(futures=..., forward=..., diff=..., se_diff=..., P0T=...).
    """
    raise NotImplementedError

No runs yet

The stub's functions raise NotImplementedError until you write them. Fill in one, press Run tests, and watch its tests turn green.

Reference solution

Available after your first run, with an account approved for code runs.

Create a free account

Reading is open to everyone. An account keeps your progress, answers, notes and code drafts, and gives you the study plan, the journals and the simulator. Running code needs an account approved for it.