quantmod 0.0.5__tar.gz → 0.0.6__tar.gz
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-0.0.5 → quantmod-0.0.6}/PKG-INFO +9 -10
- {quantmod-0.0.5 → quantmod-0.0.6}/README.md +8 -8
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/derivatives/__init__.py +2 -2
- quantmod-0.0.6/quantmod/derivatives/nse.py +274 -0
- quantmod-0.0.6/quantmod/models/montecarlo.py +152 -0
- quantmod-0.0.6/quantmod/version.py +1 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/PKG-INFO +9 -10
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/SOURCES.txt +1 -1
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/requires.txt +0 -1
- quantmod-0.0.5/quantmod/derivatives/optionchain.py +0 -249
- quantmod-0.0.5/quantmod/models/montecarlo.py +0 -145
- quantmod-0.0.5/quantmod/version.py +0 -1
- {quantmod-0.0.5 → quantmod-0.0.6}/LICENSE.txt +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/_version.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/datasets/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/datasets/data/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/datasets/data/nifty50.csv +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/datasets/data/spx.csv +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/datasets/dataloader.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/indicators/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/indicators/indicators.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/main.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/markets/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/markets/bb.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/markets/yahoo.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/models/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/models/binomial.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/models/blackscholes.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/models/optioninputs.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/risk/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/risk/var.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/risk/varbacktest.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/risk/varinputs.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/timeseries/__init__.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/timeseries/performance.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/timeseries/timeseries.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod/utils.py +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/dependency_links.txt +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/entry_points.txt +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/not-zip-safe +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/quantmod.egg-info/top_level.txt +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/setup.cfg +0 -0
- {quantmod-0.0.5 → quantmod-0.0.6}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: quantmod
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Quantmod Python Package
|
|
5
5
|
Home-page: https://kannansingaravelu.com/
|
|
6
6
|
Author: Kannan Singaravelu
|
|
@@ -20,7 +20,6 @@ Requires-Python: >=3.10
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.txt
|
|
22
22
|
Requires-Dist: joblib
|
|
23
|
-
Requires-Dist: jugaad-data
|
|
24
23
|
Requires-Dist: matplotlib
|
|
25
24
|
Requires-Dist: numpy>=2.0.2
|
|
26
25
|
Requires-Dist: pandas>=2.2.2
|
|
@@ -57,13 +56,13 @@ pip install quantmod
|
|
|
57
56
|
|
|
58
57
|
## Modules
|
|
59
58
|
|
|
60
|
-
* [markets](https://kannansingaravelu.com/
|
|
61
|
-
* [models](https://kannansingaravelu.com/
|
|
62
|
-
* [risk](https://kannansingaravelu.com/
|
|
63
|
-
* [timeseries](https://kannansingaravelu.com/
|
|
64
|
-
* [indicators](https://kannansingaravelu.com/
|
|
65
|
-
* [derivatives](https://kannansingaravelu.com/
|
|
66
|
-
* [datasets](https://kannansingaravelu.com/
|
|
59
|
+
* [markets](https://kannansingaravelu.com/quantmod/markets/)
|
|
60
|
+
* [models](https://kannansingaravelu.com/quantmod/models/)
|
|
61
|
+
* [risk](https://kannansingaravelu.com/quantmod/risk/)
|
|
62
|
+
* [timeseries](https://kannansingaravelu.com/quantmod/timeseries/)
|
|
63
|
+
* [indicators](https://kannansingaravelu.com/quantmod/indicators/)
|
|
64
|
+
* [derivatives](https://kannansingaravelu.com/quantmod/derivatives/)
|
|
65
|
+
* [datasets](https://kannansingaravelu.com/quantmod/datasets/)
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
## Quickstart
|
|
@@ -99,7 +98,7 @@ Refer to the [examples](https://kannansingaravelu.com/) section for more details
|
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
## Changelog
|
|
102
|
-
The list of changes to quantmod between each release can be found [here](https://kannansingaravelu.com/
|
|
101
|
+
The list of changes to quantmod between each release can be found [here](https://kannansingaravelu.com/quantmod/changelog/)
|
|
103
102
|
|
|
104
103
|
|
|
105
104
|
## Community
|
|
@@ -12,13 +12,13 @@ pip install quantmod
|
|
|
12
12
|
|
|
13
13
|
## Modules
|
|
14
14
|
|
|
15
|
-
* [markets](https://kannansingaravelu.com/
|
|
16
|
-
* [models](https://kannansingaravelu.com/
|
|
17
|
-
* [risk](https://kannansingaravelu.com/
|
|
18
|
-
* [timeseries](https://kannansingaravelu.com/
|
|
19
|
-
* [indicators](https://kannansingaravelu.com/
|
|
20
|
-
* [derivatives](https://kannansingaravelu.com/
|
|
21
|
-
* [datasets](https://kannansingaravelu.com/
|
|
15
|
+
* [markets](https://kannansingaravelu.com/quantmod/markets/)
|
|
16
|
+
* [models](https://kannansingaravelu.com/quantmod/models/)
|
|
17
|
+
* [risk](https://kannansingaravelu.com/quantmod/risk/)
|
|
18
|
+
* [timeseries](https://kannansingaravelu.com/quantmod/timeseries/)
|
|
19
|
+
* [indicators](https://kannansingaravelu.com/quantmod/indicators/)
|
|
20
|
+
* [derivatives](https://kannansingaravelu.com/quantmod/derivatives/)
|
|
21
|
+
* [datasets](https://kannansingaravelu.com/quantmod/datasets/)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
## Quickstart
|
|
@@ -54,7 +54,7 @@ Refer to the [examples](https://kannansingaravelu.com/) section for more details
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
## Changelog
|
|
57
|
-
The list of changes to quantmod between each release can be found [here](https://kannansingaravelu.com/
|
|
57
|
+
The list of changes to quantmod between each release can be found [here](https://kannansingaravelu.com/quantmod/changelog/)
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
## Community
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# improvised from nsepython
|
|
2
|
+
# to be used only for tutorial purposes
|
|
3
|
+
# for production use, please reach out to NSE India
|
|
4
|
+
import os, sys
|
|
5
|
+
import requests
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import json
|
|
9
|
+
import random
|
|
10
|
+
import datetime, time
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import urllib.parse
|
|
14
|
+
|
|
15
|
+
# Constants
|
|
16
|
+
indices = ["NIFTY", "FINNIFTY", "BANKNIFTY"]
|
|
17
|
+
|
|
18
|
+
mode = "local"
|
|
19
|
+
|
|
20
|
+
if mode == "vpn":
|
|
21
|
+
|
|
22
|
+
def nsefetch(payload):
|
|
23
|
+
if ("%26" in payload) or ("%20" in payload):
|
|
24
|
+
encoded_url = payload
|
|
25
|
+
else:
|
|
26
|
+
encoded_url = urllib.parse.quote(payload, safe=":/?&=")
|
|
27
|
+
payload_var = 'curl -b cookies.txt "' + encoded_url + '"' + curl_headers + ""
|
|
28
|
+
try:
|
|
29
|
+
output = os.popen(payload_var).read()
|
|
30
|
+
output = json.loads(output)
|
|
31
|
+
except ValueError: # includes simplejson.decoder.JSONDecodeError:
|
|
32
|
+
payload2 = "https://www.nseindia.com"
|
|
33
|
+
output2 = os.popen(
|
|
34
|
+
'curl -c cookies.txt "' + payload2 + '"' + curl_headers + ""
|
|
35
|
+
).read()
|
|
36
|
+
|
|
37
|
+
output = os.popen(payload_var).read()
|
|
38
|
+
output = json.loads(output)
|
|
39
|
+
return output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if mode == "local":
|
|
43
|
+
|
|
44
|
+
def nsefetch(payload):
|
|
45
|
+
try:
|
|
46
|
+
output = requests.get(payload, headers=headers).json()
|
|
47
|
+
# print(output)
|
|
48
|
+
except ValueError:
|
|
49
|
+
s = requests.Session()
|
|
50
|
+
output = s.get("http://nseindia.com", headers=headers)
|
|
51
|
+
output = s.get(payload, headers=headers).json()
|
|
52
|
+
return output
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
headers = {
|
|
56
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
57
|
+
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8,en-GB;q=0.7",
|
|
58
|
+
"cache-control": "max-age=0",
|
|
59
|
+
"priority": "u=0, i",
|
|
60
|
+
"sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
|
|
61
|
+
"sec-ch-ua-mobile": "?0",
|
|
62
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
63
|
+
"sec-fetch-dest": "document",
|
|
64
|
+
"sec-fetch-mode": "navigate",
|
|
65
|
+
"sec-fetch-site": "none",
|
|
66
|
+
"sec-fetch-user": "?1",
|
|
67
|
+
"upgrade-insecure-requests": "1",
|
|
68
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
|
|
69
|
+
}
|
|
70
|
+
# Curl headers
|
|
71
|
+
curl_headers = """ -H "authority: beta.nseindia.com" -H "cache-control: max-age=0" -H "dnt: 1" -H "upgrade-insecure-requests: 1" -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36" -H "sec-fetch-user: ?1" -H "accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" -H "sec-fetch-site: none" -H "sec-fetch-mode: navigate" -H "accept-encoding: gzip, deflate, br" -H "accept-language: en-US,en;q=0.9,hi;q=0.8" --compressed"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class OptionData:
|
|
75
|
+
"""
|
|
76
|
+
A class to fetch and analyze option chain data from NSE.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
symbol : str
|
|
81
|
+
Trading symbol of the stock/index (e.g., 'NIFTY', 'RELIANCE')
|
|
82
|
+
expiry_dt : str
|
|
83
|
+
Expiry date in format '%d-%b-%Y' (e.g., '27-Mar-2025')
|
|
84
|
+
Note: Month should be first 3 letters capitalized (Jan, Feb, Mar, etc.)
|
|
85
|
+
|
|
86
|
+
Attributes
|
|
87
|
+
----------
|
|
88
|
+
get_put_call_ratio : float
|
|
89
|
+
Put-Call ratio based on open interest
|
|
90
|
+
get_maximum_pain_strike : float
|
|
91
|
+
Maximum pain strike price
|
|
92
|
+
get_call_option_data : pandas.DataFrame
|
|
93
|
+
Call option chain data
|
|
94
|
+
get_put_option_data : pandas.DataFrame
|
|
95
|
+
Put option chain data
|
|
96
|
+
|
|
97
|
+
Methods
|
|
98
|
+
-------
|
|
99
|
+
get_option_quote : float
|
|
100
|
+
Get option quote for specific strike price, option type and transaction intent
|
|
101
|
+
get_synthetic_future_price : float
|
|
102
|
+
Calculate synthetic futures price using put-call parity
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, symbol, expiry_dt):
|
|
106
|
+
"""
|
|
107
|
+
Initialize the OptionData class.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
symbol : str
|
|
112
|
+
Trading symbol of the stock/index (e.g., 'NIFTY', 'RELIANCE')
|
|
113
|
+
expiry_dt : str
|
|
114
|
+
Expiry date in format '%d-%b-%Y' (e.g., '27-Mar-2025')
|
|
115
|
+
Note: Month should be first 3 letters capitalized (Jan, Feb, Mar, etc.)
|
|
116
|
+
"""
|
|
117
|
+
self.expiry_dt = expiry_dt
|
|
118
|
+
self.symbol = symbol.replace(
|
|
119
|
+
"&", "%26"
|
|
120
|
+
) # URL Parse for Stocks Like M&M Finance
|
|
121
|
+
self.payload = self._nse_optionchain_scrapper()
|
|
122
|
+
|
|
123
|
+
self.get_put_call_ratio = self._get_option_pcr()
|
|
124
|
+
self.get_maximum_pain_strike = self._get_maximum_pain_strike()
|
|
125
|
+
self.get_call_option_data = self._get_call_option_data()
|
|
126
|
+
self.get_put_option_data = self._get_put_option_data()
|
|
127
|
+
|
|
128
|
+
def _nse_optionchain_scrapper(self):
|
|
129
|
+
"""
|
|
130
|
+
Fetch option chain data from NSE website.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
dict
|
|
135
|
+
Raw option chain data from NSE API
|
|
136
|
+
"""
|
|
137
|
+
if any(x in self.symbol for x in indices):
|
|
138
|
+
payload = nsefetch(
|
|
139
|
+
"https://www.nseindia.com/api/option-chain-indices?symbol="
|
|
140
|
+
+ self.symbol
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
payload = nsefetch(
|
|
144
|
+
"https://www.nseindia.com/api/option-chain-equities?symbol="
|
|
145
|
+
+ self.symbol
|
|
146
|
+
)
|
|
147
|
+
return payload
|
|
148
|
+
|
|
149
|
+
def get_option_quote(self, strikePrice, optionType, intent=""):
|
|
150
|
+
"""
|
|
151
|
+
Get option quote for specific strike price and option type.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
strikePrice : float
|
|
156
|
+
Strike price of the option
|
|
157
|
+
optionType : str
|
|
158
|
+
Type of option, either 'CE' (Call) or 'PE' (Put)
|
|
159
|
+
intent : str, optional
|
|
160
|
+
Quote type:
|
|
161
|
+
- '' (default) for last traded price
|
|
162
|
+
- 'sell' for bid price
|
|
163
|
+
- 'buy' for ask price
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
float
|
|
168
|
+
Option price based on the specified intent
|
|
169
|
+
"""
|
|
170
|
+
for x in range(len(self.payload["records"]["data"])):
|
|
171
|
+
if (self.payload["records"]["data"][x]["strikePrice"] == strikePrice) & (
|
|
172
|
+
self.payload["records"]["data"][x]["expiryDate"] == self.expiry_dt
|
|
173
|
+
):
|
|
174
|
+
if intent == "":
|
|
175
|
+
return self.payload["records"]["data"][x][optionType]["lastPrice"]
|
|
176
|
+
if intent == "sell":
|
|
177
|
+
return self.payload["records"]["data"][x][optionType]["bidprice"]
|
|
178
|
+
if intent == "buy":
|
|
179
|
+
return self.payload["records"]["data"][x][optionType]["askPrice"]
|
|
180
|
+
|
|
181
|
+
def _get_option_pcr(self):
|
|
182
|
+
"""
|
|
183
|
+
Calculate Put-Call Ratio based on open interest.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
float
|
|
188
|
+
Put-Call ratio rounded to 2 decimal places
|
|
189
|
+
"""
|
|
190
|
+
ce_oi = 0
|
|
191
|
+
pe_oi = 0
|
|
192
|
+
for i in self.payload["records"]["data"]:
|
|
193
|
+
if i["expiryDate"] == self.expiry_dt:
|
|
194
|
+
try:
|
|
195
|
+
ce_oi += i["CE"]["openInterest"]
|
|
196
|
+
pe_oi += i["PE"]["openInterest"]
|
|
197
|
+
except KeyError:
|
|
198
|
+
pass
|
|
199
|
+
return round(pe_oi / ce_oi, 2)
|
|
200
|
+
|
|
201
|
+
def get_synthetic_future_price(self, strike):
|
|
202
|
+
"""
|
|
203
|
+
Calculate synthetic futures price using put-call parity.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
strike : float
|
|
208
|
+
Strike price to use for calculation
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
float
|
|
213
|
+
Synthetic futures price
|
|
214
|
+
"""
|
|
215
|
+
synthetic_futures = (
|
|
216
|
+
strike
|
|
217
|
+
+ self.get_option_quote(strike, "CE", "buy")
|
|
218
|
+
- self.get_option_quote(strike, "PE", "sell")
|
|
219
|
+
)
|
|
220
|
+
return synthetic_futures
|
|
221
|
+
|
|
222
|
+
def _get_call_option_data(self):
|
|
223
|
+
"""
|
|
224
|
+
Get call options data for current expiry.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
pandas.DataFrame
|
|
229
|
+
DataFrame containing call options data sorted by strike price
|
|
230
|
+
"""
|
|
231
|
+
ce_values = [
|
|
232
|
+
data["CE"]
|
|
233
|
+
for data in self.payload["records"]["data"]
|
|
234
|
+
if "CE" in data and data["expiryDate"] == self.expiry_dt
|
|
235
|
+
]
|
|
236
|
+
return pd.DataFrame(ce_values).sort_values(["strikePrice"])
|
|
237
|
+
|
|
238
|
+
def _get_put_option_data(self):
|
|
239
|
+
"""
|
|
240
|
+
Get put options data for current expiry.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
pandas.DataFrame
|
|
245
|
+
DataFrame containing put options data sorted by strike price
|
|
246
|
+
"""
|
|
247
|
+
pe_values = [
|
|
248
|
+
data["PE"]
|
|
249
|
+
for data in self.payload["records"]["data"]
|
|
250
|
+
if "PE" in data and data["expiryDate"] == self.expiry_dt
|
|
251
|
+
]
|
|
252
|
+
return pd.DataFrame(pe_values).sort_values(["strikePrice"])
|
|
253
|
+
|
|
254
|
+
def _get_maximum_pain_strike(self):
|
|
255
|
+
"""
|
|
256
|
+
Calculate maximum pain strike price.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
float
|
|
261
|
+
Strike price where maximum pain occurs
|
|
262
|
+
"""
|
|
263
|
+
calls = self._get_call_option_data()
|
|
264
|
+
strikes = calls["strikePrice"]
|
|
265
|
+
ce_oi = calls["openInterest"]
|
|
266
|
+
pe_oi = self._get_put_option_data()["openInterest"]
|
|
267
|
+
|
|
268
|
+
total_pain = [
|
|
269
|
+
sum(ce_oi * np.maximum(0, expiry_price - strikes))
|
|
270
|
+
+ sum(pe_oi * np.maximum(0, strikes - expiry_price))
|
|
271
|
+
for expiry_price in strikes
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
return strikes[np.argmin(total_pain)]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from .optioninputs import OptionInputs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OptionType(str, Enum):
|
|
9
|
+
CALL = "call"
|
|
10
|
+
PUT = "put"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExerciseStyle(str, Enum):
|
|
14
|
+
EUROPEAN = "european"
|
|
15
|
+
ASIAN = "asian"
|
|
16
|
+
BARRIER = "barrier"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BarrierType(str, Enum):
|
|
20
|
+
UP_AND_OUT = "up_and_out"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MonteCarloOptionPricing:
|
|
24
|
+
"""
|
|
25
|
+
Monte Carlo Pricing for options.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
inputs : OptionInputs
|
|
30
|
+
The inputs for the option pricing model.
|
|
31
|
+
nsims : int, optional
|
|
32
|
+
Number of simulations (default is Field(..., gt=0)).
|
|
33
|
+
timestep : int, optional
|
|
34
|
+
Time step (default is Field(..., gt=0)).
|
|
35
|
+
option_type : OptionType
|
|
36
|
+
Type of option (Call or Put).
|
|
37
|
+
exercise_style : ExerciseStyle
|
|
38
|
+
Style of exercise (American, European, or Barrier).
|
|
39
|
+
barrier_level : float, optional
|
|
40
|
+
Barrier level for barrier options (default is None).
|
|
41
|
+
barrier_rebate : int, optional
|
|
42
|
+
Barrier rebate for barrier options (default is None).
|
|
43
|
+
barrier_type : BarrierType, optional
|
|
44
|
+
Type of barrier option (default is None).
|
|
45
|
+
|
|
46
|
+
Attributes
|
|
47
|
+
----------
|
|
48
|
+
option_price : float
|
|
49
|
+
The calculated option price.
|
|
50
|
+
|
|
51
|
+
Raises
|
|
52
|
+
------
|
|
53
|
+
ValueError
|
|
54
|
+
If an unsupported exercise style or barrier type is provided.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
inputs: OptionInputs,
|
|
60
|
+
nsims: int = Field(..., gt=0, description="Number of simulations"),
|
|
61
|
+
timestep: int = Field(..., gt=0, description="Time step"),
|
|
62
|
+
option_type: OptionType = Field(..., description="Call or Put"),
|
|
63
|
+
exercise_style: ExerciseStyle = Field(
|
|
64
|
+
..., description="American or European or Barrier"
|
|
65
|
+
),
|
|
66
|
+
barrier_level: Optional[float] = Field(
|
|
67
|
+
None, gt=0, description="Barrier level (for barrier options)"
|
|
68
|
+
),
|
|
69
|
+
barrier_rebate: Optional[int] = Field(
|
|
70
|
+
None, gt=0, description="Barrier rebate (for barrier options)"
|
|
71
|
+
),
|
|
72
|
+
barrier_type: Optional[BarrierType] = Field(
|
|
73
|
+
None, description="Type of barrier option"
|
|
74
|
+
),
|
|
75
|
+
) -> None:
|
|
76
|
+
self.inputs = inputs
|
|
77
|
+
self.spot = self.inputs.spot
|
|
78
|
+
self.strike = self.inputs.strike
|
|
79
|
+
self.rate = self.inputs.rate
|
|
80
|
+
self.ttm = self.inputs.ttm
|
|
81
|
+
self.sigma = self.inputs.volatility
|
|
82
|
+
|
|
83
|
+
self.nsims = nsims
|
|
84
|
+
self.timestep = timestep
|
|
85
|
+
self.option_type = option_type
|
|
86
|
+
self.exercise_style = exercise_style
|
|
87
|
+
self.barrier_level = barrier_level
|
|
88
|
+
self.barrier_rebate = barrier_rebate
|
|
89
|
+
self.barrier_type = barrier_type
|
|
90
|
+
|
|
91
|
+
# Calculate the price immediately upon initialization
|
|
92
|
+
self._calculate_price()
|
|
93
|
+
|
|
94
|
+
def _calculate_price(self):
|
|
95
|
+
"""
|
|
96
|
+
Calculate the option price using Monte Carlo simulation.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
float
|
|
101
|
+
The calculated option price.
|
|
102
|
+
"""
|
|
103
|
+
dt = self.ttm / self.timestep
|
|
104
|
+
discount_factor = np.exp(-self.rate * self.ttm)
|
|
105
|
+
|
|
106
|
+
# Simulate price paths
|
|
107
|
+
price_paths = np.zeros((self.nsims, self.timestep + 1))
|
|
108
|
+
price_paths[:, 0] = self.spot
|
|
109
|
+
|
|
110
|
+
for t in range(1, self.timestep + 1):
|
|
111
|
+
z = np.random.standard_normal(self.nsims)
|
|
112
|
+
price_paths[:, t] = price_paths[:, t - 1] * np.exp(
|
|
113
|
+
(self.rate - 0.5 * self.sigma**2) * dt + self.sigma * np.sqrt(dt) * z
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Calculate payoff based on option style
|
|
117
|
+
if self.exercise_style == ExerciseStyle.EUROPEAN:
|
|
118
|
+
if self.option_type == OptionType.CALL:
|
|
119
|
+
payoff = np.maximum(price_paths[:, -1] - self.strike, 0)
|
|
120
|
+
else:
|
|
121
|
+
payoff = np.maximum(self.strike - price_paths[:, -1], 0)
|
|
122
|
+
elif self.exercise_style == ExerciseStyle.ASIAN:
|
|
123
|
+
avg_price = np.mean(price_paths, axis=1)
|
|
124
|
+
if self.option_type == OptionType.CALL:
|
|
125
|
+
payoff = np.maximum(avg_price - self.strike, 0)
|
|
126
|
+
else:
|
|
127
|
+
payoff = np.maximum(self.strike - avg_price, 0)
|
|
128
|
+
elif self.exercise_style == ExerciseStyle.BARRIER:
|
|
129
|
+
if self.barrier_type == BarrierType.UP_AND_OUT:
|
|
130
|
+
barrier_shift = self.barrier_level * np.exp(
|
|
131
|
+
0.5826 * self.sigma * np.sqrt(self.ttm / self.timestep)
|
|
132
|
+
)
|
|
133
|
+
if self.option_type == OptionType.CALL:
|
|
134
|
+
payoff = np.where(
|
|
135
|
+
np.max(price_paths, axis=1) < barrier_shift,
|
|
136
|
+
np.maximum(price_paths[:, -1] - self.strike, 0),
|
|
137
|
+
self.barrier_rebate,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
payoff = np.where(
|
|
141
|
+
np.max(price_paths, axis=1) < barrier_shift,
|
|
142
|
+
np.maximum(self.strike - price_paths[:, -1], 0),
|
|
143
|
+
self.barrier_rebate,
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
raise ValueError("Currently only supports Up-and-out barrier options.")
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError(f"Unsupported exercise style: {self.exercise_style}")
|
|
149
|
+
|
|
150
|
+
# Calculate option price
|
|
151
|
+
self.option_price = discount_factor * np.mean(payoff)
|
|
152
|
+
return self.option_price
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.0.6"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: quantmod
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Quantmod Python Package
|
|
5
5
|
Home-page: https://kannansingaravelu.com/
|
|
6
6
|
Author: Kannan Singaravelu
|
|
@@ -20,7 +20,6 @@ Requires-Python: >=3.10
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.txt
|
|
22
22
|
Requires-Dist: joblib
|
|
23
|
-
Requires-Dist: jugaad-data
|
|
24
23
|
Requires-Dist: matplotlib
|
|
25
24
|
Requires-Dist: numpy>=2.0.2
|
|
26
25
|
Requires-Dist: pandas>=2.2.2
|
|
@@ -57,13 +56,13 @@ pip install quantmod
|
|
|
57
56
|
|
|
58
57
|
## Modules
|
|
59
58
|
|
|
60
|
-
* [markets](https://kannansingaravelu.com/
|
|
61
|
-
* [models](https://kannansingaravelu.com/
|
|
62
|
-
* [risk](https://kannansingaravelu.com/
|
|
63
|
-
* [timeseries](https://kannansingaravelu.com/
|
|
64
|
-
* [indicators](https://kannansingaravelu.com/
|
|
65
|
-
* [derivatives](https://kannansingaravelu.com/
|
|
66
|
-
* [datasets](https://kannansingaravelu.com/
|
|
59
|
+
* [markets](https://kannansingaravelu.com/quantmod/markets/)
|
|
60
|
+
* [models](https://kannansingaravelu.com/quantmod/models/)
|
|
61
|
+
* [risk](https://kannansingaravelu.com/quantmod/risk/)
|
|
62
|
+
* [timeseries](https://kannansingaravelu.com/quantmod/timeseries/)
|
|
63
|
+
* [indicators](https://kannansingaravelu.com/quantmod/indicators/)
|
|
64
|
+
* [derivatives](https://kannansingaravelu.com/quantmod/derivatives/)
|
|
65
|
+
* [datasets](https://kannansingaravelu.com/quantmod/datasets/)
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
## Quickstart
|
|
@@ -99,7 +98,7 @@ Refer to the [examples](https://kannansingaravelu.com/) section for more details
|
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
## Changelog
|
|
102
|
-
The list of changes to quantmod between each release can be found [here](https://kannansingaravelu.com/
|
|
101
|
+
The list of changes to quantmod between each release can be found [here](https://kannansingaravelu.com/quantmod/changelog/)
|
|
103
102
|
|
|
104
103
|
|
|
105
104
|
## Community
|
|
@@ -20,7 +20,7 @@ quantmod/datasets/data/__init__.py
|
|
|
20
20
|
quantmod/datasets/data/nifty50.csv
|
|
21
21
|
quantmod/datasets/data/spx.csv
|
|
22
22
|
quantmod/derivatives/__init__.py
|
|
23
|
-
quantmod/derivatives/
|
|
23
|
+
quantmod/derivatives/nse.py
|
|
24
24
|
quantmod/indicators/__init__.py
|
|
25
25
|
quantmod/indicators/indicators.py
|
|
26
26
|
quantmod/markets/__init__.py
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
import numpy as np
|
|
3
|
-
from jugaad_data.nse import NSELive
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# construct a class object
|
|
7
|
-
class OptionChain:
|
|
8
|
-
"""
|
|
9
|
-
A class to fetch and process option chain data from NSE (National Stock Exchange).
|
|
10
|
-
|
|
11
|
-
Parameters
|
|
12
|
-
----------
|
|
13
|
-
symbol : str
|
|
14
|
-
Trading symbol (e.g., 'NIFTY', 'BANKNIFTY' or any equity symbol)
|
|
15
|
-
expiry_date : str
|
|
16
|
-
Expiry date of the options
|
|
17
|
-
|
|
18
|
-
Attributes
|
|
19
|
-
----------
|
|
20
|
-
option_chain : pandas.DataFrame
|
|
21
|
-
Processed option chain data
|
|
22
|
-
call_option_data : pandas.DataFrame
|
|
23
|
-
Call options data
|
|
24
|
-
put_option_date : pandas.DataFrame
|
|
25
|
-
Put options data
|
|
26
|
-
option_pain : float
|
|
27
|
-
Maximum pain strike price
|
|
28
|
-
|
|
29
|
-
Methods
|
|
30
|
-
-------
|
|
31
|
-
get_option_price()
|
|
32
|
-
Get the option price (last traded, bid, or ask) for a specific option
|
|
33
|
-
get_synthetic_futures()
|
|
34
|
-
Calculate synthetic futures price for a given strike price
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def __init__(self, symbol, expiry_date):
|
|
38
|
-
self.n = NSELive()
|
|
39
|
-
self.symbol = symbol
|
|
40
|
-
self.expiry_date = expiry_date
|
|
41
|
-
|
|
42
|
-
# flatten json and convert to dataframe
|
|
43
|
-
if symbol in ["NIFTY", "BANKNIFTY"]:
|
|
44
|
-
df = self.n.index_option_chain(self.symbol)["records"]["data"]
|
|
45
|
-
else:
|
|
46
|
-
df = self.n.equities_option_chain(self.symbol)["records"]["data"]
|
|
47
|
-
df = pd.json_normalize(df).fillna(0)
|
|
48
|
-
self.df = df[df["expiryDate"] == self.expiry_date]
|
|
49
|
-
|
|
50
|
-
# the __dict__ attributes
|
|
51
|
-
self.option_chain = self._get_option_chain()
|
|
52
|
-
self.call_option_data = self._get_call_option_data()
|
|
53
|
-
self.put_option_data = self._get_put_option_data()
|
|
54
|
-
self.option_pain = self._get_option_pain()
|
|
55
|
-
|
|
56
|
-
# get option chain
|
|
57
|
-
def _get_option_chain(self):
|
|
58
|
-
"""
|
|
59
|
-
Retrieve the complete option chain data.
|
|
60
|
-
|
|
61
|
-
Returns
|
|
62
|
-
-------
|
|
63
|
-
pandas.DataFrame
|
|
64
|
-
Complete option chain data for the specified symbol and expiry
|
|
65
|
-
"""
|
|
66
|
-
return self.df
|
|
67
|
-
|
|
68
|
-
# Create function to get call and put options
|
|
69
|
-
def _get_call_option_data(self):
|
|
70
|
-
"""
|
|
71
|
-
Extract and process call option data from the option chain.
|
|
72
|
-
|
|
73
|
-
Returns
|
|
74
|
-
-------
|
|
75
|
-
pandas.DataFrame
|
|
76
|
-
Processed call options data with simplified column names
|
|
77
|
-
"""
|
|
78
|
-
calls = self.option_chain.iloc[:, 21:].reset_index(drop=True)
|
|
79
|
-
cols = [i[3:] for i in calls.columns]
|
|
80
|
-
calls.columns = cols
|
|
81
|
-
return calls
|
|
82
|
-
|
|
83
|
-
def _get_put_option_data(self):
|
|
84
|
-
"""
|
|
85
|
-
Extract and process put option data from the option chain.
|
|
86
|
-
|
|
87
|
-
Returns
|
|
88
|
-
-------
|
|
89
|
-
pandas.DataFrame
|
|
90
|
-
Processed put options data with simplified column names
|
|
91
|
-
"""
|
|
92
|
-
puts = self.option_chain.iloc[:, 2:21].reset_index(drop=True)
|
|
93
|
-
cols = [i[3:] for i in puts.columns]
|
|
94
|
-
puts.columns = cols
|
|
95
|
-
return puts
|
|
96
|
-
|
|
97
|
-
# get option price
|
|
98
|
-
def get_option_price(self, strike, opt_type, txn_type=None):
|
|
99
|
-
"""
|
|
100
|
-
Get the option price (last traded, bid, or ask) for a specific option.
|
|
101
|
-
|
|
102
|
-
Parameters
|
|
103
|
-
----------
|
|
104
|
-
strike : float
|
|
105
|
-
Strike price of the option
|
|
106
|
-
opt_type : str
|
|
107
|
-
Option type ('CE' for Call or 'PE' for Put)
|
|
108
|
-
txn_type : str, optional
|
|
109
|
-
Transaction type ('buy', 'sell', or None for last price)
|
|
110
|
-
|
|
111
|
-
Returns
|
|
112
|
-
-------
|
|
113
|
-
float
|
|
114
|
-
Option price based on the specified parameters
|
|
115
|
-
"""
|
|
116
|
-
price_type = (
|
|
117
|
-
"bidprice"
|
|
118
|
-
if txn_type == "sell"
|
|
119
|
-
else "askPrice"
|
|
120
|
-
if txn_type == "buy"
|
|
121
|
-
else "lastPrice"
|
|
122
|
-
)
|
|
123
|
-
col = f"{opt_type}.{price_type}"
|
|
124
|
-
|
|
125
|
-
return float(self.df[self.df["strikePrice"] == strike][col].iloc[0])
|
|
126
|
-
|
|
127
|
-
# get synthetic futures
|
|
128
|
-
def get_synthetic_futures(self, strike):
|
|
129
|
-
"""
|
|
130
|
-
Calculate synthetic futures price for a given strike price.
|
|
131
|
-
Synthetic Futures price = Strike + Call - Put
|
|
132
|
-
|
|
133
|
-
Parameters
|
|
134
|
-
----------
|
|
135
|
-
strike : float
|
|
136
|
-
Strike price for which to calculate synthetic futures
|
|
137
|
-
|
|
138
|
-
Returns
|
|
139
|
-
-------
|
|
140
|
-
float
|
|
141
|
-
Synthetic futures price
|
|
142
|
-
"""
|
|
143
|
-
return (
|
|
144
|
-
strike
|
|
145
|
-
+ self.get_option_price(strike, opt_type="CE", txn_type="buy")
|
|
146
|
-
- self.get_option_price(strike, opt_type="PE", txn_type="sell")
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
def _get_option_pain(self) -> float:
|
|
150
|
-
"""
|
|
151
|
-
Calculate the maximum pain point - the strike price where the total value of all
|
|
152
|
-
options (calls and puts) would cause the most financial pain to option writers.
|
|
153
|
-
|
|
154
|
-
Returns
|
|
155
|
-
-------
|
|
156
|
-
float
|
|
157
|
-
Strike price at which maximum pain occurs
|
|
158
|
-
"""
|
|
159
|
-
# Get data from class attributes
|
|
160
|
-
strikes = np.array(self.option_chain["strikePrice"], dtype=float)
|
|
161
|
-
call_oi = np.array(self.call_option_data["openInterest"], dtype=float)
|
|
162
|
-
put_oi = np.array(self.put_option_data["openInterest"], dtype=float)
|
|
163
|
-
|
|
164
|
-
# Calculate pain for each possible expiry price
|
|
165
|
-
total_pain = []
|
|
166
|
-
|
|
167
|
-
for expiry_price in strikes:
|
|
168
|
-
# Calculate call options pain
|
|
169
|
-
call_pain = sum(
|
|
170
|
-
call_oi[i] * max(0, expiry_price - strike)
|
|
171
|
-
for i, strike in enumerate(strikes)
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
# Calculate put options pain
|
|
175
|
-
put_pain = sum(
|
|
176
|
-
put_oi[i] * max(0, strike - expiry_price)
|
|
177
|
-
for i, strike in enumerate(strikes)
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
# Total pain at this price level
|
|
181
|
-
total_pain.append(call_pain + put_pain)
|
|
182
|
-
|
|
183
|
-
# Find strike with minimum pain
|
|
184
|
-
max_pain_index = np.argmin(total_pain)
|
|
185
|
-
return float(strikes[max_pain_index])
|
|
186
|
-
|
|
187
|
-
def plot_pain_analysis(self):
|
|
188
|
-
"""
|
|
189
|
-
Plot pain analysis for the option chain.
|
|
190
|
-
"""
|
|
191
|
-
# Get data from class attributes
|
|
192
|
-
strikes = np.array(self.option_chain["strikePrice"], dtype=float)
|
|
193
|
-
call_oi = np.array(self.call_option_data["openInterest"], dtype=float)
|
|
194
|
-
put_oi = np.array(self.put_option_data["openInterest"], dtype=float)
|
|
195
|
-
|
|
196
|
-
results = []
|
|
197
|
-
|
|
198
|
-
for expiry_price in strikes:
|
|
199
|
-
# Calculate call options pain
|
|
200
|
-
call_pain = sum(
|
|
201
|
-
call_oi[i] * max(0, expiry_price - strike)
|
|
202
|
-
for i, strike in enumerate(strikes)
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
# Calculate put options pain
|
|
206
|
-
put_pain = sum(
|
|
207
|
-
put_oi[i] * max(0, strike - expiry_price)
|
|
208
|
-
for i, strike in enumerate(strikes)
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
results.append(
|
|
212
|
-
{
|
|
213
|
-
"Strike": expiry_price,
|
|
214
|
-
"Call Pain": call_pain,
|
|
215
|
-
"Put Pain": put_pain,
|
|
216
|
-
"Total Pain": call_pain + put_pain,
|
|
217
|
-
}
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
analysis = pd.DataFrame(results)
|
|
221
|
-
# Plot the pain distribution (requires matplotlib)
|
|
222
|
-
try:
|
|
223
|
-
import matplotlib.pyplot as plt
|
|
224
|
-
|
|
225
|
-
plt.figure(figsize=(10, 6))
|
|
226
|
-
plt.plot(
|
|
227
|
-
analysis["Strike"], analysis["Total Pain"], "b-", label="Total Pain"
|
|
228
|
-
)
|
|
229
|
-
plt.plot(
|
|
230
|
-
analysis["Strike"], analysis["Call Pain"], "g--", label="Call Pain"
|
|
231
|
-
)
|
|
232
|
-
plt.plot(analysis["Strike"], analysis["Put Pain"], "r--", label="Put Pain")
|
|
233
|
-
plt.axvline(
|
|
234
|
-
x=self._get_option_pain(),
|
|
235
|
-
color="k",
|
|
236
|
-
linestyle=":",
|
|
237
|
-
label=f"Max Pain Strike: {self._get_option_pain():}",
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
plt.title("Option Pain Analysis")
|
|
241
|
-
plt.xlabel("Strike Price")
|
|
242
|
-
plt.ylabel("Pain Value")
|
|
243
|
-
plt.legend()
|
|
244
|
-
plt.grid(True)
|
|
245
|
-
|
|
246
|
-
except ImportError:
|
|
247
|
-
print("\nMatplotlib not installed. Skipping plot generation.")
|
|
248
|
-
|
|
249
|
-
plt.show()
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from pydantic import BaseModel, Field
|
|
3
|
-
from typing import Optional, Tuple
|
|
4
|
-
from .optioninputs import OptionInputs
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# Monte Carlo Option Pricing Engine
|
|
8
|
-
class MonteCarloOptionPricing:
|
|
9
|
-
"""
|
|
10
|
-
Class for Monte Carlo Option Pricing
|
|
11
|
-
|
|
12
|
-
Parameters
|
|
13
|
-
----------
|
|
14
|
-
inputs : OptionInputs
|
|
15
|
-
Object containing the following option parameters:
|
|
16
|
-
spot : float
|
|
17
|
-
Current price of the underlying asset
|
|
18
|
-
strike : float
|
|
19
|
-
Strike price of the option
|
|
20
|
-
rate : float
|
|
21
|
-
Risk-free interest rate (as a decimal)
|
|
22
|
-
ttm : float
|
|
23
|
-
Time to maturity in years
|
|
24
|
-
volatility : float
|
|
25
|
-
Implied volatility of the underlying asset (as a decimal)
|
|
26
|
-
- callprice : float, optional
|
|
27
|
-
Market price of call option (used for implied volatility calculation)
|
|
28
|
-
- putprice : float, optional
|
|
29
|
-
Market price of put option (used for implied volatility calculation)
|
|
30
|
-
nsims : int, optional
|
|
31
|
-
Number of simulations (default is Field(..., gt=0)).
|
|
32
|
-
timestep : int, optional
|
|
33
|
-
Time step (default is Field(252, gt=0)).
|
|
34
|
-
option_type : OptionType
|
|
35
|
-
Type of option (Call or Put).
|
|
36
|
-
option_style : OptionStyle
|
|
37
|
-
Style of option (American, European, or Barrier).
|
|
38
|
-
barrier_level : float, optional
|
|
39
|
-
Barrier level for barrier options (default is None).
|
|
40
|
-
barrier_rebate : int, optional
|
|
41
|
-
Barrier rebate for barrier options (default is None).
|
|
42
|
-
barrier_type : BarrierType
|
|
43
|
-
Type of barrier option.
|
|
44
|
-
|
|
45
|
-
Returns
|
|
46
|
-
-------
|
|
47
|
-
attributes: float
|
|
48
|
-
call_vanilla, put_vanilla
|
|
49
|
-
|
|
50
|
-
call_asian, put_asian
|
|
51
|
-
|
|
52
|
-
upandoutcall
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
def __init__(
|
|
56
|
-
self,
|
|
57
|
-
inputs: OptionInputs,
|
|
58
|
-
initialspot: float = Field(..., gt=0, description="Initial stock price"),
|
|
59
|
-
nsims: int = Field(..., gt=0, description="Number of simulations"),
|
|
60
|
-
timestep: int = 252,
|
|
61
|
-
barrier: Optional[float] = None,
|
|
62
|
-
rebate: Optional[int] = None,
|
|
63
|
-
) -> None:
|
|
64
|
-
self.inputs = inputs
|
|
65
|
-
self.initialspot = initialspot
|
|
66
|
-
self.nsims = nsims
|
|
67
|
-
self.timestep = timestep
|
|
68
|
-
self.barrier = barrier
|
|
69
|
-
self.rebate = rebate
|
|
70
|
-
|
|
71
|
-
self.call_vanilla, self.put_vanilla = self._vanillaoption()
|
|
72
|
-
self.call_asian, self.put_asian = self._asianoption()
|
|
73
|
-
self.upandoutcall = self._upandoutcall()
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def _discount_factor(self) -> float:
|
|
77
|
-
return np.exp(-self.inputs.rate * self.inputs.ttm)
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def _pseudorandomnumber(self) -> np.ndarray:
|
|
81
|
-
return np.random.standard_normal(self.nsims)
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def _simulatepath(self) -> np.ndarray:
|
|
85
|
-
"""Simulate price path"""
|
|
86
|
-
np.random.seed(2024)
|
|
87
|
-
|
|
88
|
-
dt = self.inputs.ttm / self.timestep
|
|
89
|
-
|
|
90
|
-
S = np.zeros((self.timestep, self.nsims))
|
|
91
|
-
S[0] = self.initialspot
|
|
92
|
-
|
|
93
|
-
for i in range(0, self.timestep - 1):
|
|
94
|
-
w = self._pseudorandomnumber
|
|
95
|
-
S[i + 1] = S[i] * (
|
|
96
|
-
1 + self.inputs.rate * dt + self.inputs.volatility * np.sqrt(dt) * w
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
return S
|
|
100
|
-
|
|
101
|
-
def _vanillaoption(self) -> Tuple[float, float]:
|
|
102
|
-
"""Calculate vanilla option payoff"""
|
|
103
|
-
S = self._simulatepath
|
|
104
|
-
|
|
105
|
-
vanilla_call = self._discount_factor * np.mean(
|
|
106
|
-
np.maximum(0, S[-1] - self.inputs.strike)
|
|
107
|
-
)
|
|
108
|
-
vanilla_put = self._discount_factor * np.mean(
|
|
109
|
-
np.maximum(0, self.inputs.strike - S[-1])
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
return [vanilla_call, vanilla_put]
|
|
113
|
-
|
|
114
|
-
def _asianoption(self) -> Tuple[float]:
|
|
115
|
-
"""Calculate asian option payoff"""
|
|
116
|
-
S = self._simulatepath
|
|
117
|
-
|
|
118
|
-
A = S.mean(axis=0)
|
|
119
|
-
|
|
120
|
-
asian_call = self._discount_factor * np.mean(
|
|
121
|
-
np.maximum(0, A - self.inputs.strike)
|
|
122
|
-
)
|
|
123
|
-
asian_put = self._discount_factor * np.mean(
|
|
124
|
-
np.maximum(0, self.inputs.strike - A)
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
return [asian_call, asian_put]
|
|
128
|
-
|
|
129
|
-
def _upandoutcall(self) -> float:
|
|
130
|
-
"""Calculate up-and-out barrier call option payoff"""
|
|
131
|
-
S = self._simulatepath
|
|
132
|
-
|
|
133
|
-
# Barrier shift
|
|
134
|
-
barriershift = self.barrier * np.exp(
|
|
135
|
-
0.5826 * self.inputs.volatility * np.sqrt(self.inputs.ttm / self.timestep)
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
value = 0
|
|
139
|
-
for i in range(self.nsims):
|
|
140
|
-
if S[:, i].max() < barriershift:
|
|
141
|
-
value += np.maximum(0, S[-1, i] - self.inputs.strike)
|
|
142
|
-
else:
|
|
143
|
-
value += self.rebate
|
|
144
|
-
|
|
145
|
-
return self._discount_factor * value / self.nsims
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version = "0.0.5"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|