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.
Files changed (45) hide show
  1. {quantmod-0.0.9 → quantmod-0.1.1}/PKG-INFO +1 -1
  2. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/charts/plotting.py +2 -2
  3. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/derivatives/nse.py +148 -58
  4. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/__init__.py +3 -3
  5. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/binomial.py +1 -14
  6. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/montecarlo.py +1 -17
  7. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/optioninputs.py +13 -0
  8. quantmod-0.1.1/quantmod/risk/var.py +137 -0
  9. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/risk/varbacktest.py +0 -2
  10. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/risk/varinputs.py +0 -5
  11. quantmod-0.1.1/quantmod/version.py +1 -0
  12. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/PKG-INFO +1 -1
  13. quantmod-0.0.9/quantmod/risk/var.py +0 -110
  14. quantmod-0.0.9/quantmod/version.py +0 -1
  15. {quantmod-0.0.9 → quantmod-0.1.1}/LICENSE.txt +0 -0
  16. {quantmod-0.0.9 → quantmod-0.1.1}/README.md +0 -0
  17. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/__init__.py +0 -0
  18. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/_version.py +0 -0
  19. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/charts/__init__.py +0 -0
  20. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/charts/themes.py +0 -0
  21. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/__init__.py +0 -0
  22. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/data/nifty50.csv +0 -0
  23. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/data/spx.csv +0 -0
  24. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/datasets/dataloader.py +0 -0
  25. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/derivatives/__init__.py +0 -0
  26. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/indicators/__init__.py +0 -0
  27. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/indicators/indicators.py +0 -0
  28. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/main.py +0 -0
  29. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/markets/__init__.py +0 -0
  30. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/markets/bb.py +0 -0
  31. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/markets/yahoo.py +0 -0
  32. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/models/blackscholes.py +0 -0
  33. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/risk/__init__.py +0 -0
  34. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/timeseries/__init__.py +0 -0
  35. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/timeseries/performance.py +0 -0
  36. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/timeseries/timeseries.py +0 -0
  37. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod/utils.py +0 -0
  38. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/SOURCES.txt +0 -0
  39. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/dependency_links.txt +0 -0
  40. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/entry_points.txt +0 -0
  41. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/not-zip-safe +0 -0
  42. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/requires.txt +0 -0
  43. {quantmod-0.0.9 → quantmod-0.1.1}/quantmod.egg-info/top_level.txt +0 -0
  44. {quantmod-0.0.9 → quantmod-0.1.1}/setup.cfg +0 -0
  45. {quantmod-0.0.9 → quantmod-0.1.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: quantmod
3
- Version: 0.0.9
3
+ Version: 0.1.1
4
4
  Summary: Quantmod Python Package
5
5
  Home-page: https://kannansingaravelu.com/
6
6
  Author: Kannan Singaravelu
@@ -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] * 100,
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] * 100,
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
- # 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":
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
- def nsefetch(payload):
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
- output = requests.get(payload, headers=headers).json()
47
- # print(output)
52
+ return json.loads(raw)
48
53
  except ValueError:
49
- s = requests.Session()
54
+ refresh_cookies()
55
+ raw = os.popen(cmd).read()
50
56
  try:
51
- output = s.get("http://nseindia.com/option-chain", headers=headers)
52
- output = s.get(payload, headers=headers).json()
57
+ return json.loads(raw)
53
58
  except ValueError:
54
- output = s.get("https://www.nseindia.com", headers=headers)
55
- output = output.json()
56
- # output = s.get("https://www.nseindia.com/option-chain", headers=headers) # replaced http://nseindia.com with https://www.nseindia.com/option-chain
57
- output = s.get(payload, headers=headers).json()
58
- return output
59
-
60
-
61
- headers = {
62
- "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",
63
- "accept-language": "en-US,en;q=0.9,en-IN;q=0.8,en-GB;q=0.7",
64
- "cache-control": "max-age=0",
65
- "priority": "u=0, i",
66
- "sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
67
- "sec-ch-ua-mobile": "?0",
68
- "sec-ch-ua-platform": '"Windows"',
69
- "sec-fetch-dest": "document",
70
- "sec-fetch-mode": "navigate",
71
- "sec-fetch-site": "none",
72
- "sec-fetch-user": "?1",
73
- "upgrade-insecure-requests": "1",
74
- "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",
75
- }
76
- # Curl headers
77
- 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"""
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 BarrierType, MonteCarloOptionPricing
22
- from .binomial import OptionType, ExerciseStyle, BinomialOptionPricing
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,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: quantmod
3
- Version: 0.0.9
3
+ Version: 0.1.1
4
4
  Summary: Quantmod Python Package
5
5
  Home-page: https://kannansingaravelu.com/
6
6
  Author: Kannan Singaravelu
@@ -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