voly 0.0.62__tar.gz → 0.0.63__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.
- {voly-0.0.62/src/voly.egg-info → voly-0.0.63}/PKG-INFO +1 -1
- {voly-0.0.62 → voly-0.0.63}/pyproject.toml +2 -2
- {voly-0.0.62 → voly-0.0.63}/src/voly/client.py +10 -8
- {voly-0.0.62 → voly-0.0.63}/src/voly/core/fit.py +36 -13
- {voly-0.0.62 → voly-0.0.63}/src/voly/formulas.py +84 -1
- {voly-0.0.62 → voly-0.0.63/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.62 → voly-0.0.63}/LICENSE +0 -0
- {voly-0.0.62 → voly-0.0.63}/README.md +0 -0
- {voly-0.0.62 → voly-0.0.63}/setup.cfg +0 -0
- {voly-0.0.62 → voly-0.0.63}/setup.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/__init__.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/core/__init__.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/core/charts.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/core/data.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/core/rnd.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/exceptions.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/models.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly/utils/logger.py +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.62 → voly-0.0.63}/src/voly.egg-info/top_level.txt +0 -0
- {voly-0.0.62 → voly-0.0.63}/tests/test_client.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "voly"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.63"
|
|
8
8
|
description = "Options & volatility research package"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -60,7 +60,7 @@ line_length = 100
|
|
|
60
60
|
multi_line_output = 3
|
|
61
61
|
|
|
62
62
|
[tool.mypy]
|
|
63
|
-
python_version = "0.0.
|
|
63
|
+
python_version = "0.0.63"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -352,27 +352,29 @@ class VolyClient:
|
|
|
352
352
|
|
|
353
353
|
@staticmethod
|
|
354
354
|
def get_iv_surface(fit_results: Dict[str, Any],
|
|
355
|
-
|
|
355
|
+
log_moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
356
356
|
) -> Dict[str, Any]:
|
|
357
357
|
"""
|
|
358
358
|
Generate implied volatility surface using optimized SVI parameters.
|
|
359
359
|
|
|
360
360
|
Parameters:
|
|
361
|
-
|
|
362
|
-
|
|
361
|
+
- fit_results: DataFrame from fit_model()
|
|
362
|
+
- log_moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
|
|
363
|
+
- return_domain: Domain for x-axis values ('log_moneyness', 'moneyness', 'strikes', 'delta')
|
|
363
364
|
|
|
364
365
|
Returns:
|
|
365
|
-
- Tuple of (
|
|
366
|
+
- Tuple of (iv_surface, x_surface)
|
|
366
367
|
"""
|
|
367
368
|
# Generate the surface
|
|
368
|
-
|
|
369
|
+
iv_surface, x_surface = get_iv_surface(
|
|
369
370
|
fit_results=fit_results,
|
|
370
|
-
|
|
371
|
+
log_moneyness_params=log_moneyness_params,
|
|
372
|
+
return_domain=return_domain
|
|
371
373
|
)
|
|
372
374
|
|
|
373
375
|
return {
|
|
374
|
-
'
|
|
375
|
-
'
|
|
376
|
+
'iv_surface': iv_surface,
|
|
377
|
+
'x_surface': x_surface
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
@staticmethod
|
|
@@ -11,6 +11,7 @@ from typing import List, Tuple, Dict, Optional, Union, Any
|
|
|
11
11
|
from scipy.optimize import least_squares
|
|
12
12
|
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
|
|
13
13
|
from voly.utils.logger import logger, catch_exception
|
|
14
|
+
from voly.formulas import get_x_domain
|
|
14
15
|
from voly.exceptions import VolyError
|
|
15
16
|
from voly.models import SVIModel
|
|
16
17
|
import warnings
|
|
@@ -136,19 +137,30 @@ def fit_model(option_chain: pd.DataFrame,
|
|
|
136
137
|
|
|
137
138
|
@catch_exception
|
|
138
139
|
def get_iv_surface(fit_results: pd.DataFrame,
|
|
139
|
-
log_moneyness_params: Tuple[float, float, int] = (-
|
|
140
|
-
) -> Tuple[
|
|
141
|
-
"""
|
|
142
|
-
|
|
140
|
+
log_moneyness_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
141
|
+
return_domain: str = 'log_moneyness') -> Tuple[Dict[str, np.ndarray], np.ndarray]:
|
|
142
|
+
"""
|
|
143
|
+
Generate implied volatility surface using optimized SVI parameters.
|
|
144
|
+
|
|
145
|
+
Parameters:
|
|
146
|
+
- fit_results: DataFrame from fit_model()
|
|
147
|
+
- log_moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
|
|
148
|
+
- return_domain: Domain for x-axis values ('log_moneyness', 'moneyness', 'strikes', 'delta')
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
- Tuple of (iv_surface, x_surface)
|
|
152
|
+
iv_surface: Dictionary mapping maturity names to IV arrays
|
|
153
|
+
x_surface: Dictionary mapping maturity names to requested x domain arrays
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
# Generate implied volatility surface in log-moneyness domain
|
|
143
157
|
min_m, max_m, num_points = log_moneyness_params
|
|
158
|
+
log_moneyness_array = np.linspace(min_m, max_m, num=num_points)
|
|
144
159
|
|
|
145
|
-
# Generate moneyness array and initialize surface dictionary
|
|
146
|
-
moneyness_array = np.linspace(min_m, max_m, num=num_points)
|
|
147
160
|
iv_surface = {}
|
|
148
|
-
|
|
149
|
-
# Generate implied volatility for each maturity
|
|
161
|
+
x_surface = {}
|
|
150
162
|
for maturity in fit_results.columns:
|
|
151
|
-
#
|
|
163
|
+
# Calculate SVI total implied variance and convert to IV
|
|
152
164
|
params = [
|
|
153
165
|
fit_results.loc['a', maturity],
|
|
154
166
|
fit_results.loc['b', maturity],
|
|
@@ -158,8 +170,19 @@ def get_iv_surface(fit_results: pd.DataFrame,
|
|
|
158
170
|
]
|
|
159
171
|
ytm = fit_results.loc['ytm', maturity]
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
iv_surface[maturity] =
|
|
173
|
+
w_svi = np.array([SVIModel.svi(x, *params) for x in log_moneyness_array])
|
|
174
|
+
iv_array = np.sqrt(w_svi / ytm)
|
|
175
|
+
iv_surface[maturity] = iv_array
|
|
176
|
+
|
|
177
|
+
x_domain = get_x_domain(
|
|
178
|
+
log_moneyness_params=log_moneyness_params,
|
|
179
|
+
return_domain=return_domain,
|
|
180
|
+
s=fit_results.loc['s', maturity],
|
|
181
|
+
r=fit_results.loc['r', maturity],
|
|
182
|
+
iv_array=iv_array,
|
|
183
|
+
ytm=fit_results.loc['ytm', maturity]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
x_surface[maturity] = x_domain
|
|
164
187
|
|
|
165
|
-
return
|
|
188
|
+
return iv_surface, x_surface
|
|
@@ -7,7 +7,7 @@ from scipy.stats import norm
|
|
|
7
7
|
from typing import Tuple, Dict, Union, List, Optional
|
|
8
8
|
from voly.utils.logger import catch_exception
|
|
9
9
|
from voly.models import SVIModel
|
|
10
|
-
from
|
|
10
|
+
from typing import Tuple
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@catch_exception
|
|
@@ -276,3 +276,86 @@ def iv(option_price: float, s: float, k: float, r: float, t: float,
|
|
|
276
276
|
|
|
277
277
|
# If we reach here, we didn't converge
|
|
278
278
|
return np.nan
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@catch_exception
|
|
282
|
+
def get_x_domain(log_moneyness_params: Tuple[float, float, int] = (-1.5, 1.5, 1000),
|
|
283
|
+
return_domain: str = 'log_moneyness',
|
|
284
|
+
s: float = None,
|
|
285
|
+
r: float = None,
|
|
286
|
+
iv_array: np.ndarray = None,
|
|
287
|
+
ytm: float = None) -> np.ndarray:
|
|
288
|
+
"""
|
|
289
|
+
Compute the x-domain for a given return type (log-moneyness, moneyness, strikes, or delta).
|
|
290
|
+
|
|
291
|
+
Parameters:
|
|
292
|
+
-----------
|
|
293
|
+
log_moneyness_params : Tuple[float, float, int],
|
|
294
|
+
Parameters for log-moneyness domain: (min_log_moneyness, max_log_moneyness, num_points).
|
|
295
|
+
Default is (-1.5, 1.5, 1000).
|
|
296
|
+
return_domain : str, optional
|
|
297
|
+
The desired domain to return. Options are 'log_moneyness', 'moneyness', 'strikes', or 'delta'.
|
|
298
|
+
Default is 'log_moneyness'.
|
|
299
|
+
s : float, optional
|
|
300
|
+
Spot price of the underlying asset. Required for 'strikes' and 'delta' domains.
|
|
301
|
+
r : float, optional
|
|
302
|
+
Risk-free interest rate. Required for 'delta' domain.
|
|
303
|
+
iv_array : np.ndarray, optional
|
|
304
|
+
Array of implied volatilities. Required for 'delta' domain.
|
|
305
|
+
ytm : float, optional
|
|
306
|
+
Time to maturity in years. Required for 'delta' domain.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
--------
|
|
310
|
+
np.ndarray
|
|
311
|
+
The x-domain array corresponding to the specified return_domain.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
-------
|
|
315
|
+
ValueError
|
|
316
|
+
If required parameters are missing for the specified return_domain.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
# Extract log-moneyness parameters
|
|
320
|
+
min_m, max_m, num_points = log_moneyness_params
|
|
321
|
+
|
|
322
|
+
# Generate the base log-moneyness array
|
|
323
|
+
log_moneyness = np.linspace(min_m, max_m, num_points)
|
|
324
|
+
|
|
325
|
+
# Handle different return domains
|
|
326
|
+
if return_domain == 'log_moneyness':
|
|
327
|
+
return log_moneyness
|
|
328
|
+
|
|
329
|
+
elif return_domain == 'moneyness':
|
|
330
|
+
return np.exp(log_moneyness)
|
|
331
|
+
|
|
332
|
+
elif return_domain == 'strikes':
|
|
333
|
+
if s is None:
|
|
334
|
+
raise ValueError("Spot price 's' is required for return_domain='strikes'.")
|
|
335
|
+
return s / np.exp(log_moneyness) # K = S/exp(log_moneyness)
|
|
336
|
+
|
|
337
|
+
elif return_domain == 'delta':
|
|
338
|
+
# Check for required parameters
|
|
339
|
+
required_params = {'s': s, 'r': r, 'iv_array': iv_array, 'ytm': ytm}
|
|
340
|
+
missing_params = [param for param, value in required_params.items() if value is None]
|
|
341
|
+
if missing_params:
|
|
342
|
+
raise ValueError(f"The following parameters are required for return_domain='delta': {missing_params}")
|
|
343
|
+
|
|
344
|
+
if len(iv_array) != len(log_moneyness):
|
|
345
|
+
raise ValueError(
|
|
346
|
+
f"iv_array must have the same length as the log-moneyness array ({len(log_moneyness)}).")
|
|
347
|
+
|
|
348
|
+
# Compute strikes
|
|
349
|
+
strikes = s / np.exp(log_moneyness)
|
|
350
|
+
|
|
351
|
+
# Compute deltas
|
|
352
|
+
delta_values = np.array([
|
|
353
|
+
delta(s, k, r, vol, ytm, 'call')
|
|
354
|
+
for k, vol in zip(strikes, iv_array)
|
|
355
|
+
])
|
|
356
|
+
|
|
357
|
+
return delta_values
|
|
358
|
+
|
|
359
|
+
else:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
f"Invalid return_domain: {return_domain}. Must be one of ['log_moneyness', 'moneyness', 'strikes', 'delta'].")
|
|
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
|