In the paper, it is stated the premium of a call option implies a certain fair price for the corresponding put option (same asset, strike price and expiration date). The Put-Call Parity is used to validate option pricing models as any pricing model that produces option prices which violate the parity should be considered flawed.
Note, since American options can be exercised before the expiration date, the Put-Call Parity only applies to European options.
The Formula¶
Let $C(t)$ and $P(t)$ be the call and put values at time $t$ for a European option with maturity $T$ and strike $K$ on a non-dividend paying asset with spot price $S(t)$. The Parity states:
Rearranging the above to isolate $P(t)$ and $C(t)$ on the left-hand side for non-dividend paying assets:
$$P(t) = C(t) - S(t) + Ke^{-r(T-t)}$$
Rearranging for continuous dividend paying assets:
$$P(t) = C(t) - S(t)e^{-q(T-t)} + Ke^{-r(T-t)}$$
With this, we can find a result for a European put option based on the corresponding call option. From the Black-Scholes formula, we can derive the call option:
From the Put-Call parity statement, we can arrive at a formula for pricing European put options knowing that $1 - N(d) = N(-a)$ for any $a \in \mathbb{R}$
$$= Ke^{-r(T-t)} - S + (S \cdot N(d_1) - Ke^{-r(T-t)} N(d_2))$$ $$= Ke^{-r(T-t)}(1 - N(d_2)) - S(1 - N(d_1))$$
Python Implementation¶
import numpy as np
import scipy as si
import sympy as sy
import sympy.stats as systats
def put_option_price(S, K, T, r, sigma):
#S: spot price
#K: strike price
#T: time to maturity
#r: interest rate
#sigma: volatility of underlying asset
S = float(S)
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
put_price = K * np.exp(-r * T) * si.stats.norm.cdf(-d2, 0.0, 1.0) - S * si.stats.norm.cdf(-d1, 0.0, 1.0)
return put_price
Python Implementation with Sympy¶
def put_option_price_sym(S, K, T, r, sigma):
#S: spot price
#K: strike price
#T: time to maturity
#r: interest rate
#sigma: volatility of underlying asset
N = Normal('x', 0.0, 1.0)
S = float(S)
d1 = (sy.ln(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sy.sqrt(T))
d2 = (sy.ln(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * sy.sqrt(T))
put_price = K * sy.exp(-r * T) * cdf(N)(-d2) - S * cdf(N)(-d1)
return put_price
For dividend paying assets:
$$= Ke^{-r(T-t)} - Se^{-q(T-t)} + (Se^{-q(T-t)} N(d_1) - Ke^{-r(T-t)} N(d_2))$$ $$= Ke^{-r(T-t)}(1 - N(d_2)) - Se^{-q(T-t)} (1 - N(d_1))$$
$$ P(S,t) = Ke^{-r(T-t)} N(-d_2) - Se^{-q(T-t)}N(-d_1)$$Python Implementation of Put Option Pricing for Dividend Paying Assets¶
def put_option_price_div(S, K, T, r, sigma, q):
#S: spot price
#K: strike price
#T: time to maturity
#r: interest rate
#sigma: volatility of underlying asset
#q: continuous dividend rate
S = float(S)
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
put_price_dividend = K * np.exp(-r * T) * si.stats.norm.cdf(-d2, 0.0, 1.0) - S * np.exp(-q * T) * si.stats.norm.cdf(-d1, 0.0, 1.0)
return put_price_dividend
Sympy Implementation of Put Option Pricing for Dividend Paying Assets¶
def put_option_price_div_sym(S, K, T, r, sigma, q):
#S: spot price
#K: strike price
#T: time to maturity
#r: interest rate
#sigma: volatility of underlying asset
#q: continuous dividend rate
N = Normal('x', 0.0, 1.0)
S = float(S)
d1 = (sy.ln(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sy.sqrt(T))
d2 = (sy.ln(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * sy.sqrt(T))
put_price_dividend = K * sy.exp(-r * T) * cdf(N)(-d2) - S * sy.exp(-q * T) * cdf(N)(-d1)
return put_price_dividend