FinanceThe Black-Sholes ModelIn PythonDiogo RibeiroBlockedUnblockFollowFollowingMay 31Photo by Chris Liverani on UnsplashThe Black-Scholes model is used to price European options (which assumes that they must be held to expiration) and related custom derivatives.

It takes into account that you have the option of investing in an asset earning the risk-free interest rate.

It acknowledges that the option price is purely a function of the volatility of the stock’s price (the higher the volatility the higher the premium on the option).

Black-Scholes treats a call option as a forward contract to deliver stock at a contractual price, which is, of course, the strike price.

The Black-Scholes model was first introduced by Fischer Black and Myron Scholes in 1973 in the paper “The Pricing of Options and Corporate Liabilities”.

Since being published, the model has become a widely used tool by investors and is still regarded as one of the best ways to determine fair prices of options.

Using the Black-Scholes Model ScholesThere are variations of the Black-Scholes model that prices for dividend payments (within the option period).

See Hull section 13.

12 to see how that is done (easy to understand).

However, because of what is said below, you really can't use Black-Scholes to estimate values of options for dividend-paying American stocks.

There is no easy estimator for American options prices, but as Hull points out in chapter 9 section 9.

5, with the exception of exercising a call option just prior to an ex-dividend date:“it is never optimal to exercise an American call option on a nondividend paying stock before the expiration date.

”The Black-Scholes model can be used to estimate “implied volatility”.

To do this, however, given an actual option value, you have to iterate to find the volatility solution (see Hull’s discussion of this in 13.

12).

This procedure is easy to program and not very time-consuming in even an Excel version of the model.

For those of you interest in another elegant implied volatility model, see Hull’s discussion of the IVF model in 26.

3.

There you will see a role played by delta and vega, but again you would have to iterate to get the value of the sensitivity of the call to the strike price.

The Essence of the Black-Scholes ApproachOnly volatility matters, the mu (drift) is not important.

The option’s premium will suffer from time decay as we approach expiration (Theta in the European model).

The stock’s underlying volatility contributes to the option’s premium (Vega).

The sensitivity of the option to a change in the stock’s value (Delta) and the rate of that sensitivity (Gamma) is important [these variables are represented mathematically in the Black-Scholes DE, next lecture].

Option values arise from arbitrage opportunities in a world where you have a risk-free choice.

AssumptionsTo determine the price of vanilla European options, several assumptions are made:European options can only be exercised at expirationNo dividends are paid during the option’s lifeMarket movements cannot be predictedThe risk-free rate and volatility are constantFollows a lognormal distributionNon-Dividend Paying Black-Scholes FormulaIn Black-Scholes formulas, the following parameters are defined.

S, the spot price of the asset at time ttT, the maturity of the option.

Time to maturity is defined as T−tK, the strike price of the optionr, the risk-free interest rate, assumed to be constant between tt and TTσ, the volatility of the underlying asset, the standard deviation of the asset returnsN(d) is the cumulative distribution of the standard normal variable ZC(S,t) is the value at time tt of a call option and P(S,t) is the value at time t of a put option.

The Black-Scholes call formula is given as:The put formula is given:Where:Python Implementation of Black-Scholes formula for non-dividend paying optionsimport numpy as npimport scipy.

stats as siimport sympy as syimport sympy.

statistics as systatsdef euro_vanilla_call(S, K, T, r, sigma): #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)) call = (S * si.

norm.

cdf(d1, 0.

0, 1.

0) – K * np.

exp(-r * T) * si.

norm.

cdf(d2, 0.

0, 1.

0)) return calleuro_vanilla_call(50, 100, 1, 0.

05, 0.

25)0.

027352509369436617def euro_vanilla_put(S, K, T, r, sigma): #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)) put = (K * np.

exp(-r * T) * si.

norm.

cdf(-d2, 0.

0, 1.

0) – S * si.

norm.

cdf(-d1, 0.

0, 1.

0)) return puteuro_vanilla_put(50, 100, 1, 0.

05, 0.

25)45.

150294959440842The next function can be called with ‘call’ or ‘put’ for the options parameter to calculate the desired optiondef euro_vanilla(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': result = (S * si.

norm.

cdf(d1, 0.

0, 1.

0) – K * np.

exp(-r * T) * si.

norm.

cdf(d2, 0.

0, 1.

0)) if option == 'put': result = (K * np.

exp(-r * T) * si.

norm.

cdf(-d2, 0.

0, 1.

0) – S * si.

norm.

cdf(-d1, 0.

0, 1.

0)) return resulteuro_vanilla(50, 100, 1, 0.

05, 0.

25, option = 'put')45.

150294959440842Sympy implementation for Exact Resultsdef euro_call_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) 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)) call = (S * N.

cdf(d1) – K * sy.

exp(-r * T) * N.

cdf(d2)) return calleuro_call_sym(50, 100, 1, 0.

05, 0.

25)-25.

0*erf(1.

22379436111989*sqrt(2)) – 22.

5614712250357 + 47.

5614712250357*erf(1.

34879436111989*sqrt(2))def euro_put_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) 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 = (K * sy.

exp(-r * T) * N.

cdf(-d2) – S * N.

cdf(-d1)) return putSympy implementation of the above function that enables one to specify a call or put the result.

def sym_euro_vanilla(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 N = systats.

Normal(0.

0, 1.

0) 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)) if option == 'call': result = (S * N.

cdf(d1) – K * sy.

exp(-r * T) * N.

cdf(d2)) if option == 'put': result = (K * sy.

exp(-r * T) * N.

cdf(-d2) – S * N.

cdf(-d1)) return resultsym_euro_vanilla(50, 100, 1, 0.

05, 0.

25, option = 'put')-25.

