SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 6.3.1 What a partial differential equation isConcept · 30 min
      2. 6.3.2 Initial, boundary and terminal conditionsConcept · 30 min
      3. 6.3.3 Practice set: partial differential equationsPractice set · 20 min
      4. 6.3.4 Case study: an option value fixed by its payoffCase study · 35 min

Level 6, Differential equations 6.3 PDE basics Lesson 4 of 4

Case studyLesson 6.3.4 in Level 6, Differential equations. About 35 minutes.

Case study: an option value fixed by its payoff

The Black–Scholes equation

Take the call of case studies 2.4.6 and 4.1.4: strike K=100K = 100, expiry T=0.5T = 0.5 years, rate r=0.03r = 0.03 and volatility σ=0.2\sigma = 0.2, both yearly decimals. Its value V(S,t)V(S, t) depends on the stock price SS and the date tt, in years from today, so T−tT - t is the time left.

Rule: The Black–Scholes equation

Vt+12σ2S2 VSS+rS VS−rV=0V_t + \tfrac{1}{2}\sigma^2S^2\,V_{SS} + rS\,V_S - rV = 0

For every stock price S>0S > 0 and every date t<Tt < T. VtV_t, VSV_S and VSSV_{SS} are partial derivatives (lesson 4.1.2). The Stochastic Calculus course derives the equation from a hedging argument; here, take it as given, as case study 2.4.6 took the formula.

Read it with lesson 6.3.1. It is linear: VV and its partials appear only on their own, multiplied by 12σ2S2\frac{1}{2}\sigma^2S^2, rSrS or −r-r. Its order is 2, from VSSV_{SS}.

Unlike the heat equation, its coefficients depend on SS. Case study 6.4.4 changes variables to turn it into the heat equation itself.

Which way it runs

Move the other terms across: Vt=−12σ2S2VSS−rSVS+rVV_t = -\frac{1}{2}\sigma^2S^2V_{SS} - rSV_S + rV. The coefficient of VSSV_{SS} is negative, as in lesson 6.3.2's ut=−k uxxu_t = -k\,u_{xx}. So the equation runs backwards in time, and needs a terminal condition, at expiry.

In the time left τ=T−t\tau = T - t, Vt=−VτV_t = -V_\tau, and the equation runs forwards in τ\tau from the payoff. That is why every formula of case study 4.1.4 contains T−tT - t.

If an option never expires, nothing depends on tt, so Vt=0V_t = 0. The equation becomes the ODE 12σ2S2V′′+rSV′−rV=0\frac{1}{2}\sigma^2S^2V'' + rSV' - rV = 0, which case study 6.2.4 solves for the perpetual American put.

Conditions: the payoff and the two ends

The region is S>0S > 0 and t≤Tt \le T: a strip in the (S,t)(S, t) plane, open on the side of large SS. Its conditions, edge by edge:

  • At expiry, t=Tt = T. The call is worth its payoff: V(S,T)=max⁡(S−K,0)V(S, T) = \max(S - K, 0).
  • At S=0S = 0. In this model a stock that reaches 0 stays there. Put S=0S = 0 in the equation: the two terms with SS vanish, leaving the ODE Vt=rVV_t = rV of module 6.1. So V(0,t)=V(0,T) e−r(T−t)V(0, t) = V(0, T)\,e^{-r(T - t)}, and for the call V(0,T)=0V(0, T) = 0: the call is worthless.
  • For large SS. Exercise is all but certain, so the call is worth the stock minus the strike paid at expiry: V≈S−Ke−r(T−t)V \approx S - Ke^{-r(T - t)}. That is case study 2.4.6's floor.

Rule: The call's conditions

V(S,T)=max⁡(S−K,0)V(0,t)=0V≈S−Ke−r(T−t) for large S\begin{gathered} V(S, T) = \max(S - K, 0) \\[4pt] V(0, t) = 0 \\[4pt] V \approx S - Ke^{-r(T - t)} \text{ for large } S \end{gathered}

A terminal condition on the edge t=Tt = T and a condition at each end of the SS range. With them the problem is well-posed (lesson 6.3.2), and the Black–Scholes formula is its solution.

