quantmod 0.0.8__tar.gz → 0.1.0__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.8 → quantmod-0.1.0}/PKG-INFO +1 -1
  2. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/charts/plotting.py +95 -12
  3. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/derivatives/nse.py +44 -26
  4. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/__init__.py +3 -2
  5. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/binomial.py +1 -14
  6. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/montecarlo.py +1 -17
  7. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/optioninputs.py +13 -0
  8. quantmod-0.1.0/quantmod/risk/var.py +137 -0
  9. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/risk/varbacktest.py +0 -2
  10. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/risk/varinputs.py +0 -5
  11. quantmod-0.1.0/quantmod/version.py +1 -0
  12. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/PKG-INFO +1 -1
  13. quantmod-0.0.8/quantmod/risk/var.py +0 -108
  14. quantmod-0.0.8/quantmod/version.py +0 -1
  15. {quantmod-0.0.8 → quantmod-0.1.0}/LICENSE.txt +0 -0
  16. {quantmod-0.0.8 → quantmod-0.1.0}/README.md +0 -0
  17. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/__init__.py +0 -0
  18. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/_version.py +0 -0
  19. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/charts/__init__.py +0 -0
  20. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/charts/themes.py +0 -0
  21. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/__init__.py +0 -0
  22. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/data/nifty50.csv +0 -0
  23. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/data/spx.csv +0 -0
  24. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/dataloader.py +0 -0
  25. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/derivatives/__init__.py +0 -0
  26. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/indicators/__init__.py +0 -0
  27. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/indicators/indicators.py +0 -0
  28. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/main.py +0 -0
  29. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/markets/__init__.py +0 -0
  30. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/markets/bb.py +0 -0
  31. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/markets/yahoo.py +0 -0
  32. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/blackscholes.py +0 -0
  33. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/risk/__init__.py +0 -0
  34. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/timeseries/__init__.py +0 -0
  35. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/timeseries/performance.py +0 -0
  36. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/timeseries/timeseries.py +0 -0
  37. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/utils.py +0 -0
  38. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/SOURCES.txt +0 -0
  39. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/dependency_links.txt +0 -0
  40. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/entry_points.txt +0 -0
  41. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/not-zip-safe +0 -0
  42. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/requires.txt +0 -0
  43. {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/top_level.txt +0 -0
  44. {quantmod-0.0.8 → quantmod-0.1.0}/setup.cfg +0 -0
  45. {quantmod-0.0.8 → quantmod-0.1.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: quantmod
3
- Version: 0.0.8
3
+ Version: 0.1.0
4
4
  Summary: Quantmod Python Package
5
5
  Home-page: https://kannansingaravelu.com/
6
6
  Author: Kannan Singaravelu
@@ -29,6 +29,7 @@ Available Chart Types:
29
29
  - "treemap": Treemap chart
30
30
  - "overlay": Overlay multiple series with secondary y-axis
31
31
  - "normalized": Normalized line chart (base=100)
32
+ - "surface": Surface Plot
32
33
 
33
34
  """
34
35
 
@@ -62,6 +63,8 @@ def _iplot(self, kind="line", **kwargs):
62
63
  return _plot_normalized(self, **kwargs)
63
64
  if kind == "overlay":
64
65
  return _plot_overlay(self, **kwargs)
66
+ if kind == "surface":
67
+ return _plot_surface(self, **kwargs)
65
68
  if isinstance(self, pd.Series):
66
69
  df = self.to_frame()
67
70
  else:
@@ -92,6 +95,8 @@ def _iplot(self, kind="line", **kwargs):
92
95
  fig = _plot_pie(df, **kwargs)
93
96
  elif kind == "treemap":
94
97
  fig = _plot_treemap(df, **kwargs)
98
+ elif kind == "surface":
99
+ fig = _plot_surface(df, **kwargs)
95
100
  else:
96
101
  raise ValueError(f"Plot type '{kind}' not supported.")
97
102
 
@@ -126,10 +131,8 @@ def _plot_line(df, x=None, y=None, **kwargs):
126
131
  x_data = df.index if x is None else df[x]
127
132
  y_data = df[y] if isinstance(y, str) else df.iloc[:, 0]
128
133
 
129
- fig = go.Figure(
130
- go.Scatter(x=x_data, y=y_data, mode="lines", name="", **trace_kwargs)
131
- )
132
- _update_layout(fig, kwargs, default_title="Stock Prices", yaxis_title="")
134
+ fig = go.Figure(go.Scatter(x=x_data, y=y_data, mode="lines", **trace_kwargs))
135
+ _update_layout(fig, kwargs, default_title="Line Charts", yaxis_title="")
133
136
  return fig
134
137
 
135
138
 
@@ -153,9 +156,7 @@ def _plot_scatter(df, x=None, y=None, **kwargs):
153
156
  x_data = df.index if x is None else df[x]
154
157
  y_data = df[y] if isinstance(y, str) else df.iloc[:, 0]
155
158
 
156
- fig = go.Figure(
157
- go.Scatter(x=x_data, y=y_data, mode="markers", name="", **trace_kwargs)
158
- )
159
+ fig = go.Figure(go.Scatter(x=x_data, y=y_data, mode="markers", **trace_kwargs))
159
160
  _update_layout(fig, kwargs, default_title="Scatter Plot", yaxis_title="")
160
161
  return fig
161
162
 
@@ -344,18 +345,58 @@ def _plot_box(df, columns=None, **kwargs):
344
345
 
345
346
 
346
347
  def _plot_pie(df, names=None, values=None, **kwargs):
348
+ """
349
+ Plot a pie chart with options to filter only positive values, show only top N slices, and aggregate small values as 'Others'.
350
+
351
+ Parameters:
352
+ - top_n: int or None. If set, only the top N values are shown, the rest are aggregated as 'Others'.
353
+ - threshold: float or None. If set, all values below this fraction (e.g., 0.01 for 1%) of the total are aggregated as 'Others'.
354
+ """
347
355
  theme_name = kwargs.get("theme", DEFAULT_THEME)
348
356
  colors = _get_colors(theme_name)
349
357
 
350
- names_data = df.index if names is None else df[names]
351
- values_data = df.iloc[:, 0] if values is None else df[values]
358
+ # Extract top_n and threshold from kwargs
359
+ top_n = kwargs.pop("top_n", None)
360
+ threshold = kwargs.pop("threshold", None)
361
+
362
+ # For single-column DataFrame, use index and first column
363
+ names_data = df.index.to_list()
364
+ values_data = df.iloc[:, 0].to_list()
365
+
366
+ # Filter out non-positive values (negative and zero)
367
+ filtered = [(n, v) for n, v in zip(names_data, values_data) if v > 0]
368
+ if not filtered:
369
+ raise ValueError("No positive values to plot in pie chart.")
370
+ names_data, values_data = zip(*filtered)
371
+
372
+ # Convert to Series for easier manipulation
373
+ pie_series = pd.Series(values_data, index=names_data)
374
+ pie_series = pie_series.sort_values(ascending=False)
375
+
376
+ # Apply threshold: aggregate values below threshold as 'Others'
377
+ if threshold is not None:
378
+ total = pie_series.sum()
379
+ mask = pie_series / total < threshold
380
+ if mask.any():
381
+ others = pie_series[mask].sum()
382
+ pie_series = pie_series[~mask]
383
+ pie_series["Others"] = others
384
+
385
+ # Apply top_n: keep only top N, aggregate the rest as 'Others'
386
+ if top_n is not None and len(pie_series) > top_n:
387
+ top = pie_series.iloc[:top_n]
388
+ others = pie_series.iloc[top_n:].sum()
389
+ top["Others"] = others
390
+ pie_series = top
352
391
 
353
392
  fig = go.Figure(
354
393
  data=[
355
394
  go.Pie(
356
- labels=names_data,
357
- values=values_data,
358
- marker_colors=colors[: len(names_data)],
395
+ labels=pie_series.index,
396
+ values=pie_series.values,
397
+ marker_colors=colors[: len(pie_series)],
398
+ hole=0.4,
399
+ textinfo="label+percent",
359
400
  )
360
401
  ]
361
402
  )
@@ -441,6 +482,48 @@ def _plot_treemap(df, path=None, values=None, labels=None, parents=None, **kwarg
441
482
  return fig
442
483
 
443
484
 
485
+ def _plot_surface(df, x=None, y=None, z=None, **kwargs):
486
+ """
487
+ Plot a 3D surface plot using Plotly.
488
+ - x: Optional, column name or array for x-axis. If None, use df.columns.
489
+ - y: Optional, column name or array for y-axis. If None, use df.index.
490
+ - z: Optional, 2D array or DataFrame for z values. If None, use df.values.
491
+ - xaxis_title: Optional, string for x-axis title. Defaults to 'X'.
492
+ - yaxis_title: Optional, string for y-axis title. Defaults to 'Y'.
493
+ - zaxis_title: Optional, string for z-axis title. Defaults to 'Z'.
494
+ - title: Optional, string for plot title. Defaults to 'Surface Plot'.
495
+ """
496
+ theme_name = kwargs.get("theme", DEFAULT_THEME)
497
+ colorscale = kwargs.pop("colorscale", "Viridis")
498
+ xaxis_title = kwargs.pop("xaxis_title", "X")
499
+ yaxis_title = kwargs.pop("yaxis_title", "Y")
500
+ zaxis_title = kwargs.pop("zaxis_title", "Z")
501
+ # Don't pop title, just let it flow to _update_layout
502
+
503
+ # Prepare data
504
+ x_data = x if x is not None else df.columns
505
+ y_data = y if y is not None else df.index
506
+ z_data = z if z is not None else df.values
507
+
508
+ fig = go.Figure(
509
+ data=[
510
+ go.Surface(z=z_data, x=x_data, y=y_data, colorscale=colorscale)
511
+ ]
512
+ )
513
+ # For 3D plots, axis titles must be set via update_scenes
514
+ fig.update_scenes(
515
+ xaxis_title_text=xaxis_title,
516
+ yaxis_title_text=yaxis_title,
517
+ zaxis_title_text=zaxis_title
518
+ )
519
+ _update_layout(
520
+ fig,
521
+ kwargs, # Now kwargs no longer contains axis titles
522
+ default_title="Surface Plot"
523
+ )
524
+ return fig
525
+
526
+
444
527
  def _update_layout(fig, user_kwargs, default_title="", xaxis_title="", yaxis_title=""):
445
528
  theme_name = user_kwargs.pop("theme", DEFAULT_THEME)
446
529
  theme_layout = THEMES.get(theme_name, {}).get("layout", {})
@@ -15,11 +15,51 @@ import urllib.parse
15
15
  # Constants
16
16
  indices = ["NIFTY", "FINNIFTY", "BANKNIFTY"]
17
17
 
18
- mode = "local"
18
+ headers = {
19
+ "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",
20
+ "accept-language": "en-US,en;q=0.9,en-IN;q=0.8,en-GB;q=0.7",
21
+ "cache-control": "max-age=0",
22
+ "priority": "u=0, i",
23
+ "sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
24
+ "sec-ch-ua-mobile": "?0",
25
+ "sec-ch-ua-platform": '"Windows"',
26
+ "sec-fetch-dest": "document",
27
+ "sec-fetch-mode": "navigate",
28
+ "sec-fetch-site": "none",
29
+ "sec-fetch-user": "?1",
30
+ "upgrade-insecure-requests": "1",
31
+ "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",
32
+ }
33
+
34
+ # Curl headers
35
+ 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"""
19
36
 
20
- if mode == "vpn":
37
+ # https://ipapi.co/json
38
+ # https://ipinfo.io/json
39
+
40
+ try:
41
+ # Try ipapi.co
42
+ response = requests.get("https://ipapi.co/json/", timeout=5)
43
+ if response.status_code == 200:
44
+ data = response.json()
45
+ country_code = data.get('country_code', '').upper()
46
+ mode = "local" if country_code == "IN" else "vpn"
47
+ else:
48
+ # Fallback to ipinfo.io
49
+ response = requests.get("https://ipinfo.io/json", timeout=5)
50
+ if response.status_code == 200:
51
+ data = response.json()
52
+ country_code = data.get('country', '').upper()
53
+ mode = "local" if country_code == "IN" else "vpn"
54
+ else:
55
+ mode = "local"
56
+
57
+ except Exception:
58
+ mode = "local"
21
59
 
22
- def nsefetch(payload):
60
+
61
+ def nsefetch(payload):
62
+ if mode == "vpn":
23
63
  if ("%26" in payload) or ("%20" in payload):
24
64
  encoded_url = payload
25
65
  else:
@@ -38,10 +78,7 @@ if mode == "vpn":
38
78
  output = json.loads(output)
39
79
  return output
40
80
 
41
-
42
- if mode == "local":
43
-
44
- def nsefetch(payload):
81
+ else: # mode == "local":
45
82
  try:
46
83
  output = requests.get(payload, headers=headers).json()
47
84
  # print(output)
@@ -58,25 +95,6 @@ if mode == "local":
58
95
  return output
59
96
 
60
97
 
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"""
78
-
79
-
80
98
  class OptionData:
81
99
  """
82
100
  A class to fetch and analyze option chain data from NSE.
@@ -16,16 +16,17 @@
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
21
  from .montecarlo import MonteCarloOptionPricing
22
- from .binomial import OptionType, ExerciseStyle, BinomialOptionPricing
22
+ from .binomial import BinomialOptionPricing
23
23
 
24
24
 
25
25
  __all__ = [
26
26
  "OptionInputs",
27
27
  "OptionType",
28
28
  "ExerciseStyle",
29
+ "BarrierType",
29
30
  "BlackScholesOptionPricing",
30
31
  "MonteCarloOptionPricing",
31
32
  "BinomialOptionPricing",
@@ -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.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: quantmod
3
- Version: 0.0.8
3
+ Version: 0.1.0
4
4
  Summary: Quantmod Python Package
5
5
  Home-page: https://kannansingaravelu.com/
6
6
  Author: Kannan Singaravelu
@@ -1,108 +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
- # attributes
49
- self.parametric_var = self._parametric_var()
50
- self.historical_var = self._historical_var()
51
- self.monte_carlo_var = self._monte_carlo_var()
52
- self.expected_shortfall = self._expected_shortfall()
53
-
54
- if self.is_single_stock:
55
- self.weights = np.array([1.0]) # Single stock, full weight
56
- else:
57
- if inputs.portfolio_weights is None:
58
- raise ValueError(
59
- "Portfolio weights must be provided for portfolio VaR calculation"
60
- )
61
- self.weights = np.array(inputs.portfolio_weights)
62
- if len(self.weights) != self.returns.shape[1]:
63
- raise ValueError("Portfolio weights must match the number of assets")
64
-
65
- def _parametric_var(self) -> float:
66
- mean_returns = np.mean(self.returns, axis=0)
67
- std = np.std(self.returns, axis=0)
68
- return self.weights @ norm.ppf(
69
- 1 - self.confidence_level, loc=mean_returns, scale=std
70
- )
71
-
72
- def _historical_var(self) -> float:
73
- portfolio_returns = (
74
- self.returns if self.is_single_stock else self.returns @ self.weights
75
- )
76
- return np.percentile(portfolio_returns, 100 * (1 - self.confidence_level))
77
-
78
- def _monte_carlo_var(self) -> float:
79
- mean_returns = np.mean(self.returns, axis=0)
80
- cov_matrix = (
81
- np.cov(self.returns.T)
82
- if not self.is_single_stock
83
- else np.var(self.returns, axis=0)
84
- )
85
-
86
- simulated_returns = (
87
- np.random.normal(mean_returns, np.sqrt(cov_matrix), self.num_simulations)
88
- if self.is_single_stock
89
- else np.random.multivariate_normal(
90
- mean_returns, cov_matrix, self.num_simulations
91
- )
92
- )
93
-
94
- portfolio_simulated_returns = (
95
- simulated_returns
96
- if self.is_single_stock
97
- else simulated_returns @ self.weights
98
- )
99
- return np.percentile(
100
- portfolio_simulated_returns, 100 * (1 - self.confidence_level)
101
- )
102
-
103
- def _expected_shortfall(self) -> float:
104
- portfolio_returns = (
105
- self.returns if self.is_single_stock else self.returns @ self.weights
106
- )
107
- var = self._historical_var()
108
- return np.mean(portfolio_returns[portfolio_returns <= var])
@@ -1 +0,0 @@
1
- version = "0.0.8"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes