Chapter 10 — The Black-Scholes Framework
"The most remarkable error in the history of economics — and the most profitable." — Fischer Black, on the Black-Scholes model
On April 23, 1973, the Chicago Board Options Exchange opened for business — the first organised marketplace for standardised equity options. Three weeks later, Fischer Black and Myron Scholes published their seminal paper in the Journal of Political Economy, and Robert Merton published a companion paper the same month. The framework they described would reshape finance, spawn a multi-trillion dollar derivatives industry, and eventually earn Scholes and Merton the Nobel Prize in Economics (Black died in 1995, before the award was given).
The central insight was not to find the right price for an option by asking what risk premium investors demand. Instead, they showed that if you hold an option and continuously adjust a position in the underlying stock, the combination is risk-free — and must therefore earn the risk-free rate. The option price is whatever makes this argument self-consistent. This eliminated subjective risk preferences from the problem entirely, which had been the great obstacle that stopped earlier researchers.
The resulting formula is compact, beautiful, and wrong in ways that practitioners discovered almost immediately. Volatility is not constant; stock returns are not normally distributed; markets are not frictionless; and liquidity disappears precisely when it is most needed. Yet the formula has endured for five decades, not because it is correct, but because it gives traders a common language and a tractable starting point from which to measure everything else. When a trader quotes an implied volatility, they are quoting how much the market deviates from Black-Scholes, not the Black-Scholes price itself.
This chapter develops the complete Black-Scholes framework from the ground up: the geometric Brownian motion model for stock prices, Itô's lemma as the fundamental calculation tool, the derivation of the PDE and its closed-form solution, all five Greeks, and the implied volatility inversion problem. The OCaml implementation is structured as a reusable module that will be extended throughout Part III.
10.1 Geometric Brownian Motion
Before we can price anything, we need a model for how stock prices move over time. The simplest model consistent with two empirical observations — that percentage returns are more natural than absolute returns, and that price changes are unpredictable — is Geometric Brownian Motion (GBM).
The "geometric" part means that the noise is multiplicative: a stock at \$200 experiences price changes twice as large in dollar terms as the same stock at \$100 with the same volatility parameter. This is natural for prices, which can never become negative under GBM. The "Brownian motion" part means the randomness has independent, normally distributed increments — the continuous-time limit of a random walk.
Formally, under the risk-neutral measure $\mathbb{Q}$ (where the drift is replaced by the risk-free rate to eliminate arbitrage — we justify this in Section 10.3), asset prices follow:
$$dS_t = r S_t \cdot dt + \sigma S_t \cdot dW_t$$
where:
- $r$ = risk-free rate
- $\sigma$ = volatility
- $W_t$ = standard Brownian motion under the risk-neutral measure $\mathbb{Q}$
The solution is:
$$S_T = S_0 \exp\left[\left(r - \frac{\sigma^2}{2}\right)T + \sigma\sqrt{T}\cdot Z\right], \quad Z \sim \mathcal{N}(0, 1)$$
Figure 10.1 — Simulated sample paths of Geometric Brownian Motion. The expected path grows at the drift rate, while the variance grows linearly with time, creating a widening envelope of uncertainty.
The $-\sigma^2/2$ term is the Itô correction — a subtlety that arises because prices are log-normal, not normal. If $S_T$ is log-normally distributed, then $\ln S_T$ is normal with mean $\ln S_0 + (r - \sigma^2/2)T$. The correction ensures that $\mathbb{E}^{\mathbb{Q}}[S_T] = S_0 e^{rT}$, i.e., the expected stock price grows at the risk-free rate under $\mathbb{Q}$. Without the $-\sigma^2/2$ adjustment, the naive exponential $e^{rT + \sigma W_T}$ would have a higher expected value due to Jensen's inequality.
(**
Simulate a GBM path.
Returns an array of length (steps + 1) with s0 at index 0.
*)
let gbm_path ~s0 ~rate ~vol ~tau ~steps rng =
let dt = tau /. float_of_int steps in
let path = Array.make (steps + 1) s0 in
for i = 1 to steps do
let z = Rng.normal rng in
path.(i) <- path.(i - 1) *.
exp ((rate -. 0.5 *. vol *. vol) *. dt +. vol *. sqrt dt *. z)
done;
path
(**
Exact simulation of GBM terminal value (no discretisation error).
More efficient when only the terminal value matters.
*)
let gbm_terminal ~s0 ~rate ~vol ~tau rng =
let z = Rng.normal rng in
s0 *. exp ((rate -. 0.5 *. vol *. vol) *. tau +. vol *. sqrt tau *. z)
The two functions illustrate an important design choice. When pricing path-dependent options (barriers, Asians, lookbacks), we need the full trajectory and gbm_path is required. For European options — whose payoff depends only on the terminal stock price — gbm_terminal is more efficient because it avoids storing the entire path and has zero discretisation error: we are using the exact distributional solution, not a numerical approximation. Calling gbm_terminal ~s0:100.0 ~rate:0.05 ~vol:0.20 ~tau:1.0 rng produces a single draw from a log-normal with mean $100 e^{0.05} \approx 105.13$ and standard deviation approximately $100 \times 0.20 = 20$ (in log-normal terms).
10.2 Itô's Lemma
Itô's lemma is the stochastic calculus chain rule. For a smooth function $f(S_t, t)$ of a GBM:
$$df = \left(\frac{\partial f}{\partial t} + r S \frac{\partial f}{\partial S} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 f}{\partial S^2}\right) dt + \sigma S \frac{\partial f}{\partial S} dW_t$$
The $\frac{1}{2}\sigma^2 S^2 \frac{\partial^2 f}{\partial S^2}$ term has no classical analogue — it arises because $dW_t^2 = dt$, not $O(dt)$ as in ordinary calculus. This is the essential strangeness of stochastic calculus: the second-order term is the same order of magnitude as the first-order term in $dt$, so it cannot be discarded.
This additional term is consequential for pricing. If we apply Itô's lemma to $f(S) = \ln S$, we recover the GBM solution above. More importantly for options: applying it to the option value $V(S, t)$ shows that both delta ($\partial V/\partial S$, the first-order sensitivity) and gamma ($\partial^2 V/\partial S^2$, the curvature) appear in the dynamics of $V$. The connection between hedging and curvature runs straight through Itô's correction.
10.3 The Black-Scholes PDE
Black and Scholes' key argument runs as follows. Suppose you hold one option with value $V(S, t)$, and you short $\Delta = \partial V/\partial S$ shares of the stock. The value of this portfolio is $\Pi = V - \Delta S$. By Itô's lemma, over a small time interval the change in $\Pi$ is:
$$d\Pi = dV - \Delta \cdot dS = \left(\frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2}\right) dt$$
The stochastic $dW_t$ terms cancel exactly because we chose $\Delta = \partial V/\partial S$. The resulting portfolio is instantaneously risk-free — its change contains no randomness. In a no-arbitrage market, any risk-free portfolio must earn the risk-free rate: $d\Pi = r \Pi \cdot dt = r(V - \Delta S) dt$. Setting these equal and substituting $\Delta = \partial V/\partial S$ gives the Black-Scholes PDE:
$$\frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2} + r S \frac{\partial V}{\partial S} - r V = 0$$
With terminal condition $V(S, T) = \text{payoff}(S)$. This PDE is remarkable: it contains no $\mu$ (the actual drift of the stock). Whether investors expect 1% or 20% annual returns, the option price is the same — because any directional exposure can be hedged away. The PDE must be solved backwards in time from the known terminal payoff toward the present.
10.4 Closed-Form Solutions
10.4.1 European Call and Put
The Black-Scholes PDE can be transformed into the heat equation of physics by a change of variables, and the heat equation has a classical closed-form solution via convolution with a Gaussian kernel. Alternatively, under the risk-neutral measure, the call price is simply a discounted expectation:
$$C = e^{-rT} \mathbb{E}^{\mathbb{Q}}[\max(S_T - K, 0)]$$
Since $S_T$ is log-normal under $\mathbb{Q}$, this expectation can be computed analytically by splitting it at the breakeven point $S_T = K$ (i.e., computing the probability that the option expires in the money and the expected stock price given that it does). The resulting formula for a European call is:
$$C = S \cdot \Phi(d_1) - K \cdot e^{-rT} \cdot \Phi(d_2)$$
$$d_1 = \frac{\ln(S/K) + (r + \sigma^2/2)T}{\sigma\sqrt{T}}, \quad d_2 = d_1 - \sigma\sqrt{T}$$
For a European put (via put-call parity $C - P = S - K e^{-rT}$):
$$P = K \cdot e^{-rT} \cdot \Phi(-d_2) - S \cdot \Phi(-d_1)$$
module Black_scholes = struct
(** Cumulative standard normal CDF *)
let norm_cdf = Numerics.Special.norm_cdf
(** Standard normal PDF *)
let norm_pdf = Numerics.Special.norm_pdf
let d1 ~spot ~strike ~rate ~div_yield ~vol ~tau =
(log (spot /. strike) +. (rate -. div_yield +. 0.5 *. vol *. vol) *. tau)
/. (vol *. sqrt tau)
let d2 ~spot ~strike ~rate ~div_yield ~vol ~tau =
d1 ~spot ~strike ~rate ~div_yield ~vol ~tau -. vol *. sqrt tau
(** European call price (Merton 1973 extension for continuous dividends) *)
let call ~spot ~strike ~rate ?(div_yield = 0.0) ~vol ~tau =
if tau <= 0.0 then Float.max 0.0 (spot -. strike)
else begin
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let d2v = d2 ~spot ~strike ~rate ~div_yield ~vol ~tau in
spot *. exp (-. div_yield *. tau) *. norm_cdf d1v
-. strike *. exp (-. rate *. tau) *. norm_cdf d2v
end
(** European put price *)
let put ~spot ~strike ~rate ?(div_yield = 0.0) ~vol ~tau =
if tau <= 0.0 then Float.max 0.0 (strike -. spot)
else begin
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let d2v = d2 ~spot ~strike ~rate ~div_yield ~vol ~tau in
strike *. exp (-. rate *. tau) *. norm_cdf (-. d2v)
-. spot *. exp (-. div_yield *. tau) *. norm_cdf (-. d1v)
end
(** Put-call parity check *)
let parity_check ~spot ~strike ~rate ~div_yield ~tau ~call_price ~put_price =
let lhs = call_price -. put_price in
let rhs = spot *. exp (-. div_yield *. tau) -. strike *. exp (-. rate *. tau) in
Float.abs (lhs -. rhs) < 1e-8
end
The OCaml implementation includes Merton's 1973 extension for continuous dividend yield $q$ — a stock paying dividends at rate $q$ has forward price $Se^{(r-q)T}$ rather than $Se^{rT}$. For non-dividend-paying stocks, div_yield = 0.0 recovers the original Black-Scholes formula. The implementation also handles the boundary case $\tau \leq 0$ by returning the intrinsic value directly, which avoids division by zero in the $d_1$ calculation.
For a concrete example: with $S = 100$, $K = 100$, $r = 5%$, $\sigma = 20%$, $T = 1$ year (an at-the-money call), we get $d_1 = (0 + 0.05 + 0.02)/0.20 = 0.35$, $d_2 = 0.15$, $\Phi(d_1) = 0.637$, $\Phi(d_2) = 0.560$, giving $C = 100 \times 0.637 - 100 e^{-0.05} \times 0.560 \approx 10.45$. The ATM call costs about 10.5% of spot — a useful sanity check that experienced practitioners verify mentally.
10.4.2 Put-Call Parity
$$C - P = S \cdot e^{-qT} - K \cdot e^{-rT}$$
This is a model-free relationship that must hold in any arbitrage-free market — it follows purely from the payoff structure $\max(S-K, 0) - \max(K-S, 0) = S - K$, without assuming any dynamics for $S$. If the parity is violated, an arbitrageur can make a riskless profit: if $C - P > Se^{-qT} - Ke^{-rT}$, sell the call, buy the put, buy the stock, and borrow $Ke^{-rT}$; the combined position costs zero and generates positive cash flows regardless of where $S$ ends up.
In practice, put-call parity holds very tightly for European options on liquid underlyings. The main sources of apparent violations are bid-ask spreads, different dividend estimates between market participants, and the fact that American options (which can be early-exercised) do not satisfy this exact relation.
10.5 The Greeks
Greeks measure the sensitivity of the option price to each of its inputs. They are the primary risk management tools for options traders and form the vocabulary of hedging. The name comes from the fact that most are denoted by Greek letters, with vega being a notable exception (it is not actually a Greek letter — some accounts attribute this to a typographical accident that became convention).
A market maker who sells an option does not guess that the stock will go up or down. Instead, they sell the option, charge a premium for it, and immediately delta-hedge the directional exposure. Their profit depends on gamma (whether the stock moves a lot), theta (time decay in their favor, since they sold the optionality), and vega (whether implied volatility moves after they sold). Understanding Greeks is understanding how options traders think.
10.5.1 Delta ($\Delta$)
$$\Delta_C = e^{-qT} \Phi(d_1), \quad \Delta_P = -e^{-qT} \Phi(-d_1)$$
Delta represents:
- Hedge ratio: the number of shares to hold (or short) to neutralise directional exposure from one option
- Risk-neutral probability: $\Phi(d_2)$ is the true risk-neutral probability of expiring in the money; $\Phi(d_1) \approx$ this adjusted for the expected value of $S_T$ given exercise
Delta ranges from 0 to 1 for calls (0 to −1 for puts). A 50-delta option (at the money) requires shorting half a share to hedge one call — the hedge ratio changes continuously as $S$ moves, which is the source of gamma P&L.
10.5.2 Gamma ($\Gamma$)
$$\Gamma = e^{-qT} \frac{\phi(d_1)}{S \sigma \sqrt{T}}$$
Gamma is the same for calls and puts (by put-call parity, since differentiating $C - P = Se^{-qT} - Ke^{-rT}$ twice with respect to $S$ gives $\Gamma_C = \Gamma_P$). Gamma is highest for at-the-money options near expiry — a fact that makes short-dated ATM options extremely expensive to delta-hedge and correspondingly cheap or expensive depending on whether realised volatility is high or low.
10.5.3 Theta ($\Theta$)
$$\Theta_C = -\frac{S \sigma e^{-qT} \phi(d_1)}{2\sqrt{T}} - r K e^{-rT} \Phi(d_2) + q S e^{-qT} \Phi(d_1)$$
Theta is the rate of value erosion due to the passage of time, holding all other inputs constant. It is almost always negative for long options (time decay hurts the option buyer). Theta and gamma are connected by the Black-Scholes PDE itself: rearranging it gives $\Theta + \frac{1}{2}\sigma^2 S^2 \Gamma = rV - rS\Delta$, or approximately $\Theta \approx -\frac{1}{2}\sigma^2 S^2 \Gamma$ for at-the-money options where $V \approx S\Delta$. This theta-gamma tradeoff means long-gamma positions decay in time value: you pay theta in exchange for gamma exposure.
Figure 10.2 — A scatter plot of daily gamma P&L vs expected theta P&L across different states. Realised gamma P&L fluctuates around expected theta P&L, illustrating the fundamental tradeoff in delta hedging.
10.5.4 Vega ($\nu$)
$$\nu = S \sqrt{T} e^{-qT} \phi(d_1)$$
Vega is the sensitivity to a change in implied volatility. It is the same for calls and puts (both benefit from higher uncertainty), and it peaks for at-the-money options because their value is most dependent on whether the stock reaches the strike or not. In practice, vega is quoted for a 1% (100bp) move in implied vol; our implementation divides by 100 to reflect this convention. For the ATM example above, vega would be approximately $S\sqrt{T}\phi(d_1)/100 \approx 100 \times 1.0 \times 0.374 / 100 \approx 0.374$, meaning the call price changes by about $0.374 for each 1% change in implied volatility.
10.5.5 Rho ($\rho$)
$$\rho_C = K T e^{-rT} \Phi(d_2), \quad \rho_P = -K T e^{-rT} \Phi(-d_2)$$
Figure 10.1 — The five major Greeks as a function of spot price. Note how Delta bounds [0,1] for calls and [-1,0] for puts, Gamma peaks at the strike, and Vega is highest at-the-money.
module Greeks = struct
open Black_scholes
let delta ?(div_yield = 0.0) ~spot ~strike ~rate ~vol ~tau ~is_call =
if tau <= 0.0 then (if is_call then (if spot > strike then 1.0 else 0.0)
else (if spot < strike then -1.0 else 0.0))
else begin
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let nd1 = norm_cdf d1v in
let df_q = exp (-. div_yield *. tau) in
if is_call then df_q *. nd1
else df_q *. (nd1 -. 1.0)
end
let gamma ?(div_yield = 0.0) ~spot ~strike ~rate ~vol ~tau =
if tau <= 0.0 then 0.0
else begin
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
exp (-. div_yield *. tau) *. norm_pdf d1v
/. (spot *. vol *. sqrt tau)
end
let theta ?(div_yield = 0.0) ~spot ~strike ~rate ~vol ~tau ~is_call =
if tau <= 0.0 then 0.0
else begin
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let d2v = d2 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let df_r = exp (-. rate *. tau) in
let df_q = exp (-. div_yield *. tau) in
let term1 = -. spot *. df_q *. norm_pdf d1v *. vol /. (2.0 *. sqrt tau) in
let term2 = if is_call then
-. rate *. strike *. df_r *. norm_cdf d2v
+. div_yield *. spot *. df_q *. norm_cdf d1v
else
rate *. strike *. df_r *. norm_cdf (-. d2v)
-. div_yield *. spot *. df_q *. norm_cdf (-. d1v)
in
(term1 +. term2) /. 365.0 (* per calendar day *)
end
let vega ?(div_yield = 0.0) ~spot ~strike ~rate ~vol ~tau =
if tau <= 0.0 then 0.0
else begin
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
spot *. sqrt tau *. exp (-. div_yield *. tau) *. norm_pdf d1v /. 100.0
(* divided by 100 to express as change per 1% vol move *)
end
let rho ~spot ~strike ~rate ~vol ~tau ~is_call =
if tau <= 0.0 then 0.0
else begin
let d2v = d2 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
if is_call then
strike *. tau *. exp (-. rate *. tau) *. norm_cdf d2v /. 100.0
else
-. strike *. tau *. exp (-. rate *. tau) *. norm_cdf (-. d2v) /. 100.0
end
(** Higher-order Greeks *)
(** Vanna = dDelta/dVol = dVega/dSpot *)
let vanna ~spot ~strike ~rate ~vol ~tau =
let d1v = d1 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
let d2v = d2 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
-. norm_pdf d1v *. d2v /. vol
(** Volga (Vomma) = dVega/dVol *)
let volga ~spot ~strike ~rate ~vol ~tau =
let d1v = d1 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
let d2v = d2 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
vega ~spot ~strike ~rate ~vol ~tau *. d1v *. d2v /. vol *. 100.0
(** Charm = dDelta/dTime *)
let charm ?(div_yield = 0.0) ~spot ~strike ~rate ~vol ~tau ~is_call =
let d1v = d1 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let d2v = d2 ~spot ~strike ~rate ~div_yield ~vol ~tau in
let df_q = exp (-. div_yield *. tau) in
let ddd = div_yield *. norm_cdf (if is_call then d1v else -. d1v)
-. norm_pdf d1v *. (2.0 *. (rate -. div_yield) *. tau
-. d2v *. vol *. sqrt tau)
/. (2.0 *. tau *. vol *. sqrt tau) in
if is_call then -. df_q *. ddd
else df_q *. ddd
end
(** Pretty-print a Greeks summary *)
let print_greeks ~spot ~strike ~rate ~vol ~tau =
Printf.printf "\n=== Black-Scholes Greeks ===\n";
Printf.printf "S=%.2f K=%.2f r=%.1f%% σ=%.1f%% τ=%.3f\n\n"
spot strike (rate *. 100.0) (vol *. 100.0) tau;
Printf.printf "%-10s %-12s %-12s\n" "Greek" "Call" "Put";
Printf.printf "%s\n" (String.make 36 '-');
let g name vc vp = Printf.printf "%-10s %-12.6f %-12.6f\n" name vc vp in
g "Price"
(Black_scholes.call ~spot ~strike ~rate ~vol ~tau)
(Black_scholes.put ~spot ~strike ~rate ~vol ~tau);
g "Delta"
(Greeks.delta ~spot ~strike ~rate ~vol ~tau ~is_call:true)
(Greeks.delta ~spot ~strike ~rate ~vol ~tau ~is_call:false);
g "Gamma"
(Greeks.gamma ~spot ~strike ~rate ~vol ~tau)
(Greeks.gamma ~spot ~strike ~rate ~vol ~tau);
g "Theta"
(Greeks.theta ~spot ~strike ~rate ~vol ~tau ~is_call:true)
(Greeks.theta ~spot ~strike ~rate ~vol ~tau ~is_call:false);
g "Vega"
(Greeks.vega ~spot ~strike ~rate ~vol ~tau)
(Greeks.vega ~spot ~strike ~rate ~vol ~tau);
g "Rho"
(Greeks.rho ~spot ~strike ~rate ~vol ~tau ~is_call:true)
(Greeks.rho ~spot ~strike ~rate ~vol ~tau ~is_call:false)
```ocaml
let () = print_greeks ~spot:100.0 ~strike:100.0 ~rate:0.05 ~vol:0.20 ~tau:1.0
Running print_greeks with the canonical ATM example ($S=K=100$, $r=5%$, $\sigma=20%$, $T=1$ year) produces approximately:
=== Black-Scholes Greeks ===
S=100.00 K=100.00 r=5.0% σ=20.0% τ=1.000
Greek Call Put
------------------------------------
Price 10.450453 5.573526
Delta 0.636831 -0.363169
Gamma 0.018762 0.018762
Theta -0.017394 -0.007353
Vega 0.374930 0.374930
Rho 0.532324 -0.418855
Several relationships are immediately visible: the put price is lower than the call (consistent with put-call parity: $C - P = 100 - 100e^{-0.05} \approx 4.88$, which checks out). Delta for the call is above 0.5 because the expected value of a log-normal is slightly above the median, shifting the probability of finishing in the money slightly above 50%. Gamma and vega are identical for calls and puts. Theta for the call is more negative than for the put because the call has more time value to decay.
10.6 Implied Volatility
Implied volatility is the volatility $\sigma$ that, when inserted into Black-Scholes, reproduces the observed market price. It is the market's consensus forecast of future volatility.
(**
Compute implied volatility from market option price.
Uses Newton-Raphson (fast near ATM) with Brent's fallback.
*)
let implied_vol ~market_price ~spot ~strike ~rate ~tau ~is_call =
let bs_price v =
if is_call then Black_scholes.call ~spot ~strike ~rate ~vol:v ~tau
else Black_scholes.put ~spot ~strike ~rate ~vol:v ~tau
in
let vega v = Greeks.vega ~spot ~strike ~rate ~vol:v ~tau *. 100.0 in
(* Sanity bounds *)
let intrinsic =
if is_call then Float.max 0.0 (spot -. strike *. exp (-. rate *. tau))
else Float.max 0.0 (strike *. exp (-. rate *. tau) -. spot) in
if market_price < intrinsic -. 1e-6 then
Error "Price below intrinsic value"
else begin
let f v = bs_price v -. market_price in
let f' v = vega v in
match newton_raphson ~f ~f' 0.25 with
| Converged iv when iv > 0.001 && iv < 10.0 -> Ok iv
| _ ->
(* Fallback to Brent *)
match brent ~f 0.001 5.0 with
| Converged iv -> Ok iv
| FailedToConverge { last; _ } ->
if Float.abs (f last) < 0.001 then Ok last
else Error "Implied vol did not converge"
end
The implied volatility solver uses Newton-Raphson as the primary method, with Brent's method as a fallback. Newton-Raphson converges quadratically when the initial guess is good — a starting point of 25% works well for near-the-money options over typical tenors. For deep in-the-money or out-of-the-money options, or very long maturities, the vega becomes very small, the Newton step becomes very large, and it can overshoot. Brent's method guarantees convergence for any bracket where the function changes sign, which is why it serves as a reliable fallback.
A critical pre-check is against intrinsic value: if the market price is below the option's intrinsic value ($\max(S - Ke^{-rT}, 0)$ for a call), no positive implied volatility can match it, and the iterative method will diverge. Our implementation guards against this with an explicit early-return Error in this case.
10.7 Black-Scholes in Practice
10.7.1 The Moneyness Spectrum
Key option metrics by moneyness:
| Moneyness | $S/K$ | Delta | Intrinsic | Time Value | Gamma | Vega |
|---|---|---|---|---|---|---|
| Deep ITM | 1.20 | ~0.95 | High | Low | Low | Low |
| ITM | 1.05 | ~0.70 | Medium | Medium | Medium | Medium |
| ATM | 1.00 | ~0.50 | Zero | Maximum | Maximum | Maximum |
| OTM | 0.95 | ~0.30 | Zero | Medium | Medium | Medium |
| Deep OTM | 0.80 | ~0.05 | Zero | Low | Low | Low |
10.7.2 P&L Attribution
The Black-Scholes P&L of a delta-hedged position over interval $[t, t+dt]$:
$$d\Pi = \frac{1}{2}\Gamma S^2 (\sigma_{\text{realised}}^2 - \sigma_{\text{implied}}^2) \cdot dt$$
This is the most important formula for options practitioners. It says that the daily P&L of a delta-hedged long option position is determined by the difference between how much the stock actually moved (realised volatility $\sigma_R$) and how much the option was priced to move when you bought it (implied volatility $\sigma_I$). If you bought at $\sigma_I = 20%$ and the stock subsequently realises $\sigma_R = 25%$, every gamma point earns positive P&L. If the stock barely moves ($\sigma_R = 10%$), you lose theta for nothing. This is the fundamental gamble in buying options: you pay theta continuously and hope for large moves to recoup it through gamma.
(**
Daily P&L attribution for a delta-hedged option book.
theta_pnl: time decay (negative for long options)
gamma_pnl: gamma P&L from stock moves
vega_pnl: change in mark-to-market from vol changes
*)
type pnl_attribution = {
theta_pnl : float;
gamma_pnl : float;
vega_pnl : float;
total_pnl : float;
}
let daily_pnl_attribution ~position ~spot_change ~vol_change ~dt =
let theta_pnl = position.theta *. dt in
let gamma_pnl = 0.5 *. position.gamma *. spot_change *. spot_change in
let vega_pnl = position.vega *. vol_change *. 100.0 in
let total_pnl = theta_pnl +. gamma_pnl +. vega_pnl in
{ theta_pnl; gamma_pnl; vega_pnl; total_pnl }
10.8 Assumptions and Limitations
Black-Scholes rests on assumptions that are violated in every real market. Understanding them is not an academic exercise — it directly determines when and how much you should trust the formula.
Constant volatility. The model assumes $\sigma$ is a fixed constant. In reality, volatility is stochastic, mean-reverting, and exhibits clustering (periods of high vol follow high vol). The entire field of stochastic volatility modelling (Chapter 13) is dedicated to relaxing this assumption. The most immediate evidence of the failure is the volatility smile: the implied vol backed out from market prices varies significantly across strikes, which is impossible if the model were correct.
Log-normal returns. Equity returns have fat tails — extreme moves occur far more frequently than a normal distribution predicts. The 1987 crash, the 2008 crisis, and the 2020 COVID selloff were all multi-sigma events that are effectively impossible under log-normal dynamics. This matters for deep out-of-the-money options (which are priced as if extreme moves can happen) and for risk management (where tail events dominate losses).
Continuous trading and no transaction costs. The delta hedge must be rebalanced continuously for the P&L formula to hold exactly. In practice, hedging is discrete (daily or less frequently), and each rebalance incurs trading costs. The residual unhedged risk from discrete hedging is one reason option market makers charge a bid-ask spread.
Constant interest rates. For short-dated equity options, this matters little. For long-dated options (two years and beyond), stochastic interest rates can contribute meaningfully to option value, particularly for bonds and rates options (which is why Chapters 8 and 11 use different models).
No dividends (or continuous dividends). The Merton extension handles continuous dividends but not discrete dividend jumps. Around ex-dividend dates, call prices fall and put prices rise in ways the model does not capture cleanly without adjusting the stock price for the present value of the dividend.
Despite these limitations, Black-Scholes remains the default pricing framework for vanilla equity options. It is used not because practitioners believe its assumptions, but because it provides a single-parameter description (implied vol) of option prices that allows traders to compare, hedge, and communicate across strikes and maturities.
10.10 Type-Safe Greeks with GADTs
The Greeks module above uses labelled arguments and is_call flags to distinguish calls from puts. A stronger approach uses GADTs to make certain invalid requests — like computing a DV01 (interest rate duration) for an equity option — into compile-time errors rather than runtime exceptions or nonsensical results.
This builds directly on the GADT pattern from Chapter 2 (§2.10). For options, we can encode both the product class and the greek kind as type parameters:
(** Product class tags — phantom types *)
type equity
type ir (* interest rate *)
type credit
(** GADT: each constructor specifies which product class it belongs to *)
type 'cls greek =
| Delta : equity greek (* equity first-order spot sensitivity *)
| Gamma : equity greek (* equity second-order spot sensitivity *)
| Vega : equity greek (* equity vol sensitivity *)
| Theta : equity greek (* time decay *)
| Rho : equity greek (* rate sensitivity (equity context) *)
| DV01 : ir greek (* interest rate duration *)
| CS01 : credit greek (* credit spread sensitivity *)
(** GADT instrument: return type determined by constructor *)
type 'cls instrument =
| Equity_option : {
spot : float;
strike : float;
vol : float;
rate : float;
tau : float;
is_call : bool;
} -> equity instrument
| Interest_rate_swap : {
fixed_rate : float;
float_rate : float;
notional : float;
tenor : float;
} -> ir instrument
(** Type-safe greek computation: greek class must match instrument class *)
let compute_greek : type c. c greek -> c instrument -> float =
fun greek instrument ->
match greek, instrument with
| Delta, Equity_option { spot; strike; vol; rate; tau; _ } ->
let d1 = Black_scholes.d1 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
Black_scholes.norm_cdf d1
| Gamma, Equity_option { spot; strike; vol; rate; tau; _ } ->
let d1 = Black_scholes.d1 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
Black_scholes.norm_pdf d1 /. (spot *. vol *. sqrt tau)
| Vega, Equity_option { spot; strike; vol; rate; tau; _ } ->
let d1 = Black_scholes.d1 ~spot ~strike ~rate ~div_yield:0.0 ~vol ~tau in
spot *. sqrt tau *. Black_scholes.norm_pdf d1 /. 100.0
| Theta, Equity_option { spot; strike; vol; rate; tau; is_call } ->
Greeks.theta ~spot ~strike ~rate ~vol ~tau ~is_call
| Rho, Equity_option { strike; rate; tau; is_call; _ } ->
Greeks.rho ~spot:100.0 ~strike ~rate ~vol:0.20 ~tau ~is_call
| DV01, Interest_rate_swap { notional; tenor; fixed_rate = _; float_rate = _ } ->
notional *. tenor *. 0.0001 (* simplified: BPV *)
(** The type system prevents nonsensical combinations at compile time. *)
(** Attempting to evaluate: *)
(** compute_greek DV01 (Equity_option {...}) *)
(** compute_greek Delta (Interest_rate_swap {...}) *)
(** ... are both compile-time type errors. *)
(** Typed aggregation: for a portfolio of equity options *)
let portfolio_greek greek options =
List.fold_left (fun total opt ->
total +. compute_greek greek opt
) 0.0 options
let () =
let opts = [
Equity_option { spot = 100.0; strike = 100.0; vol = 0.20;
rate = 0.05; tau = 1.0; is_call = true };
Equity_option { spot = 100.0; strike = 110.0; vol = 0.25;
rate = 0.05; tau = 0.5; is_call = false };
] in
Printf.printf "Portfolio delta: %.4f\n" (portfolio_greek Delta opts);
Printf.printf "Portfolio gamma: %.4f\n" (portfolio_greek Gamma opts);
Printf.printf "Portfolio vega: %.4f\n" (portfolio_greek Vega opts)
The GADT encoding ensures that portfolio_greek DV01 opts would be a compile-time error — DV01 has type ir greek, while opts contains equity instrument values, and the types do not match. This eliminates an entire category of cross-asset risk aggregation errors that would otherwise be caught only by unit tests, if at all.
For a large derivatives book mixing equity options, rates swaps, and credit default swaps, the GADT approach moves model-product compatibility from a runtime property (guarded by assertions or type-tag checks) to a compile-time guarantee. Adding a new product type forces all greek computation functions to explicitly handle it or reject it.
10.11 Chapter Summary
This chapter developed the complete Black-Scholes framework, starting from the geometric Brownian motion model for stock prices and arriving at closed-form option prices and their sensitivities. The key intellectual journey was from a stochastic process (GBM) through a stochastic calculus tool (Itô's lemma) to a deterministic partial differential equation (the BS PDE), which has an exact solution in the European case.
The Greeks — delta, gamma, theta, vega, rho — are the risk management language of options desks. The fundamental insight connecting them is the theta-gamma relationship: long options pay theta continuously in exchange for gamma (the ability to profit from large stock moves). The P&L of a delta-hedged option depends only on the difference between realised and implied volatility, not on the direction of the stock.
Implied volatility is the single most important output of the model for day-to-day practice. When traders quote options, they quote implied vol, not prices. The volatility smile — the fact that implied vol varies by strike — is the market's adjustment for the failure of Black-Scholes assumptions, particularly fat tails and skew. Chapter 13 builds the tools to model and interpolate the full volatility surface.
From a software design perspective, GADT-encoded Greeks (§10.10) demonstrate how OCaml can move model-product compatibility errors from runtime to compile time. Combined with the phantom type patterns from Chapter 1, this makes the Black-Scholes module a practical demonstration of correctness-by-construction: the type system enforces that only valid greek-instrument pairs are computed, that call and put branches are handled exhaustively, and that numerical inputs satisfy their domain constraints.
Exercises
10.1 Verify put-call parity numerically for S=100, K=95, r=5%, q=1%, σ=25%, T=0.5 years. Then verify that your call and put implementations satisfy it.
10.2 Generate a delta-hedging P&L simulation: buy a 1-year ATM call, delta-hedge daily with H=252 days, and compare the hedging P&L to the Black-Scholes premium. Run 1000 scenarios.
10.3 For a portfolio of 50 options across different strikes and maturities, compute the portfolio-level delta, gamma, theta, and vega. Implement a Portfolio_greeks module.
10.4 Derive the Black-Scholes formula for a cash-or-nothing digital call (pays $1 if $S_T > K$). Verify that its delta is unbounded near expiry.
10.5 Extend the GADT greek type from §10.10 to include a Credit_default_swap instrument constructor in the credit class with CS01 computation. Write a mixed-book risk function that computes the aggregate greek for each product class separately, then converts them to a common unit (dollar risk) for aggregation.