Exercise · m08_implied_vol.py
Implied volatility by Newton-Raphson with a Brent fallback
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.