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.
- {quantmod-0.0.8 → quantmod-0.1.0}/PKG-INFO +1 -1
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/charts/plotting.py +95 -12
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/derivatives/nse.py +44 -26
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/__init__.py +3 -2
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/binomial.py +1 -14
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/montecarlo.py +1 -17
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/optioninputs.py +13 -0
- quantmod-0.1.0/quantmod/risk/var.py +137 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/risk/varbacktest.py +0 -2
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/risk/varinputs.py +0 -5
- quantmod-0.1.0/quantmod/version.py +1 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/PKG-INFO +1 -1
- quantmod-0.0.8/quantmod/risk/var.py +0 -108
- quantmod-0.0.8/quantmod/version.py +0 -1
- {quantmod-0.0.8 → quantmod-0.1.0}/LICENSE.txt +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/README.md +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/_version.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/charts/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/charts/themes.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/data/nifty50.csv +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/data/spx.csv +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/datasets/dataloader.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/derivatives/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/indicators/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/indicators/indicators.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/main.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/markets/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/markets/bb.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/markets/yahoo.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/models/blackscholes.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/risk/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/timeseries/__init__.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/timeseries/performance.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/timeseries/timeseries.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod/utils.py +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/SOURCES.txt +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/dependency_links.txt +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/entry_points.txt +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/not-zip-safe +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/requires.txt +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/quantmod.egg-info/top_level.txt +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/setup.cfg +0 -0
- {quantmod-0.0.8 → quantmod-0.1.0}/setup.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
351
|
-
|
|
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=
|
|
357
|
-
values=
|
|
358
|
-
marker_colors=colors[: len(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,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
|
|
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
|