Put-Call Parity of Vanilla European Options and Python Implementation

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:

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

Rearranging the above to isolate $P(t)$ and $C(t)$ on the left-hand side for non-dividend paying assets:

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

Rearranging for continuous dividend paying assets:

$$C(t) = S(t)e^{-q(T-t)} + P(t) - Ke^{-r(T-t)}$$ $$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:

$$C(S,t) = S \cdot N(d_1) - Ke^{-r(T - t)} N(d_2)$$

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}$

$$P(S,t) = Ke^{-r(T-t)} - S + C(S,t)$$ $$= 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))$$

$$ P(S,t) = Ke^{-r(T-t)} N(-d_2) - S \cdot N(-d_1)$$

Python Implementation

In [3]:
import numpy as np
import scipy as si
import sympy as sy
import sympy.statistics as systats
In [2]:
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

In [4]:
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 = systats.Normal(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) * N.cdf(-d2) - S * N.cdf(-d1)
    
    return put_price

For dividend paying assets:

$$C(S,t) = Se^{-q(T - t)} N(d_1) - Ke^{-r(T - t)} N(d_2)$$

$1 - N(d) = N(-a)$ for any $a \in \mathbb{R}$

$$P(S,t) = Ke^{-r(T-t)} - Se^{-q(T-t)} + C(S,t)$$ $$= 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

In [5]:
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

In [5]:
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 = systats.Normal(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) * N.cdf(-d2) - S * sy.exp(-q * T) * N.cdf(-d1)
    
    return put_price_dividend