voly 0.0.29__tar.gz → 0.0.32__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.29/src/voly.egg-info → voly-0.0.32}/PKG-INFO +1 -1
- {voly-0.0.29 → voly-0.0.32}/pyproject.toml +2 -2
- {voly-0.0.29 → voly-0.0.32}/src/voly/client.py +49 -31
- {voly-0.0.29 → voly-0.0.32}/src/voly/core/charts.py +2 -2
- {voly-0.0.29 → voly-0.0.32}/src/voly/core/fit.py +8 -8
- {voly-0.0.29 → voly-0.0.32}/src/voly/core/rnd.py +45 -41
- {voly-0.0.29 → voly-0.0.32}/src/voly/formulas.py +0 -5
- {voly-0.0.29 → voly-0.0.32/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.29 → voly-0.0.32}/LICENSE +0 -0
- {voly-0.0.29 → voly-0.0.32}/README.md +0 -0
- {voly-0.0.29 → voly-0.0.32}/setup.cfg +0 -0
- {voly-0.0.29 → voly-0.0.32}/setup.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/__init__.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/core/__init__.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/core/data.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/exceptions.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/models.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly/utils/logger.py +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.29 → voly-0.0.32}/src/voly.egg-info/top_level.txt +0 -0
- {voly-0.0.29 → voly-0.0.32}/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.32"
|
|
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.32"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -18,8 +18,8 @@ from voly.formulas import (
|
|
|
18
18
|
bs, delta, gamma, vega, theta, rho, vanna, volga, charm, greeks, iv
|
|
19
19
|
)
|
|
20
20
|
from voly.core.data import fetch_option_chain, process_option_chain
|
|
21
|
-
from voly.core.fit import fit_model,
|
|
22
|
-
from voly.core.rnd import
|
|
21
|
+
from voly.core.fit import fit_model, get_iv_surface
|
|
22
|
+
from voly.core.rnd import get_rnd, get_rnd_surface, calculate_pdf, calculate_cdf, calculate_strike_probability
|
|
23
23
|
from voly.core.interpolate import interpolate_model
|
|
24
24
|
from voly.core.charts import (
|
|
25
25
|
plot_all_smiles, plot_3d_surface, plot_parameters, plot_fit_performance,
|
|
@@ -322,9 +322,9 @@ class VolyClient:
|
|
|
322
322
|
return fit_results
|
|
323
323
|
|
|
324
324
|
@staticmethod
|
|
325
|
-
def
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
def get_iv_surface(fit_results: Dict[str, Any],
|
|
326
|
+
moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
327
|
+
) -> Dict[str, Any]:
|
|
328
328
|
"""
|
|
329
329
|
Generate implied volatility surface using optimized SVI parameters.
|
|
330
330
|
|
|
@@ -333,18 +333,15 @@ class VolyClient:
|
|
|
333
333
|
- moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
|
|
334
334
|
|
|
335
335
|
Returns:
|
|
336
|
-
- Tuple of (
|
|
336
|
+
- Tuple of (moneyness_array, iv_surface)
|
|
337
337
|
"""
|
|
338
338
|
# Generate the surface
|
|
339
|
-
|
|
339
|
+
iv_surface = get_iv_surface(
|
|
340
340
|
fit_results=fit_results,
|
|
341
341
|
moneyness_params=moneyness_params
|
|
342
342
|
)
|
|
343
343
|
|
|
344
|
-
return
|
|
345
|
-
'moneyness_grid': moneyness_grid,
|
|
346
|
-
'iv_surface': iv_surface
|
|
347
|
-
}
|
|
344
|
+
return iv_surface
|
|
348
345
|
|
|
349
346
|
@staticmethod
|
|
350
347
|
def plot_model(fit_results: Dict[str, Any],
|
|
@@ -364,7 +361,7 @@ class VolyClient:
|
|
|
364
361
|
"""
|
|
365
362
|
plots = {}
|
|
366
363
|
|
|
367
|
-
|
|
364
|
+
moneyness_array, iv_surface = get_iv_surface(fit_results, moneyness_params)
|
|
368
365
|
|
|
369
366
|
# Extract data from fit results
|
|
370
367
|
raw_param_matrix = fit_results['raw_param_matrix']
|
|
@@ -372,10 +369,10 @@ class VolyClient:
|
|
|
372
369
|
fit_performance = fit_results['fit_performance']
|
|
373
370
|
|
|
374
371
|
# Plot volatility smiles
|
|
375
|
-
plots['smiles'] = plot_all_smiles(
|
|
372
|
+
plots['smiles'] = plot_all_smiles(moneyness_array, iv_surface, market_data)
|
|
376
373
|
|
|
377
374
|
# Plot 3D surface
|
|
378
|
-
plots['surface_3d'] = plot_3d_surface(
|
|
375
|
+
plots['surface_3d'] = plot_3d_surface(moneyness_array, iv_surface)
|
|
379
376
|
|
|
380
377
|
# Plot parameters
|
|
381
378
|
plots['raw_params'], plots['jw_params'] = plot_parameters(raw_param_matrix, jw_param_matrix)
|
|
@@ -390,34 +387,55 @@ class VolyClient:
|
|
|
390
387
|
# -------------------------------------------------------------------------
|
|
391
388
|
|
|
392
389
|
@staticmethod
|
|
393
|
-
def
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
plot: bool = False) -> Dict[str, Any]:
|
|
390
|
+
def get_rnd(svi_params_list: float[List],
|
|
391
|
+
moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
392
|
+
) -> np.ndarray:
|
|
397
393
|
"""
|
|
398
394
|
Calculate risk-neutral density from fitted model.
|
|
399
395
|
|
|
400
396
|
Parameters:
|
|
401
|
-
- fit_results: Dictionary with fitting results from fit_model()
|
|
402
|
-
- maturity: Optional maturity name to calculate RND for a specific expiry
|
|
403
|
-
- spot_price: Current spot price
|
|
404
|
-
- plot: Whether to generate and return plots
|
|
405
397
|
|
|
406
398
|
Returns:
|
|
407
|
-
-
|
|
399
|
+
- Array with RND results over moneyness
|
|
408
400
|
"""
|
|
409
401
|
logger.info("Calculating risk-neutral density")
|
|
410
402
|
|
|
411
|
-
#
|
|
412
|
-
|
|
403
|
+
# Generate the surface
|
|
404
|
+
moneyness_array, iv_surface = get_rnd(
|
|
405
|
+
svi_params_list=svi_params_list,
|
|
406
|
+
moneyness_params=moneyness_params
|
|
407
|
+
)
|
|
413
408
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
409
|
+
return {
|
|
410
|
+
'moneyness_grid': moneyness_array,
|
|
411
|
+
'rnd_values': rnd_values
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
def get_rnd_surface(fit_results: Dict[str, Any],
|
|
416
|
+
moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
417
|
+
) -> Dict[str, np.ndarray]:
|
|
418
|
+
"""
|
|
419
|
+
Calculate risk-neutral density from fitted model.
|
|
420
|
+
|
|
421
|
+
Parameters:
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
- Array with RND results over moneyness
|
|
425
|
+
"""
|
|
426
|
+
logger.info("Calculating risk-neutral density")
|
|
427
|
+
|
|
428
|
+
# Generate the surface
|
|
429
|
+
moneyness_array, iv_surface = get_rnd_surface(
|
|
430
|
+
fit_results=fit_results,
|
|
431
|
+
moneyness_params=moneyness_params
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
'moneyness_grid': moneyness_grid,
|
|
436
|
+
'rnd_values': rnd_values
|
|
437
|
+
}
|
|
419
438
|
|
|
420
|
-
return rnd_results
|
|
421
439
|
|
|
422
440
|
@staticmethod
|
|
423
441
|
def pdf(rnd_results: Dict[str, Any],
|
|
@@ -358,8 +358,8 @@ def plot_3d_surface(moneyness_grid: np.ndarray,
|
|
|
358
358
|
Returns:
|
|
359
359
|
- Plotly figure
|
|
360
360
|
"""
|
|
361
|
-
start_color = '#
|
|
362
|
-
end_color = '#
|
|
361
|
+
start_color = '#60AEFC'
|
|
362
|
+
end_color = '#002040' # Darker blue
|
|
363
363
|
custom_blue_scale = [[0, start_color], [1, end_color]]
|
|
364
364
|
yte_values = list(iv_surface.keys())
|
|
365
365
|
|
|
@@ -265,18 +265,18 @@ def fit_model(market_data: pd.DataFrame,
|
|
|
265
265
|
|
|
266
266
|
|
|
267
267
|
@catch_exception
|
|
268
|
-
def
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
def get_iv_surface(fit_results: Dict[str, Any],
|
|
269
|
+
moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
270
|
+
) -> Dict[str, Any]:
|
|
271
271
|
"""
|
|
272
272
|
Generate implied volatility surface using optimized SVI parameters.
|
|
273
273
|
|
|
274
274
|
Parameters:
|
|
275
|
-
-
|
|
275
|
+
- fit_results: results from fit_model()
|
|
276
276
|
- moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
|
|
277
277
|
|
|
278
278
|
Returns:
|
|
279
|
-
-
|
|
279
|
+
- iv_surface
|
|
280
280
|
"""
|
|
281
281
|
iv_surface = {}
|
|
282
282
|
|
|
@@ -284,7 +284,7 @@ def get_surface(fit_results: Dict[str, Any],
|
|
|
284
284
|
min_m, max_m, num_points = moneyness_params
|
|
285
285
|
|
|
286
286
|
# Generate moneyness grid
|
|
287
|
-
|
|
287
|
+
moneyness_array = np.linspace(min_m, max_m, num=num_points)
|
|
288
288
|
|
|
289
289
|
# Get YTE values from the parameter matrix attributes
|
|
290
290
|
yte_values = fit_results['fit_performance']['YTE']
|
|
@@ -294,7 +294,7 @@ def get_surface(fit_results: Dict[str, Any],
|
|
|
294
294
|
# Generate implied volatility for each expiry
|
|
295
295
|
for maturity, yte in zip(maturity_values, yte_values):
|
|
296
296
|
svi_params = param_matrix[maturity].values
|
|
297
|
-
w_svi = [SVIModel.svi(x, *svi_params) for x in
|
|
297
|
+
w_svi = [SVIModel.svi(x, *svi_params) for x in moneyness_array]
|
|
298
298
|
iv_surface[yte] = np.sqrt(np.array(w_svi) / yte)
|
|
299
299
|
|
|
300
|
-
return
|
|
300
|
+
return iv_surface
|
|
@@ -9,71 +9,75 @@ from typing import Dict, List, Tuple, Optional, Union, Any
|
|
|
9
9
|
from voly.utils.logger import logger, catch_exception
|
|
10
10
|
from voly.exceptions import VolyError
|
|
11
11
|
from voly.models import SVIModel
|
|
12
|
-
from voly.formulas import rnd
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
@catch_exception
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
params: List[float],
|
|
19
|
-
model: Any = SVIModel
|
|
20
|
-
) -> np.ndarray:
|
|
21
|
-
"""
|
|
22
|
-
Calculate the risk-neutral density (RND) from model parameters.
|
|
15
|
+
def rnd(moneyness_array: float, total_var: float) -> float:
|
|
16
|
+
return np.exp(-(moneyness_array ** 2) / (2 * total_var)) / (np.sqrt(2 * np.pi * total_var))
|
|
23
17
|
|
|
24
|
-
Parameters:
|
|
25
|
-
- log_moneyness: Array of log-moneyness values
|
|
26
|
-
- params: Model parameters (e.g., SVI parameters [a, b, sigma, rho, m])
|
|
27
|
-
- model: Model class to use (default: SVIModel)
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
@catch_exception
|
|
20
|
+
def get_rnd(svi_params_list: float[List],
|
|
21
|
+
moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
22
|
+
) -> np.ndarray:
|
|
23
|
+
|
|
32
24
|
# Adjust parameter order to match function signatures
|
|
33
|
-
a, b, sigma, rho, m =
|
|
25
|
+
a, b, sigma, rho, m = svi_params_list
|
|
26
|
+
|
|
27
|
+
# Extract moneyness parameters
|
|
28
|
+
min_m, max_m, num_points = moneyness_params
|
|
29
|
+
|
|
30
|
+
# Generate moneyness grid
|
|
31
|
+
moneyness_array = np.linspace(min_m, max_m, num=num_points)
|
|
34
32
|
|
|
35
33
|
# Calculate total variance
|
|
36
|
-
total_var = np.array([
|
|
34
|
+
total_var = np.array([SVIModel.svi(x, a, b, sigma, rho, m) for x in moneyness_array])
|
|
37
35
|
|
|
38
36
|
# Calculate risk-neutral density using the base RND function
|
|
39
|
-
rnd_values = np.array([rnd(x, var) for x, var in zip(
|
|
37
|
+
rnd_values = np.array([rnd(x, var) for x, var in zip(moneyness_array, total_var)])
|
|
40
38
|
|
|
41
|
-
return rnd_values
|
|
39
|
+
return moneyness_array, rnd_values
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
@catch_exception
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
model: Any = SVIModel
|
|
49
|
-
) -> Dict[str, np.ndarray]:
|
|
43
|
+
def get_rnd_surface(fit_results: Dict[str, Any],
|
|
44
|
+
moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
|
|
45
|
+
) -> Dict[str, np.ndarray]:
|
|
50
46
|
"""
|
|
51
|
-
Calculate RND for all expiries
|
|
47
|
+
Calculate RND for all expiries using the SVI parameter matrix.
|
|
52
48
|
|
|
53
49
|
Parameters:
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- model: Model class to use (default: SVIModel)
|
|
50
|
+
- moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
|
|
51
|
+
- fit_results: results from fit_model()
|
|
57
52
|
|
|
58
53
|
Returns:
|
|
59
54
|
- Dictionary mapping maturity names to RND arrays
|
|
60
55
|
"""
|
|
61
56
|
rnd_surface = {}
|
|
62
57
|
|
|
63
|
-
#
|
|
64
|
-
|
|
58
|
+
# Extract moneyness parameters
|
|
59
|
+
min_m, max_m, num_points = moneyness_params
|
|
60
|
+
|
|
61
|
+
# Generate moneyness grid
|
|
62
|
+
moneyness_array = np.linspace(min_m, max_m, num=num_points)
|
|
63
|
+
|
|
64
|
+
# Get YTE values from the fit results attributes
|
|
65
|
+
yte_values = fit_results['fit_performance']['YTE']
|
|
66
|
+
maturity_values = fit_results['fit_performance']['Maturity']
|
|
67
|
+
param_matrix = fit_results['raw_param_matrix']
|
|
68
|
+
|
|
69
|
+
# Generate implied volatility for each expiry
|
|
70
|
+
for maturity, yte in zip(maturity_values, yte_values):
|
|
71
|
+
svi_params_list = list(param_matrix[maturity].values)
|
|
72
|
+
|
|
73
|
+
# Adjust parameter order to match function signatures
|
|
74
|
+
a, b, sigma, rho, m = svi_params_list
|
|
75
|
+
|
|
76
|
+
# Calculate total variance
|
|
77
|
+
total_var = np.array([SVIModel.svi(x, a, b, sigma, rho, m) for x in moneyness_array])
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
params = param_matrix[maturity_name].values
|
|
69
|
-
|
|
70
|
-
# Calculate RND
|
|
71
|
-
rnd_values = calculate_risk_neutral_density(
|
|
72
|
-
moneyness_grid,
|
|
73
|
-
params,
|
|
74
|
-
yte,
|
|
75
|
-
model=model
|
|
76
|
-
)
|
|
79
|
+
# Calculate risk-neutral density using the base RND function
|
|
80
|
+
rnd_values = np.array([rnd(x, var) for x, var in zip(moneyness_array, total_var)])
|
|
77
81
|
|
|
78
82
|
rnd_surface[maturity_name] = rnd_values
|
|
79
83
|
|
|
@@ -236,8 +236,3 @@ def iv(option_price: float, s: float, k: float, r: float, t: float,
|
|
|
236
236
|
|
|
237
237
|
# If we reach here, we didn't converge
|
|
238
238
|
return np.nan
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
@catch_exception
|
|
242
|
-
def rnd(log_moneyness: float, total_var: float) -> float:
|
|
243
|
-
return np.exp(-(log_moneyness ** 2) / (2 * total_var)) / (np.sqrt(2 * np.pi * total_var))
|
|
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
|