voly 0.0.73__tar.gz → 0.0.75__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.73/src/voly.egg-info → voly-0.0.75}/PKG-INFO +1 -1
- {voly-0.0.73 → voly-0.0.75}/pyproject.toml +2 -2
- {voly-0.0.73 → voly-0.0.75}/src/voly/client.py +2 -2
- {voly-0.0.73 → voly-0.0.75}/src/voly/core/charts.py +54 -98
- {voly-0.0.73 → voly-0.0.75}/src/voly/core/data.py +3 -1
- {voly-0.0.73 → voly-0.0.75/src/voly.egg-info}/PKG-INFO +1 -1
- {voly-0.0.73 → voly-0.0.75}/LICENSE +0 -0
- {voly-0.0.73 → voly-0.0.75}/README.md +0 -0
- {voly-0.0.73 → voly-0.0.75}/setup.cfg +0 -0
- {voly-0.0.73 → voly-0.0.75}/setup.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/__init__.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/core/__init__.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/core/fit.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/core/interpolate.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/core/rnd.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/exceptions.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/formulas.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/models.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/utils/__init__.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly/utils/logger.py +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly.egg-info/SOURCES.txt +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly.egg-info/dependency_links.txt +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly.egg-info/requires.txt +0 -0
- {voly-0.0.73 → voly-0.0.75}/src/voly.egg-info/top_level.txt +0 -0
- {voly-0.0.73 → voly-0.0.75}/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.75"
|
|
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.75"
|
|
64
64
|
warn_return_any = true
|
|
65
65
|
warn_unused_configs = true
|
|
66
66
|
disallow_untyped_defs = true
|
|
@@ -361,7 +361,7 @@ class VolyClient:
|
|
|
361
361
|
- return_domain: str Domain for x-axis values ('log_moneyness', 'moneyness', 'strikes', 'delta')
|
|
362
362
|
|
|
363
363
|
Returns:
|
|
364
|
-
-
|
|
364
|
+
- Surface. Dict composed of (iv_surface, x_surface)
|
|
365
365
|
"""
|
|
366
366
|
# Generate the surface
|
|
367
367
|
iv_surface, x_surface = get_iv_surface(
|
|
@@ -370,7 +370,7 @@ class VolyClient:
|
|
|
370
370
|
return_domain=return_domain
|
|
371
371
|
)
|
|
372
372
|
|
|
373
|
-
return
|
|
373
|
+
return iv_surface, x_surface
|
|
374
374
|
|
|
375
375
|
@staticmethod
|
|
376
376
|
def plot_model(fit_results: pd.DataFrame,
|
|
@@ -19,38 +19,38 @@ pio.renderers.default = "browser"
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@catch_exception
|
|
22
|
-
def plot_volatility_smile(
|
|
22
|
+
def plot_volatility_smile(x_array: np.ndarray,
|
|
23
23
|
iv_array: np.ndarray,
|
|
24
24
|
option_chain: pd.DataFrame = None,
|
|
25
25
|
maturity: Optional[str] = None,
|
|
26
|
-
|
|
26
|
+
return_domain: str = 'log_moneyness') -> go.Figure:
|
|
27
27
|
"""
|
|
28
28
|
Plot volatility smile for a single expiry.
|
|
29
29
|
|
|
30
30
|
Parameters:
|
|
31
|
-
-
|
|
31
|
+
- x_array: Array of x-axis values in the specified domain
|
|
32
32
|
- iv_array: Implied volatility values
|
|
33
33
|
- option_chain: Optional market data for comparison
|
|
34
34
|
- maturity: Maturity name for filtering market data
|
|
35
|
-
-
|
|
35
|
+
- return_domain: Type of x-domain ('log_moneyness', 'moneyness', 'strikes', 'delta')
|
|
36
36
|
|
|
37
37
|
Returns:
|
|
38
38
|
- Plotly figure
|
|
39
39
|
"""
|
|
40
|
-
fig = go.Figure()
|
|
41
|
-
|
|
42
40
|
# Map domain types to axis labels
|
|
43
41
|
domain_labels = {
|
|
44
42
|
'log_moneyness': 'Log Moneyness',
|
|
45
|
-
'moneyness': 'Moneyness
|
|
43
|
+
'moneyness': 'Moneyness',
|
|
46
44
|
'strikes': 'Strike Price',
|
|
47
|
-
'delta': '
|
|
45
|
+
'delta': 'Delta'
|
|
48
46
|
}
|
|
49
47
|
|
|
48
|
+
fig = go.Figure()
|
|
49
|
+
|
|
50
50
|
# Add model curve
|
|
51
51
|
fig.add_trace(
|
|
52
52
|
go.Scatter(
|
|
53
|
-
x=
|
|
53
|
+
x=x_array,
|
|
54
54
|
y=iv_array * 100, # Convert to percentage
|
|
55
55
|
mode='lines',
|
|
56
56
|
name='Model',
|
|
@@ -63,37 +63,20 @@ def plot_volatility_smile(x_domain: np.ndarray,
|
|
|
63
63
|
maturity_data = option_chain[option_chain['maturity_name'] == maturity]
|
|
64
64
|
|
|
65
65
|
if not maturity_data.empty:
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# For delta, we'd need more complex conversion - skip market data for this domain
|
|
77
|
-
market_x = None
|
|
78
|
-
|
|
79
|
-
# Add bid and ask IVs if the domain type allows
|
|
80
|
-
if domain_type != 'delta' and market_x is not None:
|
|
81
|
-
for iv_type in ['bid_iv', 'ask_iv']:
|
|
82
|
-
if iv_type in maturity_data.columns:
|
|
83
|
-
fig.add_trace(
|
|
84
|
-
go.Scatter(
|
|
85
|
-
x=market_x,
|
|
86
|
-
y=maturity_data[iv_type] * 100, # Convert to percentage
|
|
87
|
-
mode='markers',
|
|
88
|
-
name=iv_type.replace('_', ' ').upper(),
|
|
89
|
-
marker=dict(size=8, symbol='circle', opacity=0.7)
|
|
90
|
-
)
|
|
66
|
+
# Add bid and ask IVs if available
|
|
67
|
+
for iv_type in ['bid_iv', 'ask_iv']:
|
|
68
|
+
if iv_type in maturity_data.columns:
|
|
69
|
+
fig.add_trace(
|
|
70
|
+
go.Scatter(
|
|
71
|
+
x=maturity_data[return_domain],
|
|
72
|
+
y=maturity_data[iv_type] * 100, # Convert to percentage
|
|
73
|
+
mode='markers',
|
|
74
|
+
name=iv_type.replace('_', ' ').upper(),
|
|
75
|
+
marker=dict(size=8, symbol='circle', opacity=0.7)
|
|
91
76
|
)
|
|
77
|
+
)
|
|
92
78
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# Update layout with DTE
|
|
96
|
-
title = f'Vol Smile for {maturity} (DTE: {dte_value:.1f})'
|
|
79
|
+
title = f'Vol Smile for {maturity} (DTM: {maturity_data["dtm"].iloc[0]:.1f})'
|
|
97
80
|
else:
|
|
98
81
|
title = f'Vol Smile for {maturity}'
|
|
99
82
|
else:
|
|
@@ -102,7 +85,7 @@ def plot_volatility_smile(x_domain: np.ndarray,
|
|
|
102
85
|
# Update layout
|
|
103
86
|
fig.update_layout(
|
|
104
87
|
title=title,
|
|
105
|
-
xaxis_title=domain_labels.get(
|
|
88
|
+
xaxis_title=domain_labels.get(return_domain, 'X Domain'),
|
|
106
89
|
yaxis_title='Implied Volatility (%)',
|
|
107
90
|
template='plotly_dark',
|
|
108
91
|
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
|
|
@@ -115,7 +98,7 @@ def plot_volatility_smile(x_domain: np.ndarray,
|
|
|
115
98
|
def plot_all_smiles(x_surface: Dict[str, np.ndarray],
|
|
116
99
|
iv_surface: Dict[str, np.ndarray],
|
|
117
100
|
option_chain: Optional[pd.DataFrame] = None,
|
|
118
|
-
|
|
101
|
+
return_domain: str = 'log_moneyness') -> List[go.Figure]:
|
|
119
102
|
"""
|
|
120
103
|
Plot volatility smiles for all expiries.
|
|
121
104
|
|
|
@@ -123,18 +106,18 @@ def plot_all_smiles(x_surface: Dict[str, np.ndarray],
|
|
|
123
106
|
- x_surface: Dictionary mapping maturity names to x-domain arrays
|
|
124
107
|
- iv_surface: Dictionary mapping maturity names to IV arrays
|
|
125
108
|
- option_chain: Optional market data for comparison
|
|
126
|
-
-
|
|
109
|
+
- return_domain: Type of x-domain ('log_moneyness', 'moneyness', 'strikes', 'delta')
|
|
127
110
|
|
|
128
111
|
Returns:
|
|
129
112
|
- List of Plotly figures
|
|
130
113
|
"""
|
|
131
114
|
return [
|
|
132
115
|
plot_volatility_smile(
|
|
133
|
-
|
|
116
|
+
x_array=x_surface[maturity],
|
|
134
117
|
iv_array=iv_surface[maturity],
|
|
135
118
|
option_chain=option_chain,
|
|
136
119
|
maturity=maturity,
|
|
137
|
-
|
|
120
|
+
return_domain=return_domain
|
|
138
121
|
)
|
|
139
122
|
for maturity in iv_surface.keys()
|
|
140
123
|
]
|
|
@@ -165,8 +148,8 @@ def plot_raw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
|
165
148
|
maturity_names = fit_results.index
|
|
166
149
|
|
|
167
150
|
# Create hover text with maturity info
|
|
168
|
-
tick_labels = [f"{m} (
|
|
169
|
-
|
|
151
|
+
tick_labels = [f"{m} (DTM: {fit_results.loc[m, 'dtm']:.1f}"
|
|
152
|
+
for m in maturity_names]
|
|
170
153
|
|
|
171
154
|
# Plot each parameter
|
|
172
155
|
for i, param in enumerate(param_names):
|
|
@@ -230,7 +213,7 @@ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
|
|
|
230
213
|
|
|
231
214
|
# Create hover text with maturity info
|
|
232
215
|
tick_labels = [f"{m} (DTE: {fit_results.loc[m, 'dtm']:.1f}, YTE: {fit_results.loc[m, 'ytm']:.4f})"
|
|
233
|
-
|
|
216
|
+
for m in maturity_names]
|
|
234
217
|
|
|
235
218
|
# Plot each parameter
|
|
236
219
|
for i, param in enumerate(param_names):
|
|
@@ -378,53 +361,31 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
|
|
|
378
361
|
# Default to sequential values
|
|
379
362
|
maturity_values = list(range(len(maturity_names)))
|
|
380
363
|
|
|
381
|
-
#
|
|
382
|
-
|
|
383
|
-
if domain_type in ['log_moneyness', 'moneyness']:
|
|
384
|
-
# Create mesh grid
|
|
385
|
-
X, Y = np.meshgrid(list(x_surface.values())[0], maturity_values)
|
|
386
|
-
Z = np.array([iv_surface[m] * 100 for m in maturity_names]) # Convert to percentage
|
|
387
|
-
|
|
388
|
-
# Create figure
|
|
389
|
-
fig = go.Figure(data=[
|
|
390
|
-
go.Surface(
|
|
391
|
-
z=Z,
|
|
392
|
-
x=X,
|
|
393
|
-
y=Y,
|
|
394
|
-
colorscale=custom_blue_scale,
|
|
395
|
-
contours_z=dict(
|
|
396
|
-
show=True,
|
|
397
|
-
usecolormap=True,
|
|
398
|
-
highlightcolor="#0080FF",
|
|
399
|
-
project_z=True
|
|
400
|
-
)
|
|
401
|
-
)
|
|
402
|
-
])
|
|
364
|
+
# Create 3D surface using lines for each maturity
|
|
365
|
+
fig = go.Figure()
|
|
403
366
|
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
#
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
name=maturity
|
|
427
|
-
))
|
|
367
|
+
# Add a line for each maturity
|
|
368
|
+
for i, maturity in enumerate(maturity_names):
|
|
369
|
+
x_values = x_surface[maturity]
|
|
370
|
+
z_values = iv_surface[maturity] * 100 # Convert to percentage
|
|
371
|
+
y_values = np.full_like(x_values, maturity_values[i])
|
|
372
|
+
|
|
373
|
+
# Color based on maturity (blue to light blue gradient)
|
|
374
|
+
color_intensity = i / max(1, len(maturity_names) - 1)
|
|
375
|
+
color = f'rgb({int(30 + 170*color_intensity)}, {int(120 + 80*color_intensity)}, {int(220 - 120*color_intensity)})'
|
|
376
|
+
|
|
377
|
+
# Add trace
|
|
378
|
+
fig.add_trace(go.Scatter3d(
|
|
379
|
+
x=x_values,
|
|
380
|
+
y=y_values,
|
|
381
|
+
z=z_values,
|
|
382
|
+
mode='lines',
|
|
383
|
+
name=maturity,
|
|
384
|
+
line=dict(
|
|
385
|
+
color=color,
|
|
386
|
+
width=5
|
|
387
|
+
)
|
|
388
|
+
))
|
|
428
389
|
|
|
429
390
|
# Update layout
|
|
430
391
|
fig.update_layout(
|
|
@@ -433,12 +394,7 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
|
|
|
433
394
|
scene=dict(
|
|
434
395
|
xaxis_title=domain_labels.get(domain_type, 'X Domain'),
|
|
435
396
|
yaxis_title='Days to Expiry',
|
|
436
|
-
zaxis_title='Implied Volatility (%)'
|
|
437
|
-
aspectmode='manual',
|
|
438
|
-
aspectratio=dict(x=1.5, y=1, z=1),
|
|
439
|
-
camera=dict(
|
|
440
|
-
eye=dict(x=1.5, y=-1.5, z=1)
|
|
441
|
-
)
|
|
397
|
+
zaxis_title='Implied Volatility (%)'
|
|
442
398
|
),
|
|
443
399
|
margin=dict(l=65, r=50, b=65, t=90)
|
|
444
400
|
)
|
|
@@ -224,7 +224,9 @@ def process_option_chain(df: pd.DataFrame, currency: str) -> pd.DataFrame:
|
|
|
224
224
|
df['ask_iv'] = df['ask_iv'].replace({0: np.nan}) / 100
|
|
225
225
|
|
|
226
226
|
# Calculate log-moneyness
|
|
227
|
-
df['log_moneyness'] = np.log(df['
|
|
227
|
+
df['log_moneyness'] = np.log(df['index_price'] / df['strike'])
|
|
228
|
+
# Calculate moneyness
|
|
229
|
+
df['moneyness'] = np.exp(df['log_moneyness'])
|
|
228
230
|
|
|
229
231
|
logger.info(f"Processing complete!")
|
|
230
232
|
|
|
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
|