-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathen_options.py
266 lines (212 loc) · 10.3 KB
/
en_options.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# -*- coding: utf-8 -*-
"""
________ ___ ___ ________ ________ ___ ________ _______ ________ ___ _________ ________
|\ ____\|\ \|\ \|\ ____\|\ ____\|\ \|\ __ \|\ ___ \ |\ __ \|\ \|\___ ___\\ __ \
\ \ \___|\ \ \\\ \ \ \___|\ \ \___|\ \ \ \ \|\ \ \ __/|\ \ \|\ \ \ \|___ \ \_\ \ \|\ \
\ \ \ __\ \ \\\ \ \ \ \ \ \ \ \ \ \ ____\ \ \_|/_\ \ ____\ \ \ \ \ \ \ \ \\\ \
\ \ \|\ \ \ \\\ \ \ \____\ \ \____\ \ \ \ \___|\ \ \_|\ \ \ \___|\ \ \ \ \ \ \ \ \\\ \
\ \_______\ \_______\ \_______\ \_______\ \__\ \__\ \ \_______\ \__\ \ \__\ \ \__\ \ \_______\
\|_______|\|_______|\|_______|\|_______|\|__|\|__| \|_______|\|__| \|__| \|__| \|_______|
____ ___ ____ ____ _ _ ____ ___ ___ _ ____ _ _ ____ ____ _ _ ____ _ _ _ ____ _ ____ ___ ____ ____ _
[__ | | | | |_/ | | |__] | | | | |\ | [__ |__| |\ | |__| | \_/ [__ | [__ | | | | | |
___] | |__| |___ | \_ |__| | | | |__| | \| ___] | | | \| | | |___ | ___] | ___] | |__| |__| |___
"""
import subprocess
import os
import numpy as np
from scipy.stats import norm
from datetime import datetime
import yfinance as yf
import pandas as pd
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font
from google.colab import files
from pandas_datareader import data as pdr
# Install necessary libraries
subprocess.run(['pip', 'install', 'yfinance', 'numpy', 'scipy', 'pandas_datareader'])
def get_option_expiration_dates(symbol):
"""
Gets the expiration dates of options for a given symbol.
Args:
symbol: Stock symbol
Returns:
List of option expiration dates
"""
ticker = yf.Ticker(symbol) # Retrieving ticker data
expirations = ticker.options # Retrieving option expiration dates
return expirations
def calculate_implied_volatility(S, K, T, r, price, option_type):
"""
Calculates the implied volatility using the Black-Scholes model.
Args:
S: Current stock price
K: Option strike price
T: Time to expiration in years
r: Risk-free interest rate
price: Option price
option_type: 'call' or 'put'
Returns:
Implied volatility
"""
tol = 0.0001 # Tolerance for implied volatility convergence
max_iter = 100 # Maximum number of iterations
def black_scholes(option_type, S, K, T, r, sigma):
"""
Calculates the option price using the Black-Scholes model.
"""
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if option_type == 'call':
option_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
else:
option_price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
return option_price
sigma = 0.5 # Initial guess for implied volatility
price_est = black_scholes(option_type, S, K, T, r, sigma) # Initial estimate of option price
diff = price_est - price # Difference between estimated price and observed price
iter_count = 0 # Iteration counter initialization
while abs(diff) > tol and iter_count < max_iter:
vega = S * np.sqrt(T) * norm.pdf((np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T)))
price_est = black_scholes(option_type, S, K, T, r, sigma) # Estimate of option price
diff = price_est - price # New difference between estimated price and observed price
if abs(diff) < tol: # Checking for convergence
break
sigma = sigma - (diff / vega) # Updating implied volatility
iter_count += 1 # Incrementing iteration counter
return sigma
def calculate_intrinsic_value(S, K, option_type):
"""
Calculates the intrinsic value of an option.
Args:
S: Current stock price
K: Option strike price
option_type: 'call' or 'put'
Returns:
Intrinsic value
"""
if option_type == 'call':
return max(0, S - K)
else:
return max(0, K - S)
def calculate_time_value(price, intrinsic_value):
"""
Calculates the time value of an option.
Args:
price: Option price
intrinsic_value: Intrinsic value of the option
Returns:
Time value
"""
return max(0, price - intrinsic_value)
def calculate_historical_volatility(symbol, start_date, end_date):
"""
Calculates historical volatility using historical price data.
Args:
symbol: Stock symbol
start_date: Start date for historical data
end_date: End date for historical data
Returns:
Historical volatility
"""
stock = yf.Ticker(symbol) # Retrieving stock data
historical_data = stock.history(start=start_date, end=end_date) # Retrieving historical data
returns = np.log(historical_data['Close'] / historical_data['Close'].shift(1)) # Calculating log returns
volatility = returns.std() * np.sqrt(252) # Calculating historical volatility (252 trading days in a year)
return volatility
def get_risk_free_rate():
"""
Gets the risk-free rate using the 10-year Treasury constant maturity rate.
"""
risk_free_rate_data = pdr.get_data_fred('DGS10')
return risk_free_rate_data['DGS10'][-1] / 100 # Convert percentage to decimal
def fetch_option_info(ticker, expiry_date, risk_free_rate):
"""
Fetches option information for a given ticker, expiry date, and risk-free rate.
Args:
ticker: Stock ticker
expiry_date: Option expiration date
risk_free_rate: Risk-free interest rate
Returns:
Option information
"""
stock = yf.Ticker(ticker) # Retrieving stock data
trade_date = datetime.now() # Current date
trade_date = trade_date.replace(tzinfo=None) # Converting to datetime without timezone
expiry_date = datetime.strptime(expiry_date, '%Y-%m-%d').replace(tzinfo=None) # Converting expiration date to datetime object
if expiry_date < trade_date:
raise ValueError("The specified expiration date has already passed.")
option_chain = stock.option_chain(expiry_date.strftime('%Y-%m-%d')) # Retrieving option chain data
if len(option_chain.calls) > 0:
option = option_chain.calls.iloc[0] # Assuming you're interested in the first available call option
underlying_price = stock.history(period='1d').iloc[-1]['Close'] # Underlying stock price
strike_price = option['strike'] # Option strike price
days_to_expiry = (expiry_date - trade_date).days / 365 # Time to expiration in years
# Calculating implied volatility
implied_volatility = calculate_implied_volatility(underlying_price, strike_price, days_to_expiry, risk_free_rate, option['lastPrice'], 'call')
# Calculating intrinsic value for call option
call_intrinsic_value = calculate_intrinsic_value(underlying_price, strike_price, 'call')
# Calculating intrinsic value for put option
put_intrinsic_value = calculate_intrinsic_value(underlying_price, strike_price, 'put')
# Calculating time value
time_value = calculate_time_value(option['lastPrice'], call_intrinsic_value)
return underlying_price, strike_price, days_to_expiry, risk_free_rate, implied_volatility, call_intrinsic_value, put_intrinsic_value, time_value
else:
raise ValueError("No call option available for the given ticker and expiry date.")
def main():
symbol = input("Enter the stock symbol: ")
risk_free_rate = get_risk_free_rate()
expiration_dates = get_option_expiration_dates(symbol)
if expiration_dates:
option_data_list = []
for exp_date in expiration_dates:
try:
option_data = fetch_option_info(symbol, exp_date, risk_free_rate)
# Calculating historical volatility
start_date = (datetime.now() - pd.Timedelta(days=365)).strftime('%Y-%m-%d') # One year ago
end_date = datetime.now().strftime('%Y-%m-%d')
historical_volatility = calculate_historical_volatility(symbol, start_date, end_date)
option_data_list.append([exp_date] + list(option_data) + [historical_volatility])
except ValueError as e:
print(e)
# Creating a DataFrame with option data
columns = ['Expiration Date', 'Underlying Price', 'Strike Price', 'Days to Expiry', 'Risk-Free Rate', 'Implied Volatility', 'Call Intrinsic Value', 'Put Intrinsic Value', 'Time Value', 'Historical Volatility']
df = pd.DataFrame(option_data_list, columns=columns)
# Counting the number of existing files for the same ticker
num_files = sum(f.startswith(f"{symbol}_option_data") for f in os.listdir('.'))
num_files += 1
# Exporting data to an Excel file with increasing numbering
excel_file = f"{symbol}_option_data_{num_files}.xlsx"
with pd.ExcelWriter(excel_file, engine='openpyxl') as writer:
df.to_excel(writer, index=False, sheet_name='Option Data')
# Applying style to the Excel file
workbook = writer.book
worksheet = writer.sheets['Option Data']
header_font = Font(bold=True)
align_center = Alignment(horizontal='center')
for cell in worksheet["1:1"]:
cell.font = header_font
cell.alignment = align_center
# Adding a title
title_cell = worksheet.cell(row=1, column=1)
title_cell.value = "Option Data"
title_cell.font = Font(size=14, bold=True)
title_cell.alignment = Alignment(horizontal='center')
# Automatically adjusting column widths
for column_cells in worksheet.columns:
max_length = 0
column = column_cells[0].column_letter
for cell in column_cells:
try:
if len(str(cell.value)) > max_length:
max_length = len(cell.value)
except:
pass
adjusted_width = (max_length + 2) * 1.2
worksheet.column_dimensions[column].width = adjusted_width
print(f"Option data has been saved to {excel_file}")
# Downloading the Excel file
files.download(excel_file)
else:
print("No option expiration dates found for {}".format(symbol))
if __name__ == "__main__":
main()