4 minute read

Implementing Black Scholes Using Python

#importing all of the necessary modules that we are going to use 

import numpy as np
import scipy.stats as si
import sympy as sy
from sympy.stats import Normal, cdf
from sympy import init_printing
init_printing()

The function below creates our black-scholes option pricer.

Function Used to Create Black-Scholes Option Pricer

def black_scholes(S, K, T, r, sigma, option = 'call'):
    
    #S: spot price
    #K: strike price
    #T: time to maturity
    #r: interest rate
    #sigma: volatility of underlying asset
    
    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))
    
    if option == 'call': 
        call = (S * si.norm.cdf(d1, 0.0, 1.0) - K * np.exp(-r * T) * si.norm.cdf(d2, 0.0, 1.0))
        return call 
    if option == 'put':
        put = (K * np.exp(-r * T) * si.norm.cdf(-d2, 0.0, 1.0) - S * si.norm.cdf(-d1, 0.0, 1.0))
        return put 
black_scholes(100, 105, 0.5, 0.06, 0.2783, option = 'call')

png

This cell verifies that our black-scholes option pricer is working properly. The goal was to verify that our pricer spits out the same call price that is given on our lecture slides.

import numpy as np
from datascience import *

# These lines set up graphing capabilities.
import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
import warnings
warnings.simplefilter('ignore', FutureWarning)

from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

Now, we will build a table and use our option pricer to graph our results.

new_table = Table().with_column("Level of Underlying", np.arange(0,101,10))
new_table.show()

Initial Table

Level of Underlying
0
10
20
30
40
50
60
70
80
90
100
#making sure that our Black-Scholes Option pricer works correctly, so we run a sample output and verify with the slide deck from class
#our given inputs are the following: 
    
    #S: spot price
    #K: strike price
    #T: time to maturity
    #r: interest rate
    #sigma: volatility of underlying asset
black_scholes(0.0001, 50, 1, 0.1, 0.39115)

png

time_to_expiration_of_one_year_array = make_array()
for i in np.arange(0, 101, 10): 
    time_to_expiration_of_one_year_array = np.append(time_to_expiration_of_one_year_array, black_scholes(i, 50, 1, 0.1, 0.39115))
<ipython-input-2-76ac2c17deda>:9: RuntimeWarning: divide by zero encountered in log
  d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
<ipython-input-2-76ac2c17deda>:10: RuntimeWarning: divide by zero encountered in log
  d2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
time_to_expiration_of_one_year_array
array([0.00000000e+00, 1.08176967e-04, 7.77138496e-02, 1.07658328e+00,
       4.30794966e+00, 9.99974419e+00, 1.75336194e+01, 2.62035114e+01,
       3.55074908e+01, 4.51478326e+01, 5.49623485e+01])
new_table = new_table.with_column("Time to Expiration of One Year", time_to_expiration_of_one_year_array)
new_table.show()

Table of Time to Expiration of One Year

Level of Underlying Time to Expiration of One Year
0 0
10 0.000108177
20 0.0777138
30 1.07658
40 4.30795
50 9.99974
60 17.5336
70 26.2035
80 35.5075
90 45.1478
100 54.9623
no_time_to_expiration_array = make_array()
for i in np.arange(0, 101, 10): 
    no_time_to_expiration_array = np.append(no_time_to_expiration_array, black_scholes(i, 50, 0.0000001, 0.1, 0.39115))
<ipython-input-2-76ac2c17deda>:9: RuntimeWarning: divide by zero encountered in log
  d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
<ipython-input-2-76ac2c17deda>:10: RuntimeWarning: divide by zero encountered in log
  d2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
no_time_to_expiration_array 
array([0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
       0.00000000e+00, 2.46755821e-03, 1.00000005e+01, 2.00000005e+01,
       3.00000005e+01, 4.00000005e+01, 5.00000005e+01])
new_table = new_table.with_column("Time to Expiration of None", no_time_to_expiration_array )
new_table.show()

Graph of Time to Expiration of Both One and Zero Years

Level of Underlying Time to Expiration of One Year Time to Expiration of None
0 0 0
10 0.000108177 0
20 0.0777138 0
30 1.07658 0
40 4.30795 0
50 9.99974 0.00246756
60 17.5336 10
70 26.2035 20
80 35.5075 30
90 45.1478 40
100 54.9623 50
new_table.plot("Level of Underlying")

png

three_year_array = make_array()
for i in np.arange(0, 101, 10): 
    three_year_array = np.append(three_year_array, black_scholes(i, 50, 3, 0.1, 0.39115))
<ipython-input-2-76ac2c17deda>:9: RuntimeWarning: divide by zero encountered in log
  d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
<ipython-input-2-76ac2c17deda>:10: RuntimeWarning: divide by zero encountered in log
  d2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
three_year_array
array([ 0.        ,  0.12653854,  1.75641839,  5.77813145, 11.7582735 ,
       19.0826488 , 27.28807781, 36.06767126, 45.22466791, 54.63318365,
       64.21192494])
new_table = new_table.with_column("Time to Expiration of Three Years", three_year_array)
new_table.show()

Graph of All Three Times to Expiration

Level of Underlying Time to Expiration of One Year Time to Expiration of None Time to Expiration of Three Years
0 0 0 0
10 0.000108177 0 0.126539
20 0.0777138 0 1.75642
30 1.07658 0 5.77813
40 4.30795 0 11.7583
50 9.99974 0.00246756 19.0826
60 17.5336 10 27.2881
70 26.2035 20 36.0677
80 35.5075 30 45.2247
90 45.1478 40 54.6332
100 54.9623 50 64.2119
new_table.plot("Level of Underlying")
plt.xlabel('Level of Underlying (Arbitrary) ')
plt.ylabel('Price (Arbitrary) ')
Text(0, 0.5, 'Price (Arbitrary) ')

png

Final Graph

This is the final graph that we were supposed to replicate from our lecture slides. Mission accomplished! We can see that, at option expiration, the graph of the underlying and price has the sharp, rigid slope that we were looking for. In contrast, the other two curves are just that, curved. That is exactly what we would expect with the given inputs.

def black_scholes(S, K, T, r, sigma, option = 'call'):
    
    #S: spot price
    #K: strike price
    #T: time to maturity
    #r: interest rate
    #sigma: volatility of underlying asset
    
    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))
    
    if option == 'call': 
        call = (S * si.norm.cdf(d1, 0.0, 1.0) - K * np.exp(-r * T) * si.norm.cdf(d2, 0.0, 1.0))
        return call 
    if option == 'put':
        put = (K * np.exp(-r * T) * si.norm.cdf(-d2, 0.0, 1.0) - S * si.norm.cdf(-d1, 0.0, 1.0))
        return put 
black_scholes(100, 105, 0.5, 0.06, 0.2783, option = 'call')

png

black_scholes(85, 105, 0.5, 0.06, 0.2783, option = 'call')

png

black_scholes(85, 105, 0.5, 0.06, 0.2783, option = 'put')

png

black_scholes(100, 105, 0.5, 0.06, 0.5, option = 'call')

png

Conclusion

These last few lines were further verification of our option pricer. We tested different volatilities and spot prices, and also looked at the different prices associated with calls and puts.

Back to Top

Updated: