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.
@@ -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,3 @@
1
+ # main.py
2
+ def hello():
3
+ print("Welcome to the world of Quant!")
@@ -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