The formula meets each condition. Today, at S=150S = 150 it gives 51.4960 against the floor's 51.4888, and at S=200S = 200 the gap is only 1.0×10−61.0 \times 10^{-6}. At S=20S = 20 the call is worth less than 10−2010^{-20}.

The call's value as a surface over (S,t)(S, t) and as slices. The equation carries the payoff, on the back edge, back to today's smooth curve, which nears the payoff's corner as expiry approaches.

Try it

call is case study 4.1.4's formula with the date t. The first line compares the formula at S=150S = 150 and S=200S = 200 with the floor S−Ke−rTS - Ke^{-rT}.

The second line shows the call all but worthless far below the strike: at S=20S = 20 and S=1S = 1 it is below 10−2010^{-20} and 10−20010^{-200}.

⌘/Ctrl + Enter runs the cell

Checking the formula by finite differences

Lesson 6.3.1 tested a candidate by computing its partials and substituting. Here, reprice the call at bumped points and estimate VtV_t, VSV_S and VSSV_{SS} by central differences (lesson 4.1.2). The residual is then the left side of the equation.

With desk bumps, 1% of SS and one day each way in tt:

(S,t)(S, t)VtV_tVSV_SVSSV_{SS}residualwith bumps 10 times smaller
(80,0)(80, 0)−1.8731-1.87310.0807510.0132160.00076.8×10−66.8 \times 10^{-6}
(100,0)(100, 0)−7.0738-7.07380.5700540.027762−0.0023-0.0023−2.3×10−5-2.3 \times 10^{-5}
(120,0)(120, 0)−4.9932-4.99320.9284910.0080330.00111.1×10−51.1 \times 10^{-5}
(100,0.25)(100, 0.25)−9.4354-9.43540.5495900.039553−0.0067-0.0067−6.7×10−5-6.7 \times 10^{-5}
(100,0.45)(100, 0.45)−19.3312-19.33120.5219600.088703−0.0805-0.0805−8.1×10−4-8.1 \times 10^{-4}

Every residual is small next to the terms, which run to 19. Bumps 10 times smaller make it 100 times smaller: truncation error of size h2h^2 (lesson 2.8.1), the mark of a true solution. The formula solves the equation.

Try it

residual estimates the three partials by central differences and returns them with the left side of the equation. hS is the bump in SS, ht the bump in tt, in years.

Each line prints a point, its estimates and residual, then the residual with bumps of 0.1% and a tenth of a day.

⌘/Ctrl + Enter runs the cell

A candidate that meets the payoff but fails

Matching the terminal condition is not enough. Take the discounted payoff, e−r(T−t)max⁡(S−K,0)e^{-r(T - t)}\max(S - K, 0): at t=Tt = T it is exactly the payoff. Below the strike it is 0, which solves the equation.

Above the strike it is e−r(T−t)(S−K)e^{-r(T - t)}(S - K), with Vt=r e−r(T−t)(S−K)V_t = r\,e^{-r(T - t)}(S - K), VS=e−r(T−t)V_S = e^{-r(T - t)} and VSS=0V_{SS} = 0. The residual is rS e−r(T−t)rS\,e^{-r(T - t)}: 3.5464 at S=120S = 120 today, and the finite differences agree. The equation is what fixes the value before expiry.

Close to expiry

With one day left, at S=100S = 100, the call is worth 0.4217 and Θ=−77.7143\Theta = -77.7143 a year. A $1 bump now gives a gamma of 0.3545 instead of 0.3811, as case study 2.8.4 found, and a residual of −5.3078-5.3078.

That residual measures the bump, not the formula. The bend crowds into about SσT−t=1.05S\sigma\sqrt{T - t} = 1.05 dollars (case study 2.8.4), so a $1 bump is far too wide. With a 10-cent bump the residual is −0.0588-0.0588, and with 1 cent −0.0015-0.0015, with the time bump a hundredth of a day.

Theta changes fast too, so the time bump must shrink as well: with a tenth of a day, even the 1-cent bump leaves −0.0963-0.0963. And a central step of one day, from one day before expiry, reaches T−t=0T - t = 0, where the formula divides by zero.

