quantmod 0.0.1__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.
- quantmod/__init__.py +43 -0
- quantmod/_version.py +683 -0
- quantmod/datasets/__init__.py +24 -0
- quantmod/datasets/dataloader.py +61 -0
- quantmod/derivatives/__init__.py +23 -0
- quantmod/derivatives/options.py +47 -0
- quantmod/indicators/__init__.py +23 -0
- quantmod/indicators/indicators.py +51 -0
- quantmod/main.py +3 -0
- quantmod/markets/__init__.py +24 -0
- quantmod/markets/bb.py +48 -0
- quantmod/markets/yahoo.py +141 -0
- quantmod/models/__init__.py +28 -0
- quantmod/models/blackscholes.py +156 -0
- quantmod/models/montecarlo.py +115 -0
- quantmod/models/optioninputs.py +44 -0
- quantmod/risk/__init__.py +27 -0
- quantmod/risk/riskinputs.py +36 -0
- quantmod/risk/var.py +103 -0
- quantmod/risk/varbacktester.py +85 -0
- quantmod/timeseries/__init__.py +93 -0
- quantmod/timeseries/performance.py +237 -0
- quantmod/timeseries/timeseries.py +134 -0
- quantmod/utils.py +26 -0
- quantmod-0.0.1.dist-info/LICENSE.txt +203 -0
- quantmod-0.0.1.dist-info/METADATA +93 -0
- quantmod-0.0.1.dist-info/RECORD +30 -0
- quantmod-0.0.1.dist-info/WHEEL +5 -0
- quantmod-0.0.1.dist-info/entry_points.txt +2 -0
- quantmod-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Quantmod Python Package
|
|
2
|
+
# https://kannansingaravelu.com/
|
|
3
|
+
|
|
4
|
+
# Copyright 2024 Kannan Singaravelu
|
|
5
|
+
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from .dataloader import load_historical_data
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"load_historical_data",
|
|
24
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from quantmod.utils import convert_date_format
|
|
3
|
+
|
|
4
|
+
def fetch_historical_data(symbol: str, start_date: str=None, end_date: str=None) -> pd.DataFrame:
|
|
5
|
+
"""
|
|
6
|
+
Load historical data for a given symbol
|
|
7
|
+
|
|
8
|
+
Parameters
|
|
9
|
+
----------
|
|
10
|
+
symbol : str
|
|
11
|
+
The symbol to load data for, either 'spx' or 'nifty'
|
|
12
|
+
start_date : str, optional
|
|
13
|
+
The start date to load data for, in 'yyyy-mm-dd' format, by default None
|
|
14
|
+
end_date : str, optional
|
|
15
|
+
The end date to load data for, in 'yyyy-mm-dd' format, by default None
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
pd.DataFrame
|
|
20
|
+
The historical data for the given symbol
|
|
21
|
+
|
|
22
|
+
Raises
|
|
23
|
+
------
|
|
24
|
+
ValueError
|
|
25
|
+
If the symbol is invalid or the start date is greater than the end date
|
|
26
|
+
If the start date is less than the first date in the dataset or the end date is greater than the last date in the dataset
|
|
27
|
+
If the start date is greater than the last date in the dataset or the end date is less than the first date in the dataset
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
if symbol.lower() == "spx":
|
|
31
|
+
df = pd.read_csv("./data/spx.csv")
|
|
32
|
+
elif symbol.lower() == "nifty":
|
|
33
|
+
df = pd.read_csv("./data/nifty50.csv")
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError("Invalid symbol. Only SPX & NIFTY is supported")
|
|
36
|
+
|
|
37
|
+
df = df.rename(columns=str.lower)
|
|
38
|
+
df = convert_date_format(df, 'date')
|
|
39
|
+
|
|
40
|
+
if start_date is None or end_date is None:
|
|
41
|
+
return df
|
|
42
|
+
|
|
43
|
+
elif start_date > end_date:
|
|
44
|
+
raise ValueError("Start date should be less than or equal to end date")
|
|
45
|
+
|
|
46
|
+
elif start_date < df.date.iloc[0] or end_date < df.date.iloc[0]:
|
|
47
|
+
raise ValueError(f"Date should be greater than or equal to {df.date.iloc[0]}")
|
|
48
|
+
|
|
49
|
+
elif start_date > df.date.iloc[-1] or end_date > df.date.iloc[-1]:
|
|
50
|
+
raise ValueError(f"Date should be less than or equal to {df.date.iloc[-1]}")
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
# Use query method for filtering based on date range
|
|
54
|
+
query_str = f"date >= '{start_date}' and date <= '{end_date}'"
|
|
55
|
+
return df.query(query_str)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# if __name__ == "__main__":
|
|
59
|
+
# df = fetch_historical_data("nifty")
|
|
60
|
+
# print(df.head())
|
|
61
|
+
# print(df.tail())
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Quantmod Python Package
|
|
2
|
+
# https://kannansingaravelu.com/
|
|
3
|
+
|
|
4
|
+
# Copyright 2024 Kannan Singaravelu
|
|
5
|
+
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from .options import maxpain
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"maxpain",
|
|
23
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Maximum Pain Level
|
|
2
|
+
def maxpain(strike: list, calloi: list, putoi: list) -> float:
|
|
3
|
+
"""
|
|
4
|
+
Calculate option max pain for a given range of strike price, call and put open interest
|
|
5
|
+
Max pain is the (strike) price at which least amount of pain (money is lost) by
|
|
6
|
+
option writers, thereby causing maximum pain to option buyers. This level
|
|
7
|
+
is assumed to be the price at which the market is most likely to expire on
|
|
8
|
+
the (derivatives) contract mautiry date.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
strike : list
|
|
13
|
+
list of strike prices
|
|
14
|
+
calloi : list
|
|
15
|
+
list of call open interest
|
|
16
|
+
putoi : list
|
|
17
|
+
list of put open interst
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
float
|
|
22
|
+
maximum pain strike level
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
nrows = len(strike)
|
|
26
|
+
cvalue = [0]*nrows
|
|
27
|
+
pvalue = [0]*nrows
|
|
28
|
+
tvalue = [0]*nrows
|
|
29
|
+
|
|
30
|
+
for i in range(nrows-1, 0, -1):
|
|
31
|
+
csum = 0
|
|
32
|
+
for j in range(i):
|
|
33
|
+
csum = csum + (strike[i] - strike[j]) * calloi[j]
|
|
34
|
+
cvalue[i] = csum
|
|
35
|
+
|
|
36
|
+
for i in range(nrows-1):
|
|
37
|
+
psum = 0
|
|
38
|
+
for j in range(i+1,nrows,1):
|
|
39
|
+
psum = psum + (strike[i] - strike[j]) * -1 * putoi[j]
|
|
40
|
+
pvalue[i] = psum
|
|
41
|
+
|
|
42
|
+
for i in range(nrows):
|
|
43
|
+
tvalue[i] = cvalue[i] + pvalue[i]
|
|
44
|
+
|
|
45
|
+
mp = min(tvalue)
|
|
46
|
+
|
|
47
|
+
return strike[tvalue.index(mp)]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Quantmod Python Package
|
|
2
|
+
# https://kannansingaravelu.com/
|
|
3
|
+
|
|
4
|
+
# Copyright 2024 Kannan Singaravelu
|
|
5
|
+
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from .indicators import ATR
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ATR",
|
|
23
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
def ATR(df: pd.DataFrame, lookback: int = 14) -> pd.Series:
|
|
4
|
+
"""
|
|
5
|
+
Calculate the Average True Range (ATR).
|
|
6
|
+
|
|
7
|
+
ATR is a volatility indicator that measures the average of the true range values
|
|
8
|
+
over a specified period. An expanding ATR indicates increased volatility, while
|
|
9
|
+
a low ATR value indicates a series of periods with small price ranges.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
df : pd.DataFrame
|
|
14
|
+
DataFrame with OHLC (Open, High, Low, Close) price data.
|
|
15
|
+
lookback : int, optional
|
|
16
|
+
Number of periods to use for ATR calculation, by default 14.
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
pd.Series
|
|
21
|
+
A pandas Series containing the ATR values for each period.
|
|
22
|
+
|
|
23
|
+
Examples
|
|
24
|
+
--------
|
|
25
|
+
>>> import pandas as pd
|
|
26
|
+
>>> df = pd.DataFrame({
|
|
27
|
+
... 'Open': [10, 11, 12],
|
|
28
|
+
... 'High': [12, 13, 14],
|
|
29
|
+
... 'Low': [9, 10, 11],
|
|
30
|
+
... 'Close': [11, 12, 13]
|
|
31
|
+
... })
|
|
32
|
+
>>> atr = ATR(df, lookback=2)
|
|
33
|
+
>>> print(atr)
|
|
34
|
+
0 NaN
|
|
35
|
+
1 3.000000
|
|
36
|
+
2 2.750000
|
|
37
|
+
dtype: float64
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
df = df.copy()
|
|
41
|
+
|
|
42
|
+
df['H-L']=abs(df['High']-df['Low'])
|
|
43
|
+
df['H-PC']=abs(df['High']-df['Close'].shift(1))
|
|
44
|
+
df['L-PC']=abs(df['Low']-df['Close'].shift(1))
|
|
45
|
+
|
|
46
|
+
df['TR']=df[['H-L','H-PC','L-PC']].max(axis=1,skipna=False)
|
|
47
|
+
df['ATR']=df['TR'].rolling(lookback).mean()
|
|
48
|
+
|
|
49
|
+
data = df.drop(['H-L','H-PC','L-PC'],axis=1) # drop columns
|
|
50
|
+
|
|
51
|
+
return data['ATR']
|
quantmod/main.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Quantmod Python Package
|
|
2
|
+
# https://kannansingaravelu.com/
|
|
3
|
+
|
|
4
|
+
# Copyright 2024 Kannan Singaravelu
|
|
5
|
+
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from .yahoo import getData, getTicker
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"getData",
|
|
23
|
+
"getTicker",
|
|
24
|
+
]
|
quantmod/markets/bb.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# current under testing / development
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from openbb import obb
|
|
6
|
+
from typing import List, Union, Optional, Literal
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def getData(symbol: Union[str, List[str]],
|
|
10
|
+
start_date: Union[datetime.date, None, str]=None ,
|
|
11
|
+
end_date: Union[datetime.date, None, str]=None,
|
|
12
|
+
provider: str = 'yfinance',
|
|
13
|
+
interval: str = '1d') -> pd.DataFrame:
|
|
14
|
+
"""
|
|
15
|
+
Get historical price data for a given stock.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
symbol : Union[str, List[str]]
|
|
20
|
+
Symbol to get data for. Multiple comma separated items allowed for provider(s): fmp, polygon, tiingo, yfinance.
|
|
21
|
+
start_date : Union[date, None, str]
|
|
22
|
+
Start date of the data, in YYYY-MM-DD format.
|
|
23
|
+
end_date : Union[date, None, str]
|
|
24
|
+
End date of the data, in YYYY-MM-DD format.
|
|
25
|
+
provider : str, optional
|
|
26
|
+
The provider to use, by default 'yfinance'. If None, the priority list configured in the settings is used. Default priority: fmp, intrinio, polygon, tiingo, yfinance.
|
|
27
|
+
interval : str, optional
|
|
28
|
+
Time interval of the data to return. Default is '1d'.
|
|
29
|
+
valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1W,1M,1Q
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
pd.DataFrame
|
|
34
|
+
DataFrame with OHLC[A]V (Open, High, Low, Close, Adj Close, Volume).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
output = obb.equity.price.historical(
|
|
38
|
+
symbol,
|
|
39
|
+
start_date=start_date,
|
|
40
|
+
end_date=end_date,
|
|
41
|
+
interval=interval
|
|
42
|
+
).to_df()
|
|
43
|
+
|
|
44
|
+
return output.groupby('column')
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
df = getData(["AAPL","SPY"], interval="1d")
|
|
48
|
+
print(df.head())
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import yfinance as yf
|
|
3
|
+
import joblib
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
from typing import Union, List
|
|
7
|
+
|
|
8
|
+
# # Directory to store cache files
|
|
9
|
+
# CACHE_DIR = '.cache'
|
|
10
|
+
|
|
11
|
+
# def _get_cache_file_name(tickers: Union[str, List[str]], start_date: str, end_date: str, period: str, interval: str) -> str:
|
|
12
|
+
# """
|
|
13
|
+
# Generate a cache file name based on the parameters.
|
|
14
|
+
# """
|
|
15
|
+
# if isinstance(tickers, str):
|
|
16
|
+
# tickers = [tickers]
|
|
17
|
+
# key = f"{','.join(tickers)}_{start_date}_{end_date}_{period}_{interval}"
|
|
18
|
+
# cache_key = hashlib.md5(key.encode()).hexdigest()
|
|
19
|
+
# return os.path.join(CACHE_DIR, f"{cache_key}.pkl")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# def getData(tickers: Union[str, List[str]], start_date: str = None, end_date: str = None, period: str = '1mo', interval: str = '1d') -> pd.DataFrame:
|
|
23
|
+
# """
|
|
24
|
+
# Retrieve data from yfinance library for specified tickers, with caching.
|
|
25
|
+
|
|
26
|
+
# Parameters
|
|
27
|
+
# ----------
|
|
28
|
+
# tickers : str or list
|
|
29
|
+
# Symbol or list of symbols.
|
|
30
|
+
# start_date : str, optional
|
|
31
|
+
# Start date, by default None.
|
|
32
|
+
# end_date : str, optional
|
|
33
|
+
# End date, by default None.
|
|
34
|
+
# period : str, optional
|
|
35
|
+
# Period, by default '1mo'.
|
|
36
|
+
# Valid periods: 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max.
|
|
37
|
+
# interval : str, optional
|
|
38
|
+
# Interval, by default '1d', max 60 days.
|
|
39
|
+
# Valid intervals: 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo.
|
|
40
|
+
|
|
41
|
+
# Returns
|
|
42
|
+
# -------
|
|
43
|
+
# pd.DataFrame
|
|
44
|
+
# DataFrame with OHLC[A]V (Open, High, Low, Close, Adj Close, Volume).
|
|
45
|
+
# """
|
|
46
|
+
# # Ensure cache directory exists
|
|
47
|
+
# if not os.path.exists(CACHE_DIR):
|
|
48
|
+
# os.makedirs(CACHE_DIR)
|
|
49
|
+
|
|
50
|
+
# cache_file = _get_cache_file_name(tickers, start_date, end_date, period, interval)
|
|
51
|
+
|
|
52
|
+
# # Check if cached file exists
|
|
53
|
+
# if os.path.exists(cache_file):
|
|
54
|
+
# # print(f"Loading data from cache: {cache_file}")
|
|
55
|
+
# return joblib.load(cache_file)
|
|
56
|
+
|
|
57
|
+
# try:
|
|
58
|
+
# # Download data from yfinance
|
|
59
|
+
# data = yf.download(tickers, start=start_date, end=end_date, auto_adjust=True, progress=False, period=period, interval=interval)
|
|
60
|
+
|
|
61
|
+
# # Save data to cache
|
|
62
|
+
# joblib.dump(data, cache_file)
|
|
63
|
+
# # print(f"Data cached to: {cache_file}")
|
|
64
|
+
|
|
65
|
+
# except Exception as e:
|
|
66
|
+
# print(f"Error downloading data: {e}")
|
|
67
|
+
# raise
|
|
68
|
+
|
|
69
|
+
# return data
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def getData(tickers: Union[str, List[str]], start_date: str = None, end_date: str = None, period: str = '1mo', interval: str = '1d') -> pd.DataFrame:
|
|
73
|
+
"""
|
|
74
|
+
Retrieve data from yfinance library for specified tickers.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
tickers : str or list
|
|
79
|
+
symbol or list of symbols
|
|
80
|
+
start : str, optional
|
|
81
|
+
start date, by default None
|
|
82
|
+
end : str, optional
|
|
83
|
+
end date, by default None
|
|
84
|
+
period : str, optional
|
|
85
|
+
period, by default '1mo'
|
|
86
|
+
valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
|
|
87
|
+
interval : str, optional
|
|
88
|
+
interval, by default '1d', max 60 days
|
|
89
|
+
valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
pd.DataFrame
|
|
94
|
+
DataFrame with OHLC[A]V (Open, High, Low, Close, Adj Close, Volume).
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
return yf.download(tickers, start=None, end=None, auto_adjust=True, progress=False, period=period, interval=interval)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# Retrieve ticker object from yfinance library
|
|
101
|
+
def getTicker(ticker: str) -> yf.Ticker:
|
|
102
|
+
"""
|
|
103
|
+
Retrieve ticker object from yfinance library.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
ticker : str
|
|
108
|
+
symbol
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
yf.Ticker
|
|
113
|
+
Ticker object
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
# for all available options, refer yfinance
|
|
117
|
+
# .info
|
|
118
|
+
# .history
|
|
119
|
+
# .actions
|
|
120
|
+
# .dividends
|
|
121
|
+
# .splits
|
|
122
|
+
# .financials
|
|
123
|
+
# .quarterly_financials
|
|
124
|
+
# .major_holders
|
|
125
|
+
# .institutional_holders
|
|
126
|
+
# .balance_sheet
|
|
127
|
+
# .quarterly_balance_sheet
|
|
128
|
+
# .cashflow
|
|
129
|
+
# .quarterly_cashflow
|
|
130
|
+
# .earnings
|
|
131
|
+
# .quarterly_earnings
|
|
132
|
+
# .sustainability
|
|
133
|
+
# .recommendations
|
|
134
|
+
# .calendar
|
|
135
|
+
# .earnings_dates
|
|
136
|
+
# .isin
|
|
137
|
+
# .options
|
|
138
|
+
#. option_chain('YYYY-MM-DD')
|
|
139
|
+
# .news
|
|
140
|
+
|
|
141
|
+
return yf.Ticker(ticker)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Quantmod Python Package
|
|
2
|
+
# https://kannansingaravelu.com/
|
|
3
|
+
|
|
4
|
+
# Copyright 2024 Kannan Singaravelu
|
|
5
|
+
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
from .optioninputs import OptionInputs
|
|
20
|
+
from .blackscholes import BlackScholesOptionPricing
|
|
21
|
+
from .montecarlo import MonteCarloOptionPricing
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"OptionInputs",
|
|
26
|
+
"BlackScholesOptionPricing",
|
|
27
|
+
"MonteCarloOptionPricing",
|
|
28
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.stats import norm
|
|
4
|
+
from scipy.optimize import brentq
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
from .optioninputs import OptionInputs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BlackScholesOptionPricing:
|
|
10
|
+
"""
|
|
11
|
+
Class for Black-Scholes Option Pricing
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
inputs : OptionInputs
|
|
16
|
+
Option inputs parameters
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
attributes: float
|
|
21
|
+
call_price, put_price
|
|
22
|
+
|
|
23
|
+
call_delta, put_delta
|
|
24
|
+
|
|
25
|
+
gamma
|
|
26
|
+
|
|
27
|
+
vega
|
|
28
|
+
|
|
29
|
+
call_theta, put_theta
|
|
30
|
+
|
|
31
|
+
call_rho, put_rho
|
|
32
|
+
|
|
33
|
+
impvol
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, inputs: OptionInputs) -> None:
|
|
37
|
+
self.inputs = inputs
|
|
38
|
+
self._a_ = self.inputs.volatility * np.sqrt(self.inputs.ttm)
|
|
39
|
+
|
|
40
|
+
self._d1_ = (np.log(self.inputs.spot / self.inputs.strike) +
|
|
41
|
+
(self.inputs.rate + (self.inputs.volatility**2) / 2) * self.inputs.ttm) / self._a_
|
|
42
|
+
|
|
43
|
+
self._d2_ = self._d1_ - self._a_
|
|
44
|
+
self._b_ = np.exp(-self.inputs.rate * self.inputs.ttm)
|
|
45
|
+
|
|
46
|
+
self.call_price, self.put_price = self._price()
|
|
47
|
+
self.call_delta, self.put_delta = self._delta()
|
|
48
|
+
self.gamma = self._gamma()
|
|
49
|
+
self.vega = self._vega()
|
|
50
|
+
self.call_theta, self.put_theta = self._theta()
|
|
51
|
+
self.call_rho, self.put_rho = self._rho()
|
|
52
|
+
self.impvol = self._impvol()
|
|
53
|
+
|
|
54
|
+
def _price(self) -> Tuple[float, float]:
|
|
55
|
+
"""
|
|
56
|
+
Calculate option prices: Call price and Put price
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
Tuple[float, float]
|
|
61
|
+
Call price and Put price
|
|
62
|
+
"""
|
|
63
|
+
call = self.inputs.spot * norm.cdf(self._d1_) - self.inputs.strike * self._b_ * norm.cdf(self._d2_)
|
|
64
|
+
put = self.inputs.strike * self._b_ * norm.cdf(-self._d2_) - self.inputs.spot * norm.cdf(-self._d1_)
|
|
65
|
+
return call, put
|
|
66
|
+
|
|
67
|
+
def _delta(self) -> Tuple[float, float]:
|
|
68
|
+
"""
|
|
69
|
+
Calculate option deltas: Call delta and Put delta
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
Tuple[float, float]
|
|
74
|
+
Call delta and Put delta
|
|
75
|
+
"""
|
|
76
|
+
call = norm.cdf(self._d1_)
|
|
77
|
+
put = -norm.cdf(-self._d1_)
|
|
78
|
+
return call, put
|
|
79
|
+
|
|
80
|
+
def _gamma(self) -> float:
|
|
81
|
+
"""
|
|
82
|
+
Calculate option gamma
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
float
|
|
87
|
+
Gamma
|
|
88
|
+
"""
|
|
89
|
+
return norm.pdf(self._d1_) / (self.inputs.spot * self._a_)
|
|
90
|
+
|
|
91
|
+
def _vega(self) -> float:
|
|
92
|
+
"""
|
|
93
|
+
Calculate option vega
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
float
|
|
98
|
+
Vega
|
|
99
|
+
"""
|
|
100
|
+
return self.inputs.spot * norm.pdf(self._d1_) * np.sqrt(self.inputs.ttm) / 100
|
|
101
|
+
|
|
102
|
+
def _theta(self) -> Tuple[float, float]:
|
|
103
|
+
"""
|
|
104
|
+
Calculate option thetas: Call theta and Put theta
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
Tuple[float, float]
|
|
109
|
+
Call theta and Put theta
|
|
110
|
+
"""
|
|
111
|
+
call = -self.inputs.spot * norm.pdf(self._d1_) * self.inputs.volatility / (2 * np.sqrt(self.inputs.ttm)) - \
|
|
112
|
+
self.inputs.rate * self.inputs.strike * self._b_ * norm.cdf(self._d2_)
|
|
113
|
+
|
|
114
|
+
put = -self.inputs.spot * norm.pdf(self._d1_) * self.inputs.volatility / (2 * np.sqrt(self.inputs.ttm)) + \
|
|
115
|
+
self.inputs.rate * self.inputs.strike * self._b_ * norm.cdf(-self._d2_)
|
|
116
|
+
return call / 365, put / 365
|
|
117
|
+
|
|
118
|
+
def _rho(self) -> Tuple[float, float]:
|
|
119
|
+
"""
|
|
120
|
+
Calculate option rhos: Call rho and Put rho
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
Tuple[float, float]
|
|
125
|
+
Call rho and Put rho
|
|
126
|
+
"""
|
|
127
|
+
call = self.inputs.strike * self.inputs.ttm * self._b_ * norm.cdf(self._d2_) / 100
|
|
128
|
+
put = -self.inputs.strike * self.inputs.ttm * self._b_ * norm.cdf(-self._d2_) / 100
|
|
129
|
+
return call, put
|
|
130
|
+
|
|
131
|
+
def _impvol(self) -> float:
|
|
132
|
+
"""
|
|
133
|
+
Calculate option implied volatility
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
float
|
|
138
|
+
Implied volatility
|
|
139
|
+
"""
|
|
140
|
+
if self.inputs.callprice is None and self.inputs.putprice is None:
|
|
141
|
+
return self.inputs.volatility
|
|
142
|
+
else:
|
|
143
|
+
def f(sigma: float) -> float:
|
|
144
|
+
option = BlackScholesOptionPricing(OptionInputs(spot=self.inputs.spot, strike=self.inputs.strike, rate=self.inputs.rate, ttm=self.inputs.ttm, volatility=sigma))
|
|
145
|
+
if self.inputs.callprice is not None:
|
|
146
|
+
model_call_price = option.call_price
|
|
147
|
+
return model_call_price - self.inputs.callprice
|
|
148
|
+
else:
|
|
149
|
+
model_put_price = option.put_price
|
|
150
|
+
return model_put_price - self.inputs.putprice
|
|
151
|
+
try:
|
|
152
|
+
implied_vol = brentq(f, a=1e-5, b=5.0, xtol=1e-8, rtol=1e-8, maxiter=100)
|
|
153
|
+
implied_vol = max(implied_vol, 1e-5)
|
|
154
|
+
except ValueError:
|
|
155
|
+
implied_vol = np.nan
|
|
156
|
+
return implied_vol
|