├── .gitignore ├── BinomialModel ├── BinomialPricing.py └── __init__.py ├── BlackAndScholes ├── BSPricing.py ├── Greeks.py └── __init__.py ├── CNAME ├── DataRequest ├── __init__.py ├── y_finane_option_data.py └── y_finane_stock_data.py ├── Enum ├── BuySellSide.py ├── OptionType.py └── __init__.py ├── Images ├── CallSkew.png ├── Delta.png ├── DupireVolVsBlack.png ├── Gamma.png ├── Heston Vol.png ├── IVImpactCallValue.png ├── IV_CALL.png ├── IV_PUT.png ├── IV_vs_LV.png ├── LongButterfly.png ├── MktPriceCP.png ├── OTM_IV.png ├── PutSkew.png ├── Put_CP.png ├── ShortButterFly.png ├── SyntheticCall.png ├── SyntheticPUT.png ├── TSLA Price.png ├── TSLA Vol.png ├── TSLA returns.png ├── Theta.png ├── TimeImpact.png ├── Vega.png ├── callSpread.png ├── calls_CP.png ├── dataFrame_TSLA.png ├── localVolatility.png ├── putSpread8090.png ├── straddle.png └── strangle.png ├── Option ├── Option.py ├── OptionStrategies.py └── __init__.py ├── README.md ├── Volatility ├── DupireVolatility.py ├── ImpliedVolatiliy.py ├── StochasticVolatility.py ├── __init__.py └── hestonModel.py ├── main.py ├── mainCallPutParity.py ├── mainHestonSimulation.py ├── mainImpliedVolatility.py ├── mainOptionStrategies.py └── mainPricingModel.py /.gitignore: -------------------------------------------------------------------------------- 1 | *pyc 2 | *.pyc 3 | *.idea 4 | -------------------------------------------------------------------------------- /BinomialModel/BinomialPricing.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pandas_datareader.data as web 3 | import numpy as np 4 | import datetime as dt 5 | import math 6 | 7 | import matplotlib.pyplot as plt 8 | 9 | 10 | def combos(n, i): 11 | return math.factorial(n) / (math.factorial(n - i) * math.factorial(i)) 12 | 13 | 14 | def binom_EU1(S0, K, T, r, sigma, N, type_='call'): 15 | dt = T / N 16 | u = np.exp(sigma * np.sqrt(dt)) 17 | d = np.exp(-sigma * np.sqrt(dt)) 18 | p = (np.exp(r * dt) - d) / (u - d) 19 | value = 0 20 | for i in range(N + 1): 21 | node_prob = combos(N, i) * p ** i * (1 - p) ** (N - i) 22 | ST = S0 * (u) ** i * (d) ** (N - i) 23 | if type_ == 'CALL': 24 | value += max(ST - K, 0) * node_prob 25 | elif type_ == 'PUT': 26 | value += max(K - ST, 0) * node_prob 27 | else: 28 | raise ValueError("type_ must be 'call' or 'put'") 29 | 30 | return value * np.exp(-r * T) 31 | 32 | -------------------------------------------------------------------------------- /BinomialModel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/BinomialModel/__init__.py -------------------------------------------------------------------------------- /BlackAndScholes/BSPricing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.stats import norm 3 | import Option.Option as Option 4 | N = norm.cdf 5 | 6 | 7 | def BS_CALL(S, K, T, r, sigma): 8 | d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T)) 9 | d2 = d1 - sigma * np.sqrt(T) 10 | return S * N(d1) - K * np.exp(-r*T)* N(d2) 11 | 12 | 13 | def BS_PUT(S, K, T, r, sigma): 14 | d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T)) 15 | d2 = d1 - sigma* np.sqrt(T) 16 | return K* np.exp(-r*T) * N(-d2) - S*N(-d1) 17 | 18 | 19 | def priceBS(S, K, T, r, sigma ,type='CALL'): 20 | if type == 'CALL': 21 | return BS_CALL(S, K, T, r, sigma) 22 | if type == 'PUT': 23 | return BS_PUT(S, K, T, r, sigma) 24 | else: 25 | raise ValueError('Unrecognized type') 26 | 27 | 28 | -------------------------------------------------------------------------------- /BlackAndScholes/Greeks.py: -------------------------------------------------------------------------------- 1 | from scipy.stats import norm 2 | from Enum.OptionType import OpionType 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | N = norm.cdf 7 | N_prime = norm.pdf 8 | 9 | def d1(S, K, T, r, sigma) -> float: 10 | return (np.log(S/K) + (r + sigma**2/2)*T) /\ 11 | sigma*np.sqrt(T) 12 | 13 | 14 | def d2(S, K, T, r, sigma) -> float: 15 | return d1(S, K, T, r, sigma) - sigma* np.sqrt(T) 16 | 17 | def Delta( S, K , T , r , sigma, type:OpionType) -> float: 18 | if type == OpionType.CALL: 19 | return N(d1(S, K, T, r, sigma)) 20 | elif type == OpionType.PUT: 21 | return - N(-d1(S, K, T, r, sigma)) 22 | 23 | def Gamma(S, K, T, r, sigma) -> float: 24 | N_prime = norm.pdf 25 | return N_prime(d1(S,K, T, r, sigma))/(S*sigma*np.sqrt(T)) 26 | 27 | def Vega(S, K, T, r, sigma) -> float: 28 | return S*np.sqrt(T)*N_prime(d1(S,K,T,r,sigma)) 29 | 30 | def Theta(S, K, T, r, sigma , type:OpionType) -> float: 31 | if type == OpionType.CALL: 32 | p1 = - S*N_prime(d1(S, K, T, r, sigma))*sigma / (2 * np.sqrt(T)) 33 | p2 = r*K*np.exp(-r*T)*N(d2(S, K, T, r, sigma)) 34 | return p1 - p2 35 | elif type == OpionType.PUT: 36 | p1 = - S * N_prime(d1(S, K, T, r, sigma)) * sigma / (2 * np.sqrt(T)) 37 | p2 = r * K * np.exp(-r * T) * N(-d2(S, K, T, r, sigma)) 38 | return p1 + p2 39 | 40 | def Rho(S, K, T, r, sigma , type:OpionType) -> float: 41 | if type == OpionType.CALL: 42 | return K * T * np.exp(-r * T) * N(d2(S, K, T, r, sigma)) 43 | elif type == OpionType.PUT: 44 | return -K * T * np.exp(-r * T) * N(-d2(S, K, T, r, sigma)) 45 | 46 | #Example 47 | S = 100 48 | K = 100 49 | T = 1 50 | r = 0.00 51 | sigma = 0.25 52 | 53 | prices = np.arange(1, 250,1) 54 | 55 | deltas_c = Delta(prices, K, T, r, sigma , OpionType.CALL) -------------------------------------------------------------------------------- /BlackAndScholes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/BlackAndScholes/__init__.py -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | mytest.adriencassagnes.com -------------------------------------------------------------------------------- /DataRequest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/DataRequest/__init__.py -------------------------------------------------------------------------------- /DataRequest/y_finane_option_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import yfinance as yf 3 | import datetime 4 | 5 | 6 | def get_option_data(symbol): 7 | tk = yf.Ticker(symbol) 8 | expirations = tk.options 9 | 10 | options = pd.DataFrame() 11 | 12 | for e in expirations: 13 | opt = tk.option_chain(e) 14 | opt = pd.concat([opt.calls,opt.puts]) 15 | opt['expirationDate'] = e 16 | options = pd.concat([options, opt] ,ignore_index=True) 17 | 18 | options['expirationDate'] = pd.to_datetime(options['expirationDate']) 19 | options['T_days'] = (options['expirationDate'] - datetime.datetime.today()).dt.days 20 | options['T_days'] = options['T_days'].apply(lambda x: 0 if x < 0 else x) 21 | options['Type'] = options['contractSymbol'].str[4:].apply(lambda x: "CALL" if 'C' in x else "PUT" ) 22 | options[['bid', 'ask', 'strike']] = options[['bid', 'ask', 'strike']].apply(pd.to_numeric) 23 | 24 | return options 25 | 26 | 27 | def get_stock_price(ticker): 28 | data = yf.download(ticker) 29 | return pd.DataFrame(data) 30 | 31 | 32 | def get_Option_Data_full(symbol) -> pd.DataFrame: 33 | print('getting options quotes..') 34 | option_df = get_option_data(symbol) 35 | print('getting stock price..') 36 | stockPrices_ = get_stock_price(symbol) 37 | 38 | last_price = stockPrices_.tail(1)['Adj Close'].values[0] 39 | option_df['underlying_LastPrice'] = last_price 40 | option_df['mid_price'] = (option_df['bid'] + option_df['ask']) /2 41 | 42 | return option_df 43 | 44 | -------------------------------------------------------------------------------- /DataRequest/y_finane_stock_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import yfinance as yf 3 | 4 | 5 | 6 | def get_stock_price(ticker): 7 | data = yf.download(ticker) 8 | return pd.DataFrame(data) 9 | -------------------------------------------------------------------------------- /Enum/BuySellSide.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class BuySellSide(Enum): 5 | NONE = 0 6 | BUY = 1 7 | SELL = -1 8 | -------------------------------------------------------------------------------- /Enum/OptionType.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OpionType(Enum): 5 | NONE = 0 6 | PUT = 1 7 | CALL = 2 8 | -------------------------------------------------------------------------------- /Enum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Enum/__init__.py -------------------------------------------------------------------------------- /Images/CallSkew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/CallSkew.png -------------------------------------------------------------------------------- /Images/Delta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/Delta.png -------------------------------------------------------------------------------- /Images/DupireVolVsBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/DupireVolVsBlack.png -------------------------------------------------------------------------------- /Images/Gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/Gamma.png -------------------------------------------------------------------------------- /Images/Heston Vol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/Heston Vol.png -------------------------------------------------------------------------------- /Images/IVImpactCallValue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/IVImpactCallValue.png -------------------------------------------------------------------------------- /Images/IV_CALL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/IV_CALL.png -------------------------------------------------------------------------------- /Images/IV_PUT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/IV_PUT.png -------------------------------------------------------------------------------- /Images/IV_vs_LV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/IV_vs_LV.png -------------------------------------------------------------------------------- /Images/LongButterfly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/LongButterfly.png -------------------------------------------------------------------------------- /Images/MktPriceCP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/MktPriceCP.png -------------------------------------------------------------------------------- /Images/OTM_IV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/OTM_IV.png -------------------------------------------------------------------------------- /Images/PutSkew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/PutSkew.png -------------------------------------------------------------------------------- /Images/Put_CP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/Put_CP.png -------------------------------------------------------------------------------- /Images/ShortButterFly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/ShortButterFly.png -------------------------------------------------------------------------------- /Images/SyntheticCall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/SyntheticCall.png -------------------------------------------------------------------------------- /Images/SyntheticPUT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/SyntheticPUT.png -------------------------------------------------------------------------------- /Images/TSLA Price.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/TSLA Price.png -------------------------------------------------------------------------------- /Images/TSLA Vol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/TSLA Vol.png -------------------------------------------------------------------------------- /Images/TSLA returns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/TSLA returns.png -------------------------------------------------------------------------------- /Images/Theta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/Theta.png -------------------------------------------------------------------------------- /Images/TimeImpact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/TimeImpact.png -------------------------------------------------------------------------------- /Images/Vega.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/Vega.png -------------------------------------------------------------------------------- /Images/callSpread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/callSpread.png -------------------------------------------------------------------------------- /Images/calls_CP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/calls_CP.png -------------------------------------------------------------------------------- /Images/dataFrame_TSLA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/dataFrame_TSLA.png -------------------------------------------------------------------------------- /Images/localVolatility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/localVolatility.png -------------------------------------------------------------------------------- /Images/putSpread8090.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/putSpread8090.png -------------------------------------------------------------------------------- /Images/straddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/straddle.png -------------------------------------------------------------------------------- /Images/strangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Images/strangle.png -------------------------------------------------------------------------------- /Option/Option.py: -------------------------------------------------------------------------------- 1 | from Enum import OptionType 2 | 3 | class Option: 4 | def __init__(self, type : OptionType, K, price): 5 | self.type = type 6 | self.K = K 7 | self.price = price 8 | 9 | class Stock: 10 | def __init__(self , price ): 11 | self.price= price -------------------------------------------------------------------------------- /Option/OptionStrategies.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from Option.Option import Option ,Stock 3 | import matplotlib.pyplot as plt 4 | from Enum.BuySellSide import BuySellSide 5 | from Enum.OptionType import OpionType 6 | from BlackAndScholes import Greeks 7 | from typing import Union 8 | 9 | class OptionStrategies: 10 | """ 11 | the purpose of this class is to implement option trading strategies on one and the same underlying 12 | """ 13 | def __init__(self, name, St): 14 | self.name = name 15 | self.St = St 16 | self.STs = np.arange(1, St * 2, 1) 17 | self.payoffs = np.zeros_like(self.STs) 18 | self.instruments = [] 19 | self.side = [] 20 | 21 | self.gamma=np.zeros_like(self.STs) 22 | self.delta=np.zeros_like(self.STs) 23 | self.theta=np.zeros_like(self.STs) 24 | self.vega=np.zeros_like(self.STs) 25 | 26 | 27 | def add_Option(self , option : Option , buySell:BuySellSide , option_number = 1) -> None: 28 | 29 | for nb in range(1 , option_number+1): 30 | self.instruments.append(option) 31 | self.side.append(buySell.value) 32 | 33 | self.payoffs = self.payoffs + self._payoff(option,buySell,option_number) 34 | 35 | print(f"{str(option_number)} option(s) added ") 36 | 37 | def add_deltaOne(self, stock:Stock, buySell:BuySellSide, stock_number=1) -> None: 38 | 39 | for nb in range(1 , stock_number+1): 40 | self.instruments.append(stock) ## add later stockPrice 41 | 42 | self.payoffs = self.payoffs + self._payoff(stock,buySell,stock_number) 43 | 44 | print(f"{str(stock_number)} option(s) added ") 45 | 46 | def _payoff(self , instr :Union[Option , Stock] , side:BuySellSide , number): 47 | 48 | payoff= None 49 | if(type(instr) == Option): 50 | option = instr 51 | K = option.K 52 | if side == BuySellSide.BUY: 53 | if option.type ==OpionType.CALL: 54 | payoff = np.array([max(S - K, 0) - option.price for S in self.STs]) * number 55 | elif option.type == OpionType.PUT: 56 | payoff = np.array([max(K - S, 0) - option.price for S in self.STs]) * number 57 | 58 | elif side == BuySellSide.SELL: 59 | if option.type == OpionType.CALL: 60 | payoff = np.array([- (max(S - K, 0) - option.price )for S in self.STs]) * number 61 | elif option.type == OpionType.PUT: 62 | payoff = np.array([-(max(K - S, 0) - option.price )for S in self.STs]) * number 63 | 64 | elif (type(instr) == Stock): 65 | stock = instr 66 | if side == BuySellSide.BUY: 67 | payoff = np.array([S - stock.price for S in self.STs]) * number 68 | elif side == BuySellSide.SELL: 69 | payoff = np.array([ stock.price -S for S in self.STs]) * number 70 | return payoff 71 | 72 | def plot(self, **params): 73 | plt.plot(self.STs, self.payoffs, **params) 74 | plt.title(f"Payoff Diagram for {self.name}") 75 | plt.fill_between(self.STs, self.payoffs,where=(self.payoffs > 0), facecolor='g', alpha=0.4) 76 | plt.fill_between(self.STs, self.payoffs,where=(self.payoffs < 0), facecolor='r', alpha=0.4) 77 | plt.xlabel(r'$S_T$') 78 | plt.ylabel('Profit in $') 79 | plt.show() 80 | 81 | def describe(self): 82 | max_profit = self.payoffs.max() 83 | max_loss = self.payoffs.min() 84 | print(f"Max Profit: ${round(max_profit, 3)}") 85 | print(f"Max loss: ${round(max_loss, 3)}") 86 | c = 0 87 | for o in self.instruments: 88 | print(o) 89 | if o.type == 'call' and o.side == 1: 90 | c += o.price 91 | elif o.type == 'call' and o.side == -1: 92 | c -= o.price 93 | elif o.type == 'put' and o.side == 1: 94 | c += o.price 95 | elif o.type == 'put' and o.side == -1: 96 | c - + o.price 97 | 98 | print(f"Cost of entering position ${c}") 99 | 100 | def compute_greek_profile(self , T, r, sigma ): 101 | 102 | instr_side =list(zip(self.side,self.instruments)) 103 | 104 | for side , instr in instr_side: 105 | self.delta = self.delta + side * Greeks.Delta(self.STs ,instr.K ,T , r , sigma , instr.type) 106 | self.gamma = self.gamma + side * Greeks.Gamma(self.STs ,instr.K ,T , r , sigma) 107 | self.vega = self.vega + side * Greeks.Vega(self.STs ,instr.K ,T , r , sigma ) 108 | self.theta = self.theta + side * Greeks.Theta(self.STs ,instr.K ,T , r , sigma , instr.type) 109 | 110 | def plotGreek(self,greekStr, **params): 111 | greek= getattr(self, greekStr) 112 | plt.plot(self.STs, greek, **params) 113 | plt.title(f"{greekStr} Profile") 114 | plt.xlabel(r'$S_T$') 115 | plt.show() -------------------------------------------------------------------------------- /Option/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Option/__init__.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Option trading and analysis 2 | 3 | Designed for educational purposes. Here are some of the key topics: 4 | 5 | 6 | - **Data request** : Recovery of Option & stock financial data via the yFinance API 7 | - **Option Trading Strategies**: We'll explore various strategies for trading options, including both traditional and more exotics techniques. 8 | - **Black & Scholes Pricing Model**: Implementation of the theoretical foundations of the Black & Scholes model:Observation of the parameters affecting the value of an option. 9 | - **Implied Volatility Surface / Local Volatility**: Recovery and calculation of implied volatilities quoted on the market. Observing the long and short term skew/smile.Calculation of local volatility with the Dupire formula. 10 | - **Stochastic Volatility**: We'll explore the concept of stochastic volatility with Heston Model 11 | - **Greeks**: We'll cover the key "Greeks" (Delta, Gamma, Theta, Vega, Rho) and their profiles with different options strategies. 12 | 13 | 14 | ---------------------- 15 | 16 | 17 | 18 | **Data Retrieval** 19 | 20 | The initial step in this code utilizes the yahoofinance API to access financial quotes. This is achieved through the use of the internal modules *y_finane_option_data* and *y_finane_stock_data*, which are utilized to retrieve option and stock data for a specific ticker symbol. 21 | 22 | During all the analysis of Read.me we will use as symbol *TSLA* 23 | 24 | 25 | Here is the simplest code to retrieve data: 26 | 27 | ```ruby 28 | 29 | from DataRequest import y_finane_option_data , y_finane_stock_data 30 | 31 | # Choose the instruments you want to recover 32 | ticker = 'TSLA' 33 | 34 | ## Get Option & underlying stock price 35 | option_df = y_finane_option_data.get_option_data(ticker) 36 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 37 | ``` 38 | 39 | Click here to access to dataFolder scprit [Data Request folder](https://github.com/AdrienCss/OptionTrading/blob/main/DataRequest) 40 | 41 | Example of output with TLSA: 42 | 43 | ![](Images/dataFrame_TSLA.png) 44 | 45 | # **Creating Option trading stategies** 46 | 47 | - [mainOptionStrategies.py](https://github.com/AdrienCss/OptionTrading/blob/main/mainOptionStrategies.py)<= 48 | 49 | 50 | In this script, we will construct a variety of strategies using real optional data. 51 | We will establish "Option"& "Stocks" data type and devise distinct strategies from it. 52 | Additionally, we will plot the corresponding Payoff profiles for these strategies. We will have the capability to display the Greek profiles of these strategies, projected over a range of underlying prices. This will allow us to analyze and evaluate the potential outcomes and risks associated with each strategies. 53 | 54 | In the case of the following strategies we have taken real options that quote the market on the underlying TSLA. 55 | 56 | Click here to see the code of the strategy Engine => [OptionStrategies.py](https://github.com/AdrienCss/OptionTrading/blob/main/Option/OptionStrategies.py)<= 57 | 58 | ```ruby 59 | 60 | # We take the 5th closest maturity 61 | maturity =option_df['T_days'].unique()[5] 62 | 63 | options_df = option_df[option_df['T_days'] ==maturity] 64 | call_df = options_df[options_df['Type'] =='CALL'] 65 | put_df = options_df[options_df['Type'] =='PUT'] 66 | 67 | #Getting real Quotes options 68 | call_90_df = call_df.iloc[(call_df['strike']-(currentprice * 0.90)).abs().argsort()[:1]] 69 | call_80_df = call_df.iloc[(call_df['strike']-(currentprice * 0.80)).abs().argsort()[:1]] 70 | 71 | put_90_df = put_df.iloc[(put_df['strike']-(currentprice * 0.90)).abs().argsort()[:1]] 72 | put_80_df = put_df.iloc[(put_df['strike']-(currentprice * 0.80)).abs().argsort()[:1]] 73 | 74 | 75 | # Creating Call spread 80 / 90 76 | call90 = Option(price=call_90_df['lastPrice'].values[0], K=call_90_df['strike'].values[0] , type= OpionType.CALL) 77 | call80 = Option(price=call_80_df['lastPrice'].values[0], K=call_80_df['strike'].values[0] , type= OpionType.CALL) 78 | 79 | 80 | strategy = OptionStrategies(name = "Call spread 90/80" ,St = currentprice) 81 | strategy.add_Option(option= call90 ,buySell= BuySellSide.SELL , option_number=1 ) 82 | strategy.add_Option(option= call80 ,buySell= BuySellSide.BUY , option_number=1 ) 83 | strategy.plot() 84 | 85 | 86 | # Creating put spread 80 / 90 87 | put90 = Option(price=put_90_df['lastPrice'].values[0], K=put_90_df['strike'].values[0] , type= OpionType.PUT) 88 | put80 = Option(price=put_80_df['lastPrice'].values[0], K=put_80_df['strike'].values[0] , type= OpionType.PUT) 89 | 90 | 91 | strategy = OptionStrategies(name = "PUT Spread 90/ 80" ,St = currentprice) 92 | strategy.add_Option(option= put90 ,buySell= BuySellSide.BUY , option_number=1 ) 93 | strategy.add_Option(option= put80,buySell= BuySellSide.SELL , option_number=1 ) 94 | strategy.plot() 95 | ``` 96 | 97 | 98 | Put Spread Payoff 80%/90% | Call Spread Payoff 80%/90% 99 | :-------------------------:|:-------------------------: 100 | | 101 | 102 | 103 | 104 | -Butterflies , Straddle , Strangle 105 | 106 | 107 | 108 | Straddle Payoff | Strangle Payoff 109 | :-------------------------:|:-------------------------: 110 | | 111 | 112 | 113 | Long Butterfly spread | Short Butterfly spread 114 | :-------------------------:|:-------------------------: 115 | | 116 | 117 | 118 | 119 | 120 | A synthetic call is a combination of a stock and a cash position, such as a long stock position and a short put option position, that simulates the payoff of a long call option. A synthetic put is a combination of a stock and a cash position, such as a short stock position and a long call option position, that simulates the payoff of a long put option. These options strategies can be used to replicate the payout of a call or put option, while potentially reducing the cost or risk associated with buying or selling the actual option. 121 | 122 | ```ruby 123 | #synthetic call 124 | 125 | put_df1 = put_df.iloc[(put_df['strike']-(currentprice - 15)).abs().argsort()[:1]] 126 | 127 | put = Option(price=put_df1['lastPrice'].values[0], K=put_df1['strike'].values[0] , type= OpionType.PUT) 128 | stock = Stock(price = last_price) 129 | 130 | strategy = OptionStrategies(name = "Synthetic call" ,St = currentprice) 131 | strategy.add_Option(option= put ,buySell= BuySellSide.BUY, option_number=1 ) 132 | strategy.add_deltaOne(stock=stock,buySell= BuySellSide.BUY ) 133 | strategy.plot() 134 | 135 | 136 | 137 | #synthetic PUT 138 | 139 | call_df1 = call_df.iloc[(call_df['strike']-(currentprice - 15)).abs().argsort()[:1]] 140 | 141 | call = Option(price=call_df1['lastPrice'].values[0], K=call_df1['strike'].values[0] , type= OpionType.CALL) 142 | stock = Stock(price = currentprice) 143 | 144 | strategy = OptionStrategies(name = "Synthetic PUT", St = currentprice) 145 | strategy.add_Option(option= call ,buySell= BuySellSide.BUY, option_number=1 ) 146 | strategy.add_deltaOne(stock=stock,buySell= BuySellSide.SELL ) 147 | strategy.plot() 148 | ``` 149 | 150 | 151 | 152 | Synthetic call | Synthetic Put 153 | :-------------------------:|:-------------------------: 154 | | 155 | 156 | 157 | -Covered call/ Put 158 | 159 | **Call Spread Greeks Profile** 160 | The engine also allows you to profile greek strategies. 161 | Here are the results obtained for a call spread 162 | 163 | ```ruby 164 | T = maturity /252 165 | r = 0.015 166 | vol = np.average(call_90_df['impliedVolatility'].values[0] + call_80_df['impliedVolatility'].values[0]) 167 | 168 | strategy.compute_greek_profile(T ,r , vol) 169 | strategy.plotGreek(greekStr='gamma') 170 | strategy.plotGreek(greekStr='theta') 171 | strategy.plotGreek(greekStr='delta') 172 | strategy.plotGreek(greekStr='vega') 173 | ``` 174 | 175 | Delta profile | Gamma Profile 176 | :-------------------------:|:-------------------------: 177 | | 178 | Vega profile | Theta Profile 179 | | 180 | 181 | # **Call/ Put Parity** 182 | 183 | - [mainCallPutParity.py](https://github.com/AdrienCss/OptionTrading/blob/main/mainPricingModel.py) 184 | - 185 | Call-Put Parity is a concept that refers to the relationship between call options and put options on the same underlying asset. 186 | The relationship is given by the following formula: 187 | 188 | $$ 189 | C +K*\exp(-rT) = S_0 + P 190 | $$ 191 | 192 | Through this simple relationship we will see if there are arbitrage opportunities in the market. 193 | First, let's observe for a chosen maturity the call and put prices on the market for a strike range 194 | 195 | 196 | As we can see, the market prices seem to resemble classic option prices, without any particular problem. 197 | 198 | Now let's calculate the corresponding prices of calls and puts to see if arbitrage opportunities can exist.. 199 | 200 | 201 | Call | Put 202 | :-------------------------:|:-------------------------: 203 | | 204 | 205 | 206 | In both cases the two curves are not distinctly observable! 207 | This proves that the market is totally efficient and that arbitrage opportunities do not exist on these options 208 | 209 | # **Option Princing : Binomial & B&S** 210 | 211 | - [mainPricingModel.py](https://github.com/AdrienCss/OptionTrading/blob/main/mainPricingModel.py) 212 | 213 | The B&S model is based on the following assumptions: 214 | 215 | - The underlying asset price follows a geometric Brownian motion with constant drift and volatility. 216 | - There are no transaction costs or taxes. 217 | - Trading can be done continuously. 218 | - The risk-free interest rate is constant. 219 | - The option can only be exercised at expiration. 220 | 221 | Under these assumptions, the Black-Scholes model provides a closed-form solution for the price of a European call or put option. 222 | 223 | The binomial model provides a multi-period view of the underlying asset price as well as the price of the option. In contrast to the Black-Scholes model, which provides a numerical result based on inputs, the binomial model allows for the calculation of the asset and the option for multiple periods along with the range of possible results for each period . 224 | 225 | 226 | ```ruby 227 | 228 | #Binomial Method 229 | 230 | def combos(n, i): 231 | return math.factorial(n) / (math.factorial(n - i) * math.factorial(i)) 232 | 233 | 234 | def binom_EU1(S0, K, T, r, sigma, N, type_='call'): 235 | dt = T / N 236 | u = np.exp(sigma * np.sqrt(dt)) 237 | d = np.exp(-sigma * np.sqrt(dt)) 238 | p = (np.exp(r * dt) - d) / (u - d) 239 | value = 0 240 | for i in range(N + 1): 241 | node_prob = combos(N, i) * p ** i * (1 - p) ** (N - i) 242 | ST = S0 * (u) ** i * (d) ** (N - i) 243 | if type_ == 'CALL': 244 | value += max(ST - K, 0) * node_prob 245 | elif type_ == 'PUT': 246 | value += max(K - ST, 0) * node_prob 247 | else: 248 | raise ValueError("type_ must be 'call' or 'put'") 249 | 250 | return value * np.exp(-r * T) 251 | ``` 252 | 253 | 254 | 255 | ```ruby 256 | 257 | #B&S Method 258 | def BS_CALL(S, K, T, r, sigma): 259 | d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T)) 260 | d2 = d1 - sigma * np.sqrt(T) 261 | return S * N(d1) - K * np.exp(-r*T)* N(d2) 262 | 263 | 264 | def BS_PUT(S, K, T, r, sigma): 265 | d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T)) 266 | d2 = d1 - sigma* np.sqrt(T) 267 | return K* np.exp(-r*T) * N(-d2) - S*N(-d1) 268 | 269 | 270 | def priceBS(S, K, T, r, sigma ,type='CALL'): 271 | if type == 'CALL': 272 | return BS_CALL(S, K, T, r, sigma) 273 | if type == 'PUT': 274 | return BS_PUT(S, K, T, r, sigma) 275 | else: 276 | raise ValueError('Unrecognized type') 277 | 278 | ``` 279 | 280 | 281 | Volatility and time are two important factors that can significantly impact the price of a call option. 282 | 283 | -The higher the Implied volatility, the greater the likelihood that the option will expire in-the-money, and thus the higher the price of the call option. This is because with high volatility, there is a greater chance that the underlying asset's price will move above the strike price, making the option profitable. 284 | 285 | -Time, also known as time decay, is another important factor that can impact the price of a call option. As the expiration date of the option approaches, the option's value decreases. This is because there is less time for the underlying asset's price to move above the strike price and for the option to become profitable. The closer the expiration date, the greater the time decay, and the lower the price of the call option. 286 | 287 | Here is an example of how to observe the impact of volatility and Time on the price of an option: 288 | 289 | ```ruby 290 | #static Parameters 291 | import numpy as np 292 | from BlackAndScholes.BSPricing import BS_CALL ,BS_PUT 293 | import matplotlib.pyplot as plt 294 | 295 | K = 100 296 | r = 0.1 297 | sig = 0.2 298 | St = stockPrices_['Adj Close'].tail(1).values[0] 299 | 300 | S = np.arange(0 ,St + 60,1) 301 | 302 | #Using != Time 303 | callsPrice2 = [BS_CALL(s, K, 3, r, sig) for s in S] 304 | callsPrice3 = [BS_CALL(s, K,2, r, sig) for s in S] 305 | callsPrice4 = [BS_CALL(s, K, 1, r, sig) for s in S] 306 | callsPrice5 = [BS_CALL(s, K, 0.50, r,sig) for s in S] 307 | 308 | IntrinsicValue = [max(s - K ,0)for s in S] 309 | 310 | plt.plot(S, callsPrice2, label='Call Value T = 3') 311 | plt.plot(S, callsPrice3, label='Call Value T = 2') 312 | plt.plot(S, callsPrice4, label='Call Value T = 1') 313 | plt.plot(S, callsPrice5, label='Call Value T = 0.5') 314 | plt.plot(S, IntrinsicValue, label='IntrinsicValue') 315 | plt.xlabel('$S_t$') 316 | plt.ylabel(' Value') 317 | plt.title('Time impact on call value') 318 | plt.legend() 319 | plt.show() 320 | 321 | 322 | #Using != volatilities 323 | T= 1 324 | callsPrice2 = [BS_CALL(s, K, T, r, 0.2) for s in S] 325 | callsPrice3 = [BS_CALL(s, K, T, r, 0.3) for s in S] 326 | callsPrice4 = [BS_CALL(s, K, T, r, 0.4) for s in S] 327 | callsPrice5 = [BS_CALL(s, K, T, r, 0.5) for s in S] 328 | 329 | 330 | ``` 331 | 332 | 333 | Implied Volatility | Time 334 | :-------------------------:|:-------------------------: 335 | | 336 | 337 | 338 | 339 | # **Implied Volatility Calculation and Plotting for Options** 340 | - [mainImpliedVolatility.py](https://github.com/AdrienCss/OptionTrading/blob/main/mainImpliedVolatility.py) 341 | 342 | - Ploting observed implied volatility of real option's quotes. 343 | - Computing implied volatility using Newton-Raphson Model. 344 | - ploting skew/smile on short and long maturites ( short/long Smile) 345 | 346 | 347 | 348 | We can observe implied ( black) volatility Skew at different maturites ( long/short smile) 349 | 350 | **Volatility Skew for different maturities ( days)** 351 | 352 | ```ruby 353 | ticker ='TSLA' 354 | 355 | #Requesting stock price 356 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 357 | 358 | #Compute daily's Log return 359 | stockPrices_['returns_1D'] = np.log(stockPrices_['Adj Close'] / stockPrices_['Adj Close'].shift(1)) 360 | returns = stockPrices_['returns_1D'].dropna(axis=0) 361 | 362 | # details 363 | kurt = kurtosis(returns.values) 364 | sk = skew( returns.values) 365 | 366 | # Compute & plot volatility 367 | stockPrices_['realised_volatility_3M'] =stockPrices_['returns_1D'].rolling(60).std() * np.sqrt(252) 368 | stockPrices_['realised_volatility_6M'] =stockPrices_['returns_1D'].rolling(120).std()* np.sqrt(252) 369 | 370 | ``` 371 | 372 | PUT Skew | CALL Skew 373 | :-------------------------:|:-------------------------: 374 | | 375 | 376 | 377 | **Observation of Implied Volatility Surface** 378 | 379 | ```ruby 380 | import matplotlib.pyplot as plt 381 | from matplotlib import cm 382 | 383 | ## Get Option/underlying stock Prices 384 | option_df = y_finane_option_data.get_option_data(ticker) 385 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 386 | last_price = stockPrices_.tail(1)['Adj Close'].values[0] 387 | option_df['underlying_LastPrice'] = last_price 388 | 389 | type = 'CALL' 390 | opt = option_df[(option_df.Type==type)] 391 | opt = opt[(opt.strike <= last_price+100)] 392 | opt = opt[(opt.strike >= last_price-100)] 393 | opt = opt[(opt.T_days <=200)] 394 | opt['MoneyNess'] = opt['underlying_LastPrice'] / opt['strike'] 395 | opt = opt[['strike' , 'T_days','impliedVolatility']] 396 | 397 | # Initiate figure 398 | fig = plt.figure(figsize=(7, 7)) 399 | axs = plt.axes(projection="3d") 400 | axs.plot_trisurf(opt.MoneyNess, opt.T_days , opt.impliedVolatility, cmap=cm.coolwarm) 401 | axs.view_init(40, 65) 402 | plt.xlabel("Strike") 403 | plt.ylabel("Days to expire") 404 | plt.title(f"Volatility Surface for {type} {ticker} - Implied Volatility as a Function of K and T") 405 | plt.show() 406 | ``` 407 | Black Implied Volatility OTM | - 408 | :-------------------------:|:-------------------------: 409 | | - 410 | 411 | 412 | We can observe it for each type of option also 413 | 414 | Black Implied Volatility CALL | Black Implied Volatility PUT 415 | :-------------------------:|:-------------------------: 416 | | 417 | 418 | 419 | 420 | 421 | # **Local volatility vs implied Volatility** 422 | 423 | 424 | Steps to calculate locaL VOLATILITY 425 | 426 | -First, use the available quoted price to calculate the implied volatilities. 427 | 428 | -Appy interpolation method to produce a smooth implied volatility surface. 429 | 430 | -Plug implied volatilities into BSM model to get all the market prices of European calls. 431 | 432 | -Calculate the local volatility according to Dupire formula. 433 | 434 | $$ \begin{cases} 435 | 436 | \sqrt{\frac{DC}{DT} / \left(\frac{1}{2} * K^2 * \left(\frac{d^2C}{dK^2}\right)\right)} 437 | 438 | \end{cases}$$ 439 | 440 | 441 | Local volatility surface : 442 | 443 | 444 | 445 | 446 | Local volatility vs Implied volatility observation 447 | 448 | Local Volatility | Obseratiob 449 | :-------------------------:|:-------------------------: 450 | | Local volatility varies with market level about twice as rapidly as implied volatility varies with strike. 451 | 452 | 453 | 454 | # **Simulating heston Volatility** 455 | 456 | -[mainHestonSimulation.py](https://github.com/AdrienCss/OptionTrading/blob/main/mainHestonSimulation.py) 457 | 458 | The basic idea behind the Heston model is that the volatility of an asset's price is not constant over time, but rather follows a stochastic process. The model describes the dynamics of the asset's price and volatility using two state variables: the current price of the asset and its current volatility. The model then uses a set of parameters to describe how these state variables change over time. 459 | 460 | 461 | The Heston process is described by the system of SDEs: 462 | 463 | $$ \begin{cases} 464 | dS_t = \mu S_t dt + \sqrt{v_t} S_t dW^1_t \\ 465 | dv_t = \kappa (\theta - v_t) dt + \sigma \sqrt{v_t} dW^2_t 466 | \end{cases}$$ 467 | 468 | so we can use folowing discretionnary process: 469 | 470 | $$ \begin{cases} 471 | S_(t+1) =S_t + \mu S_t dt + \sqrt{v_t} S_t dW^1_t \\ 472 | v_(t+1) =v_t + \kappa (\theta - v_t) dt + \sigma \sqrt{v_t} dW^2_t 473 | \end{cases}$$ 474 | 475 | 476 | The parameters are: 477 | - $\mu$ drift of the stock process 478 | - $\kappa$ mean reversion coefficient of the variance process 479 | - $\theta$ long term mean of the variance process 480 | - $\sigma$ volatility coefficient of the variance process 481 | - $\rho$ correlation between $W^1$ and $W^2$ i.e. $dW^1_t dW^2_t = \rho dt$ 482 | 483 | A few brief observations on the TSLA share price 484 | 485 | ```ruby 486 | ticker ='TSLA' 487 | 488 | #Requesting stock price 489 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 490 | 491 | #Compute daily's Log return 492 | stockPrices_['returns_1D'] = np.log(stockPrices_['Adj Close'] / stockPrices_['Adj Close'].shift(1)) 493 | returns = stockPrices_['returns_1D'].dropna(axis=0) 494 | 495 | # details 496 | kurt = kurtosis(returns.values) 497 | sk = skew( returns.values) 498 | 499 | # Compute & plot volatility 500 | stockPrices_['realised_volatility_3M'] =stockPrices_['returns_1D'].rolling(60).std() * np.sqrt(252) 501 | stockPrices_['realised_volatility_6M'] =stockPrices_['returns_1D'].rolling(120).std()* np.sqrt(252) 502 | 503 | ``` 504 | 505 | 506 | Returns | Realized Volatility 507 | :-------------------------:|:-------------------------: 508 | | 509 | 510 | 511 | The kurtosis of 5.08 indicates that the distribution of the data is higher than that of a normal distribution (kurtosis = 3), which means that there are more observations in the extreme values (highs and lows) than one would expect for a normal distribution. The skewness of -0.05 indicates that the distribution is slightly skewed to the left. 512 | 513 | 514 | In the case of the rolling volatility graph of TESLA stock returns, we can observe how the variability of returns changes over time. The average return to variance is 0.30. 515 | 516 | 517 | 518 | Result : 519 | 520 | Heston Volatility | Price trajectories (Monte Carlo) 521 | :-------------------------:|:-------------------------: 522 | | 523 | 524 | 525 | 526 | 527 | -------------------------------------------------------------------------------- /Volatility/DupireVolatility.py: -------------------------------------------------------------------------------- 1 | from BlackAndScholes import BSPricing 2 | import numpy as np 3 | 4 | 5 | from scipy.stats import norm 6 | 7 | N_prime = norm.pdf 8 | 9 | 10 | def ComputeDupireVolatility(S , K , r , T , sigma , type , increment = 1.5): 11 | 12 | BSp = BSPricing.priceBS 13 | 14 | delta_T_finite = (BSp(S=S , K=K , r=r, T=(T + increment), type=type, sigma=sigma) - BSp(S=S, K=K , r=r, T=T, type=type, sigma=sigma)) / (increment) 15 | 16 | delta_K_finite = (BSp(S=S , K=K + increment, r=r, T=T, type=type, sigma=sigma) - BSp(S=S, K=K , r=r, T=T, type=type, sigma=sigma)) / (increment) 17 | 18 | delta_2K_finite = (BSp(S=S , K=K + increment, r=r, T=T, type=type, sigma=sigma) - 2 * BSp(S=S, K=K , r=r, T=T, type=type, sigma=sigma) 19 | + BSp(S=S , K= K - increment, r=r, T=T, type=type, sigma=sigma)) / (increment) 20 | 21 | local_volatility = np.sqrt( (delta_T_finite + r * K * delta_K_finite) / (0.5 * K * K * delta_2K_finite)) 22 | 23 | return local_volatility 24 | 25 | 26 | def ComputeDupireVolatilityQC(S , K , r , T , sigma , type , increment = 1.5): 27 | 28 | BSp = BSPricing.priceBS 29 | 30 | delta_T_finite = (BSp(S=S , K=K , r=r, T=(T + increment), type=type, sigma=sigma) - BSp(S=S, K=K , r=r, T=T, type=type, sigma=sigma)) / (2 *increment) 31 | 32 | delta_2K_finite = (BSp(S=S , K=K + increment, r=r, T=T, type=type, sigma=sigma) - 2 * BSp(S=S, K=K , r=r, T=T, type=type, sigma=sigma) 33 | + BSp(S=S , K= K - increment, r=r, T=T, type=type, sigma=sigma)) / (increment)**2 34 | 35 | local_volatility = np.sqrt( (delta_T_finite ) / (0.5 * K * K * delta_2K_finite)) 36 | 37 | return local_volatility 38 | 39 | 40 | ##Check 41 | 42 | S=770.05 43 | K = 850 44 | sigma = 0.194722 45 | T =0.07 46 | increment = 1.5 47 | 48 | r = 0.0066 49 | 50 | localIV = ComputeDupireVolatility(S, K,r,T,sigma , 'CALL',increment) -------------------------------------------------------------------------------- /Volatility/ImpliedVolatiliy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | from DataRequest import y_finane_option_data , y_finane_stock_data 5 | from BlackAndScholes.BSPricing import BS_PUT , BS_CALL 6 | from BlackAndScholes.Greeks import Vega 7 | 8 | from scipy.optimize import minimize_scalar 9 | 10 | 11 | 12 | def compute_theorical_IV(opt_value, S, K, T, r, type_='CALL' ): 13 | def call_obj(sigma): 14 | return abs(BS_CALL(S, K, T, r, sigma) - opt_value) 15 | 16 | def put_obj(sigma): 17 | return abs(BS_PUT(S, K, T, r, sigma) - opt_value) 18 | 19 | if type_ == 'CALL': 20 | res = minimize_scalar(call_obj, bounds=(0.01, 10), method='bounded') 21 | return res.x 22 | elif type_ == 'PUT': 23 | res = minimize_scalar(put_obj, bounds=(0.01, 10), 24 | method='bounded') 25 | return res.x 26 | else: 27 | raise ValueError("type_ must be 'put' or 'call'") 28 | 29 | 30 | 31 | def plot_ImpliedVolatility(option_df : pd.DataFrame , type,minDay , maxDay): 32 | df_optionsCall = option_df[option_df['Type'] == type] 33 | df_optionsCall = df_optionsCall[(option_df['T_days'] > minDay) & (option_df['T_days'] < maxDay)] 34 | 35 | 36 | grouped_df = df_optionsCall.groupby('T_days') 37 | 38 | fig, ax = plt.subplots() 39 | 40 | for key, group in grouped_df: 41 | ax.plot(group['strike'], group['impliedVolatility'], label=key) 42 | 43 | ax.legend() 44 | ax.set_xlabel('Strike') 45 | ax.set_ylabel('ImpliedVolatility') 46 | plt.title(f'{type} Options Volatility skew at different maturities (days) ') 47 | plt.show() 48 | 49 | 50 | def plot_ImpliedVolatilityCalculated(option_df : pd.DataFrame , type,minDay , maxDay): 51 | df_optionsCall = option_df[option_df['Type'] == type] 52 | df_optionsCall = df_optionsCall[(option_df['T_days'] > minDay) & (option_df['T_days'] < maxDay)] 53 | 54 | 55 | grouped_df = df_optionsCall.groupby('T_days') 56 | 57 | fig, ax = plt.subplots() 58 | 59 | for key, group in grouped_df: 60 | ax.plot(group['strike'], group['IV_Calculated'], label=key) 61 | 62 | ax.legend() 63 | ax.set_xlabel('Strike') 64 | ax.set_ylabel('ImpliedVolatility') 65 | plt.title(f'{type} Options Volatility skew at different maturities (days) ') 66 | plt.show() 67 | 68 | 69 | import numpy as np 70 | from scipy.stats import norm 71 | 72 | N_prime = norm.pdf 73 | N = norm.cdf 74 | 75 | 76 | def implied_volatility_Raphton(C, S, K, T, r, tol=0.0001, 77 | max_iterations=100): 78 | sigma = 0.8 79 | 80 | for i in range(max_iterations): 81 | diff = BS_CALL(S, K, T, r, sigma) - C 82 | if abs(diff) < tol: 83 | print(f'found on {i}th iteration') 84 | print(f'difference is equal to {diff}') 85 | break 86 | 87 | sigma = sigma - diff / Vega(S, K, T, r, sigma) 88 | 89 | return sigma 90 | 91 | def implied_volatility_put(P, S, K, T, r, tol=0.0001, 92 | max_iterations=100): 93 | sigma = 0.3 94 | 95 | for i in range(max_iterations): 96 | diff =BS_PUT(S, K, T, r, sigma) - P 97 | if abs(diff) < tol: 98 | print(f'found on {i}th iteration') 99 | print(f'difference is equal to {diff}') 100 | break 101 | 102 | sigma = sigma - diff / Vega(S, K, T, r, sigma) 103 | 104 | return sigma -------------------------------------------------------------------------------- /Volatility/StochasticVolatility.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pandas as pd 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import yfinance as yf 6 | import datetime 7 | import sys 8 | 9 | class VolData: 10 | def __init__(self, ticker): 11 | self.ticker = ticker 12 | p = yf.Ticker(self.ticker).info 13 | self.mid_price = (p['ask'] + p['bid'])/2 14 | 15 | """ 16 | Initializing Class. 17 | Parameters 18 | ---------- 19 | ticker: string 20 | The stock ticker of interest 21 | mid_price: integer 22 | Retrieves the mid-price of the stock which will be used in the class methods 23 | Example 24 | --------- 25 | >>> import yahoo_vol as vol 26 | >>> data = vol.Options(ticker="AAPL") 27 | """ 28 | 29 | def get_input_data(self, save_csv=False): 30 | """ 31 | Intermediate function used to collect the pricing data for plotting purposes. 32 | Parameter: 33 | ------------ 34 | save_csv: boolean 35 | Select True to save the options data to a csv file 36 | Returns 37 | ------------- 38 | dataframe: 39 | A dataframe with corresponding statistics for each option 40 | csv: 41 | A csv file will be saved if `save_csv` is set to True 42 | Example 43 | ------------- 44 | df = data.get_input_data(save_csv=True) 45 | """ 46 | storage = [] 47 | x = yf.Ticker(self.ticker) 48 | option_dates = x.options 49 | if len(option_dates) == 0: 50 | sys.exit("Ticker does not have any options. Please try another ticker") 51 | for date in option_dates: 52 | try: 53 | call, put = x.option_chain(date)[0], x.option_chain(date)[1] 54 | call['option_type'], put['option_type'] = ('call', 'put') 55 | call['maturity'], put['maturity'] = (date, date) 56 | d = pd.concat([call, put]) 57 | storage.append(d) 58 | except: 59 | print(date, "option maturity failed to download") 60 | pass 61 | print("All option dates successfully downloaded: ", len(storage) == len(option_dates)) 62 | df = pd.concat(storage) 63 | df = df.reset_index() 64 | if save_csv==True: 65 | df.to_csv(str(self.ticker)+"_option_data.csv") 66 | return df 67 | 68 | def plot_vol_smile(self): 69 | """ 70 | Plots the raw implied volatility against the respective strike values for all option 71 | maturities. 72 | Returns 73 | ------------- 74 | plt.plot figure: 75 | Plots for each option maturity 76 | Example 77 | ------------- 78 | data.plot_vol_smile() 79 | """ 80 | data = self.get_input_data() 81 | # Extract and sort unique option maturities 82 | maturities = list(set(data.maturity)) 83 | maturities.sort() 84 | # Loop through each maturity and plot the respective volatility smile 85 | for date in maturities: 86 | call = data[(data.maturity == date) & (data.option_type == 'call')] 87 | put = data[(data.maturity == date) & (data.option_type == 'put')] 88 | # Plotting Code 89 | plt.scatter(call.strike, call.impliedVolatility*100, c='g', label='Call Smile', alpha=0.50) 90 | plt.scatter(put.strike, put.impliedVolatility*100, c='r', label='Put Smile', alpha=0.50) 91 | plt.axvline(self.mid_price, linestyle='dashed', c='b', alpha=0.30, label='Mid Price (ATM)') 92 | plt.title(self.ticker.upper() + " Volatility Smile: " + date) 93 | plt.ylabel("Implied Volatility (%)") 94 | plt.xlabel("Strike") 95 | plt.legend() 96 | plt.show() 97 | 98 | def plot_vol_term_structure(self, n=3): 99 | """ 100 | Plots the at-the-money (ATM) implied volatility for each respective option maturity to form 101 | a term structure. 102 | Parameter 103 | ------------- 104 | n: integer 105 | "n" represents number of options above and below at-the-money (ATM) used to calculate the 106 | volatility term structure. For example, assuming the strikes are separated by $1, if n=3 107 | and ATM Price=100, then the following 6 strikes will be used for calculating the implied 108 | volatility term structure: 109 | - Lower ATM: 97, 98, 99 110 | - Upper ATM: 101, 102, 103 111 | Returns 112 | ------------- 113 | plt.plot figure: 114 | Plot of implied volatility term structure 115 | Example 116 | ------------- 117 | data.plot_term_structure(n=3) 118 | """ 119 | data = self.get_input_data() 120 | strikes = list(set(data.strike)) 121 | maturities = list(set(data.maturity)) 122 | # Find the closest 'n' strikes to the current mid-price to approximate ATM option volatility 123 | get_closest_strike = min(range(len(strikes)), key=lambda i: abs(strikes[i]- self.mid_price)) 124 | closest_ATM_indexes = list(range(get_closest_strike - (n-1), 125 | get_closest_strike + n)) 126 | closest_ATM_strikes = [strikes[i] for i in closest_ATM_indexes] 127 | # Loop through all maturities to calculate the average implied volatility for each point 128 | storage = [] 129 | for date in maturities: 130 | try: 131 | temp = data[(data.maturity == date)] 132 | storage.append(temp[temp['strike'].isin(closest_ATM_strikes)].impliedVolatility.mean()) 133 | except: 134 | print(date, " failed to be included in the plot") 135 | pass 136 | df = pd.DataFrame([maturities, storage]).T 137 | df = df.sort_values(0) 138 | df = df.dropna() 139 | # Plotting Code 140 | plt.figure(figsize=(10,5)) 141 | plt.plot(df[0], df[1]*100, marker='o') 142 | plt.gcf().autofmt_xdate() 143 | plt.xlabel("Date") 144 | plt.ylabel("Implied Volatility (%)") 145 | plt.title(str(self.ticker).upper() + " Volatility Term Structure") 146 | plt.tight_layout() 147 | plt.grid(True) 148 | plt.show() -------------------------------------------------------------------------------- /Volatility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdrienCss/OptionTrading/27a6a88ddc827482beae7538756ad6a90f4fd238/Volatility/__init__.py -------------------------------------------------------------------------------- /Volatility/hestonModel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from DataRequest import y_finane_option_data , y_finane_stock_data 4 | import pandas as pd 5 | 6 | 7 | 8 | def heston_model_sim(S0, v0, rho, kappa, theta, sigma, T, r, M=10, ): 9 | N = 252 10 | dt = T / N 11 | mu = np.array([0, 0]) 12 | cov = np.array([[1, rho], [rho, 1]]) 13 | 14 | S = np.full(shape=(N + 1, M), fill_value=S0) 15 | v = np.full(shape=(N + 1, M), fill_value=v0) 16 | 17 | W = np.random.multivariate_normal(mu, cov, (N, M)) 18 | 19 | for i in range(1, N + 1): 20 | S[i] = S[i - 1] * np.exp((r - 0.5 * v[i - 1]) * dt + np.sqrt(v[i - 1] * dt) * W[i - 1, :, 0]) 21 | v[i] = np.maximum(v[i - 1] + kappa * (theta - v[i - 1]) * dt + sigma * np.sqrt(v[i - 1] * dt) * W[i - 1, :, 1] , 0) 22 | 23 | return S, v 24 | 25 | 26 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from Volatility import ImpliedVolatiliy 2 | from Option.OptionStrategies import OptionStrategies 3 | from Option.Option import Option , Stock 4 | from Enum.OptionType import OpionType 5 | from Enum.BuySellSide import BuySellSide 6 | from DataRequest import y_finane_option_data , y_finane_stock_data 7 | from Volatility.ImpliedVolatiliy import compute_theorical_IV, plot_ImpliedVolatility 8 | 9 | # Requesting data 10 | ticker = 'TSLA' 11 | 12 | ## Get Option/underlying stock Prices 13 | option_df = y_finane_option_data.get_option_data(ticker) 14 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 15 | 16 | last_price = stockPrices_.tail(1)['Adj Close'].values[0] 17 | option_df['underlying_LastPrice'] = last_price 18 | currentRiskFreeRate = 0.015 19 | 20 | ## Get all Option at +/- 50 strike from underlyinng 21 | option_df = option_df[(option_df['strike'] < option_df['underlying_LastPrice'] + 50) & (option_df['strike'] > option_df['underlying_LastPrice'] - 50)] 22 | 23 | 24 | ## Compute & plot Implied Volatility 25 | option_df['calculated_IV'] = option_df.apply(lambda x: compute_theorical_IV(x['lastPrice'], 26 | x['underlying_LastPrice'], 27 | x['strike'], 28 | x['T_days'] / 365 , 29 | currentRiskFreeRate, 30 | x['Type']), axis=1) 31 | 32 | 33 | ## Compute & plot Implied Volatility 34 | plot_ImpliedVolatility(option_df , 'CALL', 60 , 244) 35 | plot_ImpliedVolatility(option_df , 'PUT', 60 , 244) 36 | 37 | 38 | option_df = option_df[option_df['Type'] == "CALL"] # and df_options['T_days'] >1] 39 | option_df = option_df[option_df['T_days'] == 6] # and df_options['T_days'] >1] 40 | 41 | import matplotlib.pyplot as plt 42 | option_df.plot(x='strike', y=['impliedVolatility' ,'calculated_IV' ]) 43 | plt.axvline(x =last_price , color = 'r', label = 'CurrentPrice') 44 | plt.title(f"{ticker}'s Option Implied volatility") 45 | plt.show() 46 | 47 | 48 | 49 | #Underlying pricen quote 50 | currentprice = last_price 51 | r=0.01 52 | vol = 0.05 53 | T =1 54 | 55 | callOption = Option(price= 3 , K=15 , type= OpionType.CALL) 56 | callOption2 = Option(price= 1 , K=25 , type= OpionType.CALL) 57 | #stock = Stock(price = currentprice) 58 | 59 | strategy = OptionStrategies(name = "Bear Spread" ,St = currentprice) 60 | 61 | strategy.add_Option(option= callOption ,buySell= BuySellSide.BUY , option_number=1 ) 62 | strategy.add_Option(option= callOption2 ,buySell= BuySellSide.SELL , option_number=1 ) 63 | 64 | strategy.compute_greek_profile(T ,r , vol) 65 | 66 | strategy.plot() 67 | strategy.plotGreek(greekStr='gamma') 68 | strategy.plotGreek(greekStr='theta') 69 | strategy.plotGreek(greekStr='delta') 70 | strategy.plotGreek(greekStr='vega') 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /mainCallPutParity.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from DataRequest.y_finane_option_data import get_Option_Data_full , get_stock_price 3 | import matplotlib.pyplot as plt 4 | 5 | # the purpose of this script is to demonstrate de call/put parity 6 | # C + Ke(-rT) = P + S0 7 | 8 | ticker = 'TSLA' 9 | options_df = get_Option_Data_full(ticker) 10 | 11 | #Taking one maturity for observation 12 | maturities = options_df['T_days'].unique() 13 | maturity = maturities[3] 14 | 15 | options_df = options_df[options_df['T_days'] == maturity] 16 | options_df = options_df[['mid_price' , 'T_days' , 'strike','Type' , 'underlying_LastPrice']] 17 | 18 | call = options_df[options_df['Type'] =='CALL'] 19 | put = options_df[options_df['Type'] =='PUT'] 20 | 21 | df = pd.merge(call, put, how='inner', on ='strike') 22 | df = df.rename(columns={"mid_price_x": "calls_prices", "mid_price_y": "put_prices"}) 23 | 24 | #plot 25 | df.plot(x='strike' , y =['calls_prices' , 'put_prices']) 26 | plt.title(f'Observed market prices for call and put options for {ticker} , T = {maturity}d') 27 | plt.show() 28 | 29 | 30 | df['P + S -K'] = df.put_prices + df.underlying_LastPrice_y - df.strike 31 | 32 | df.plot(x='strike' , y =['calls_prices' , 'P + S -K']) 33 | plt.title(f'Market calls prices vs calculated (C/P Parity) {ticker} , T = {maturity}d') 34 | plt.show() 35 | 36 | 37 | df['C + K -S'] = df.calls_prices + df.strike - df.underlying_LastPrice_y 38 | df.plot(x='strike' , y =['put_prices' , 'C + K -S']) 39 | plt.title(f'Market calls prices vs calculated (C/P Parity) {ticker} , T = {maturity}d') 40 | plt.show() 41 | -------------------------------------------------------------------------------- /mainHestonSimulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from DataRequest import y_finane_option_data , y_finane_stock_data 4 | from scipy.stats import kurtosis, skew , norm,stats 5 | from Volatility.hestonModel import heston_model_sim 6 | from Volatility.ImpliedVolatiliy import compute_theorical_IV 7 | import pandas as pd 8 | import warnings 9 | warnings.simplefilter("ignore", category=FutureWarning) 10 | 11 | 12 | ticker ='TSLA' 13 | 14 | #Requesting stock price 15 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 16 | optionPrice_ = y_finane_option_data.get_option_data(ticker) 17 | 18 | stockPrices_['returns_1D'] = np.log(stockPrices_['Adj Close'] / stockPrices_['Adj Close'].shift(1)) 19 | returns = stockPrices_['returns_1D'].dropna(axis=0) 20 | 21 | # details 22 | kurt = kurtosis(returns.values) 23 | sk = skew( returns.values) 24 | 25 | # Plot chart 26 | plt.hist(returns, bins=100, density=True, color='blue', alpha=0.6) 27 | plt.text(0.6, 0.8, f'Kurtosis: {kurt:.2f}', transform=plt.gca().transAxes) 28 | plt.text(0.6, 0.7, f'Skewness: {sk:.2f}', transform=plt.gca().transAxes) 29 | plt.xlabel('Returns') 30 | plt.ylabel('Frequency') 31 | plt.title('Histogram of TSLA\'s Daily Returns') 32 | plt.show() 33 | 34 | # Compute & plot volatility 35 | stockPrices_['realised_volatility_3M'] =stockPrices_['returns_1D'].rolling(60).std() * np.sqrt(252) 36 | stockPrices_['realised_volatility_6M'] =stockPrices_['returns_1D'].rolling(120).std()* np.sqrt(252) 37 | 38 | stockPrices_.plot( y=['realised_volatility_3M','realised_volatility_6M']) 39 | plt.title(f'{ticker} stock Realized volatilities') 40 | plt.show() 41 | 42 | # Parameters 43 | timeSeries = stockPrices_.tail(300) 44 | currentPrice = stockPrices_.tail(1)['Adj Close'].values[0] 45 | 46 | timeSeries['variance_6M'] = timeSeries['realised_volatility_6M'] **2 47 | 48 | S0 = currentPrice # asset price 49 | r = 0.02 # risk-free rate 50 | T = 1.0 # time in years intil maturity , let's say 1year 51 | 52 | # Heston dependent parameters 53 | kappa = 3 #rate of mean reversion of variance under risk-neutral dynamics 54 | theta = timeSeries['realised_volatility_6M'].mean() **2 # long-term mean of variance under risk-neutral dynamics 55 | v0 = timeSeries['realised_volatility_6M'].tail(1).values[0] **2 # # initial variance under risk-neutral dynamics 56 | rho = 0.055604 57 | sigma = 0.6 58 | 59 | 60 | s_sim , vol_sim = heston_model_sim(S0, v0, rho, kappa, theta, sigma, T, r,10) 61 | 62 | timeSeries = timeSeries['Adj Close'] 63 | 64 | startDate = timeSeries.tail(1).index.values 65 | startDate = startDate.astype(str)[0][:10] 66 | 67 | new_index = pd.date_range(start=startDate, end='2030-01-25', freq='B') 68 | s_sim = pd.DataFrame(s_sim) 69 | s_sim.index = new_index[:len(s_sim)] 70 | new_df = pd.concat([timeSeries,s_sim]) 71 | new_df = new_df.fillna(method='ffill', axis=1) 72 | 73 | 74 | #Ploting volatilitty 75 | plt.plot(new_df.head(len(timeSeries)), color='blue') 76 | plt.plot(new_df.tail(len(s_sim)), color='red') 77 | plt.xticks(rotation=45) 78 | plt.ylabel('Price') 79 | plt.legend() 80 | plt.title(f'{ticker} Heston Price Paths simulation' ) 81 | plt.show() 82 | 83 | #Ploting volatilitty 84 | vol_sim = pd.DataFrame(vol_sim) 85 | vol_sim.index = new_index[:len(vol_sim)] 86 | vol_sim.plot() 87 | plt.ylabel('Volatility') 88 | plt.title('Heston Stochatic Vol Simulation') 89 | plt.show() 90 | 91 | -------------------------------------------------------------------------------- /mainImpliedVolatility.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from DataRequest import y_finane_option_data , y_finane_stock_data 3 | from Volatility.ImpliedVolatiliy import compute_theorical_IV , implied_volatility_Raphton 4 | import warnings 5 | import matplotlib.pyplot as plt 6 | from matplotlib import cm 7 | warnings.simplefilter("ignore", category=FutureWarning) 8 | 9 | # Requesting data 10 | ticker = 'TSLA' 11 | 12 | ## Get Option/underlying stock Prices 13 | option_df = y_finane_option_data.get_option_data(ticker) 14 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 15 | last_price = stockPrices_.tail(1)['Adj Close'].values[0] 16 | option_df['underlying_LastPrice'] = last_price 17 | currentRiskFreeRate = 0.015 18 | 19 | ## Get all Option at +/- 50 strike from underlyinng 20 | option_df = option_df[(option_df['strike'] < option_df['underlying_LastPrice'] + 50) & (option_df['strike'] > option_df['underlying_LastPrice'] - 1000)] 21 | option_df['mid_Price'] = (option_df['bid'] + option_df['ask']) /2 22 | 23 | ## Compute Implied Volatility with 2 methdods 24 | IV = [] 25 | for row in option_df.itertuples(): 26 | price = compute_theorical_IV(row.mid_Price , row.underlying_LastPrice ,row.strike, row.T_days / 365, currentRiskFreeRate) 27 | IV.append(price) 28 | 29 | option_df['IV_Calculated_b'] = IV 30 | 31 | 32 | IV = [] 33 | for row in option_df.itertuples(): 34 | price = implied_volatility_Raphton(row.mid_Price , row.underlying_LastPrice ,row.strike, row.T_days / 365, currentRiskFreeRate ) 35 | IV.append(price) 36 | 37 | option_df['IV_Calculated_n'] = IV 38 | 39 | 40 | 41 | # plot CALL Skew for each Maturities 42 | for matu in option_df.T_days.unique(): 43 | exp = option_df[(option_df.T_days == matu) & (option_df.Type=='CALL')] 44 | plt.plot(exp.strike, exp.impliedVolatility, label='IV market') 45 | plt.plot(exp.strike, exp.IV_Calculated_b, label='IV_Calculated_b') 46 | plt.xlabel('Strike') 47 | plt.ylabel(f'Volatility') 48 | plt.title(f'Implied Volatility Skew ,for T = {matu}') 49 | plt.legend() 50 | plt.show() 51 | 52 | 53 | ## Get Option/underlying stock Prices 54 | 55 | plotSurface = False 56 | 57 | if plotSurface is True: 58 | opt = option_df[(option_df.inTheMoney==False)] 59 | opt = opt[(opt.strike <= last_price+100)] 60 | opt = opt[(opt.strike >= last_price-100)] 61 | opt = opt[(opt.T_days <=200)] 62 | opt = opt[['strike' , 'T_days','impliedVolatility']] 63 | 64 | # Initiate figure 65 | fig = plt.figure(figsize=(7, 7)) 66 | axs = plt.axes(projection="3d") 67 | axs.plot_trisurf(opt.strike, opt.T_days , opt.impliedVolatility, cmap=cm.coolwarm) 68 | axs.view_init(40, 65) 69 | plt.xlabel("Strike") 70 | plt.ylabel("Days to expire") 71 | plt.title(f"Volatility Surface for OTM {ticker} - Implied Volatility as a Function of K and T") 72 | plt.show() 73 | 74 | 75 | ## Computing Dupire Volality for call Option 76 | 77 | from Volatility.DupireVolatility import ComputeDupireVolatility,ComputeDupireVolatilityQC 78 | 79 | 80 | Local_DupireIV = [] 81 | for row in option_df.itertuples(): 82 | localIV = ComputeDupireVolatility(row.underlying_LastPrice , row.strike , 0.0015 , row.T_days / 252,row.IV_Calculated_b ,row.Type,1.5) 83 | Local_DupireIV.append(localIV) 84 | 85 | option_df['Local_DupireIV'] = Local_DupireIV 86 | 87 | 88 | #Other formula of Dupire Volatility 89 | 90 | #Local_DupireIVQC = [] 91 | #for row in option_df.itertuples(): 92 | # localIV = ComputeDupireVolatilityQC(row.underlying_LastPrice , row.strike , 0.0015 , row.T_days / 252,row.IV_Calculated_b ,row.Type,1.5) 93 | # Local_DupireIVQC.append(localIV) 94 | 95 | #option_df['Local_DupireQC'] = Local_DupireIVQC 96 | 97 | matu = np.unique(option_df.T_days) 98 | 99 | for m in matu: 100 | opt = option_df[(option_df.Type=='CALL')] 101 | opt = opt[(opt.T_days ==m)] 102 | opt = opt[(opt.strike <= last_price+20)] 103 | 104 | plt.plot(opt.strike, opt.impliedVolatility, label='Black Implied volatility') 105 | plt.plot(opt.strike, opt.Local_DupireIV, label='Local volatility Dupire') 106 | 107 | plt.xlabel('Strike') 108 | plt.ylabel('volatility') 109 | plt.title(f'Black IV vs Dupire LV , T( days) ={m} ') 110 | plt.legend() 111 | plt.show() 112 | 113 | 114 | opt['ratio'] = opt.impliedVolatility /opt.Local_DupireIV 115 | plt.plot(opt.strike, opt.ratio, label="ratio") 116 | plt.xlabel('Strike') 117 | plt.ylabel('volatility') 118 | plt.title(f'ratio IV / LV') 119 | plt.legend() 120 | plt.show() -------------------------------------------------------------------------------- /mainOptionStrategies.py: -------------------------------------------------------------------------------- 1 | from Option.OptionStrategies import OptionStrategies 2 | from Option.Option import Option , Stock 3 | from Enum.OptionType import OpionType 4 | from Enum.BuySellSide import BuySellSide 5 | from DataRequest import y_finane_option_data , y_finane_stock_data 6 | import numpy as np 7 | 8 | ticker = 'TSLA' 9 | 10 | ## Get Option/underlying stock Prices 11 | option_df = y_finane_option_data.get_option_data(ticker) 12 | stockPrices_ = y_finane_stock_data.get_stock_price(ticker) 13 | currentprice = stockPrices_.tail(1)['Adj Close'].values[0] 14 | 15 | #chose options 16 | # We take the 5th closest maturity 17 | maturity =option_df['T_days'].unique()[5] 18 | 19 | options_df = option_df[option_df['T_days'] ==maturity] 20 | call_df = options_df[options_df['Type'] =='CALL'] 21 | put_df = options_df[options_df['Type'] =='PUT'] 22 | 23 | 24 | 25 | #Getting real Quotes options 26 | call_90_df = call_df.iloc[(call_df['strike']-(currentprice * 0.90)).abs().argsort()[:1]] 27 | call_80_df = call_df.iloc[(call_df['strike']-(currentprice * 0.80)).abs().argsort()[:1]] 28 | 29 | put_90_df = put_df.iloc[(put_df['strike']-(currentprice * 0.90)).abs().argsort()[:1]] 30 | put_80_df = put_df.iloc[(put_df['strike']-(currentprice * 0.80)).abs().argsort()[:1]] 31 | 32 | 33 | # Creating Call spread 80 / 90 34 | call90 = Option(price=call_90_df['lastPrice'].values[0], K=call_90_df['strike'].values[0] , type= OpionType.CALL) 35 | call80 = Option(price=call_80_df['lastPrice'].values[0], K=call_80_df['strike'].values[0] , type= OpionType.CALL) 36 | 37 | 38 | strategy = OptionStrategies(name = "Call spread 90/80" ,St = currentprice) 39 | strategy.add_Option(option= call90 ,buySell= BuySellSide.SELL , option_number=1 ) 40 | strategy.add_Option(option= call80 ,buySell= BuySellSide.BUY , option_number=1 ) 41 | strategy.plot() 42 | 43 | 44 | # Creating put spread 80 / 90 45 | put90 = Option(price=put_90_df['lastPrice'].values[0], K=put_90_df['strike'].values[0] , type= OpionType.PUT) 46 | put80 = Option(price=put_80_df['lastPrice'].values[0], K=put_80_df['strike'].values[0] , type= OpionType.PUT) 47 | 48 | 49 | strategy = OptionStrategies(name = "PUT Spread 90/ 80" ,St = currentprice) 50 | strategy.add_Option(option= put90 ,buySell= BuySellSide.BUY , option_number=1 ) 51 | strategy.add_Option(option= put80,buySell= BuySellSide.SELL , option_number=1 ) 52 | strategy.plot() 53 | 54 | 55 | T = maturity /252 56 | r = 0.015 57 | vol = np.average(call_90_df['impliedVolatility'].values[0] + call_80_df['impliedVolatility'].values[0]) 58 | 59 | strategy.compute_greek_profile(T ,r , vol) 60 | strategy.plotGreek(greekStr='gamma') 61 | strategy.plotGreek(greekStr='theta') 62 | strategy.plotGreek(greekStr='delta') 63 | strategy.plotGreek(greekStr='vega') 64 | 65 | 66 | 67 | 68 | # Creating Straddle 69 | 70 | call_dfs= call_df.iloc[(call_df['strike']-(currentprice - 15)).abs().argsort()[:1]] 71 | put_dfs = put_df[(put_df['strike'] == call_df['strike'].values[0])] 72 | 73 | put = Option(price=put_dfs['lastPrice'].values[0], K=put_dfs['strike'].values[0] , type= OpionType.PUT) 74 | call = Option(price=call_dfs['lastPrice'].values[0], K=call_dfs['strike'].values[0] , type= OpionType.CALL) 75 | 76 | 77 | strategy = OptionStrategies(name = "Straddle" ,St = currentprice) 78 | strategy.add_Option(option= put ,buySell= BuySellSide.BUY , option_number=1 ) 79 | strategy.add_Option(option= call,buySell= BuySellSide.BUY , option_number=1 ) 80 | strategy.plot() 81 | 82 | 83 | # Creating Strangle 84 | 85 | call_df= call_df.iloc[(call_df['strike']-(currentprice - 15)).abs().argsort()[:1]] 86 | put_df= put_df.iloc[(put_df['strike']-(currentprice - 70)).abs().argsort()[:1]] 87 | 88 | 89 | put = Option(price=put_df['lastPrice'].values[0], K=put_df['strike'].values[0] , type= OpionType.PUT) 90 | call = Option(price=call_df['lastPrice'].values[0], K=call_df['strike'].values[0] , type= OpionType.CALL) 91 | 92 | strategy = OptionStrategies(name = "Strangle" ,St = currentprice) 93 | strategy.add_Option(option= call ,buySell= BuySellSide.BUY, option_number=1 ) 94 | strategy.add_Option(option= put ,buySell= BuySellSide.BUY, option_number=1 ) 95 | strategy.plot() 96 | 97 | 98 | #synthetic call 99 | 100 | put_df1 = put_df.iloc[(put_df['strike']-(currentprice - 15)).abs().argsort()[:1]] 101 | 102 | put = Option(price=put_df1['lastPrice'].values[0], K=put_df1['strike'].values[0] , type= OpionType.PUT) 103 | stock = Stock(price = currentprice) 104 | 105 | strategy = OptionStrategies(name = "Synthetic call" ,St = currentprice) 106 | strategy.add_Option(option= put ,buySell= BuySellSide.BUY, option_number=1 ) 107 | strategy.add_deltaOne(stock=stock,buySell= BuySellSide.BUY ) 108 | strategy.plot() 109 | 110 | 111 | #synthetic PUT 112 | 113 | call_df1 = call_df.iloc[(call_df['strike']-(currentprice - 15)).abs().argsort()[:1]] 114 | 115 | call = Option(price=call_df1['lastPrice'].values[0], K=call_df1['strike'].values[0] , type= OpionType.CALL) 116 | stock = Stock(price = currentprice) 117 | 118 | strategy = OptionStrategies(name = "Synthetic PUT", St = currentprice) 119 | strategy.add_Option(option= call ,buySell= BuySellSide.BUY, option_number=1 ) 120 | strategy.add_deltaOne(stock=stock,buySell= BuySellSide.SELL ) 121 | 122 | strategy.plot() 123 | 124 | # Long Butterfly spread 125 | 126 | call1 = Option(price=8, K=currentprice - 25 , type = OpionType.CALL) 127 | call2 = Option(price=2, K=currentprice + 25, type = OpionType.CALL) 128 | call3 = Option(price=4, K=currentprice, type = OpionType.CALL) 129 | 130 | 131 | strategy = OptionStrategies('Butterfly Spread', St = currentprice ) 132 | strategy.add_Option(option= call1 ,buySell= BuySellSide.BUY, option_number=1 ) 133 | strategy.add_Option(option= call2 ,buySell= BuySellSide.BUY, option_number=1 ) 134 | strategy.add_Option(option= call3 ,buySell= BuySellSide.SELL, option_number=2 ) 135 | strategy.plot() 136 | 137 | # Short Butterfly spread 138 | 139 | strategy = OptionStrategies('Butterfly Spread', St = currentprice ) 140 | strategy.add_Option(option= call1 ,buySell= BuySellSide.SELL, option_number=1 ) 141 | strategy.add_Option(option= call2 ,buySell= BuySellSide.SELL, option_number=1 ) 142 | strategy.add_Option(option= call3 ,buySell= BuySellSide.BUY, option_number=2 ) 143 | strategy.plot() 144 | -------------------------------------------------------------------------------- /mainPricingModel.py: -------------------------------------------------------------------------------- 1 | from BinomialModel.BinomialPricing import binom_EU1 2 | from BlackAndScholes.BSPricing import priceBS 3 | from DataRequest import y_finane_option_data , y_finane_stock_data 4 | from Volatility.ImpliedVolatiliy import compute_theorical_IV 5 | 6 | df = y_finane_option_data.get_option_data('TSLA') 7 | stockPrices_ = y_finane_stock_data.get_stock_price('TSLA') 8 | 9 | df['Underlying_Price'] = stockPrices_['Adj Close'].tail(1).values[0] 10 | 11 | prices_Bi = [] 12 | 13 | for row in df.itertuples(): 14 | price = binom_EU1(row.Underlying_Price, row.strike, row.T_days / 252, 0.01, 0.5, 200, row.Type) 15 | prices_Bi.append(price) 16 | 17 | df['Binomial_Prices'] = prices_Bi 18 | df['mid_Price'] = (df['bid'] + df['ask']) /2 19 | 20 | prices_BS = [] 21 | for row in df.itertuples(): 22 | price = priceBS(row.Underlying_Price, row.strike, row.T_days / 252, 0.01 ,row.impliedVolatility , row.Type) 23 | prices_BS.append(price) 24 | 25 | 26 | df['Black_And_Scholes_Prices'] = prices_BS 27 | 28 | IV = [] 29 | for row in df.itertuples(): 30 | price = compute_theorical_IV(row.mid_Price , row.Underlying_Price ,row.strike, row.T_days / 252, 0.01 , row.Type) 31 | IV.append(price) 32 | df['IV_Calculated'] = IV 33 | 34 | 35 | 36 | # Chose Expiration 37 | exp1 = df[(df.T_days == df.T_days.unique()[2]) & (df.Type=='CALL')] 38 | 39 | import matplotlib.pyplot as plt 40 | plt.plot(exp1.strike, exp1.mid_Price, label='Mid Price') 41 | plt.plot(exp1.strike, exp1.Black_And_Scholes_Prices, label='B&S Prices') 42 | plt.plot(exp1.strike, exp1.Binomial_Prices, label='Binomial Prices') 43 | 44 | plt.xlabel('Strike') 45 | plt.ylabel('Call Values') 46 | plt.legend() 47 | plt.show() 48 | 49 | # plotIV 50 | import matplotlib.pyplot as plt 51 | plt.plot(exp1.strike, exp1.impliedVolatility, label='IV market') 52 | plt.plot(exp1.strike, exp1.IV_Calculated, label='IV calculated') 53 | 54 | plt.xlabel('Strike') 55 | plt.ylabel('Implied Volatility Values') 56 | plt.legend() 57 | plt.show() 58 | 59 | 60 | #static Parameters 61 | import numpy as np 62 | 63 | K = 100 64 | r = 0.1 65 | T = 1 66 | St = stockPrices_['Adj Close'].tail(1).values[0] 67 | 68 | from BlackAndScholes.BSPricing import BS_CALL ,BS_PUT 69 | import matplotlib.pyplot as plt 70 | 71 | S = np.arange(0 ,St + 60,1) 72 | 73 | #Using != volatility 74 | callsPrice2 = [BS_CALL(s, K, T, r, 0.2) for s in S] 75 | callsPrice3 = [BS_CALL(s, K, T, r, 0.3) for s in S] 76 | callsPrice4 = [BS_CALL(s, K, T, r, 0.4) for s in S] 77 | callsPrice5 = [BS_CALL(s, K, T, r, 0.5) for s in S] 78 | 79 | IntrinsicValue = [max(s - K ,0)for s in S] 80 | 81 | plt.plot(S, callsPrice2, label='Call Value sig = 0.20') 82 | plt.plot(S, callsPrice3, label='Call Value sig = 0.30') 83 | plt.plot(S, callsPrice4, label='Call Value sig = 0.40') 84 | plt.plot(S, callsPrice5, label='Call Value sig = 0.50') 85 | plt.plot(S, IntrinsicValue, label='IntrinsicValue') 86 | plt.xlabel('$S_t$') 87 | plt.ylabel(' Value') 88 | plt.title('Implied volatility impact on call value') 89 | plt.legend() 90 | plt.show() 91 | 92 | 93 | 94 | 95 | #static Parameters 96 | import numpy as np 97 | 98 | K = 100 99 | r = 0.1 100 | T = 1 101 | sig = 0.2 102 | St = stockPrices_['Adj Close'].tail(1).values[0] 103 | 104 | from BlackAndScholes.BSPricing import BS_CALL ,BS_PUT 105 | import matplotlib.pyplot as plt 106 | 107 | S = np.arange(0 ,St + 60,1) 108 | 109 | #Using != volatility 110 | callsPrice2 = [BS_CALL(s, K, 3, r, sig) for s in S] 111 | callsPrice3 = [BS_CALL(s, K,2, r, sig) for s in S] 112 | callsPrice4 = [BS_CALL(s, K, 1, r, sig) for s in S] 113 | callsPrice5 = [BS_CALL(s, K, 0.50, r,sig) for s in S] 114 | 115 | IntrinsicValue = [max(s - K ,0)for s in S] 116 | 117 | plt.plot(S, callsPrice2, label='Call Value T = 3') 118 | plt.plot(S, callsPrice3, label='Call Value T = 2') 119 | plt.plot(S, callsPrice4, label='Call Value T = 1') 120 | plt.plot(S, callsPrice5, label='Call Value T = 0.5') 121 | plt.plot(S, IntrinsicValue, label='IntrinsicValue') 122 | plt.xlabel('$S_t$') 123 | plt.ylabel(' Value') 124 | plt.title('Time impact on call value') 125 | plt.legend() 126 | plt.show() 127 | 128 | 129 | --------------------------------------------------------------------------------