Try it

The first lines test the discounted payoff below and above the strike: the residual is 0, then rSe−r(T−t)rSe^{-r(T - t)}.

The loop is the one-day call with three bumps in SS. The time bump is a hundredth of a day, and a tenth in the last row. Each line prints the bumps, the estimated gamma and the residual.

The last lines take a one-day step in tt, which reaches expiry.

⌘/Ctrl + Enter runs the cell

The equation in Greeks

The three partials have names on a desk (case study 4.1.4): Vt=ΘV_t = \Theta, VS=ΔV_S = \Delta and VSS=ΓV_{SS} = \Gamma. So the equation is a relation among the Greeks.

Rule: Black–Scholes in Greeks

Θ+12σ2S2 Γ+rS Δ−rV=0\Theta + \tfrac{1}{2}\sigma^2S^2\,\Gamma + rS\,\Delta - rV = 0

With Θ\Theta per year, Γ\Gamma per dollar of stock, and σ\sigma and rr as yearly decimals. It holds at every (S,t)(S, t) before expiry, for any option whose value the Black–Scholes equation describes.

With the closed forms of case studies 2.7.4 and 4.1.4, each row adds up to 0:

(S,t)(S, t)VVΘ\Theta12σ2S2Γ\frac{1}{2}\sigma^2S^2\GammarSΔrS\Delta−rV-rV
(80,0)(80, 0)0.3915−1.8731-1.87311.69140.1934−0.0117-0.0117
(100,0)(100, 0)6.3710−7.0738-7.07385.55441.7105−0.1911-0.1911
(120,0)(120, 0)22.0575−4.9932-4.99322.31173.3432−0.6617-0.6617
(100,0.25)(100, 0.25)4.3576−9.4352-9.43527.91681.6492−0.1307-0.1307
(100,0.45)(100, 0.45)1.8586−19.3245-19.324517.81341.5669−0.0558-0.0558

The algebra shows why. Case study 4.1.4 split theta into two parts, and each cancels one side of the rest:

  1. 1

    Θ=−12σ2S2Γ−rKe−r(T−t)N(d2)\Theta = -\frac{1}{2}\sigma^2S^2\Gamma - rKe^{-r(T - t)}N(d_2), here −5.5544−1.5193-5.5544 - 1.5193.

  2. 2

    rSΔ−rV=rKe−r(T−t)N(d2)rS\Delta - rV = rKe^{-r(T - t)}N(d_2), here 1.7105−0.1911=1.51931.7105 - 0.1911 = 1.5193 before rounding.

  3. 3

    Θ+12σ2S2Γ+rSΔ−rV=0\Theta + \frac{1}{2}\sigma^2S^2\Gamma + rS\Delta - rV = 0.

Try it

greeks returns the value, delta, gamma and theta (per year) from the closed forms of case studies 2.7.4 and 4.1.4.

Each line prints a point, the value, the four terms of the equation, and whether they add up to 0 within 10−1210^{-12}.

⌘/Ctrl + Enter runs the cell

Theta pays for gamma

The equation has a desk reading. Hold the call and sell Δ\Delta shares against it, a delta hedge, with the cash from the sale earning the rate rr. Over a short time dtdt, with a stock move dSdS, the position gains about

12Γ (dS)2+Θ dt−r(V−ΔS) dt=12Γ[(dS)2−σ2S2 dt]\begin{gathered} \tfrac{1}{2}\Gamma\,(dS)^2 + \Theta\,dt - r(V - \Delta S)\,dt \\[4pt] = \tfrac{1}{2}\Gamma\left[(dS)^2 - \sigma^2S^2\,dt\right] \end{gathered}

The first line is case study 2.7.4's delta–gamma estimate with the delta term hedged away, plus theta and the interest on the cash. The equation turns it into the second line: the time decay and the interest together cost exactly 12σ2S2Γ dt\frac{1}{2}\sigma^2S^2\Gamma\,dt.

So the hedged position breaks even when the stock moves by σSdt\sigma S\sqrt{dt}, about one standard deviation: $1.0468 for a day. Smaller moves lose, larger moves gain. Repricing the call agrees:

