Markets: A Rigorous Course

Exercise · m08_binomial.py

One-period replication and the Cox-Ross-Rubinstein tree

Run: cd exercises && .venv/bin/python -m pytest modules/M08-option-pricing/tests/test_m08_binomial.py numpy is allowed. Vectorise the backward induction over nodes at each time step; n = 2000 must run fast.

Tests (8)

  • test_one_period_hull_example
  • test_arbitrage_condition_enforced
  • test_hull_american_put
  • test_crr_convergence_values
  • test_crr_converges_to_bs
  • test_american_call_no_dividends_equals_european
  • test_american_put_premium_and_dividend_call
  • test_crr_with_dividend_yield_matches_bsm

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

not run yet

Not saved: sign up to keep your code
"""M08 exercise: one-period replication and the Cox-Ross-Rubinstein tree (notes §8.3-§8.4).

Run: cd exercises && .venv/bin/python -m pytest modules/M08-option-pricing/tests/test_m08_binomial.py
numpy is allowed. Vectorise the backward induction over nodes at each time step; n = 2000 must run fast.
"""

from __future__ import annotations

import math

import numpy as np


def one_period_replicate(S0: float, u: float, d: float, r: float, dt: float, V_up: float, V_down: float) -> tuple[float, float, float]:
    """Replicate a claim paying V_up if S goes to u*S0 and V_down if it goes to d*S0 (notes Prop. 8.3.2).

    Solve  Delta*u*S0 + B*e^{r dt} = V_up,  Delta*d*S0 + B*e^{r dt} = V_down  for the share holding
    Delta and the bond holding B (rupees invested at the risk-free rate today). Return (Delta, B, V0)
    with V0 = Delta*S0 + B.
    Raise ValueError unless d < e^{r dt} < u (otherwise there is an arbitrage, notes Lemma 8.3.1).
    Check (Hull): S0=20, u=1.1, d=0.9, r=12%, dt=0.25, call K=21 -> Delta=0.25, V0=0.633.
    """
    raise NotImplementedError


def risk_neutral_prob(u: float, d: float, r: float, dt: float, q: float = 0.0) -> float:
    """p = (e^{(r-q) dt} - d) / (u - d), notes eq. (8.10). Raise ValueError unless d < e^{(r-q)dt} < u."""
    raise NotImplementedError


def crr_params(sigma: float, r: float, dt: float, q: float = 0.0) -> tuple[float, float, float]:
    """CRR parametrisation: u = e^{sigma sqrt(dt)}, d = 1/u, p from risk_neutral_prob. Return (u, d, p)."""
    raise NotImplementedError


def crr_price(S: float, K: float, T: float, r: float, sigma: float, n: int, kind: str = "call",
              american: bool = False, q: float = 0.0) -> float:
    """Price a European or American call/put on an n-step CRR tree (notes Algorithm 8.4.1).

    Terminal nodes S u^j d^{n-j}, j = 0..n. Step back with V = e^{-r dt}[p V_up + (1-p) V_down].
    If american, at each node take max(continuation, intrinsic).
    References: crr_price(50, 50, 5/12, 0.10, 0.40, 5, 'put', american=True) = 4.488459 (Hull 4.49);
    European 4.319019; ATM S=K=100, T=1, r=5%, sigma=20% call: n=10 -> 10.253409,
    n=100 -> 10.430612, n=1000 -> 10.448584 (BS 10.4505835722).
    """
    raise NotImplementedError


def early_exercise_premium(S: float, K: float, T: float, r: float, sigma: float, n: int,
                           kind: str = "put", q: float = 0.0) -> float:
    """American minus European CRR price with the same tree. Zero for calls when q = 0 (notes Prop. 8.2.4)."""
    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.