Black-Scholes Pricer Library (cantaro86)¶
Background¶
cantaro86_bs_pricer.py Black-Scholes Pricer -- Multi-Method Educational Implementation
Credits¶
Based on the BS_pricer class from: cantaro86, "Financial Models Numerical Methods" (FMNM) https://github.com/cantaro86/Financial-Models-Numerical-Methods
Adapted as a SELF-CONTAINED educational module for the "Quant Finance with Python" course (Chapter 6 -- Black-Scholes).
All external FMNM dependencies have been inlined so the file runs stand-alone with only NumPy, SciPy, and Matplotlib.
Methods implemented¶
- Closed-form Black-Scholes formula (call and put)
- Vega (dPrice / dSigma)
- Monte Carlo simulation (European options)
- PDE solver -- implicit finite-difference scheme via sparse LU
- Longstaff-Schwartz Method (LSM) for American put options
Code¶
```python
!/usr/bin/env python3¶
-- coding: utf-8 --¶
""" cantaro86_bs_pricer.py Black-Scholes Pricer -- Multi-Method Educational Implementation
Credits¶
Based on the BS_pricer class from: cantaro86, "Financial Models Numerical Methods" (FMNM) https://github.com/cantaro86/Financial-Models-Numerical-Methods
Adapted as a SELF-CONTAINED educational module for the "Quant Finance with Python" course (Chapter 6 -- Black-Scholes).
All external FMNM dependencies have been inlined so the file runs stand-alone with only NumPy, SciPy, and Matplotlib.
Methods implemented¶
- Closed-form Black-Scholes formula (call and put)
- Vega (dPrice / dSigma)
- Monte Carlo simulation (European options)
- PDE solver -- implicit finite-difference scheme via sparse LU
- Longstaff-Schwartz Method (LSM) for American put options """
import numpy as np import scipy.stats as ss from scipy import sparse from scipy.sparse.linalg import splu import matplotlib.pyplot as plt from time import time
============================================================================¶
1. CLOSED-FORM BLACK-SCHOLES FORMULA¶
============================================================================¶
def bs_price(payoff, S0, K, T, r, sigma): """ Analytical Black-Scholes price for a European option.
Parameters
----------
payoff : str
"call" or "put"
S0 : float
Current stock / index price
K : float
Strike price
T : float
Time to maturity in years
r : float
Risk-free interest rate (continuous compounding)
sigma : float
Annualised volatility
Returns
-------
float
Option present value
"""
d1 = (np.log(S0 / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = (np.log(S0 / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
if payoff == "call":
return S0 * ss.norm.cdf(d1) - K * np.exp(-r * T) * ss.norm.cdf(d2)
elif payoff == "put":
return K * np.exp(-r * T) * ss.norm.cdf(-d2) - S0 * ss.norm.cdf(-d1)
else:
raise ValueError("payoff must be 'call' or 'put'")
def bs_vega(S0, K, T, r, sigma): """ Black-Scholes Vega: partial derivative of the option price w.r.t. sigma.
Vega is the same for calls and puts under Black-Scholes.
Parameters
----------
S0, K, T, r, sigma : float
Standard Black-Scholes parameters
Returns
-------
float
Vega value
"""
d1 = (np.log(S0 / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
return S0 * np.sqrt(T) * ss.norm.pdf(d1)
============================================================================¶
2. BS_PRICER CLASS -- MULTI-METHOD ENGINE¶
============================================================================¶
class BS_pricer: """ Self-contained Black-Scholes pricer with multiple pricing methods.
Pricing methods available through instance methods:
closed_formula() -- exact Black-Scholes formula
MC() -- Monte Carlo simulation (European)
PDE_price() -- implicit finite-difference PDE solver
LSM() -- Longstaff-Schwartz for American puts
Static helpers:
BlackScholes() -- quick static closed-form price
vega() -- quick static vega calculation
Parameters
----------
S0 : float Current stock price
K : float Strike price
T : float Time to maturity (years)
r : float Risk-free rate
sigma : float Volatility
payoff : str "call" or "put"
exercise : str "European" or "American"
"""
def __init__(self, S0, K, T, r, sigma, payoff="call", exercise="European"):
self.S0 = float(S0)
self.K = float(K)
self.T = float(T)
self.r = float(r)
self.sig = float(sigma)
self.payoff = payoff
self.exercise = exercise
# Populated by PDE_price for plotting
self.price = 0.0
self.S_vec = None
self.price_vec = None
self.mesh = None
# ----------------------------------------------------------------
# Payoff helper
# ----------------------------------------------------------------
def payoff_f(self, S):
"""Compute the payoff vector for an array of spot prices."""
if self.payoff == "call":
return np.maximum(S - self.K, 0)
elif self.payoff == "put":
return np.maximum(self.K - S, 0)
else:
raise ValueError("payoff must be 'call' or 'put'")
# ----------------------------------------------------------------
# 2a. Static Black-Scholes helpers
# ----------------------------------------------------------------
@staticmethod
def BlackScholes(payoff="call", S0=100.0, K=100.0, T=1.0, r=0.1, sigma=0.2):
"""Static convenience wrapper around the module-level bs_price()."""
return bs_price(payoff, S0, K, T, r, sigma)
@staticmethod
def vega(sigma, S0, K, T, r):
"""Static convenience wrapper around the module-level bs_vega()."""
return bs_vega(S0, K, T, r, sigma)
# ----------------------------------------------------------------
# 2b. Closed-form (instance method)
# ----------------------------------------------------------------
def closed_formula(self):
"""
Black-Scholes closed-form price using instance parameters.
Returns
-------
float
Option price
"""
return bs_price(self.payoff, self.S0, self.K, self.T, self.r, self.sig)
# ----------------------------------------------------------------
# 2c. Monte Carlo
# ----------------------------------------------------------------
def MC(self, N=100_000, Err=False, Time=False):
"""
Monte Carlo pricing of a European option under GBM.
The terminal stock price is simulated directly (no path needed
for European options):
S_T = S0 * exp((r - 0.5*sig^2)*T + sig*sqrt(T)*Z), Z ~ N(0,1)
Parameters
----------
N : int
Number of simulation paths
Err : bool
If True, also return the standard error of the MC estimate
Time : bool
If True, also return the elapsed wall-clock time
Returns
-------
float or tuple
Price, and optionally (std_error, elapsed_time)
"""
t_init = time()
# Simulate terminal stock prices under risk-neutral measure
Z = np.random.standard_normal(N)
S_T = self.S0 * np.exp(
(self.r - 0.5 * self.sig ** 2) * self.T
+ self.sig * np.sqrt(self.T) * Z
)
# Discounted payoffs
disc_payoffs = np.exp(-self.r * self.T) * self.payoff_f(S_T)
V = np.mean(disc_payoffs)
elapsed = time() - t_init
std_err = ss.sem(disc_payoffs)
if Err and Time:
return V, std_err, elapsed
elif Err:
return V, std_err
elif Time:
return V, elapsed
else:
return V
# ----------------------------------------------------------------
# 2d. PDE solver (implicit finite-difference via sparse LU)
# ----------------------------------------------------------------
def PDE_price(self, steps, Time=False):
"""
Solve the Black-Scholes PDE on a log-price grid using an
implicit (backward Euler) finite-difference scheme.
The PDE in log-price x = ln(S):
dV/dt + (r - sig^2/2) dV/dx + 0.5*sig^2 d^2V/dx^2 - r*V = 0
The implicit scheme leads to a tridiagonal system solved at each
time step via a sparse LU factorisation (scipy.sparse.linalg.splu).
For American options the early-exercise constraint is imposed at
each time step by taking the element-wise maximum against the
intrinsic payoff.
Parameters
----------
steps : tuple of int
(Nspace, Ntime) -- number of spatial and temporal grid points
Time : bool
If True, also return the elapsed wall-clock time
Returns
-------
float or tuple
Interpolated price at S0, and optionally elapsed_time
"""
t_init = time()
Nspace, Ntime = steps
# Spatial domain in log-price
S_max = 6.0 * self.K
S_min = self.K / 6.0
x_max = np.log(S_max)
x_min = np.log(S_min)
x0 = np.log(self.S0) # current log-price
x, dx = np.linspace(x_min, x_max, Nspace, retstep=True)
t, dt = np.linspace(0, self.T, Ntime, retstep=True)
self.S_vec = np.exp(x) # grid of stock prices
Payoff = self.payoff_f(self.S_vec)
# Solution matrix: V[i, j] = option value at (x_i, t_j)
V = np.zeros((Nspace, Ntime))
# ---- Terminal condition (at maturity t = T) ----
V[:, -1] = Payoff
# ---- Boundary conditions ----
if self.payoff == "call":
V[-1, :] = np.exp(x_max) - self.K * np.exp(-self.r * t[::-1])
V[0, :] = 0.0
else: # put
V[-1, :] = 0.0
V[0, :] = Payoff[0] * np.exp(-self.r * t[::-1])
# ---- Tridiagonal coefficients for the implicit scheme ----
sig2 = self.sig ** 2
dxx = dx ** 2
a = (dt / 2.0) * ((self.r - 0.5 * sig2) / dx - sig2 / dxx)
b = 1.0 + dt * (sig2 / dxx + self.r)
c = -(dt / 2.0) * ((self.r - 0.5 * sig2) / dx + sig2 / dxx)
# Build sparse tridiagonal matrix (interior points only)
D = sparse.diags(
[a, b, c], [-1, 0, 1],
shape=(Nspace - 2, Nspace - 2)
).tocsc()
# Pre-factor with sparse LU for efficiency
DD = splu(D)
offset = np.zeros(Nspace - 2)
# ---- Backward time-stepping ----
if self.exercise == "European":
for i in range(Ntime - 2, -1, -1):
offset[0] = a * V[0, i]
offset[-1] = c * V[-1, i]
V[1:-1, i] = DD.solve(V[1:-1, i + 1] - offset)
elif self.exercise == "American":
for i in range(Ntime - 2, -1, -1):
offset[0] = a * V[0, i]
offset[-1] = c * V[-1, i]
V[1:-1, i] = np.maximum(
DD.solve(V[1:-1, i + 1] - offset),
Payoff[1:-1]
)
else:
raise ValueError("exercise must be 'European' or 'American'")
# Interpolate to find the price at the current stock price S0
self.price = np.interp(x0, x, V[:, 0])
self.price_vec = V[:, 0]
self.mesh = V
elapsed = time() - t_init
if Time:
return self.price, elapsed
return self.price
# ----------------------------------------------------------------
# 2e. Longstaff-Schwartz Method (American put)
# ----------------------------------------------------------------
def LSM(self, N=10_000, paths=10_000, order=2):
"""
Longstaff-Schwartz Method for pricing American put options.
At each exercise date the method performs a least-squares polynomial
regression of the discounted future cash-flows on the current stock
price. Paths where the immediate exercise value exceeds the
estimated continuation value are exercised early.
Parameters
----------
N : int
Number of time steps per path
paths : int
Number of simulated paths
order : int
Polynomial order for the continuation-value regression
Returns
-------
float
Estimated American put price
"""
if self.payoff != "put":
raise ValueError("LSM is implemented for put options only")
dt = self.T / (N - 1)
df = np.exp(-self.r * dt) # one-step discount factor
# Simulate log-price increments and build the price matrix
X0 = np.zeros((paths, 1))
increments = ss.norm.rvs(
loc=(self.r - 0.5 * self.sig ** 2) * dt,
scale=self.sig * np.sqrt(dt),
size=(paths, N - 1),
)
X = np.concatenate((X0, increments), axis=1).cumsum(axis=1)
S = self.S0 * np.exp(X) # paths x N price matrix
# Intrinsic value (put payoff) along every path and time step
H = np.maximum(self.K - S, 0.0)
# Value matrix -- initialised at terminal payoff
V = np.zeros_like(H)
V[:, -1] = H[:, -1]
# ---- Backward induction with regression ----
for t_idx in range(N - 2, 0, -1):
in_the_money = H[:, t_idx] > 0 # only regress on ITM paths
if np.sum(in_the_money) == 0:
V[:, t_idx] = V[:, t_idx + 1] * df
continue
# Polynomial regression: continuation value ~ f(S)
rg = np.polyfit(
S[in_the_money, t_idx],
V[in_the_money, t_idx + 1] * df,
order,
)
C = np.polyval(rg, S[in_the_money, t_idx])
# Decision: exercise now if intrinsic > continuation estimate
exercise = np.zeros(paths, dtype=bool)
exercise[in_the_money] = H[in_the_money, t_idx] > C
V[exercise, t_idx] = H[exercise, t_idx]
V[exercise, t_idx + 1:] = 0 # kill future cash-flows
discount_path = V[:, t_idx] == 0
V[discount_path, t_idx] = V[discount_path, t_idx + 1] * df
# Price = discounted average of values at t=1
V0 = np.mean(V[:, 1]) * df
return V0
# ----------------------------------------------------------------
# 2f. Plotting helpers
# ----------------------------------------------------------------
def plot(self, axis=None):
"""
Plot the intrinsic payoff alongside the Black-Scholes price curve
obtained from the PDE solver.
If the PDE has not been run yet, it is executed automatically with
a fine grid (7000 x 5000).
"""
if self.S_vec is None or self.price_vec is None:
self.PDE_price((7000, 5000))
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(self.S_vec, self.payoff_f(self.S_vec),
color="blue", linewidth=2, label="Payoff at expiry")
ax.plot(self.S_vec, self.price_vec,
color="red", linewidth=2, label="BS price (PDE)")
if isinstance(axis, list):
ax.axis(axis)
ax.set_xlabel("Stock price $S$", fontsize=12)
ax.set_ylabel("Option value", fontsize=12)
ax.set_title(
f"{self.exercise} {self.payoff.capitalize()} -- "
f"Black-Scholes Price Curve",
fontsize=13,
)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def mesh_plt(self):
"""
3-D surface plot of the option value over (S, t) from the PDE mesh.
"""
if self.S_vec is None or self.mesh is None:
self.PDE_price((7000, 5000))
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection="3d")
T_grid = np.linspace(0, self.T, self.mesh.shape[1])
X, Y = np.meshgrid(T_grid, self.S_vec)
ax.plot_surface(Y, X, self.mesh, cmap="ocean", alpha=0.85)
ax.set_title(
f"{self.exercise} {self.payoff.capitalize()} -- "
f"BS Price Surface",
fontsize=13,
)
ax.set_xlabel("S")
ax.set_ylabel("t")
ax.set_zlabel("V")
ax.view_init(30, -100)
plt.tight_layout()
plt.show()
============================================================================¶
3. COMPREHENSIVE DEMO¶
============================================================================¶
def main(): """ Demonstrate all pricing methods and compare results in a summary table. """
print("=" * 75)
print(" Black-Scholes Multi-Method Pricer")
print(" Based on cantaro86 / Financial-Models-Numerical-Methods")
print("=" * 75)
# ------------------------------------------------------------------
# Market parameters
# ------------------------------------------------------------------
S0 = 100.0 # current stock price
K = 100.0 # strike price (at-the-money)
T = 1.0 # time to maturity (1 year)
r = 0.05 # risk-free rate (5 %)
sigma = 0.20 # volatility (20 %)
print(f"\nMarket Parameters:")
print(f" Spot price S0 = {S0}")
print(f" Strike price K = {K}")
print(f" Maturity T = {T} year")
print(f" Risk-free r = {r:.2%}")
print(f" Volatility sigma = {sigma:.2%}")
# ------------------------------------------------------------------
# A. Closed-form pricing (call and put)
# ------------------------------------------------------------------
print("\n" + "-" * 75)
print("A. CLOSED-FORM BLACK-SCHOLES PRICES")
print("-" * 75)
call_pricer = BS_pricer(S0, K, T, r, sigma, payoff="call", exercise="European")
put_pricer = BS_pricer(S0, K, T, r, sigma, payoff="put", exercise="European")
call_cf = call_pricer.closed_formula()
put_cf = put_pricer.closed_formula()
print(f" European Call : {call_cf:.6f}")
print(f" European Put : {put_cf:.6f}")
# Put-call parity check
parity_lhs = call_cf - put_cf
parity_rhs = S0 - K * np.exp(-r * T)
print(f"\n Put-Call Parity check: C - P = S0 - K*exp(-rT)")
print(f" LHS (C - P) = {parity_lhs:.6f}")
print(f" RHS = {parity_rhs:.6f}")
print(f" Difference = {abs(parity_lhs - parity_rhs):.2e}")
# Vega
v = bs_vega(S0, K, T, r, sigma)
print(f"\n Vega (dPrice/dSigma) = {v:.6f}")
# ------------------------------------------------------------------
# B. Monte Carlo pricing
# ------------------------------------------------------------------
print("\n" + "-" * 75)
print("B. MONTE CARLO PRICING (European)")
print("-" * 75)
np.random.seed(42)
N_mc = 500_000
call_mc, call_se, call_t = call_pricer.MC(N=N_mc, Err=True, Time=True)
put_mc, put_se, put_t = put_pricer.MC(N=N_mc, Err=True, Time=True)
print(f" Paths = {N_mc:,}")
print(f" Call MC price : {call_mc:.6f} (SE = {call_se:.6f}, time = {call_t:.3f}s)")
print(f" Put MC price : {put_mc:.6f} (SE = {put_se:.6f}, time = {put_t:.3f}s)")
print(f" Call error vs closed-form : {abs(call_mc - call_cf):.6f}")
print(f" Put error vs closed-form : {abs(put_mc - put_cf):.6f}")
# ------------------------------------------------------------------
# C. PDE pricing and convergence
# ------------------------------------------------------------------
print("\n" + "-" * 75)
print("C. PDE PRICING (Implicit Finite-Difference, Sparse LU)")
print("-" * 75)
grid_sizes = [
(200, 100),
(500, 250),
(1000, 500),
(2000, 1000),
(5000, 2500),
]
print(f"\n {'Nspace':>8} {'Ntime':>8} {'Call PDE':>12} {'Error':>12} {'Time (s)':>10}")
print(f" {'-'*54}")
for ns, nt in grid_sizes:
pricer_tmp = BS_pricer(S0, K, T, r, sigma, payoff="call", exercise="European")
pde_val, pde_t = pricer_tmp.PDE_price((ns, nt), Time=True)
err = abs(pde_val - call_cf)
print(f" {ns:>8} {nt:>8} {pde_val:>12.6f} {err:>12.6f} {pde_t:>10.4f}")
# Run a fine-grid PDE for the put as well (used for plotting later)
fine_ns, fine_nt = 5000, 2500
put_pde_val, put_pde_t = put_pricer.PDE_price((fine_ns, fine_nt), Time=True)
call_pde_val, _ = call_pricer.PDE_price((fine_ns, fine_nt), Time=True)
print(f"\n Fine-grid ({fine_ns} x {fine_nt}):")
print(f" Call PDE = {call_pde_val:.6f} (error = {abs(call_pde_val - call_cf):.2e})")
print(f" Put PDE = {put_pde_val:.6f} (error = {abs(put_pde_val - put_cf):.2e})")
# ------------------------------------------------------------------
# D. American put via Longstaff-Schwartz
# ------------------------------------------------------------------
print("\n" + "-" * 75)
print("D. AMERICAN PUT -- LONGSTAFF-SCHWARTZ METHOD (LSM)")
print("-" * 75)
np.random.seed(42)
am_pricer = BS_pricer(S0, K, T, r, sigma, payoff="put", exercise="American")
# Run LSM several times to show variability
n_runs = 5
lsm_prices = []
print(f"\n LSM parameters: N_steps=10000, paths=50000, poly_order=2")
print(f" Running {n_runs} independent trials ...\n")
for run in range(1, n_runs + 1):
lsm_val = am_pricer.LSM(N=10_000, paths=50_000, order=2)
lsm_prices.append(lsm_val)
print(f" Trial {run}: American Put = {lsm_val:.6f}")
lsm_mean = np.mean(lsm_prices)
lsm_std = np.std(lsm_prices, ddof=1)
print(f"\n LSM mean = {lsm_mean:.6f}")
print(f" LSM std = {lsm_std:.6f}")
print(f" European put (closed-form) = {put_cf:.6f}")
print(f" Early-exercise premium ~ {lsm_mean - put_cf:.6f}")
# Also price American put via PDE for comparison
am_pde_pricer = BS_pricer(S0, K, T, r, sigma, payoff="put", exercise="American")
am_pde_val = am_pde_pricer.PDE_price((5000, 2500))
print(f"\n American put via PDE (5000x2500) = {am_pde_val:.6f}")
print(f" PDE early-exercise premium = {am_pde_val - put_cf:.6f}")
# ------------------------------------------------------------------
# E. Summary comparison table
# ------------------------------------------------------------------
print("\n" + "=" * 75)
print("E. SUMMARY -- ALL METHODS")
print("=" * 75)
header = (
f" {'Method':<28} {'Call':>12} {'Put':>12} {'Am. Put':>12}"
)
print(header)
print(f" {'-' * 66}")
print(f" {'Closed-form (exact)':<28} {call_cf:>12.6f} {put_cf:>12.6f} {'n/a':>12}")
print(f" {'Monte Carlo (500k paths)':<28} {call_mc:>12.6f} {put_mc:>12.6f} {'n/a':>12}")
print(f" {'PDE implicit (5000x2500)':<28} {call_pde_val:>12.6f} {put_pde_val:>12.6f} {am_pde_val:>12.6f}")
print(f" {'LSM (50k paths, mean)':<28} {'n/a':>12} {'n/a':>12} {lsm_mean:>12.6f}")
print(f" {'-' * 66}")
print("\n Observations:")
print(f" - MC and PDE converge to the closed-form for European options.")
print(f" - American put > European put by the early-exercise premium.")
print(f" - LSM and PDE American prices should be close; LSM has")
print(f" simulation noise while PDE has discretisation error.")
# ------------------------------------------------------------------
# F. Plot: payoff vs BS price curve (European call)
# ------------------------------------------------------------------
print("\n" + "-" * 75)
print("F. PLOT -- Payoff vs Black-Scholes Price Curve (European Call)")
print("-" * 75)
print(" (Close the plot window to continue.)\n")
call_pricer.plot(axis=[0, 3 * K, -5, K])
print("\n" + "=" * 75)
print(" Demo complete.")
print("=" * 75)
if name == "main": main() ```
Exercises¶
Exercise 1. The BS pricer implements five methods. Rank them by computational cost and explain when each is the best choice.
Solution to Exercise 1
| Method | Cost | Best for |
|---|---|---|
| Closed-form | \(O(1)\) | European vanilla options |
| Vega | \(O(1)\) | Implied vol computation, hedging |
| Monte Carlo | \(O(N)\) | Path-dependent or high-dimensional options |
| PDE (implicit FD) | \(O(N_S \cdot N_t)\) | American options, smooth Greeks |
| LSM | \(O(N \cdot M \cdot p)\) | American/Bermudan options |
For European options, always use the closed form. For American puts, PDE or LSM. For exotic path-dependent options, Monte Carlo.
Exercise 2. The PDE solver uses an implicit finite-difference scheme with sparse LU factorization. Explain why sparse LU is efficient for the BS PDE.
Solution to Exercise 2
The implicit scheme produces a tridiagonal system \(A\mathbf{V}^j = \mathbf{b}^j\) at each time step. A tridiagonal matrix is extremely sparse (\(O(N)\) nonzeros out of \(O(N^2)\) entries).
Sparse LU factorization of a tridiagonal matrix costs \(O(N)\) and needs to be done only once (since \(A\) is the same at every time step). Each subsequent solve is also \(O(N)\). This is much faster than dense LU (\(O(N^3)\)) and makes the implicit scheme practical for large grids.
Exercise 3. The Longstaff-Schwartz Method (LSM) uses regression to estimate the continuation value. Describe the regression step at time \(t_k\).
Solution to Exercise 3
At time \(t_k\), for paths that are in the money:
- Compute the discounted future cash flows \(Y_i\) (from \(t_{k+1}\) onward) for each path \(i\).
- Regress \(Y_i\) on basis functions of \(S_{t_k}^{(i)}\) (e.g., \(1, S, S^2\)): \(\hat{Y} = \beta_0 + \beta_1 S + \beta_2 S^2\).
- The fitted values \(\hat{Y}_i\) estimate the continuation value \(E[Y \mid S_{t_k}]\).
- Compare with the exercise value \(\max(K - S_{t_k}^{(i)}, 0)\): exercise if immediate value exceeds continuation.
This backward induction determines the optimal exercise strategy, and the price is the average discounted payoff under this strategy.
Exercise 4. Compare the five pricing methods for an American put with \(S = 100\), \(K = 100\), \(T = 1\), \(r = 0.05\), \(\sigma = 0.20\). Which methods can handle early exercise?
Solution to Exercise 4
Only the PDE solver and LSM can handle early exercise:
- PDE: At each time step, enforce \(V \ge \max(K - S, 0)\) (the early exercise constraint).
- LSM: Uses regression-based backward induction to approximate the optimal exercise boundary.
The closed-form formula gives only the European put price (a lower bound). Monte Carlo can price European options but needs LSM for American options. Vega applies only to European options.
For this example, the American put premium (excess over European) is typically 1--3% of the option price, depending on the parameters.