stock move in a day00.50±1.0468\pm 1.0468+2+2−2-2+3+3
repriced gain−0.0152-0.0152−0.0119-0.0119∓0.0003\mp 0.00030.03920.04160.1063
12Γ[(dS)2−σ2S2dt]\frac{1}{2}\Gamma[(dS)^2 - \sigma^2S^2dt]−0.0152-0.0152−0.0117-0.011700.04030.04030.1098

What is left grows like the cube of the move, as in case study 2.7.4.

Try it

The first line prints the break-even move σSdt\sigma S\sqrt{dt} and the day's cost, 12σ2S2Γ dt\frac{1}{2}\sigma^2S^2\Gamma\,dt.

The loop reprices the hedged position a day later, with the interest on the cash, and prints it beside the estimate.

⌘/Ctrl + Enter runs the cell
Check yourself
A trader holds a delta-hedged long call, as above. Over one day the stock moves by $0.50. Roughly what does the position make, interest included?

The put: same equation, another payoff

A put, the right to sell at the strike, satisfies the same equation; only the conditions change. At expiry it pays max⁡(K−S,0)\max(K - S, 0), and for large SS it is worthless.

At S=0S = 0 the ODE Vt=rVV_t = rV now starts from V(0,T)=KV(0, T) = K. So V(0,t)=Ke−r(T−t)V(0, t) = Ke^{-r(T - t)}: $98.5112 today.

The solution is the Black–Scholes put, P=Ke−r(T−t)N(−d2)−SN(−d1)P = Ke^{-r(T - t)}N(-d_2) - SN(-d_1), worth 4.8822 today: the price case study 5.11.4's tree closed in on. Its delta is N(d1)−1=−0.429842N(d_1) - 1 = -0.429842, its gamma is the call's, and its theta is

Θ=−Sφ(d1)σ2T−t+rKe−r(T−t)N(−d2)\Theta = -\frac{S\varphi(d_1)\sigma}{2\sqrt{T - t}} + rKe^{-r(T - t)}N(-d_2)

which is −4.1184-4.1184 a year, −0.0113-0.0113 a day. In the equation: −4.1184+5.5544−1.2895−0.1465=0-4.1184 + 5.5544 - 1.2895 - 0.1465 = 0.

Put–call parity from the equation

The equation is linear, so the call minus the put, C−PC - P, is a solution too (lesson 6.3.1). Its terminal condition is max⁡(S−K,0)−max⁡(K−S,0)=S−K\max(S - K, 0) - \max(K - S, 0) = S - K.

A simpler solution has the same terminal value: S−Ke−r(T−t)S - Ke^{-r(T - t)}. Both SS and Ke−r(T−t)Ke^{-r(T - t)} solve the equation on their own, and at t=Tt = T their difference is S−KS - K. A well-posed problem has only one solution (lesson 6.3.2), so

C−P=S−Ke−r(T−t)C - P = S - Ke^{-r(T - t)}

which is put–call parity (case study 3.3.4): 6.3710−4.8822=1.48886.3710 - 4.8822 = 1.4888.

When theta is positive

Case study 4.1.4 found that a deep in-the-money put, at strike 130, gains 0.0064 a day as time passes. The equation says why: Θ=rV−rSΔ−12σ2S2Γ\Theta = rV - rS\Delta - \frac{1}{2}\sigma^2S^2\Gamma.

For that put V=28.3235V = 28.3235, Δ=−0.9534\Delta = -0.9534 and gamma is small, 0.006897. So Θ=0.8497+2.8601−1.3794=2.3304\Theta = 0.8497 + 2.8601 - 1.3794 = 2.3304 a year, or 0.0064 a day: the interest terms outweigh the gamma term.

Try it

put_greeks returns the put's value, delta, gamma and theta. Each of the first two lines prints them and checks the equation, at strike 100 and at strike 130 (theta also per day).

SymPy then checks that SS and Ke−r(T−t)Ke^{-r(T - t)} each solve the equation, and the last line is parity.

⌘/Ctrl + Enter runs the cell

Try it

The traps of the note, with the six-month call's numbers: the per-day theta, the time-to-expiry sign, and the theta the equation implies.