0*erf(1.

22379436111989*sqrt(2)) + 22.

5614712250357 + 47.

5614712250357*erf(1.

34879436111989*sqrt(2))Dividend Paying Black-Scholes FormulaFor assets that pay dividends, the Black-Scholes formula is rather similar to the non-dividend paying asset formula; however, a new parameter q, is added.

S, the spot price of the asset at time tT, the maturity of the option.

Time to maturity is defined as T−tK, the strike price of the optionr, the risk-free interest rate, assumed to be constant between t and Tσ, the volatility of the underlying asset, the standard deviation of the asset returnsq, the dividend rate of the asset.

This is assumed to pay dividends at a continuous rateIn this case, the q parameter is now included in C(S,t) and P(S,t).

Then, d1 and d2 are slightly modified to include the continuous dividendsPython Implementationdef black_scholes_call_div(S, K, T, r, q, sigma): #S: spot price #K: strike price #T: time to maturity #r: interest rate #q: rate of continuous dividend paying asset #sigma: volatility of underlying asset d1 = (np.

log(S / K) + (r – q + 0.

5 * sigma ** 2) * T) / (sigma * np.

sqrt(T)) d2 = (np.

log(S / K) + (r – q – 0.

5 * sigma ** 2) * T) / (sigma * np.

sqrt(T)) call = (S * np.

exp(-q * T) * si.

norm.

cdf(d1, 0.

0, 1.

0) – K * np.

exp(-r * T) * si.

norm.

cdf(d2, 0.

0, 1.

0)) return calldef black_scholes_put_div(S, K, T, r, q, sigma): #S: spot price #K: strike price #T: time to maturity #r: interest rate #q: rate of continuous dividend paying asset #sigma: volatility of underlying asset d1 = (np.

log(S / K) + (r – q + 0.

5 * sigma ** 2) * T) / (sigma * np.

sqrt(T)) d2 = (np.

log(S / K) + (r – q – 0.

5 * sigma ** 2) * T) / (sigma * np.

sqrt(T)) put = (K * np.

exp(-r * T) * si.

norm.

cdf(-d2, 0.

0, 1.

0) – S * np.

exp(-q * T) * si.

norm.

cdf(-d1, 0.

0, 1.

0)) return putThe implementation that can be used to determine the put or call option price depending on the specificationdef euro_vanilla_dividend(S, K, T, r, q, sigma, option = 'call'): #S: spot price #K: strike price #T: time to maturity #r: interest rate #q: rate of continuous dividend paying asset #sigma: volatility of underlying asset d1 = (np.

log(S / K) + (r – q + 0.

5 * sigma ** 2) * T) / (sigma * np.

sqrt(T)) d2 = (np.

log(S / K) + (r – q – 0.

5 * sigma ** 2) * T) / (sigma * np.

sqrt(T)) if option == 'call': result = (S * np.

exp(-q * T) * si.

norm.

cdf(d1, 0.

0, 1.

0) – K * np.

exp(-r * T) * si.

norm.

cdf(d2, 0.

0, 1.

0)) if option == 'put': result = (K * np.

exp(-r * T) * si.

norm.

cdf(-d2, 0.

0, 1.

0) – S * np.

exp(-q * T) * si.

norm.

cdf(-d1, 0.

0, 1.

0)) return resultSympy Implementation of Black-Scholes with Dividend-paying assetdef black_scholes_call_div_sym(S, K, T, r, q, sigma): #S: spot price #K: strike price #T: time to maturity #r: interest rate #q: rate of continuous dividend paying asset #sigma: volatility of underlying asset N = systats.

Normal(0.

0, 1.

0) d1 = (sy.

ln(S / K) + (r – q + 0.

5 * sigma ** 2) * T) / (sigma * sy.

sqrt(T)) d2 = (sy.

ln(S / K) + (r – q – 0.

5 * sigma ** 2) * T) / (sigma * sy.

sqrt(T)) call = S * sy.

exp(-q * T) * N.

cdf(d1) – K * sy.

exp(-r * T) * N.

cdf(d2) return calldef black_scholes_call_put_sym(S, K, T, r, q, sigma): #S: spot price #K: strike price #T: time to maturity #r: interest rate #q: rate of continuous dividend paying asset #sigma: volatility of underlying asset N = systats.

Normal(0.

0, 1.

0) d1 = (sy.

ln(S / K) + (r – q + 0.

5 * sigma ** 2) * T) / (sigma * sy.

sqrt(T)) d2 = (sy.

ln(S / K) + (r – q – 0.

5 * sigma ** 2) * T) / (sigma * sy.

sqrt(T)) put = K * sy.

exp(-r * T) * N.

cdf(-d2) – S * sy.

exp(-q * T) * N.

cdf(-d1) return putSympy implementation of pricing a European put or call option depending on the specificationdef sym_euro_vanilla_dividend(S, K, T, r, q, sigma, option = 'call'): #S: spot price #K: strike price #T: time to maturity #r: interest rate #q: rate of continuous dividend paying asset #sigma: volatility of underlying asset N = systats.

Normal(0.

0, 1.

0) d1 = (sy.

ln(S / K) + (r – q + 0.

5 * sigma ** 2) * T) / (sigma * sy.

sqrt(T)) d2 = (sy.

ln(S / K) + (r – q – 0.

5 * sigma ** 2) * T) / (sigma * sy.

sqrt(T)) if option == 'call': result = S * sy.

exp(-q * T) * N.

cdf(d1) – K * sy.

exp(-r * T) * N.

cdf(d2) if option == 'put': result = K * sy.

exp(-r * T) * N.

cdf(-d2) – S * sy.

exp(-q * T) * N.

cdf(-d1) return result.