quantmod 0.0.9__tar.gz → 0.1.1__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.9 → quantmod-0.1.1}/PKG-INFO +1 -1
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/charts/plotting.py +2 -2
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/derivatives/nse.py +148 -58
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/__init__.py +3 -3
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/binomial.py +1 -14
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/montecarlo.py +1 -17
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/optioninputs.py +13 -0
- quantmod-0.1.1/quantmod/risk/var.py +137 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/risk/varbacktest.py +0 -2
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/risk/varinputs.py +0 -5
- quantmod-0.1.1/quantmod/version.py +1 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/PKG-INFO +1 -1
- quantmod-0.0.9/quantmod/risk/var.py +0 -110
- quantmod-0.0.9/quantmod/version.py +0 -1
- {quantmod-0.0.9 → quantmod-0.1.1}/LICENSE.txt +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/README.md +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/_version.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/charts/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/charts/themes.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/data/nifty50.csv +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/data/spx.csv +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/dataloader.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/derivatives/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/indicators/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/indicators/indicators.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/main.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/markets/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/markets/bb.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/markets/yahoo.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/blackscholes.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/risk/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/timeseries/__init__.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/timeseries/performance.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/timeseries/timeseries.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/utils.py +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/SOURCES.txt +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/dependency_links.txt +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/entry_points.txt +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/not-zip-safe +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/requires.txt +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/top_level.txt +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/setup.cfg +0 -0
- {quantmod-0.0.9 → quantmod-0.1.1}/setup.py +0 -0
|
@@ -235,7 +235,7 @@ def _plot_histogram(df, columns=None, **kwargs):
|
|
|
235
235
|
for i, col in enumerate(columns):
|
|
236
236
|
fig.add_trace(
|
|
237
237
|
go.Histogram(
|
|
238
|
-
x=df[col]
|
|
238
|
+
x=df[col],
|
|
239
239
|
nbinsx=kwargs.get("nbinsx", 50),
|
|
240
240
|
name=col,
|
|
241
241
|
opacity=0.75,
|
|
@@ -249,7 +249,7 @@ def _plot_histogram(df, columns=None, **kwargs):
|
|
|
249
249
|
r, c = divmod(i, ncols)
|
|
250
250
|
fig.add_trace(
|
|
251
251
|
go.Histogram(
|
|
252
|
-
x=df[col]
|
|
252
|
+
x=df[col],
|
|
253
253
|
nbinsx=kwargs.get("nbinsx", 50),
|
|
254
254
|
name=col,
|
|
255
255
|
opacity=0.75,
|
|
@@ -12,69 +12,159 @@ import logging
|
|
|
12
12
|
import re
|
|
13
13
|
import urllib.parse
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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":
|
|
15
|
+
# --- Configuration ---
|
|
16
|
+
mode = "auto" # can be "local", "vpn", or "auto"
|
|
17
|
+
|
|
18
|
+
headers = {"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",
|
|
19
|
+
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8,en-GB;q=0.7",
|
|
20
|
+
"cache-control": "max-age=0",
|
|
21
|
+
"priority": "u=0, i",
|
|
22
|
+
"sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"', "sec-ch-ua-mobile": "?0",
|
|
23
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
24
|
+
"sec-fetch-dest": "document",
|
|
25
|
+
"sec-fetch-mode": "navigate",
|
|
26
|
+
"sec-fetch-site": "none",
|
|
27
|
+
"sec-fetch-user": "?1",
|
|
28
|
+
"upgrade-insecure-requests": "1",
|
|
29
|
+
"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",
|
|
30
|
+
}
|
|
43
31
|
|
|
44
|
-
|
|
32
|
+
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'''
|
|
33
|
+
|
|
34
|
+
# --- Main function ---
|
|
35
|
+
def nsefetch(payload: str):
|
|
36
|
+
def encode(url: str) -> str:
|
|
37
|
+
if "%26" in url or "%20" in url:
|
|
38
|
+
return url
|
|
39
|
+
return urllib.parse.quote(url, safe=":/?&=")
|
|
40
|
+
|
|
41
|
+
def refresh_cookies():
|
|
42
|
+
os.popen(f'curl -c cookies.txt "https://www.nseindia.com" {curl_headers}').read()
|
|
43
|
+
os.popen(f'curl -b cookies.txt -c cookies.txt "https://www.nseindia.com/option-chain" {curl_headers}').read()
|
|
44
|
+
|
|
45
|
+
def curl_fetch(url: str):
|
|
46
|
+
encoded_url = encode(url)
|
|
47
|
+
if not os.path.exists("cookies.txt"):
|
|
48
|
+
refresh_cookies()
|
|
49
|
+
cmd = f'curl -b cookies.txt "{encoded_url}" {curl_headers}'
|
|
50
|
+
raw = os.popen(cmd).read()
|
|
45
51
|
try:
|
|
46
|
-
|
|
47
|
-
# print(output)
|
|
52
|
+
return json.loads(raw)
|
|
48
53
|
except ValueError:
|
|
49
|
-
|
|
54
|
+
refresh_cookies()
|
|
55
|
+
raw = os.popen(cmd).read()
|
|
50
56
|
try:
|
|
51
|
-
|
|
52
|
-
output = s.get(payload, headers=headers).json()
|
|
57
|
+
return json.loads(raw)
|
|
53
58
|
except ValueError:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
headers =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
return {}
|
|
60
|
+
|
|
61
|
+
def requests_fetch(url: str):
|
|
62
|
+
try:
|
|
63
|
+
s = requests.Session()
|
|
64
|
+
s.get("https://www.nseindia.com", headers=headers, timeout=10)
|
|
65
|
+
s.get("https://www.nseindia.com/option-chain", headers=headers, timeout=10)
|
|
66
|
+
return s.get(url, headers=headers, timeout=10).json()
|
|
67
|
+
except Exception:
|
|
68
|
+
return {}
|
|
69
|
+
|
|
70
|
+
# --- Auto / Mode selection ---
|
|
71
|
+
if mode == "local":
|
|
72
|
+
return requests_fetch(payload)
|
|
73
|
+
elif mode == "vpn":
|
|
74
|
+
return curl_fetch(payload)
|
|
75
|
+
else:
|
|
76
|
+
# Auto mode: try requests first, fallback to curl
|
|
77
|
+
data = requests_fetch(payload)
|
|
78
|
+
if not data:
|
|
79
|
+
print("⚠️ Local fetch failed — switching to curl + cookies.")
|
|
80
|
+
data = curl_fetch(payload)
|
|
81
|
+
return data
|
|
82
|
+
|
|
83
|
+
# --- Utility constants ---
|
|
84
|
+
indices = ["NIFTY", "FINNIFTY", "BANKNIFTY"]
|
|
85
|
+
|
|
86
|
+
# # Constants
|
|
87
|
+
# indices = ["NIFTY", "FINNIFTY", "BANKNIFTY"]
|
|
88
|
+
|
|
89
|
+
# headers = {
|
|
90
|
+
# "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",
|
|
91
|
+
# "accept-language": "en-US,en;q=0.9,en-IN;q=0.8,en-GB;q=0.7",
|
|
92
|
+
# "cache-control": "max-age=0",
|
|
93
|
+
# "priority": "u=0, i",
|
|
94
|
+
# "sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
|
|
95
|
+
# "sec-ch-ua-mobile": "?0",
|
|
96
|
+
# "sec-ch-ua-platform": '"Windows"',
|
|
97
|
+
# "sec-fetch-dest": "document",
|
|
98
|
+
# "sec-fetch-mode": "navigate",
|
|
99
|
+
# "sec-fetch-site": "none",
|
|
100
|
+
# "sec-fetch-user": "?1",
|
|
101
|
+
# "upgrade-insecure-requests": "1",
|
|
102
|
+
# "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",
|
|
103
|
+
# }
|
|
104
|
+
|
|
105
|
+
# # Curl headers
|
|
106
|
+
# 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"""
|
|
107
|
+
|
|
108
|
+
# # https://ipapi.co/json
|
|
109
|
+
# # https://ipinfo.io/json
|
|
110
|
+
|
|
111
|
+
# try:
|
|
112
|
+
# # Try ipapi.co
|
|
113
|
+
# response = requests.get("https://ipapi.co/json/", timeout=5)
|
|
114
|
+
# if response.status_code == 200:
|
|
115
|
+
# data = response.json()
|
|
116
|
+
# country_code = data.get('country_code', '').upper()
|
|
117
|
+
# mode = "local" if country_code == "IN" else "vpn"
|
|
118
|
+
# else:
|
|
119
|
+
# # Fallback to ipinfo.io
|
|
120
|
+
# response = requests.get("https://ipinfo.io/json", timeout=5)
|
|
121
|
+
# if response.status_code == 200:
|
|
122
|
+
# data = response.json()
|
|
123
|
+
# country_code = data.get('country', '').upper()
|
|
124
|
+
# mode = "local" if country_code == "IN" else "vpn"
|
|
125
|
+
# else:
|
|
126
|
+
# mode = "local"
|
|
127
|
+
|
|
128
|
+
# except Exception:
|
|
129
|
+
# mode = "local"
|
|
130
|
+
|
|
131
|
+
# # Force local mode only if you’re sure your machine is in India
|
|
132
|
+
# mode = "local"
|
|
133
|
+
# def nsefetch(payload):
|
|
134
|
+
# if mode == "vpn":
|
|
135
|
+
# if ("%26" in payload) or ("%20" in payload):
|
|
136
|
+
# encoded_url = payload
|
|
137
|
+
# else:
|
|
138
|
+
# encoded_url = urllib.parse.quote(payload, safe=":/?&=")
|
|
139
|
+
# payload_var = 'curl -b cookies.txt "' + encoded_url + '"' + curl_headers + ""
|
|
140
|
+
# try:
|
|
141
|
+
# output = os.popen(payload_var).read()
|
|
142
|
+
# output = json.loads(output)
|
|
143
|
+
# except ValueError: # includes simplejson.decoder.JSONDecodeError:
|
|
144
|
+
# payload2 = "https://www.nseindia.com"
|
|
145
|
+
# output2 = os.popen(
|
|
146
|
+
# 'curl -c cookies.txt "' + payload2 + '"' + curl_headers + ""
|
|
147
|
+
# ).read()
|
|
148
|
+
|
|
149
|
+
# output = os.popen(payload_var).read()
|
|
150
|
+
# output = json.loads(output)
|
|
151
|
+
# return output
|
|
152
|
+
|
|
153
|
+
# else: # mode == "local":
|
|
154
|
+
# try:
|
|
155
|
+
# output = requests.get(payload, headers=headers).json()
|
|
156
|
+
# # print(output)
|
|
157
|
+
# except ValueError:
|
|
158
|
+
# s = requests.Session()
|
|
159
|
+
# try:
|
|
160
|
+
# output = s.get("http://nseindia.com/option-chain", headers=headers)
|
|
161
|
+
# output = s.get(payload, headers=headers).json()
|
|
162
|
+
# except ValueError:
|
|
163
|
+
# output = s.get("https://www.nseindia.com", headers=headers)
|
|
164
|
+
# output = output.json()
|
|
165
|
+
# # output = s.get("https://www.nseindia.com/option-chain", headers=headers) # replaced http://nseindia.com with https://www.nseindia.com/option-chain
|
|
166
|
+
# output = s.get(payload, headers=headers).json()
|
|
167
|
+
# return output
|
|
78
168
|
|
|
79
169
|
|
|
80
170
|
class OptionData:
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
#
|
|
18
18
|
|
|
19
|
-
from .optioninputs import OptionInputs
|
|
19
|
+
from .optioninputs import OptionInputs, OptionType, ExerciseStyle, BarrierType
|
|
20
20
|
from .blackscholes import BlackScholesOptionPricing
|
|
21
|
-
from .montecarlo import
|
|
22
|
-
from .binomial import
|
|
21
|
+
from .montecarlo import MonteCarloOptionPricing
|
|
22
|
+
from .binomial import BinomialOptionPricing
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
__all__ = [
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from pydantic import Field
|
|
3
|
-
from enum import Enum
|
|
4
3
|
import matplotlib.pyplot as plt
|
|
5
4
|
from typing import Optional
|
|
6
|
-
from .optioninputs import OptionInputs
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Enums for option types
|
|
10
|
-
class OptionType(str, Enum):
|
|
11
|
-
CALL = "call"
|
|
12
|
-
PUT = "put"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# Enums for exercise styles
|
|
16
|
-
class ExerciseStyle(str, Enum):
|
|
17
|
-
AMERICAN = "american"
|
|
18
|
-
EUROPEAN = "european"
|
|
5
|
+
from .optioninputs import OptionInputs, OptionType, ExerciseStyle
|
|
19
6
|
|
|
20
7
|
|
|
21
8
|
# Class for Binomial Option Pricing
|
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from enum import Enum
|
|
3
2
|
from pydantic import Field
|
|
4
3
|
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"
|
|
4
|
+
from .optioninputs import OptionInputs, OptionType, ExerciseStyle, BarrierType
|
|
21
5
|
|
|
22
6
|
|
|
23
7
|
class MonteCarloOptionPricing:
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
from pydantic import BaseModel, Field
|
|
2
|
+
from enum import Enum
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
5
|
+
class OptionType(str, Enum):
|
|
6
|
+
CALL = "call"
|
|
7
|
+
PUT = "put"
|
|
8
|
+
|
|
9
|
+
class ExerciseStyle(str, Enum):
|
|
10
|
+
ASIAN = "asian"
|
|
11
|
+
BARRIER = "barrier"
|
|
12
|
+
EUROPEAN = "european"
|
|
13
|
+
AMERICAN = "american"
|
|
14
|
+
|
|
15
|
+
class BarrierType(str, Enum):
|
|
16
|
+
UP_AND_OUT = "up_and_out"
|
|
4
17
|
|
|
5
18
|
class OptionInputs(BaseModel):
|
|
6
19
|
"""
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.stats import norm
|
|
3
|
+
from .varinputs import RiskInputs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Risk Metrics
|
|
7
|
+
class VaRMetrics:
|
|
8
|
+
"""
|
|
9
|
+
Class to calculate various Value at Risk (VaR) metrics.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
inputs : RiskInputs
|
|
14
|
+
Object containing the following option parameters:
|
|
15
|
+
- confidence_level : float
|
|
16
|
+
The confidence level for the VaR calculation.
|
|
17
|
+
- num_simulations : int
|
|
18
|
+
The number of Monte Carlo simulations.
|
|
19
|
+
- portfolio_returns : pd.DataFrame
|
|
20
|
+
Historical returns of portfolio assets or single stock.
|
|
21
|
+
- is_single_stock : bool
|
|
22
|
+
Flag to indicate single stock calculation.
|
|
23
|
+
- portfolio_weights : list of float, optional
|
|
24
|
+
Weights of assets in the portfolio (None for single stock).
|
|
25
|
+
|
|
26
|
+
Attributes
|
|
27
|
+
----------
|
|
28
|
+
parametric_var : float
|
|
29
|
+
The parametric VaR value.
|
|
30
|
+
historical_var : float
|
|
31
|
+
The historical VaR value.
|
|
32
|
+
monte_carlo_var : float
|
|
33
|
+
The Monte Carlo VaR value.
|
|
34
|
+
expected_shortfall : float
|
|
35
|
+
The expected shortfall value.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, inputs: RiskInputs):
|
|
40
|
+
self.confidence_level = inputs.confidence_level
|
|
41
|
+
self.num_simulations = inputs.num_simulations
|
|
42
|
+
self.is_single_stock = inputs.is_single_stock
|
|
43
|
+
|
|
44
|
+
# Convert returns to NumPy array and ensure 2D shape
|
|
45
|
+
self.returns = inputs.portfolio_returns.to_numpy()
|
|
46
|
+
if self.is_single_stock:
|
|
47
|
+
self.returns = self.returns.reshape(-1, 1) # Ensure 2D for consistency
|
|
48
|
+
self.weights = np.array([1.0])
|
|
49
|
+
else:
|
|
50
|
+
if inputs.portfolio_weights is None:
|
|
51
|
+
raise ValueError("Portfolio weights must be provided for portfolio VaR")
|
|
52
|
+
self.weights = np.array(inputs.portfolio_weights)
|
|
53
|
+
if len(self.weights) != self.returns.shape[1]:
|
|
54
|
+
raise ValueError("Portfolio weights must match the number of assets")
|
|
55
|
+
if not np.all(self.weights >= 0):
|
|
56
|
+
raise ValueError("Portfolio weights must be non-negative")
|
|
57
|
+
if not np.isclose(np.sum(self.weights), 1.0, rtol=1e-3):
|
|
58
|
+
raise ValueError("Portfolio weights must sum to 1")
|
|
59
|
+
|
|
60
|
+
# Precompute portfolio returns for efficiency
|
|
61
|
+
self.portfolio_returns = (
|
|
62
|
+
self.returns if self.is_single_stock else self.returns @ self.weights
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if np.any(np.isnan(self.portfolio_returns)) or np.any(np.isinf(self.portfolio_returns)):
|
|
66
|
+
raise ValueError("portfolio_returns contains NaN or infinite values")
|
|
67
|
+
|
|
68
|
+
# Compute risk metrics
|
|
69
|
+
self.parametric_var = self._parametric_var()
|
|
70
|
+
self.historical_var = self._historical_var()
|
|
71
|
+
self.monte_carlo_var = self._monte_carlo_var()
|
|
72
|
+
self.expected_shortfall = self._expected_shortfall()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _parametric_var(self) -> float:
|
|
76
|
+
"""
|
|
77
|
+
Calculate parametric VaR using normal distribution.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
float
|
|
82
|
+
Parametric VaR value.
|
|
83
|
+
"""
|
|
84
|
+
mean_returns = np.mean(self.portfolio_returns)
|
|
85
|
+
std_returns = np.std(self.portfolio_returns, ddof=1) # Use sample standard deviation
|
|
86
|
+
return norm.ppf(1 - self.confidence_level, loc=mean_returns, scale=std_returns)
|
|
87
|
+
|
|
88
|
+
def _historical_var(self) -> float:
|
|
89
|
+
"""
|
|
90
|
+
Calculate historical VaR based on empirical distribution.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
float
|
|
95
|
+
Historical VaR value.
|
|
96
|
+
"""
|
|
97
|
+
return np.percentile(self.portfolio_returns, 100 * (1 - self.confidence_level))
|
|
98
|
+
|
|
99
|
+
def _monte_carlo_var(self) -> float:
|
|
100
|
+
"""
|
|
101
|
+
Calculate Monte Carlo VaR using simulated returns.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
float
|
|
106
|
+
Monte Carlo VaR value.
|
|
107
|
+
"""
|
|
108
|
+
mean_returns = np.mean(self.returns, axis=0)
|
|
109
|
+
|
|
110
|
+
if self.is_single_stock:
|
|
111
|
+
std_returns = np.std(self.returns[:, 0])
|
|
112
|
+
simulated_returns = np.random.normal(
|
|
113
|
+
mean_returns[0], std_returns, self.num_simulations
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
cov_matrix = np.cov(self.returns.T)
|
|
117
|
+
# Regularize covariance matrix
|
|
118
|
+
cov_matrix += np.eye(cov_matrix.shape[0]) * 1e-6
|
|
119
|
+
simulated_returns = np.random.multivariate_normal(
|
|
120
|
+
mean_returns, cov_matrix, self.num_simulations
|
|
121
|
+
)
|
|
122
|
+
simulated_returns = simulated_returns @ self.weights
|
|
123
|
+
|
|
124
|
+
return np.percentile(simulated_returns, 100 * (1 - self.confidence_level))
|
|
125
|
+
|
|
126
|
+
def _expected_shortfall(self) -> float:
|
|
127
|
+
"""
|
|
128
|
+
Calculate expected shortfall (conditional VaR).
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
float
|
|
133
|
+
Expected shortfall value. Returns NaN if no returns are below VaR.
|
|
134
|
+
"""
|
|
135
|
+
var = self._historical_var()
|
|
136
|
+
tail_returns = self.portfolio_returns[self.portfolio_returns <= var]
|
|
137
|
+
return np.mean(tail_returns) if tail_returns.size > 0 else np.nan
|
|
@@ -15,8 +15,6 @@ class VaRAnalyzer:
|
|
|
15
15
|
Object containing the following option parameters:
|
|
16
16
|
- confidence_level : float
|
|
17
17
|
The confidence level for the VaR calculation.
|
|
18
|
-
- lookback_period : int
|
|
19
|
-
The number of historical days for risk estimation.
|
|
20
18
|
- num_simulations : int
|
|
21
19
|
The number of Monte Carlo simulations.
|
|
22
20
|
- portfolio_returns : pd.DataFrame
|
|
@@ -12,8 +12,6 @@ class RiskInputs(BaseModel):
|
|
|
12
12
|
- confidence_level : float
|
|
13
13
|
The confidence level for the VaR calculation.
|
|
14
14
|
Must be between 0.90 and 1.0.
|
|
15
|
-
- lookback_period : int
|
|
16
|
-
Number of historical days for risk estimation.
|
|
17
15
|
Must be greater than or equal to 1.
|
|
18
16
|
- num_simulations : int, default: 10000
|
|
19
17
|
Number of Monte Carlo simulations.
|
|
@@ -41,9 +39,6 @@ class RiskInputs(BaseModel):
|
|
|
41
39
|
confidence_level: float = Field(
|
|
42
40
|
..., ge=0.90, le=1.0, description="The confidence level for the VaR calculation"
|
|
43
41
|
)
|
|
44
|
-
lookback_period: int = Field(
|
|
45
|
-
..., ge=1, description="Number of historical days for risk estimation"
|
|
46
|
-
)
|
|
47
42
|
num_simulations: int = Field(
|
|
48
43
|
10000, ge=1000, le=100000, description="Number of Monte Carlo simulations"
|
|
49
44
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.1.1"
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from scipy.stats import norm
|
|
3
|
-
from .varinputs import RiskInputs
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# Risk Metrics
|
|
7
|
-
class VaRMetrics:
|
|
8
|
-
"""
|
|
9
|
-
Class to calculate various Value at Risk (VaR) metrics.
|
|
10
|
-
|
|
11
|
-
Parameters
|
|
12
|
-
----------
|
|
13
|
-
inputs : RiskInputs
|
|
14
|
-
Object containing the following option parameters:
|
|
15
|
-
- confidence_level : float
|
|
16
|
-
The confidence level for the VaR calculation.
|
|
17
|
-
- lookback_period : int
|
|
18
|
-
The number of historical days for risk estimation.
|
|
19
|
-
- num_simulations : int
|
|
20
|
-
The number of Monte Carlo simulations.
|
|
21
|
-
- portfolio_returns : pd.DataFrame
|
|
22
|
-
Historical returns of portfolio assets or single stock.
|
|
23
|
-
- is_single_stock : bool
|
|
24
|
-
Flag to indicate single stock calculation.
|
|
25
|
-
- portfolio_weights : list of float, optional
|
|
26
|
-
Weights of assets in the portfolio (None for single stock).
|
|
27
|
-
|
|
28
|
-
Attributes
|
|
29
|
-
----------
|
|
30
|
-
parametric_var : float
|
|
31
|
-
The parametric VaR value.
|
|
32
|
-
historical_var : float
|
|
33
|
-
The historical VaR value.
|
|
34
|
-
monte_carlo_var : float
|
|
35
|
-
The Monte Carlo VaR value.
|
|
36
|
-
expected_shortfall : float
|
|
37
|
-
The expected shortfall value.
|
|
38
|
-
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
def __init__(self, inputs: RiskInputs):
|
|
42
|
-
self.confidence_level = inputs.confidence_level
|
|
43
|
-
self.lookback_period = inputs.lookback_period
|
|
44
|
-
self.num_simulations = inputs.num_simulations
|
|
45
|
-
self.returns = inputs.portfolio_returns
|
|
46
|
-
self.is_single_stock = inputs.is_single_stock
|
|
47
|
-
|
|
48
|
-
# Set weights before risk metric calculations
|
|
49
|
-
if self.is_single_stock:
|
|
50
|
-
self.weights = np.array([1.0]) # Single stock, full weight
|
|
51
|
-
else:
|
|
52
|
-
if inputs.portfolio_weights is None:
|
|
53
|
-
raise ValueError(
|
|
54
|
-
"Portfolio weights must be provided for portfolio VaR calculation"
|
|
55
|
-
)
|
|
56
|
-
self.weights = np.array(inputs.portfolio_weights)
|
|
57
|
-
# Only check shape for portfolio (2D returns)
|
|
58
|
-
if len(self.weights) != self.returns.shape[1]:
|
|
59
|
-
raise ValueError("Portfolio weights must match the number of assets")
|
|
60
|
-
|
|
61
|
-
# attributes
|
|
62
|
-
self.parametric_var = self._parametric_var()
|
|
63
|
-
self.historical_var = self._historical_var()
|
|
64
|
-
self.monte_carlo_var = self._monte_carlo_var()
|
|
65
|
-
self.expected_shortfall = self._expected_shortfall()
|
|
66
|
-
|
|
67
|
-
def _parametric_var(self) -> float:
|
|
68
|
-
mean_returns = np.mean(self.returns, axis=0)
|
|
69
|
-
std = np.std(self.returns, axis=0)
|
|
70
|
-
return self.weights @ norm.ppf(
|
|
71
|
-
1 - self.confidence_level, loc=mean_returns, scale=std
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
def _historical_var(self) -> float:
|
|
75
|
-
portfolio_returns = (
|
|
76
|
-
self.returns if self.is_single_stock else self.returns @ self.weights
|
|
77
|
-
)
|
|
78
|
-
return np.percentile(portfolio_returns, 100 * (1 - self.confidence_level))
|
|
79
|
-
|
|
80
|
-
def _monte_carlo_var(self) -> float:
|
|
81
|
-
mean_returns = np.mean(self.returns, axis=0)
|
|
82
|
-
cov_matrix = (
|
|
83
|
-
np.cov(self.returns.T)
|
|
84
|
-
if not self.is_single_stock
|
|
85
|
-
else np.var(self.returns, axis=0)
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
simulated_returns = (
|
|
89
|
-
np.random.normal(mean_returns, np.sqrt(cov_matrix), self.num_simulations)
|
|
90
|
-
if self.is_single_stock
|
|
91
|
-
else np.random.multivariate_normal(
|
|
92
|
-
mean_returns, cov_matrix, self.num_simulations
|
|
93
|
-
)
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
portfolio_simulated_returns = (
|
|
97
|
-
simulated_returns
|
|
98
|
-
if self.is_single_stock
|
|
99
|
-
else simulated_returns @ self.weights
|
|
100
|
-
)
|
|
101
|
-
return np.percentile(
|
|
102
|
-
portfolio_simulated_returns, 100 * (1 - self.confidence_level)
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
def _expected_shortfall(self) -> float:
|
|
106
|
-
portfolio_returns = (
|
|
107
|
-
self.returns if self.is_single_stock else self.returns @ self.weights
|
|
108
|
-
)
|
|
109
|
-
var = self._historical_var()
|
|
110
|
-
return np.mean(portfolio_returns[portfolio_returns <= var])
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version = "0.0.9"
|
|
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
|