Markets: A Rigorous Course

Exercise · m08_implied_vol.py

Implied volatility by Newton-Raphson with a Brent fallback

Run: cd exercises && .venv/bin/python -m pytest modules/M08-option-pricing/tests/test_m08_implied_vol.py You may use scipy.optimize.brentq for the bracketing solver.

bs_price and bs_greeks below are the course's reference Black-Scholes (toolkit.options), so this exercise does not depend on your m08_black_scholes being finished. Once your version passes its tests you can swap the import for from m08_black_scholes import ....

Tests (8)

  • test_round_trip
  • test_atm_reference
  • test_newton_converges_fast
  • test_brent_agrees_with_newton
  • test_fallback_to_brent_when_vega_tiny
  • test_outside_bounds_raises
  • test_below_intrinsic_raises
  • test_iv_is_increasing_function_of_price

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

not run yet

Not saved: sign up to keep your code
"""M08 exercise: implied volatility by Newton-Raphson with a Brent fallback (notes §8.10).

Run: cd exercises && .venv/bin/python -m pytest modules/M08-option-pricing/tests/test_m08_implied_vol.py
You may use scipy.optimize.brentq for the bracketing solver.

bs_price and bs_greeks below are the course's reference Black-Scholes (toolkit.options), so
this exercise does not depend on your m08_black_scholes being finished. Once your version
passes its tests you can swap the import for `from m08_black_scholes import ...`.
"""

from __future__ import annotations

import math
from dataclasses import dataclass

from toolkit.options import bs_greeks, bs_price  # noqa: F401  (provided)

SIGMA_LO, SIGMA_HI = 1e-6, 5.0


@dataclass
class IVResult:
    sigma: float
    method: str  # "newton" or "brent"
    iterations: int


def price_bounds(S: float, K: float, T: float, r: float, kind: str = "call", q: float = 0.0) -> tuple[float, float]:
    """No-arbitrage bounds (notes Prop. 8.2.2), returned as (lower, upper):

        call: (max(S e^{-qT} - K e^{-rT}, 0),  S e^{-qT})
        put:  (max(K e^{-rT} - S e^{-qT}, 0),  K e^{-rT})

    The BS price maps sigma in (0, inf) onto exactly this open interval (notes Prop. 8.10.1).
    """
    raise NotImplementedError


def iv_newton(price: float, S: float, K: float, T: float, r: float, kind: str = "call", q: float = 0.0,
              tol: float = 1e-12, max_iter: int = 50, sigma0: float | None = None) -> tuple[float, int]:
    """Newton-Raphson on f(sigma) = bs_price(sigma) - price using vega as f'(sigma). Return (sigma, iterations).

    * Raise ValueError if price is not strictly inside price_bounds.
    * Start: sigma0 if given, else the Manaster-Koehler point
          sigma* = max( sqrt(2 |ln(S/K) + (r-q)T| / T), 1e-3 ),
      the inflection point of C(sigma); from there Newton converges monotonically (notes Prop. 8.10.2).
    * Stop when |step| < tol.
    * Raise RuntimeError if vega < 1e-10, if sigma leaves [SIGMA_LO, SIGMA_HI], or after max_iter steps.
    """
    raise NotImplementedError


def iv_brent(price: float, S: float, K: float, T: float, r: float, kind: str = "call", q: float = 0.0,
             tol: float = 1e-14) -> tuple[float, int]:
    """Bracketing root-finder on [SIGMA_LO, SIGMA_HI]. Return (sigma, iterations).
    Raise ValueError if price is outside the bounds."""
    raise NotImplementedError


def solve_iv(price: float, S: float, K: float, T: float, r: float, kind: str = "call", q: float = 0.0,
             sigma0: float | None = None) -> IVResult:
    """Try iv_newton (passing sigma0); on RuntimeError fall back to iv_brent. Record which method succeeded.
    ValueError (price outside bounds) must propagate."""
    raise NotImplementedError


def implied_vol(price: float, S: float, K: float, T: float, r: float, kind: str = "call", q: float = 0.0) -> float:
    """Convenience wrapper: solve_iv(...).sigma."""
    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.