fima 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fima/Bonds.py ADDED
@@ -0,0 +1,102 @@
1
+ import requests
2
+ import pandas as pd
3
+ import numpy as np
4
+ from bs4 import BeautifulSoup
5
+ from io import StringIO
6
+ import jdatetime as jd
7
+
8
+
9
+ def get_risk_free_rate() -> float:
10
+ all_bonds_without_coupons = get_all_bonds_without_coupons(deprecated=False)
11
+ all_t_notes = all_bonds_without_coupons[all_bonds_without_coupons['Ticker'].str.contains('سخاب|اخزا')].copy()
12
+ today_date = jd.date.today()
13
+ all_t_notes['DaysTillMaturity'] = (all_t_notes['MaturityDate'] - today_date).apply(lambda delta: delta.days)
14
+ last_traded_t_notes = all_t_notes[all_t_notes['LastTradedDate'] == all_t_notes['LastTradedDate'].max()]
15
+ rf = last_traded_t_notes['YTM'].mean()
16
+ return rf
17
+
18
+
19
+ def get_all_bonds_without_coupons(deprecated: bool = True) -> pd.DataFrame:
20
+ url = "https://ifb.ir/YTM.aspx"
21
+
22
+ session = requests.Session()
23
+ response = session.get(url)
24
+
25
+ if response.status_code != 200:
26
+ print(f"Failed to access page. Status code: {response.status_code}")
27
+ return None
28
+
29
+ soup = BeautifulSoup(response.content, "html.parser")
30
+ table = soup.find("table", {"id": "ContentPlaceHolder1_grdytmforkhazaneh", "class": "KhazanehGrid"})
31
+
32
+ if table is None:
33
+ print("Table not found.")
34
+ return None
35
+
36
+ df_list = pd.read_html(StringIO(str(table)))
37
+ bonds_without_coupons = df_list[0]
38
+
39
+ if ~deprecated:
40
+ bonds_without_coupons = bonds_without_coupons[bonds_without_coupons['YTM'] != 'سررسید شده'].copy()
41
+
42
+ bonds_without_coupons.columns = ['Index', 'Ticker', 'LastTradedPrice', 'LastTradedDate', 'MaturityDate', 'YTM', 'SimpleReturn']
43
+
44
+ bonds_without_coupons['LastTradedDate'] = \
45
+ bonds_without_coupons['LastTradedDate'].apply(lambda str_date: jd.date(year=int(str_date[:4]),
46
+ month=int(str_date[5:7]),
47
+ day=int(str_date[8:])))
48
+
49
+ bonds_without_coupons['MaturityDate'] = \
50
+ bonds_without_coupons['MaturityDate'].apply(lambda str_date: jd.date(year=int(str_date[:4]),
51
+ month=int(str_date[5:7]),
52
+ day=int(str_date[8:])))
53
+
54
+ bonds_without_coupons['YTM'] = \
55
+ bonds_without_coupons['YTM'].apply(lambda str_ytm: float(str_ytm.replace('/', '.').replace('%', '')) / 100)
56
+ bonds_without_coupons['SimpleReturn'] = \
57
+ bonds_without_coupons['SimpleReturn'].apply(lambda str_ytm: float(str_ytm.replace('/', '.').replace('%', '')) / 100)
58
+
59
+ bonds_without_coupons.drop('Index', inplace=True, axis=1)
60
+ bonds_without_coupons.reset_index(inplace=True, drop=True)
61
+
62
+ return bonds_without_coupons
63
+
64
+
65
+ def get_all_bonds_with_coupons(deprecated: bool = True) -> pd.DataFrame:
66
+ url = "https://ifb.ir/YTM.aspx"
67
+
68
+ session = requests.Session()
69
+ response = session.get(url)
70
+
71
+ if response.status_code != 200:
72
+ print(f"Failed to access page. Status code: {response.status_code}")
73
+ return None
74
+
75
+ soup = BeautifulSoup(response.content, "html.parser")
76
+ table = soup.find("table", {"id": "ContentPlaceHolder1_grdytm", "class": "mGrid"})
77
+
78
+ if table is None:
79
+ print("Table not found.")
80
+ return None
81
+
82
+ df_list = pd.read_html(StringIO(str(table)))
83
+ bonds_with_coupons = df_list[0]
84
+
85
+ if ~deprecated:
86
+ bonds_with_coupons = bonds_with_coupons[bonds_with_coupons['YTM'] != 'سررسید شده'].copy()
87
+
88
+ bonds_with_coupons.columns = ['Index', 'Ticker', 'LastTradedPrice', 'LastTradedDate', 'MaturityDate', 'YTM']
89
+
90
+ bonds_with_coupons['LastTradedDate'] = bonds_with_coupons['LastTradedDate'].apply(
91
+ lambda str_date: jd.date(year=int(str_date[:4]), month=int(str_date[5:7]), day=int(str_date[8:])) if pd.notna(str_date) else np.nan)
92
+
93
+ bonds_with_coupons['MaturityDate'] = bonds_with_coupons['MaturityDate'].apply(
94
+ lambda str_date: jd.date(year=int(str_date[:4]), month=int(str_date[5:7]), day=int(str_date[8:])) if str_date != '0' else np.nan)
95
+
96
+ bonds_with_coupons['YTM'] = bonds_with_coupons['YTM'].apply(
97
+ lambda str_ytm: float(str_ytm.replace('/', '.').replace('%', '')) / 100 if (pd.notna(str_ytm) and str_ytm != 'سررسید شده') else np.nan)
98
+
99
+ bonds_with_coupons.drop('Index', inplace=True, axis=1)
100
+ bonds_with_coupons.reset_index(inplace=True, drop=True)
101
+
102
+ return bonds_with_coupons
fima/Options.py ADDED
@@ -0,0 +1,675 @@
1
+ import pandas as pd
2
+ import requests
3
+ import jdatetime as jd
4
+ from fima.Bonds import get_risk_free_rate
5
+ from mibian import BS
6
+ from scipy.stats import norm
7
+ import numpy as np
8
+
9
+
10
+ def calculate_d1(s, k, t, r_f, sigma) -> float:
11
+ return (np.log(s / k) + (r_f + 0.5 * sigma ** 2) * t) / (sigma * np.sqrt(t))
12
+
13
+
14
+ def calculate_d2(s, k, t, r_f, sigma) -> float:
15
+ return calculate_d1(s, k, t, r_f, sigma) - sigma * np.sqrt(t)
16
+
17
+
18
+ def calculate_delta(s, k, t, r_f, sigma, option_type) -> float:
19
+ if option_type == 'Call':
20
+ return norm.cdf(calculate_d1(s, k, t, r_f, sigma))
21
+ elif option_type == 'Put':
22
+ return -norm.cdf(-(np.log(s / k) + (r_f + sigma ** 2 / 2) * t) / (sigma * np.sqrt(t)))
23
+ return None
24
+
25
+
26
+ def calculate_theta(s, k, t, r_f, sigma, option_type) -> float:
27
+ if option_type == 'Call':
28
+ p1 = - s * norm.pdf(calculate_d1(s, k, t, r_f, sigma)) * sigma / (2 * np.sqrt(t))
29
+ p2 = r_f * k * np.exp(-r_f * t) * norm.cdf(calculate_d2(s, k, t, r_f, sigma))
30
+ return p1 - p2
31
+ elif option_type == 'Put':
32
+ p1 = - s * norm.pdf(calculate_d1(s, k, t, r_f, sigma)) * sigma / (2 * np.sqrt(t))
33
+ p2 = r_f * k * np.exp(-r_f * t) * norm.cdf(-calculate_d2(s, k, t, r_f, sigma))
34
+ return p1 + p2
35
+ return None
36
+
37
+
38
+ def calculate_gamma(s, k, t, r_f, sigma) -> float:
39
+ return norm.pdf(calculate_d1(s, k, t, r_f, sigma)) / (s * sigma * np.sqrt(t))
40
+
41
+
42
+ def calculate_vega(s, k, t, r_f, sigma) -> float:
43
+ return s * np.sqrt(t) * norm.pdf(calculate_d1(s, k, t, r_f, sigma))
44
+
45
+
46
+ def calculate_rho(s, k, t, r_f, sigma, option_type) -> float:
47
+ if option_type == 'Call':
48
+ return k * t * np.exp(-r_f * t) * norm.cdf(calculate_d2(s, k, t, r_f, sigma))
49
+ elif option_type == 'Put':
50
+ return -k * t * np.exp(-r_f * t) * norm.cdf(-calculate_d2(s, k, t, r_f, sigma))
51
+ return None
52
+
53
+
54
+ def calculate_row_wise_implied_volatility(row) -> float:
55
+ # print(f"Calculating {row['Ticker']} implied volatility...")
56
+ return get_implied_volatility(ticker=row['Ticker'], ticker_info_df=row.to_frame().T, r_f=row['RiskFreeRate'],
57
+ ua_historical_data=row['HistoricalData-UA'])
58
+
59
+
60
+ def get_implied_volatility(ticker: str, ticker_info_df: pd.DataFrame = None, volatility_window_size: int = None,
61
+ r_f: float = None, minimum_required_history: int = 30, ua_historical_data: pd.DataFrame = None) -> float:
62
+
63
+ if ticker_info_df is None:
64
+ ticker_info_df = ticker_info(ticker=ticker, j_date=True)
65
+
66
+ if ua_historical_data is None:
67
+ try:
68
+ _, ua_historical_data = download_historical_data(ticker)
69
+ except requests.exceptions.HTTPError:
70
+ print(f'Couldn\'t download historical data for: {ticker}')
71
+ return None
72
+
73
+ if ua_historical_data is None:
74
+ return None
75
+
76
+ if r_f is None:
77
+ r_f = get_risk_free_rate()
78
+
79
+ stock_days_available = len(ua_historical_data)
80
+ if stock_days_available < minimum_required_history:
81
+ print(
82
+ f"Not enough stock history for {ticker_info_df['Ticker-UA']} ({stock_days_available} days available, "
83
+ f"{minimum_required_history} days required).")
84
+ return None
85
+
86
+ days_to_maturity = ticker_info_df['DaysToMaturity'].values[0]
87
+ if days_to_maturity < 10:
88
+ print(f"Option {ticker} expires too soon ({days_to_maturity} days left). Calculated implied volatility may not be reliable.")
89
+ return None
90
+
91
+ max_safe_window = min(stock_days_available, days_to_maturity)
92
+ if volatility_window_size is None:
93
+ volatility_window_size = min(30, max(5, max_safe_window // 2)) # dynamic choice
94
+ else:
95
+ volatility_window_size = min(volatility_window_size, max_safe_window) # force safe
96
+
97
+ if volatility_window_size < 5:
98
+ print(f"Volatility window too small ({volatility_window_size} days). Cannot calculate meaningful volatility for {ticker}.")
99
+ return None
100
+
101
+ sigma = calculate_volatility(ua_historical_data, volatility_window_size)
102
+ s = ticker_info_df['LastPrice-UA'].values[0]
103
+ k = ticker_info_df['StrikePrice'].values[0]
104
+ t = days_to_maturity / 365
105
+ option_type = ticker_info_df['Type'].values[0]
106
+ premium = ticker_info_df['LastPrice'].values[0]
107
+
108
+ if sigma == 0 or t == 0:
109
+ print("Zero volatility or expired option.")
110
+ return None
111
+
112
+ try:
113
+ if option_type == 'Call':
114
+ ticker_implied_volatility = BS([s, k, r_f, t * 365], callPrice=premium)
115
+ elif option_type == 'Put':
116
+ ticker_implied_volatility = BS([s, k, r_f, t * 365], putPrice=premium)
117
+ else:
118
+ print('Option type is not a valid one.')
119
+ return None
120
+ except (UnboundLocalError, IndexError):
121
+ print('mibian library error.')
122
+ return None
123
+ return ticker_implied_volatility.impliedVolatility / 100
124
+
125
+
126
+ def get_greeks(ticker: str, ticker_info_df: pd.DataFrame = None, volatility_window_size: int = None, r_f: float = None,
127
+ minimum_required_history: int = 30, ua_historical_data: pd.DataFrame = None) -> pd.Series:
128
+
129
+ delta, gamma, theta, vega, rho = (None, None, None, None, None)
130
+
131
+ if ticker_info_df is None:
132
+ ticker_info_df = ticker_info(ticker=ticker, j_date=True)
133
+
134
+ if ua_historical_data is None:
135
+ try:
136
+ _, ua_historical_data = download_historical_data(ticker)
137
+ except requests.exceptions.HTTPError:
138
+ print(f'Couldn\'t download historical data for: {ticker}')
139
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
140
+
141
+ if ua_historical_data is None:
142
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
143
+
144
+ if r_f is None:
145
+ r_f = get_risk_free_rate()
146
+
147
+ stock_days_available = len(ua_historical_data)
148
+ if stock_days_available < minimum_required_history:
149
+ print(
150
+ f"Not enough stock history for {ticker_info_df['Ticker-UA']} ({stock_days_available} days available, "
151
+ f"{minimum_required_history} days required).")
152
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
153
+
154
+ days_to_maturity = ticker_info_df['DaysToMaturity'].values[0]
155
+ if days_to_maturity < 10:
156
+ print(f"Option {ticker} expires too soon ({days_to_maturity} days left). Calculated greeks may not be reliable.")
157
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
158
+
159
+ max_safe_window = min(stock_days_available, days_to_maturity)
160
+ if volatility_window_size is None:
161
+ volatility_window_size = min(30, max(5, max_safe_window // 2)) # dynamic choice
162
+ else:
163
+ volatility_window_size = min(volatility_window_size, max_safe_window) # force safe
164
+
165
+ if volatility_window_size < 5:
166
+ print(
167
+ f"Volatility window too small ({volatility_window_size} days). Cannot calculate meaningful volatility for {ticker}.")
168
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
169
+
170
+ sigma = calculate_volatility(ua_historical_data, volatility_window_size)
171
+ s = ticker_info_df['LastPrice-UA'].values[0]
172
+ k = ticker_info_df['StrikePrice'].values[0]
173
+ t = days_to_maturity / 365
174
+ option_type = ticker_info_df['Type'].values[0]
175
+
176
+ if sigma == 0 or t == 0:
177
+ print("Zero volatility or expired option.")
178
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
179
+
180
+ delta = calculate_delta(s, k, t, sigma, r_f, option_type)
181
+ gamma = calculate_gamma(s, k, t, sigma, r_f)
182
+ theta = calculate_theta(s, k, t, sigma, r_f, option_type)
183
+ vega = calculate_vega(s, k, t, sigma, r_f)
184
+ rho = calculate_rho(s, k, t, sigma, r_f, option_type)
185
+
186
+ return pd.Series({'Delta': delta, 'Gamma': gamma, 'Theta': theta, 'Vega': vega, 'Rho': rho})
187
+
188
+
189
+ def calculate_row_wise_greeks(row) -> pd.Series:
190
+ # print(f"Calculating {row['Ticker']} greeks...")
191
+ return get_greeks(ticker=row['Ticker'], ticker_info_df=row.to_frame().T, r_f=row['RiskFreeRate'], ua_historical_data=row['HistoricalData-UA'])
192
+
193
+
194
+ def calculate_row_wise_bsm(row) -> pd.Series:
195
+ # print(f"Calculating {row['Ticker']} price with Black, Scholes and Merton model...")
196
+ volatility, bsm_price = black_scholes_merton(ticker=row['Ticker'], ticker_info_df=row.to_frame().T,
197
+ r_f=row['RiskFreeRate'], ua_historical_data=row['HistoricalData-UA'])
198
+ return pd.Series({'Volatility': volatility, 'BSMPrice': bsm_price})
199
+
200
+
201
+ def ticker_info(ticker: str, j_date: bool = True) -> pd.DataFrame:
202
+ all_options_market_watch = download_market_watch(market='All', stack='Vertical', j_date=j_date)
203
+ if ticker in list(all_options_market_watch.loc[:, 'Ticker']):
204
+ ticker_info_df = all_options_market_watch[all_options_market_watch['Ticker'] == ticker].copy()
205
+ ticker_info_df.reset_index(inplace=True, drop=True)
206
+ else:
207
+ ticker_info_df = None
208
+ print(f'{ticker} not found.')
209
+ return ticker_info_df
210
+
211
+
212
+ def calculate_volatility(ua_historical_data, window_size) -> float:
213
+ log_returns = np.log(ua_historical_data['ClosePrice'] / ua_historical_data['ClosePrice'].shift(1))
214
+ volatility = np.sqrt(window_size) * log_returns.std()
215
+ return volatility
216
+
217
+
218
+ def calculate_black_scholes_merton(s, k, t, sigma, r_f, option_type) -> (float, float):
219
+ d1 = calculate_d1(s, k, t, r_f, sigma)
220
+ d2 = calculate_d2(s, k, t, r_f, sigma)
221
+
222
+ if option_type == 'Call':
223
+ price = s * norm.cdf(d1) - k * np.exp(-r_f * t) * norm.cdf(d2)
224
+ elif option_type == 'Put':
225
+ price = k * np.exp(-r_f * t) * norm.cdf(-d2) - s * norm.cdf(-d1)
226
+ else:
227
+ raise ValueError(f"Unknown option type '{option_type}'. Expected 'Call' or 'Put'.")
228
+ return price
229
+
230
+
231
+ def black_scholes_merton(ticker: str, ticker_info_df: pd.DataFrame = None, volatility_window_size: int = None,
232
+ r_f: float = None, minimum_required_history: int = 30, ua_historical_data: pd.DataFrame = None) -> (float, float):
233
+
234
+ if ticker_info_df is None:
235
+ ticker_info_df = ticker_info(ticker=ticker, j_date=True)
236
+
237
+ if ua_historical_data is None:
238
+ try:
239
+ _, ua_historical_data = download_historical_data(ticker)
240
+ except requests.exceptions.HTTPError:
241
+ print(f'Couldn\'t download historical data for: {ticker}')
242
+ return None, None
243
+
244
+ if ua_historical_data is None:
245
+ return None, None
246
+
247
+ if r_f is None:
248
+ r_f = get_risk_free_rate()
249
+
250
+ stock_days_available = len(ua_historical_data)
251
+ if stock_days_available < minimum_required_history:
252
+ print(
253
+ f"Not enough stock history for {ticker_info_df['Ticker-UA']} ({stock_days_available} days available, "
254
+ f"{minimum_required_history} days required).")
255
+ return None, None
256
+
257
+ days_to_maturity = ticker_info_df['DaysToMaturity'].values[0]
258
+ if days_to_maturity < 10:
259
+ print(f"Option {ticker} expires too soon ({days_to_maturity} days left). BSM model may not be reliable.")
260
+ return None, None
261
+
262
+ max_safe_window = min(stock_days_available, days_to_maturity)
263
+ if volatility_window_size is None:
264
+ volatility_window_size = min(30, max(5, max_safe_window // 2)) # dynamic choice
265
+ else:
266
+ volatility_window_size = min(volatility_window_size, max_safe_window) # force safe
267
+
268
+ if volatility_window_size < 5:
269
+ print(
270
+ f"Volatility window too small ({volatility_window_size} days). Cannot calculate meaningful volatility for {ticker}.")
271
+ return None, None
272
+
273
+ sigma = calculate_volatility(ua_historical_data, volatility_window_size)
274
+ s = ticker_info_df['LastPrice-UA'].values[0]
275
+ k = ticker_info_df['StrikePrice'].values[0]
276
+ t = days_to_maturity / 365
277
+ option_type = ticker_info_df['Type'].values[0]
278
+
279
+ if sigma == 0 or t == 0:
280
+ print("Zero volatility or expired option. Returning price = 0.")
281
+ return 0, 0
282
+
283
+ price = calculate_black_scholes_merton(s, k, t, sigma, r_f, option_type)
284
+
285
+ return sigma, price
286
+
287
+
288
+ def download_all_underlying_assets(all_options_market_watch: pd.DataFrame = None, market: str = 'All') -> pd.DataFrame:
289
+ if all_options_market_watch is None:
290
+ all_options_market_watch = download_market_watch(market=market, stack='Horizontal', j_date=True)
291
+ all_underlying_assets = all_options_market_watch.loc[:, [column for column in all_options_market_watch.columns if column.endswith('-UA')]]
292
+ all_underlying_assets.drop_duplicates(inplace=True)
293
+ all_underlying_assets.reset_index(inplace=True, drop=True)
294
+ all_underlying_assets.columns = [column.replace('-UA', '') for column in all_underlying_assets.columns]
295
+ return all_underlying_assets
296
+
297
+
298
+ def download_ua_historical_data(ticker: str, start_date: str = None, end_date: str = None) -> pd.DataFrame:
299
+ all_options_market_watch = download_market_watch(market='All', stack='Vertical', j_date=False)
300
+
301
+ # underlying asset
302
+ ticker_data = all_options_market_watch[all_options_market_watch['Ticker-UA'] == ticker]
303
+ ticker_instrument_code = ticker_data['InstrumentCode-UA'].values[0]
304
+ ticker_historical_data_url = f'https://cdn.tsetmc.com/api/ClosingPrice/GetClosingPriceDailyList/{ticker_instrument_code}/0'
305
+
306
+ response = requests.get(ticker_historical_data_url)
307
+ response.raise_for_status()
308
+
309
+ data = response.json()
310
+ ticker_historical_data = pd.json_normalize(data["closingPriceDaily"])
311
+
312
+ if ticker_historical_data.empty:
313
+ print(f'Couldn\'t download historical data for ticker: {ticker}')
314
+ return None
315
+
316
+ ticker_historical_data.columns = ['PriceChange', 'MinPrice', 'MaxPrice', 'YesterdayPrice', 'FirstPrice', 'Last', 'ID',
317
+ 'InstrumentCode', 'Date', 'H', 'ClosePrice', 'I', 'Y', 'LastPrice', 'Quantity', 'Volume', 'Value']
318
+ ticker_historical_data = ticker_historical_data.loc[:, ['Date', 'Quantity', 'Volume', 'Value', 'MinPrice', 'MaxPrice',
319
+ 'FirstPrice', 'LastPrice', 'ClosePrice']]
320
+ ticker_historical_data['Date'] = pd.to_datetime(ticker_historical_data['Date'].astype(str), format='%Y%m%d')
321
+ ticker_historical_data['Date'] = ticker_historical_data['Date'].dt.date
322
+ ticker_historical_data['Date'] = \
323
+ ticker_historical_data['Date'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
324
+
325
+ if start_date is not None and end_date is not None:
326
+ start_date_year, start_date_month, start_date_day = [int(date) for date in start_date.split('-')]
327
+ start_date = jd.date(start_date_year, start_date_month, start_date_day)
328
+
329
+ end_date_year, end_date_month, end_date_day = [int(date) for date in end_date.split('-')]
330
+ end_date = jd.date(end_date_year, end_date_month, end_date_day)
331
+
332
+ ticker_historical_data = ticker_historical_data[(ticker_historical_data['Date'] >= start_date) & (ticker_historical_data['Date'] <= end_date)]
333
+ ticker_historical_data.reset_index(inplace=True, drop=True)
334
+
335
+ return ticker_historical_data
336
+
337
+
338
+ def download_historical_data(ticker: str, start_date: str = None, end_date: str = None) -> (pd.DataFrame, pd.DataFrame):
339
+
340
+ all_options_market_watch = download_market_watch(market='All', stack='Vertical', j_date=False)
341
+
342
+ # option
343
+ ticker_data = all_options_market_watch[all_options_market_watch['Ticker'] == ticker]
344
+ ticker_instrument_code = ticker_data['InstrumentCode'].values[0]
345
+ ticker_historical_data_url = f'https://cdn.tsetmc.com/api/ClosingPrice/GetClosingPriceDailyList/{ticker_instrument_code}/0'
346
+
347
+ response = requests.get(ticker_historical_data_url)
348
+ response.raise_for_status()
349
+
350
+ data = response.json()
351
+ ticker_historical_data = pd.json_normalize(data["closingPriceDaily"])
352
+
353
+ if ticker_historical_data.empty:
354
+ print(f'Couldn\'t download historical data for ticker: {ticker}')
355
+ return None, None
356
+
357
+ ticker_historical_data.columns = ['PriceChange', 'MinPrice', 'MaxPrice', 'YesterdayPrice', 'FirstPrice', 'Last', 'ID',
358
+ 'InstrumentCode', 'Date', 'H', 'ClosePrice', 'I', 'Y', 'LastPrice', 'Quantity', 'Volume', 'Value']
359
+ ticker_historical_data = ticker_historical_data.loc[:, ['Date', 'Quantity', 'Volume', 'Value', 'MinPrice', 'MaxPrice',
360
+ 'FirstPrice', 'LastPrice', 'ClosePrice']]
361
+ ticker_historical_data['Date'] = pd.to_datetime(ticker_historical_data['Date'].astype(str), format='%Y%m%d')
362
+ ticker_historical_data['Date'] = ticker_historical_data['Date'].dt.date
363
+ ticker_historical_data['Date'] = \
364
+ ticker_historical_data['Date'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
365
+
366
+ # underlying asset
367
+ ua_instrument_code = ticker_data['InstrumentCode-UA'].values[0]
368
+ ua_ticker_historical_data_url = f'https://cdn.tsetmc.com/api/ClosingPrice/GetClosingPriceDailyList/{ua_instrument_code}/0'
369
+
370
+ response = requests.get(ua_ticker_historical_data_url)
371
+ response.raise_for_status()
372
+
373
+ data = response.json()
374
+ ua_ticker_historical_data = pd.json_normalize(data["closingPriceDaily"])
375
+
376
+ if ua_ticker_historical_data.empty:
377
+ print(f'Couldn\'t download historical data for ticker: {ticker}')
378
+ return None, None
379
+
380
+ ua_ticker_historical_data.columns = ['PriceChange', 'MinPrice', 'MaxPrice', 'YesterdayPrice', 'FirstPrice', 'Last', 'ID',
381
+ 'InstrumentCode', 'Date', 'H', 'ClosePrice', 'I', 'Y', 'LastPrice', 'Quantity', 'Volume', 'Value']
382
+ ua_ticker_historical_data = ua_ticker_historical_data.loc[:, ['Date', 'Quantity', 'Volume', 'Value', 'MinPrice', 'MaxPrice',
383
+ 'FirstPrice', 'LastPrice', 'ClosePrice']]
384
+ ua_ticker_historical_data['Date'] = pd.to_datetime(ua_ticker_historical_data['Date'].astype(str), format='%Y%m%d')
385
+ ua_ticker_historical_data['Date'] = ua_ticker_historical_data['Date'].dt.date
386
+ ua_ticker_historical_data['Date'] = \
387
+ ua_ticker_historical_data['Date'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
388
+
389
+ if start_date is not None and end_date is not None:
390
+ start_date_year, start_date_month, start_date_day = [int(date) for date in start_date.split('-')]
391
+ start_date = jd.date(start_date_year, start_date_month, start_date_day)
392
+
393
+ end_date_year, end_date_month, end_date_day = [int(date) for date in end_date.split('-')]
394
+ end_date = jd.date(end_date_year, end_date_month, end_date_day)
395
+
396
+ ticker_historical_data = ticker_historical_data[(ticker_historical_data['Date'] >= start_date) & (ticker_historical_data['Date'] <= end_date)]
397
+ ua_ticker_historical_data = ua_ticker_historical_data[(ua_ticker_historical_data['Date'] >= start_date) & (ua_ticker_historical_data['Date'] <= end_date)]
398
+
399
+ ticker_historical_data.reset_index(inplace=True, drop=True)
400
+ ua_ticker_historical_data.reset_index(inplace=True, drop=True)
401
+
402
+ return ticker_historical_data, ua_ticker_historical_data
403
+
404
+
405
+ def download_chain_contracts(underlying_ticker: str, j_date: str = True, bsm: bool = True, greeks: bool = False,
406
+ implied_volatility: bool = False) -> pd.DataFrame:
407
+ try:
408
+ url = "https://cdn.tsetmc.com/api/Instrument/GetInstrumentOptionMarketWatch/0"
409
+ response = requests.get(url)
410
+ response.raise_for_status()
411
+
412
+ data = response.json()
413
+ all_options_market_watch = pd.json_normalize(data["instrumentOptMarketWatch"])
414
+
415
+ all_options_market_watch.columns = ['InstrumentCode-P', 'InstrumentCode-C', 'ContractSize', 'InstrumentCode-UA',
416
+ 'Ticker-P', 'Name-P', 'Volume-P', 'Quantity-P', 'Value-P', 'NotionalValue-P',
417
+ 'ClosePrice-P', 'YesterdayPrice-P', 'OpenPositions-P', 'LastPrice-P',
418
+ 'Ticker-UA', 'ClosePrice-UA', 'LastPrice-UA', 'YesterdayPrice-UA',
419
+ 'BeginDate', 'EndDate', 'StrikePrice', 'DaysToMaturity', 'LastPrice-C', 'OpenPositions-C',
420
+ 'ClosePrice-C', 'YesterdayPrice-C', 'NotionalValue-C', 'Value-C', 'Quantity-C',
421
+ 'Volume-C', 'Name-C', 'Ticker-C', 'BidPrice-P', 'BidVolume-P', 'AskPrice-P',
422
+ 'AskVolume-P', 'BidPrice-C', 'BidVolume-C', 'AskPrice-C', 'AskVolume-C',
423
+ 'YesterdayOpenPositions-C', 'YesterdayOpenPositions-P']
424
+ chain_contracts = all_options_market_watch[all_options_market_watch['Ticker-UA'] == underlying_ticker].copy()
425
+
426
+ chain_contracts['URL-P'] = chain_contracts['InstrumentCode-P'].apply(lambda ins_code: f'https://tsetmc.ir/instInfo/{ins_code}')
427
+ chain_contracts['URL-C'] = chain_contracts['InstrumentCode-C'].apply(lambda ins_code: f'https://tsetmc.ir/instInfo/{ins_code}')
428
+ chain_contracts['URL-UA'] = chain_contracts['InstrumentCode-UA'].apply(lambda ins_code: f'https://tsetmc.ir/instInfo/{ins_code}')
429
+
430
+ chain_contracts['BeginDate'] = pd.to_datetime(chain_contracts['BeginDate'].astype(str), format='%Y%m%d')
431
+ chain_contracts['BeginDate'] = chain_contracts['BeginDate'].dt.date
432
+ chain_contracts['EndDate'] = pd.to_datetime(chain_contracts['EndDate'].astype(str), format='%Y%m%d')
433
+ chain_contracts['EndDate'] = chain_contracts['EndDate'].dt.date
434
+
435
+ if j_date:
436
+ chain_contracts['BeginDate'] = \
437
+ chain_contracts['BeginDate'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
438
+ chain_contracts['EndDate'] = \
439
+ chain_contracts['EndDate'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
440
+
441
+ conditions = [chain_contracts['LastPrice-UA'] > chain_contracts['StrikePrice'],
442
+ chain_contracts['LastPrice-UA'] < chain_contracts['StrikePrice']]
443
+ choices = ['ITM', 'OTM']
444
+ default = 'ATM'
445
+ chain_contracts['Status-C'] = np.select(conditions, choices, default=default)
446
+
447
+ conditions = [chain_contracts['LastPrice-UA'] > chain_contracts['StrikePrice'],
448
+ chain_contracts['LastPrice-UA'] < chain_contracts['StrikePrice']]
449
+ choices = ['OTM', 'ITM']
450
+ default = 'ATM'
451
+ chain_contracts['Status-P'] = np.select(conditions, choices, default=default)
452
+
453
+ call_chain_contracts_columns = ['Ticker-C', 'Name-C', 'InstrumentCode-C', 'Status-C', 'Volume-C', 'Quantity-C', 'Value-C',
454
+ 'NotionalValue-C', 'OpenPositions-C', 'ClosePrice-C', 'LastPrice-C', 'YesterdayPrice-C',
455
+ 'BidPrice-C', 'BidVolume-C', 'AskPrice-C', 'AskVolume-C', 'URL-C']
456
+ ua_chain_contracts_columns = ['ContractSize', 'StrikePrice', 'DaysToMaturity', 'BeginDate', 'EndDate',
457
+ 'Ticker-UA', 'InstrumentCode-UA', 'ClosePrice-UA', 'LastPrice-UA',
458
+ 'YesterdayPrice-UA', 'URL-UA']
459
+ put_chain_contracts_columns = ['Ticker-P', 'Name-P', 'InstrumentCode-P', 'Status-P', 'Volume-P', 'Quantity-P', 'Value-P',
460
+ 'NotionalValue-P', 'OpenPositions-P', 'ClosePrice-P', 'LastPrice-P', 'YesterdayPrice-P',
461
+ 'BidPrice-P', 'BidVolume-P', 'AskPrice-P', 'AskVolume-P', 'URL-P']
462
+
463
+
464
+ if bsm or greeks or implied_volatility:
465
+ uas_historical_data = {}
466
+ print(f'Downloading {underlying_ticker} historical data...')
467
+ ua_historical_data = download_ua_historical_data(ticker=underlying_ticker).loc[:, ['Date', 'ClosePrice']].copy()
468
+ print(f'Historical data downloaded for {underlying_ticker}.')
469
+
470
+ chain_contracts['HistoricalData-UA'] = None
471
+ for index, row in chain_contracts.iterrows():
472
+ chain_contracts.at[index, 'HistoricalData-UA'] = ua_historical_data.copy()
473
+ ua_chain_contracts_columns.append('HistoricalData-UA')
474
+
475
+ call_chain_contracts = chain_contracts.loc[:, call_chain_contracts_columns + ua_chain_contracts_columns].copy()
476
+ put_chain_contracts = chain_contracts.loc[:, put_chain_contracts_columns + ua_chain_contracts_columns].copy()
477
+ call_chain_contracts['Type'] = 'Call'
478
+ call_chain_contracts.columns = call_chain_contracts.columns.map(lambda column: column.replace('-C', ''))
479
+ call_chain_contracts_columns.append('Type')
480
+ put_chain_contracts['Type'] = 'Put'
481
+ put_chain_contracts.columns = put_chain_contracts.columns.map(lambda column: column.replace('-P', ''))
482
+ put_chain_contracts_columns.append('Type')
483
+
484
+ risk_free_rate = get_risk_free_rate()
485
+ call_chain_contracts['RiskFreeRate'] = risk_free_rate
486
+ put_chain_contracts['RiskFreeRate'] = risk_free_rate
487
+ ua_chain_contracts_columns.append('RiskFreeRate')
488
+
489
+ if bsm:
490
+ call_chain_contracts[['Volatility', 'BSMPrice']] = call_chain_contracts.apply(calculate_row_wise_bsm, axis=1)
491
+ call_chain_contracts_columns.append(['Volatility', 'BSMPrice'])
492
+ put_chain_contracts[['Volatility', 'BSMPrice']] = put_chain_contracts.apply(calculate_row_wise_bsm, axis=1)
493
+ put_chain_contracts_columns.append(['Volatility', 'BSMPrice'])
494
+
495
+ if greeks:
496
+ call_chain_contracts[['Delta', 'Gamma', 'Theta', 'Vega', 'Rho']] = call_chain_contracts.apply(calculate_row_wise_greeks, axis=1)
497
+ call_chain_contracts_columns.append(['Delta', 'Gamma', 'Theta', 'Vega', 'Rho'])
498
+ put_chain_contracts[['Delta', 'Gamma', 'Theta', 'Vega', 'Rho']] = put_chain_contracts.apply(calculate_row_wise_greeks, axis=1)
499
+ put_chain_contracts_columns.append(['Delta', 'Gamma', 'Theta', 'Vega', 'Rho'])
500
+
501
+ if implied_volatility:
502
+ call_chain_contracts['ImpliedVolatility'] = call_chain_contracts.apply(calculate_row_wise_implied_volatility, axis=1)
503
+ call_chain_contracts_columns.append(['ImpliedVolatility'])
504
+ put_chain_contracts['ImpliedVolatility'] = put_chain_contracts.apply(calculate_row_wise_implied_volatility, axis=1)
505
+ put_chain_contracts_columns.append(['ImpliedVolatility'])
506
+
507
+ call_chain_contracts.drop('HistoricalData-UA', inplace=True, axis=1)
508
+ put_chain_contracts.drop('HistoricalData-UA', inplace=True, axis=1)
509
+ ua_chain_contracts_columns.remove('HistoricalData-UA')
510
+
511
+ call_chain_contracts.columns = [f"{column}-C" for column in call_chain_contracts.columns]
512
+ put_chain_contracts.columns = [f"{column}-P" for column in put_chain_contracts.columns]
513
+ chain_contracts = pd.merge(call_chain_contracts, put_chain_contracts,
514
+ left_index=True, right_index=True, how='inner')
515
+ chain_contracts.drop([f"{column}-P" for column in ua_chain_contracts_columns], axis=1, inplace=True)
516
+ chain_contracts.columns = [column.replace('-C', '')
517
+ if column[:-2] in ua_chain_contracts_columns else column for column in chain_contracts.columns]
518
+ chain_contracts.drop(['Type-C', 'Type-P'], axis=1, inplace=True)
519
+ else:
520
+ chain_contracts = chain_contracts.loc[:, call_chain_contracts_columns + ua_chain_contracts_columns + put_chain_contracts_columns]
521
+
522
+ chain_contracts.reset_index(inplace=True, drop=True)
523
+ chain_contracts.sort_values(by=['EndDate', 'StrikePrice'], inplace=True, ignore_index=True)
524
+ return chain_contracts
525
+ except Exception as e:
526
+ print(f"Failed to get the market watch with the error:\n {e}")
527
+ return None
528
+
529
+
530
+ def download_market_watch(market: str = 'All', stack: str = 'Horizontal', j_date: bool = True,
531
+ bsm: str = False, greeks: bool = False, implied_volatility: bool = False) -> pd.DataFrame:
532
+
533
+ if market == 'TSE':
534
+ url = "https://cdn.tsetmc.com/api/Instrument/GetInstrumentOptionMarketWatch/1"
535
+ elif market == 'IFB':
536
+ url = "https://cdn.tsetmc.com/api/Instrument/GetInstrumentOptionMarketWatch/2"
537
+ else: # 'All'
538
+ url = "https://cdn.tsetmc.com/api/Instrument/GetInstrumentOptionMarketWatch/0"
539
+
540
+ try:
541
+ response = requests.get(url)
542
+ response.raise_for_status()
543
+
544
+ data = response.json()
545
+ all_options_market_watch = pd.json_normalize(data["instrumentOptMarketWatch"])
546
+
547
+ all_options_market_watch.columns = ['InstrumentCode-P', 'InstrumentCode-C', 'ContractSize', 'InstrumentCode-UA',
548
+ 'Ticker-P', 'Name-P', 'Volume-P', 'Quantity-P', 'Value-P', 'NotionalValue-P',
549
+ 'ClosePrice-P', 'YesterdayPrice-P', 'OpenPositions-P', 'LastPrice-P',
550
+ 'Ticker-UA', 'ClosePrice-UA', 'LastPrice-UA', 'YesterdayPrice-UA',
551
+ 'BeginDate', 'EndDate', 'StrikePrice', 'DaysToMaturity', 'LastPrice-C', 'OpenPositions-C',
552
+ 'ClosePrice-C', 'YesterdayPrice-C', 'NotionalValue-C', 'Value-C', 'Quantity-C',
553
+ 'Volume-C', 'Name-C', 'Ticker-C', 'BidPrice-P', 'BidVolume-P', 'AskPrice-P',
554
+ 'AskVolume-P', 'BidPrice-C', 'BidVolume-C', 'AskPrice-C', 'AskVolume-C',
555
+ 'YesterdayOpenPositions-C', 'YesterdayOpenPositions-P']
556
+ all_options_market_watch['URL-P'] = all_options_market_watch['InstrumentCode-P'].apply(lambda ins_code: f'https://tsetmc.ir/instInfo/{ins_code}')
557
+ all_options_market_watch['URL-C'] = all_options_market_watch['InstrumentCode-C'].apply(lambda ins_code: f'https://tsetmc.ir/instInfo/{ins_code}')
558
+ all_options_market_watch['URL-UA'] = all_options_market_watch['InstrumentCode-UA'].apply(lambda ins_code: f'https://tsetmc.ir/instInfo/{ins_code}')
559
+
560
+ all_options_market_watch['BeginDate'] = pd.to_datetime(all_options_market_watch['BeginDate'].astype(str), format='%Y%m%d')
561
+ all_options_market_watch['BeginDate'] = all_options_market_watch['BeginDate'].dt.date
562
+ all_options_market_watch['EndDate'] = pd.to_datetime(all_options_market_watch['EndDate'].astype(str), format='%Y%m%d')
563
+ all_options_market_watch['EndDate'] = all_options_market_watch['EndDate'].dt.date
564
+
565
+ if j_date:
566
+ all_options_market_watch['BeginDate'] = \
567
+ all_options_market_watch['BeginDate'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
568
+ all_options_market_watch['EndDate'] = \
569
+ all_options_market_watch['EndDate'].apply(lambda g_date: jd.date.fromgregorian(year=g_date.year, month=g_date.month, day=g_date.day))
570
+
571
+ conditions = [all_options_market_watch['LastPrice-UA'] > all_options_market_watch['StrikePrice'],
572
+ all_options_market_watch['LastPrice-UA'] < all_options_market_watch['StrikePrice']]
573
+ choices = ['ITM', 'OTM']
574
+ default = 'ATM'
575
+ all_options_market_watch['Status-C'] = np.select(conditions, choices, default=default)
576
+
577
+ conditions = [all_options_market_watch['LastPrice-UA'] > all_options_market_watch['StrikePrice'],
578
+ all_options_market_watch['LastPrice-UA'] < all_options_market_watch['StrikePrice']]
579
+ choices = ['OTM', 'ITM']
580
+ default = 'ATM'
581
+ all_options_market_watch['Status-P'] = np.select(conditions, choices, default=default)
582
+
583
+ call_options_market_watch_columns = ['Ticker-C', 'Name-C', 'InstrumentCode-C', 'Status-C', 'Volume-C', 'Quantity-C', 'Value-C',
584
+ 'NotionalValue-C', 'OpenPositions-C', 'ClosePrice-C', 'LastPrice-C', 'YesterdayPrice-C',
585
+ 'BidPrice-C', 'BidVolume-C', 'AskPrice-C', 'AskVolume-C', 'URL-C']
586
+ ua_market_watch_columns = ['ContractSize', 'StrikePrice', 'DaysToMaturity', 'BeginDate', 'EndDate',
587
+ 'Ticker-UA', 'InstrumentCode-UA', 'ClosePrice-UA', 'LastPrice-UA',
588
+ 'YesterdayPrice-UA', 'URL-UA']
589
+ put_options_market_watch_columns = ['Ticker-P', 'Name-P', 'InstrumentCode-P', 'Status-P', 'Volume-P', 'Quantity-P', 'Value-P',
590
+ 'NotionalValue-P', 'OpenPositions-P', 'ClosePrice-P', 'LastPrice-P', 'YesterdayPrice-P',
591
+ 'BidPrice-P', 'BidVolume-P', 'AskPrice-P', 'AskVolume-P', 'URL-P']
592
+
593
+
594
+ if bsm or greeks or implied_volatility:
595
+ uas_historical_data = {}
596
+ all_uas = download_all_underlying_assets(all_options_market_watch.copy(), market=market)
597
+ print('Starting to download historical data for all tickers...')
598
+ for ua in all_uas['Ticker']:
599
+ print(f'Downloading {ua} historical data...')
600
+ uas_historical_data[ua] = download_ua_historical_data(ticker=ua).loc[:, ['Date', 'ClosePrice']].copy()
601
+ print('Historical data downloaded for all underlying assets.')
602
+
603
+ all_options_market_watch['HistoricalData-UA'] = None
604
+ for index, row in all_options_market_watch.iterrows():
605
+ all_options_market_watch.at[index, 'HistoricalData-UA'] = uas_historical_data[row['Ticker-UA']].copy()
606
+ ua_market_watch_columns.append('HistoricalData-UA')
607
+
608
+ call_options_market_watch = all_options_market_watch.loc[:, call_options_market_watch_columns + ua_market_watch_columns].copy()
609
+ put_options_market_watch = all_options_market_watch.loc[:, put_options_market_watch_columns + ua_market_watch_columns].copy()
610
+ call_options_market_watch['Type'] = 'Call'
611
+ call_options_market_watch.columns = call_options_market_watch.columns.map(lambda column: column.replace('-C', ''))
612
+ call_options_market_watch_columns.append('Type')
613
+ put_options_market_watch['Type'] = 'Put'
614
+ put_options_market_watch.columns = put_options_market_watch.columns.map(lambda column: column.replace('-P', ''))
615
+ put_options_market_watch_columns.append('Type')
616
+
617
+ risk_free_rate = get_risk_free_rate()
618
+ call_options_market_watch['RiskFreeRate'] = risk_free_rate
619
+ put_options_market_watch['RiskFreeRate'] = risk_free_rate
620
+ ua_market_watch_columns.append('RiskFreeRate')
621
+
622
+ if bsm:
623
+ call_options_market_watch[['Volatility', 'BSMPrice']] = call_options_market_watch.apply(calculate_row_wise_bsm, axis=1)
624
+ call_options_market_watch_columns.append(['Volatility', 'BSMPrice'])
625
+ put_options_market_watch[['Volatility', 'BSMPrice']] = put_options_market_watch.apply(calculate_row_wise_bsm, axis=1)
626
+ put_options_market_watch_columns.append(['Volatility', 'BSMPrice'])
627
+
628
+ if greeks:
629
+ call_options_market_watch[['Delta', 'Gamma', 'Theta', 'Vega', 'Rho']] = call_options_market_watch.apply(calculate_row_wise_greeks, axis=1)
630
+ call_options_market_watch_columns.append(['Delta', 'Gamma', 'Theta', 'Vega', 'Rho'])
631
+ put_options_market_watch[['Delta', 'Gamma', 'Theta', 'Vega', 'Rho']] = put_options_market_watch.apply(calculate_row_wise_greeks, axis=1)
632
+ put_options_market_watch_columns.append(['Delta', 'Gamma', 'Theta', 'Vega', 'Rho'])
633
+
634
+ if implied_volatility:
635
+ call_options_market_watch['ImpliedVolatility'] = call_options_market_watch.apply(calculate_row_wise_implied_volatility, axis=1)
636
+ call_options_market_watch_columns.append(['ImpliedVolatility'])
637
+ put_options_market_watch['ImpliedVolatility'] = put_options_market_watch.apply(calculate_row_wise_implied_volatility, axis=1)
638
+ put_options_market_watch_columns.append(['ImpliedVolatility'])
639
+
640
+ call_options_market_watch.drop('HistoricalData-UA', inplace=True, axis=1)
641
+ put_options_market_watch.drop('HistoricalData-UA', inplace=True, axis=1)
642
+ ua_market_watch_columns.remove('HistoricalData-UA')
643
+
644
+ if stack == 'Vertical':
645
+ all_options_market_watch = pd.concat([call_options_market_watch, put_options_market_watch], axis=0, ignore_index=True)
646
+ else: # 'Horizontal'
647
+ call_options_market_watch.columns = [f"{column}-C" for column in call_options_market_watch.columns]
648
+ put_options_market_watch.columns = [f"{column}-P" for column in put_options_market_watch.columns]
649
+ all_options_market_watch = pd.merge(call_options_market_watch, put_options_market_watch,
650
+ left_index=True, right_index=True, how='inner')
651
+ all_options_market_watch.drop([f"{column}-P" for column in ua_market_watch_columns], axis=1, inplace=True)
652
+ all_options_market_watch.columns = [column.replace('-C', '')
653
+ if column[:-2] in ua_market_watch_columns else column for column in all_options_market_watch.columns]
654
+ all_options_market_watch.drop(['Type-C', 'Type-P'], axis=1, inplace=True)
655
+ else:
656
+ if stack == 'Vertical':
657
+ call_options_market_watch = all_options_market_watch.loc[:, call_options_market_watch_columns + ua_market_watch_columns].copy()
658
+ put_options_market_watch = all_options_market_watch.loc[:, put_options_market_watch_columns + ua_market_watch_columns].copy()
659
+ call_options_market_watch['Type'] = 'Call'
660
+ call_options_market_watch.columns = call_options_market_watch.columns.map(lambda column: column.replace('-C', ''))
661
+ call_options_market_watch_columns.append('Type')
662
+ put_options_market_watch['Type'] = 'Put'
663
+ put_options_market_watch.columns = put_options_market_watch.columns.map(lambda column: column.replace('-P', ''))
664
+ put_options_market_watch_columns.append('Type')
665
+ all_options_market_watch = pd.concat([call_options_market_watch, put_options_market_watch], axis=0, ignore_index=True)
666
+ else: # Horizontal
667
+ all_options_market_watch = all_options_market_watch.loc[:, call_options_market_watch_columns + ua_market_watch_columns + put_options_market_watch_columns]
668
+
669
+ all_options_market_watch.reset_index(inplace=True, drop=True)
670
+ all_options_market_watch.sort_values(by=['Ticker-UA', 'EndDate', 'StrikePrice'], inplace=True, ignore_index=True)
671
+ return all_options_market_watch
672
+
673
+ except Exception as e:
674
+ print(f"Failed to get the market watch with the error:\n {e}")
675
+ return None
fima/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ from .Options import (download_chain_contracts, get_greeks, get_implied_volatility, black_scholes_merton,
2
+ download_market_watch, download_all_underlying_assets, download_historical_data, ticker_info,
3
+ calculate_delta, calculate_vega, calculate_theta, calculate_gamma, calculate_rho, calculate_d1,
4
+ calculate_d2, calculate_black_scholes_merton, download_ua_historical_data, calculate_volatility)
5
+ from .Bonds import get_risk_free_rate, get_all_bonds_without_coupons, get_all_bonds_with_coupons
6
+
7
+
8
+ # __all__ = ["download_chain_contracts", "download_market_watch", "download_historical_data", "get_greeks",
9
+ # "get_implied_volatility", "black_scholes_merton", "get_risk_free_rate", "get_all_bonds_without_coupons", "get_all_bonds_with_coupons"]
@@ -0,0 +1,270 @@
1
+ Metadata-Version: 2.4
2
+ Name: fima
3
+ Version: 0.1.0
4
+ Summary: fima – A Python package for scraping and analyzing Iran’s financial market data. It currently supports bonds and options, with tools for calculating BSM prices, greeks, and implied volatility. Future versions will expand to include other instruments such as commodities and more.
5
+ Home-page: https://github.com/hemoboghosian/fima
6
+ Author: Hemo Boghosian
7
+ Author-email: Hemo Boghosian <HemoBoghosian@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/hemoboghosian/fima
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: pandas
14
+ Requires-Dist: requests
15
+ Requires-Dist: jdatetime
16
+ Requires-Dist: numpy
17
+ Requires-Dist: scipy
18
+ Requires-Dist: bs4
19
+ Requires-Dist: selenium
20
+ Requires-Dist: mibian
21
+ Requires-Dist: lxml
22
+ Dynamic: author
23
+ Dynamic: home-page
24
+ Dynamic: license-file
25
+ Dynamic: requires-python
26
+
27
+ # fima
28
+ ## معرفی و هدف
29
+
30
+
31
+ این کتابخانه جهت دسترسی آسان و سریع به داده‌های بازارهای مالی ایران با زبان برنامه‌نویسی پایتون پیاده‌سازی شده است. هدف نهایی این پروژه، ساخت کتابخانه‌ای جامع جهت استخراج، ساختاردهی و تحلیل داده‌های مالی از منابع مختلف موجود است.
32
+ **این کتابخانه هنوز در حال توسعه و تکامل است و ممکن است در برخی قسمت‌ها اشتباهاتی داشته باشد. لطفاً اگر خطا یا نقصی دیدید اطلاع دهید.**
33
+
34
+ ## اطلاعات تماس
35
+ * ایمیل: HemoBoghosian@gmail.com
36
+ * تلگرام: @HemoBoghosian
37
+
38
+ ## نصب کتابخانه
39
+ ```
40
+ pip install fima
41
+ ```
42
+
43
+ ## توابع مربوط به اختیارهای معامله
44
+ ### فراخوانی تمام توابع
45
+
46
+ ```python
47
+ from fima.Options import *
48
+ ```
49
+
50
+ ### دریافت سابقه قیمت اختیار معامله و دارایی پایه آن
51
+
52
+ ```python
53
+ from fima.Options import download_historical_data
54
+
55
+ option_historical_data, ua_historical_data = download_historical_data(ticker='ضهرم4018', start_date='1404-01-01',
56
+ end_date='1404-01-31')
57
+ ```
58
+ #### ورودی
59
+ * ticker: نماد اختیار معامله
60
+ * start_date: تاریخ شروع داده‌ها
61
+ * end_date: تاریخ پایان داده‌ها
62
+
63
+ #### خروجی
64
+ * option_historical_data: سابقه قیمت اختیار معامله
65
+ * ua_historical_data: سابقه قیمت دارایی پایه نماد وارد شده
66
+
67
+ ##### توجه: در صورت وارد نکردن تاریخ شروع و پایان، خروجی تابع تمام سابقه قیمت اختیار معامله و دارایی پایه آن خواهد بود.
68
+
69
+ ### دریافت اطلاعات لحظه‌ای یک نماد اختیار معامله
70
+
71
+ ```python
72
+ from fima.Options import ticker_info
73
+
74
+ ticker_info_data = ticker_info(ticker='ضهرم4018', j_date=True)
75
+ ```
76
+
77
+ #### ورود
78
+ * ticker: نماد اختیار معامله مد نظرم
79
+ * j_date: تاریخ شمسی
80
+
81
+ #### خروجی
82
+ * ticker_info_data: اطلاعات لحظه‌ای مربوط به نماد مد نظر
83
+
84
+ ### دریافت زنجیره قراردادهای یک دارایی پایه
85
+
86
+ ```python
87
+ from fima.Options import download_chain_contracts
88
+
89
+ chain_contracts = download_chain_contracts(underlying_ticker='اهرم', j_date=True, bsm=False, greeks=False,
90
+ implied_volatility=False)
91
+ ```
92
+ #### ورودی
93
+ * underlying_ticker: نماد دارایی پایه
94
+ * j_date: تاریخ شمسی
95
+ * bsm: محاسبه ارزش اختیار معامله با مدل بلک، شولز و مرتون
96
+ * greeks: محاسبه دلتا، تتا، گاما، رو و وگا
97
+ * implied_volatility: محاسبه نوسانات ضمنی
98
+
99
+ #### خروجی
100
+ * chain_contracts: اطلاعات تمامی زنجیره قراردادهای مربوط به دارایی پایه مد نظر
101
+
102
+ ##### توجه: برای محاسبات مربوط به مدل بلک، شولز و مرتون از میانگین نرخ بازده تا سررسید اسناد خزانه در آخرین تاریخ (دریافت‌شده از سایت فرابورس ایران) به عنوان نرخ بازده بدون ریسک استفاده می‌شود.
103
+
104
+ ### دریافت دیده‌بان بازار
105
+
106
+ ```python
107
+ from fima.Options import download_market_watch
108
+
109
+ market_watch = download_market_watch(market='All', stack='Vertical', j_date=True, bsm=False, greeks=False,
110
+ implied_volatility=False)
111
+ ```
112
+ #### ورودی
113
+ * market: بازار معامله اختیار معامله
114
+ * 'TSE': بورس اوراق بهادار تهران
115
+ * 'IFB': فرابورس ایران
116
+ * 'All': تمامی اختیارهای معامله
117
+ * stack: نحوه چینش داده‌ها
118
+ * 'Vertical': چینش عمودی داده‌ها
119
+ * 'Horizontal': چینش افقی داده‌ها
120
+ * j_date: تاریخ شمسی
121
+ * bsm: محاسبه ارزش اختیار معامله با مدل بلک، شولز و مرتون
122
+ * greeks: محاسبه دلتا، تتا، گاما، رو و وگا
123
+ * implied_volatility: محاسبه نوسانات ضمنی
124
+
125
+ #### خروجی
126
+ * market_watch: اطلاعات تمامی اختیارهای معامله بازار مد نظر
127
+
128
+ ##### توجه: برای محاسبات مربوط به مدل بلک، شولز و مرتون از میانگین نرخ بازده تا سررسید اسناد خزانه در آخرین تاریخ (دریافت‌شده از سایت فرابورس ایران) به عنوان نرخ بازده بدون ریسک استفاده می‌شود.
129
+
130
+ ### دریافت ارزش محاسبه‌شده توسط مدل بلک، شولز و مرتون برای یک نماد
131
+
132
+ ```python
133
+ from fima.Options import black_scholes_merton
134
+
135
+ volatility, bsm_price = black_scholes_merton(ticker='ضهرم4018')
136
+ ```
137
+ #### ورودی
138
+ * ticker: نماد اختیار معامله مد نظر
139
+
140
+ #### خروجی
141
+ * volatility: نوسانات دارایی پایه محاسبه‌شده در مدل
142
+ * bsm_price: ارزش محاسبه‌شده اختیار معامله مد نظر با مدل بلک، شولز و مرتون
143
+
144
+ ##### توجه
145
+ * توجه: در صورت عدم وارد کردن متغیر مربوط به نرخ بازده بدون ریسک، برای محاسبات مربوط به مدل بلک، شولز و مرتون از میانگین نرخ بازده تا سررسید اسناد خزانه در آخرین تاریخ (دریافت‌شده از سایت فرابورس ایران) به عنوان نرخ بازده بدون ریسک استفاده می‌شود.
146
+ * در صورتی که روزهای باقی‌مانده تا سررسید اختیار معامله کمتر از ۱۰ روز باشند، به دلیل احتمال خطا، مدل بلک، شولز و مرتون اعمال نخواهد شد.
147
+
148
+ ### دریافت گریکز برای یک نماد
149
+
150
+ ```python
151
+ from fima.Options import get_greeks
152
+
153
+ ticker_greeks = get_greeks(ticker='ضهرم4018')
154
+ ```
155
+ #### ورودی
156
+ * ticker: نماد اختیار معامله مد نظر
157
+ * r_f: نرخ بازده بدون ریسک
158
+ * minimum_required_history: کمینه دیتای مورد نیاز برای دارایی پایهجهت محاسبه نوسانات که به صورت پیش‌فرض ۳۰ روز در نظر گرفته شده است.
159
+
160
+ #### خروجی
161
+ * ticker_greeks: دلتا، تتا، گاما، وگا و رو برای نماد مد نظر
162
+
163
+ ##### توجه
164
+ * در صورت عدم وارد کردن متغیر مربوط به نرخ بازده بدون ریسک، برای محاسبات مربوط به مدل بلک، شولز و مرتون از میانگین نرخ بازده تا سررسید اسناد خزانه در آخرین تاریخ (دریافت‌شده از سایت فرابورس ایران) به عنوان نرخ بازده بدون ریسک استفاده می‌شود.
165
+ * در صورتی که روزهای باقی‌مانده تا سررسید اختیار معامله کمتر از ۱۰ روز باشند، به دلیل احتمال خطا، مدل بلک، شولز و مرتون اعمال نخواهد شد.
166
+
167
+ ### دریافت نوسانات ضمنی برای یک نماد
168
+
169
+ ```python
170
+ from fima.Options import get_implied_volatility
171
+
172
+ ticker_implied_volatility = get_implied_volatility(ticker='ضهرم4018')
173
+ ```
174
+ #### ورودی
175
+ * ticker: نماد اختیار معامله مد نظر
176
+ * r_f: نرخ بازده بدون ریسک
177
+ * minimum_required_history: کمینه دیتای مورد نیاز برای دارایی پایهجهت محاسبه نوسانات که به صورت پیش‌فرض ۳۰ روز در نظر گرفته شده است.
178
+
179
+ #### خروجی
180
+ * ticker_implied_volatility: نوسانات ضمنی برای نماد مد نظر
181
+
182
+ ##### توجه
183
+ * در صورت عدم وارد کردن متغیر مربوط به نرخ بازده بدون ریسک، برای محاسبات مربوط به مدل بلک، شولز و مرتون از میانگین نرخ بازده تا سررسید اسناد خزانه در آخرین تاریخ (دریافت‌شده از سایت فرابورس ایران) به عنوان نرخ بازده بدون ریسک استفاده می‌شود.
184
+ * در صورتی که روزهای باقی‌مانده تا سررسید اختیار معامله کمتر از ۱۰ روز باشند، به دلیل احتمال خطا، مدل بلک، شولز و مرتون اعمال نخواهد شد.
185
+
186
+ ### دریافت تمامی نمادهای دارای اختیار معامله
187
+
188
+ ```python
189
+ from fima.Options import download_all_underlying_assets
190
+
191
+ all_underlying_assets = download_all_underlying_assets(market='All')
192
+ ```
193
+ #### ورودی
194
+ * market: بازار معامله اختیار معامله
195
+ * 'TSE': بورس اوراق بهادار تهران
196
+ * 'IFB': فرابورس ایران
197
+ * 'All': تمامی اختیارهای معامله (حالت پیش‌فرض)
198
+
199
+ #### خروجی
200
+ * all_underlying_assets: اطلاعات مربوط به نمادهای دارای اختیار معامله
201
+
202
+ #### توجه: در صورت عدم وارد کردن market، تمامی اختیارهای معامله دریافت خواهد شد.
203
+
204
+ ### توابع مربوط به محاسبات
205
+
206
+ ```python
207
+ from fima.Options import (calculate_black_scholes_merton, calculate_delta, calculate_vega,
208
+ calculate_theta, calculate_gamma, calculate_rho)
209
+
210
+ bsm_price = calculate_black_scholes_merton(s=550, k=28000, option_type='Call', t=71, sigma=0.9588, r_f=0.3372)
211
+ delta = calculate_delta(s=550, k=28000, option_type='Call', t=71, sigma=0.9588, r_f=0.3372)
212
+ theta = calculate_theta(s=550, k=28000, option_type='Call', t=71, sigma=0.9588, r_f=0.3372)
213
+ gamma = calculate_gamma(s=550, k=28000, t=71, sigma=0.9588, r_f=0.3372)
214
+ rho = calculate_rho(s=550, k=28000, option_type='Call', t=71, sigma=0.9588, r_f=0.3372)
215
+ vega = calculate_vega(s=550, k=28000, t=71, sigma=0.9588, r_f=0.3372)
216
+ ```
217
+
218
+ ## توابع مربوط به اوراق درآمد ثابت
219
+ ### فراخوانی تمام توابع
220
+
221
+ ```python
222
+ from fima.Bonds import *
223
+ ```
224
+
225
+ ### دریافت سابقه قیمت اختیار معامله و دارایی پایه آن
226
+
227
+ ```python
228
+ from fima.Bonds import get_risk_free_rate
229
+
230
+ risk_free_rate = get_risk_free_rate()
231
+ ```
232
+
233
+ #### خروجی
234
+ * risk_free_rate: میانگین نرخ بازده تا سررسید اسناد خزانه در آخرین تاریخ (دریافت‌شده از سایت فرابورس ایران)
235
+
236
+ ### دریافت اوراق درآمد ثابت بدون کوپن
237
+
238
+ ```python
239
+ from fima.Bonds import get_all_bonds_without_coupons
240
+
241
+ all_bonds_without_coupons = get_all_bonds_without_coupons(deprecated=True)
242
+ ```
243
+
244
+ #### ورودی
245
+ * deprecated:
246
+ * True: اوراق سررسید شده نیز در خروجی بیاید
247
+ * False: اوراق سررسید شده در خروجی نیاید
248
+ #### خروجی
249
+ * all_bonds_without_coupons: اطلاعات مربوط به اوراق درآمد ثابت بدون کوپن (دریاقت‌شده از سایت فرابورس ایران)
250
+
251
+ ### دریافت اوراق درآمد ثابت با کوپن
252
+
253
+ ```python
254
+ from fima.Bonds import get_all_bonds_with_coupons
255
+
256
+ all_bonds_with_coupons = get_all_bonds_with_coupons(deprecated=True)
257
+ ```
258
+
259
+ #### ورودی
260
+ * deprecated:
261
+ * True: اوراق سررسید شده نیز در خروجی بیاید
262
+ * False: اوراق سررسید شده در خروجی نیاید
263
+ #### خروجی
264
+ * all_bonds_without_coupons: اطلاعات مربوط به اوراق درآمد ثابت با کوپن (دریاقت‌شده از سایت فرابورس ایران)
265
+
266
+ ## نقشه راه
267
+ 1. [ ] عیب‌یابی و بهبود توابع پیاده‌سازی‌شده
268
+ 2. [ ] دریافت اطلاعات از سایت tgju
269
+ 3. [ ] دریافت اطلاعات از سایت tsetmc
270
+ 4. [ ] دریافت و پاکسازی اطلاعات سایت codal
@@ -0,0 +1,11 @@
1
+ fima/Bonds.py,sha256=u12dNTrPPUPc3cnZ1aERbWhltVry7YrxFeUq8iEC0RE,4580
2
+ fima/Options.py,sha256=CUOulN8OcErTwE4Gyfid6Xct-AsyIQvmeCo_sMlY4og,39999
3
+ fima/__init__.py,sha256=pjSRFxliqrZRva4pjqzMybxZb_3xN4oPcKJWgtt94uc,823
4
+ fima-0.1.0.dist-info/licenses/LICENSE,sha256=l6425dmjnidzk4JQwhT01C8SpICmjSBMNREAdPnPA9E,1087
5
+ test/TestBonds.py,sha256=gO3tUgiOJsEzX5uD1UsKLQze4Zl8_isT6BlbcQuGeAY,1217
6
+ test/TestOptions.py,sha256=nRVR65dsOO7DZfKXWk30jtAn0_aVHzLYVx7e95F5Mbw,8523
7
+ test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ fima-0.1.0.dist-info/METADATA,sha256=-ZmCg0dsRUXzNSql_CO1KPnDmNexg_4Tsy-SmbSeUYo,13351
9
+ fima-0.1.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
10
+ fima-0.1.0.dist-info/top_level.txt,sha256=3iVjx26xETazVXH-Vc_8TGy95qyk4BX05vce6KpYP_E,10
11
+ fima-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.3.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ fima
2
+ test
test/TestBonds.py ADDED
@@ -0,0 +1,26 @@
1
+ import pytest
2
+ from fima.Bonds import get_risk_free_rate, get_all_bonds_without_coupons, get_all_bonds_with_coupons
3
+
4
+
5
+ def test_get_risk_free_rate_range():
6
+ risk_free_rate = get_risk_free_rate()
7
+ assert isinstance(risk_free_rate, float)
8
+ assert 0.25 <= risk_free_rate <= 0.5 # Typical YTM for Iranian T-bills
9
+
10
+
11
+ @pytest.mark.parametrize("deprecated", [True, False])
12
+ def test_get_all_bonds_without_coupons(deprecated):
13
+ all_bonds_without_coupons = get_all_bonds_without_coupons(deprecated=deprecated)
14
+ assert all_bonds_without_coupons is not None
15
+ assert not all_bonds_without_coupons.empty
16
+ for column in ['Ticker', 'LastTradedPrice', 'LastTradedDate', 'MaturityDate', 'YTM', 'SimpleReturn']:
17
+ assert column in all_bonds_without_coupons.columns
18
+
19
+
20
+ @pytest.mark.parametrize("deprecated", [True, False])
21
+ def test_get_all_bonds_with_coupons(deprecated):
22
+ all_bonds_with_coupons = get_all_bonds_with_coupons(deprecated=deprecated)
23
+ assert all_bonds_with_coupons is not None
24
+ assert not all_bonds_with_coupons.empty
25
+ for column in ['Ticker', 'LastTradedPrice', 'LastTradedDate', 'MaturityDate', 'YTM']:
26
+ assert column in all_bonds_with_coupons.columns
test/TestOptions.py ADDED
@@ -0,0 +1,123 @@
1
+ import pandas as pd
2
+ import pytest
3
+ from fima.Options import (get_greeks, get_implied_volatility, black_scholes_merton, download_chain_contracts,
4
+ download_market_watch, download_all_underlying_assets, download_historical_data)
5
+
6
+
7
+ @pytest.mark.parametrize("market", ["All", "IFB", "TSE"])
8
+ def test_download_all_underlying_assets(market):
9
+ all_underlying_assets = download_all_underlying_assets(market=market)
10
+ assert all_underlying_assets is not None
11
+ assert not all_underlying_assets.empty
12
+ assert isinstance(all_underlying_assets, pd.DataFrame)
13
+ for column in ['Ticker', 'InstrumentCode', 'ClosePrice', 'LastPrice', 'YesterdayPrice', 'URL']:
14
+ assert column in all_underlying_assets
15
+
16
+
17
+ @pytest.mark.parametrize(["ticker", "start_date", "end_date"], [("ضهرم4018", '1404-02-01', '1404-02-31'),
18
+ ("طهرم4018", '1404-02-01', '1404-02-31'),
19
+ ("ضهرم4018", None, None),
20
+ ("طهرم4018", None, None)])
21
+ def test_download_historical_data(ticker, start_date, end_date):
22
+ option_historical_data, ua_historical_data = download_historical_data(ticker=ticker, start_date=start_date, end_date=end_date)
23
+ assert option_historical_data is not None
24
+ assert ua_historical_data is not None
25
+ assert not option_historical_data.empty
26
+ assert not ua_historical_data.empty
27
+ assert isinstance(option_historical_data, pd.DataFrame)
28
+ assert isinstance(ua_historical_data, pd.DataFrame)
29
+ for column in ['Date', 'Quantity', 'Volume', 'Value', 'MinPrice', 'MaxPrice', 'FirstPrice', 'LastPrice', 'ClosePrice']:
30
+ assert column in option_historical_data
31
+ for column in ['Date', 'Quantity', 'Volume', 'Value', 'MinPrice', 'MaxPrice', 'FirstPrice', 'LastPrice', 'ClosePrice']:
32
+ assert column in option_historical_data
33
+
34
+
35
+ @pytest.mark.parametrize("ticker", ["ضهرم4018", "ظهرم4018"])
36
+ def test_get_greeks(ticker):
37
+ greeks = get_greeks(ticker)
38
+ assert greeks is not None
39
+ assert isinstance(greeks, pd.Series) # pandas Series
40
+ for greek in ['Delta', 'Gamma', 'Theta', 'Vega', 'Rho']:
41
+ assert greek in greeks
42
+
43
+
44
+ @pytest.mark.parametrize("ticker", ["ضهرم4018", "طهرم4018"])
45
+ def test_black_scholes_merton(ticker):
46
+ volatility, bsm_price = black_scholes_merton(ticker)
47
+ assert all(isinstance(v, float) for v in [volatility, bsm_price])
48
+ assert 0 <= volatility <= 2
49
+ assert isinstance(bsm_price, float)
50
+ assert 0 <= bsm_price
51
+ assert all(v is not None for v in [volatility, bsm_price])
52
+
53
+
54
+ @pytest.mark.parametrize("ticker", ["ضهرم4018", "طهرم4018"])
55
+ def test_get_implied_volatility(ticker):
56
+ implied_volatility = get_implied_volatility(ticker)
57
+ assert isinstance(implied_volatility, float)
58
+ assert 0 <= implied_volatility <= 2
59
+
60
+
61
+ @pytest.mark.parametrize(["underlying_ticker", "j_date", "bsm", "greeks", "implied_volatility"],
62
+ [('اهرم', True, True, True, True), ('اهرم', True, True, True, False),
63
+ ('اهرم', True, True, False, True), ('اهرم', True, True, False, False),
64
+ ('اهرم', True, False, True, True), ('اهرم', True, False, True, False),
65
+ ('اهرم', True, False, False, True), ('اهرم', True, False, False, False),
66
+ ('اهرم', False, True, True, True), ('اهرم', False, True, True, False),
67
+ ('اهرم', False, True, False, True), ('اهرم', False, True, False, False),
68
+ ('اهرم', False, False, True, True), ('اهرم', False, False, True, False),
69
+ ('اهرم', False, False, False, True), ('اهرم', False, False, False, False)])
70
+ def test_download_chain_contracts(underlying_ticker, j_date, bsm, greeks, implied_volatility):
71
+ chain_contracts = download_chain_contracts(underlying_ticker=underlying_ticker, j_date=j_date, bsm=bsm,
72
+ greeks=greeks, implied_volatility=implied_volatility)
73
+ assert chain_contracts is not None
74
+ assert not chain_contracts.empty
75
+ assert isinstance(chain_contracts, pd.DataFrame)
76
+
77
+ if bsm:
78
+ assert all(column in chain_contracts.columns for column in ['BSMPrice-C', 'BSMPrice-P', 'Volatility-C', 'Volatility-P']), "Horizontal, BSM"
79
+ if greeks:
80
+ assert all(column in chain_contracts.columns for column in ['Delta-C', 'Gamma-C', 'Theta-C', 'Vega-C', 'Rho-C',
81
+ 'Delta-P', 'Gamma-P', 'Theta-P', 'Vega-P', 'Rho-P']), "Horizontal, Greeks"
82
+ if implied_volatility:
83
+ assert all(column in chain_contracts.columns for column in ['ImpliedVolatility-C', 'ImpliedVolatility-P']), "Horizontal, IV"
84
+
85
+
86
+ @pytest.mark.parametrize(["market", "stack", "j_date", "bsm", "greeks", "implied_volatility"],
87
+ [('TSE', 'Horizontal', False, False, False, False), ('TSE', 'Vertical', False, False, False, False),
88
+ ('TSE', 'Horizontal', False, False, False, True), ('TSE', 'Vertical', False, False, False, True),
89
+ ('TSE', 'Horizontal', True, True, True, True), ('TSE', 'Horizontal', True, True, True, False),
90
+ ('TSE', 'Horizontal', True, True, False, True), ('TSE', 'Horizontal', True, True, False, False),
91
+ ('TSE', 'Horizontal', True, False, True, True), ('TSE', 'Horizontal', True, False, True, False),
92
+ ('TSE', 'Horizontal', True, False, False, True), ('TSE', 'Horizontal', True, False, False, False),
93
+ ('TSE', 'Horizontal', False, True, True, True), ('TSE', 'Horizontal', False, True, True, False),
94
+ ('TSE', 'Horizontal', False, True, False, True), ('TSE', 'Horizontal', False, True, False, False),
95
+ ('TSE', 'Horizontal', False, False, True, True), ('TSE', 'Horizontal', False, False, True, False),
96
+ ('TSE', 'Vertical', True, True, True, True), ('TSE', 'Vertical', True, True, True, False),
97
+ ('TSE', 'Vertical', True, True, False, True), ('TSE', 'Vertical', True, True, False, False),
98
+ ('TSE', 'Vertical', True, False, True, True), ('TSE', 'Vertical', True, False, True, False),
99
+ ('TSE', 'Vertical', True, False, False, True), ('TSE', 'Vertical', True, False, False, False),
100
+ ('TSE', 'Vertical', False, True, True, True), ('TSE', 'Vertical', False, True, True, False),
101
+ ('TSE', 'Vertical', False, True, False, True), ('TSE', 'Vertical', False, True, False, False),
102
+ ('TSE', 'Vertical', False, False, True, True), ('TSE', 'Vertical', False, False, True, False)])
103
+ def test_download_market_watch(market, stack, j_date, bsm, greeks, implied_volatility):
104
+ market_watch = download_market_watch(market=market, j_date=j_date, bsm=bsm, greeks=greeks, implied_volatility=implied_volatility)
105
+ assert market_watch is not None
106
+ assert not market_watch.empty
107
+ assert isinstance(market_watch, pd.DataFrame)
108
+
109
+ if stack == 'Horizontal':
110
+ if bsm:
111
+ assert all(column in market_watch.columns for column in ['BSMPrice-C', 'BSMPrice-P', 'Volatility-C', 'Volatility-P']), "Horizontal, BSM"
112
+ if greeks:
113
+ assert all(column in market_watch.columns for column in ['Delta-C', 'Gamma-C', 'Theta-C', 'Vega-C', 'Rho-C',
114
+ 'Delta-P', 'Gamma-P', 'Theta-P', 'Vega-P', 'Rho-P']), "Horizontal, Greeks"
115
+ if implied_volatility:
116
+ assert all(column in market_watch.columns for column in ['ImpliedVolatility-C', 'ImpliedVolatility-P']), "Horizontal, IV"
117
+ if stack == 'Vertical':
118
+ if bsm:
119
+ assert all(column in market_watch.columns for column in ['BSMPrice', 'Volatility']), "Vertical, BSM"
120
+ if greeks:
121
+ assert all(column in market_watch.columns for column in ['Delta', 'Gamma', 'Theta', 'Vega', 'Rho']), "Vertical, Greeks"
122
+ if implied_volatility:
123
+ assert all(column in market_watch.columns for column in ['ImpliedVolatility']), "Vertical, IV"
test/__init__.py ADDED
File without changes