⌘/Ctrl + Enter runs the cell
Check yourself
A pricer reports, for one option, V=6.3710V = 6.3710, Δ=0.5702\Delta = 0.5702, Γ=0.0278\Gamma = 0.0278 and Θ=−0.0194\Theta = -0.0194, with S=100S = 100, r=0.03r = 0.03 and σ=0.2\sigma = 0.2. The left side of the Black–Scholes equation comes out at about 7.05. What is the likeliest cause?
Check yourself
The discounted payoff e−r(T−t)max⁡(S−K,0)e^{-r(T - t)}\max(S - K, 0) equals the payoff at expiry. Why is it not the call's value before expiry?
Exercise

Theta from the equation, and a report check

Write two functions.

  1. implied_theta(V, delta, gamma, S, r, sigma, days=None) returns the theta the Black–Scholes equation implies, Θ=rV−rSΔ−12σ2S2Γ\Theta = rV - rS\Delta - \frac{1}{2}\sigma^2S^2\Gamma, per year. If days is given (365 for calendar days, 252 for trading days), return it per day instead. Raise ValueError if S or sigma is not positive, or if days is given and not positive.
  2. flag_reports(rows, tol=0.001) takes a list of risk-report rows. Each row is a dictionary with keys "V", "delta", "gamma", "theta", "S", "r" and "sigma", where "theta" is per calendar day. Return the positions (0, 1, 2, …) of the rows whose theta differs from the implied one by more than tol dollars a day.

The tests use the call and puts above, and a report with a per-year theta, a flipped sign and a per-trading-day theta.

See the tests
import math

from scipy.stats import norm


def _greeks(S, K, tau, r, sigma, kind="call"):
    """Value, delta, gamma and theta per year from the closed forms."""
    d1 = (math.log(S / K) + (r + sigma**2 / 2) * tau) / (sigma * math.sqrt(tau))
    d2 = d1 - sigma * math.sqrt(tau)
    gamma = norm.pdf(d1) / (S * sigma * math.sqrt(tau))
    vol_part = -S * norm.pdf(d1) * sigma / (2 * math.sqrt(tau))
    if kind == "call":
        value = S * norm.cdf(d1) - K * math.exp(-r * tau) * norm.cdf(d2)
        return value, norm.cdf(d1), gamma, vol_part - r * K * math.exp(-r * tau) * norm.cdf(d2)
    value = K * math.exp(-r * tau) * norm.cdf(-d2) - S * norm.cdf(-d1)
    return value, norm.cdf(d1) - 1, gamma, vol_part + r * K * math.exp(-r * tau) * norm.cdf(-d2)


def _row(S, K, tau, r, sigma, kind="call"):
    V, delta, gamma, theta = _greeks(S, K, tau, r, sigma, kind)
    return {"V": V, "delta": delta, "gamma": gamma, "theta": theta / 365, "S": S, "r": r, "sigma": sigma}


def test_call_per_year():
    """The six-month call implies theta −7.0738 a year"""
    V, delta, gamma, theta = _greeks(100, 100, 0.5, 0.03, 0.2)
    expect_close(implied_theta(V, delta, gamma, 100, 0.03, 0.2), theta, what="implied_theta for the six-month call", rel=1e-9)


def test_call_per_day():
    """Per calendar day it is −0.0194, and per trading day −0.0281"""
    V, delta, gamma, theta = _greeks(100, 100, 0.5, 0.03, 0.2)
    expect_close(implied_theta(V, delta, gamma, 100, 0.03, 0.2, days=365), theta / 365, what="implied_theta(..., days=365)", rel=1e-9)
    expect_close(implied_theta(V, delta, gamma, 100, 0.03, 0.2, days=252), theta / 252, what="implied_theta(..., days=252)", rel=1e-9)


def test_put():
    """The six-month put implies theta −4.1184 a year"""
    V, delta, gamma, theta = _greeks(100, 100, 0.5, 0.03, 0.2, kind="put")
    expect_close(implied_theta(V, delta, gamma, 100, 0.03, 0.2), theta, what="implied_theta for the put", rel=1e-9)


def test_deep_put_positive():
    """The deep in-the-money put (strike 130) has positive theta, 2.3304 a year"""
    V, delta, gamma, theta = _greeks(100, 130, 0.5, 0.03, 0.2, kind="put")
    expect_close(implied_theta(V, delta, gamma, 100, 0.03, 0.2), theta, what="implied_theta for the strike-130 put", rel=1e-9)


def test_other_option():
    """A one-year call on a $50 stock, strike 55, 35% volatility, 1% rate"""
    V, delta, gamma, theta = _greeks(50, 55, 1.0, 0.01, 0.35)
    expect_close(implied_theta(V, delta, gamma, 50, 0.01, 0.35), theta, what="implied_theta for the $50 stock", rel=1e-9)


def test_flags():
    """A report flags the per-year theta, the flipped sign and the per-trading-day theta, not the good rows"""
    good_call, good_put = _row(100, 100, 0.5, 0.03, 0.2), _row(100, 100, 0.5, 0.03, 0.2, kind="put")
    per_year = dict(good_call, theta=good_call["theta"] * 365)
    flipped = dict(good_call, theta=-good_call["theta"])
    trading = dict(good_call, theta=good_call["theta"] * 365 / 252)
    expect_equal(flag_reports([good_call, good_put, per_year, flipped, trading]), [2, 3, 4], what="flag_reports(rows)")


def test_flags_tolerance():
    """A theta off by half a cent a day is flagged at tol = 0.001 but not at tol = 0.01"""
    off = _row(100, 100, 0.5, 0.03, 0.2)
    off["theta"] += 0.005
    expect_equal(flag_reports([off]), [0], what="flag_reports([row off by 0.005])")
    expect_equal(flag_reports([off], tol=0.01), [], what="flag_reports([row off by 0.005], tol=0.01)")


def test_empty():
    """An empty report has nothing to flag"""
    expect_equal(flag_reports([]), [], what="flag_reports([])")


def test_bad_inputs():
    """A stock price or volatility that is not positive, or days = 0, raises ValueError"""
    for args, kwargs in [((6.37, 0.57, 0.028, 0, 0.03, 0.2), {}), ((6.37, 0.57, 0.028, 100, 0.03, 0), {}),
                         ((6.37, 0.57, 0.028, 100, 0.03, 0.2), {"days": 0})]:
        try:
            implied_theta(*args, **kwargs)
        except ValueError:
            continue
        raise AssertionError(f"implied_theta{args} with {kwargs} should raise ValueError")

Summary card

  • The Black–Scholes equation Vt+12σ2S2VSS+rSVS−rV=0V_t + \frac{1}{2}\sigma^2S^2V_{SS} + rSV_S - rV = 0 is a linear PDE of order 2 for V(S,t)V(S, t). It runs backwards in time, from a terminal condition at expiry.
  • A call has V(S,T)=max⁡(S−K,0)V(S, T) = \max(S - K, 0), V(0,t)=0V(0, t) = 0 and V≈S−Ke−r(T−t)V \approx S - Ke^{-r(T - t)} for large SS; at S=0S = 0 the equation becomes the ODE Vt=rVV_t = rV.
  • The Black–Scholes formula solves it: central differences leave residuals that shrink like h2h^2. Near expiry the bumps must be small next to SσT−tS\sigma\sqrt{T - t}.
  • In Greeks: Θ+12σ2S2Γ+rSΔ−rV=0\Theta + \frac{1}{2}\sigma^2S^2\Gamma + rS\Delta - rV = 0, which is −7.0738+5.5544+1.7105−0.1911=0-7.0738 + 5.5544 + 1.7105 - 0.1911 = 0 for the six-month call. A delta-hedged position breaks even on a move of σSdt\sigma S\sqrt{dt}: theta pays for gamma.
  • The put solves the same equation with the payoff max⁡(K−S,0)\max(K - S, 0); by linearity and uniqueness, C−P=S−Ke−r(T−t)C - P = S - Ke^{-r(T - t)}.
  • Theta must be per year in the equation; a per-day or flipped theta breaks the check.

Free further reading

Optional. This lesson covers what you need; these add more examples and practice. All are free to read.