voly 0.0.68__tar.gz → 0.0.70__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 (26) hide show
  1. {voly-0.0.68/src/voly.egg-info → voly-0.0.70}/PKG-INFO +1 -1
  2. {voly-0.0.68 → voly-0.0.70}/pyproject.toml +2 -2
  3. {voly-0.0.68 → voly-0.0.70}/src/voly/client.py +30 -24
  4. voly-0.0.70/src/voly/core/charts.py +520 -0
  5. {voly-0.0.68 → voly-0.0.70/src/voly.egg-info}/PKG-INFO +1 -1
  6. voly-0.0.68/src/voly/core/charts.py +0 -948
  7. {voly-0.0.68 → voly-0.0.70}/LICENSE +0 -0
  8. {voly-0.0.68 → voly-0.0.70}/README.md +0 -0
  9. {voly-0.0.68 → voly-0.0.70}/setup.cfg +0 -0
  10. {voly-0.0.68 → voly-0.0.70}/setup.py +0 -0
  11. {voly-0.0.68 → voly-0.0.70}/src/voly/__init__.py +0 -0
  12. {voly-0.0.68 → voly-0.0.70}/src/voly/core/__init__.py +0 -0
  13. {voly-0.0.68 → voly-0.0.70}/src/voly/core/data.py +0 -0
  14. {voly-0.0.68 → voly-0.0.70}/src/voly/core/fit.py +0 -0
  15. {voly-0.0.68 → voly-0.0.70}/src/voly/core/interpolate.py +0 -0
  16. {voly-0.0.68 → voly-0.0.70}/src/voly/core/rnd.py +0 -0
  17. {voly-0.0.68 → voly-0.0.70}/src/voly/exceptions.py +0 -0
  18. {voly-0.0.68 → voly-0.0.70}/src/voly/formulas.py +0 -0
  19. {voly-0.0.68 → voly-0.0.70}/src/voly/models.py +0 -0
  20. {voly-0.0.68 → voly-0.0.70}/src/voly/utils/__init__.py +0 -0
  21. {voly-0.0.68 → voly-0.0.70}/src/voly/utils/logger.py +0 -0
  22. {voly-0.0.68 → voly-0.0.70}/src/voly.egg-info/SOURCES.txt +0 -0
  23. {voly-0.0.68 → voly-0.0.70}/src/voly.egg-info/dependency_links.txt +0 -0
  24. {voly-0.0.68 → voly-0.0.70}/src/voly.egg-info/requires.txt +0 -0
  25. {voly-0.0.68 → voly-0.0.70}/src/voly.egg-info/top_level.txt +0 -0
  26. {voly-0.0.68 → voly-0.0.70}/tests/test_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voly
3
- Version: 0.0.68
3
+ Version: 0.0.70
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "voly"
7
- version = "0.0.68"
7
+ version = "0.0.70"
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.68"
63
+ python_version = "0.0.70"
64
64
  warn_return_any = true
65
65
  warn_unused_configs = true
66
66
  disallow_untyped_defs = true
@@ -348,7 +348,7 @@ class VolyClient:
348
348
  return fit_results
349
349
 
350
350
  @staticmethod
351
- def get_iv_surface(fit_results: Dict[str, Any],
351
+ def get_iv_surface(fit_results: pd.DataFrame,
352
352
  log_moneyness_params: Tuple[float, float, int] = (-2, 2, 500),
353
353
  return_domain: str = 'log_moneyness',
354
354
  ) -> Dict[str, Any]:
@@ -370,47 +370,53 @@ class VolyClient:
370
370
  return_domain=return_domain
371
371
  )
372
372
 
373
- return {
374
- 'iv_surface': iv_surface,
375
- 'x_surface': x_surface
376
- }
373
+ return {'iv_surface': iv_surface, 'x_surface': x_surface}
377
374
 
378
375
  @staticmethod
379
- def plot_model(fit_results: Dict[str, Any],
376
+ def plot_model(fit_results: pd.DataFrame,
380
377
  option_chain: pd.DataFrame = None,
381
- moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
382
- ) -> Dict[str, go.Figure]:
378
+ log_moneyness_params: Tuple[float, float, int] = (-2, 2, 500),
379
+ return_domain: str = 'log_moneyness',
380
+ ) -> Dict[str, Any]:
383
381
  """
384
- Generate all plots for the fitted model and RND results.
382
+ Generate all plots for the fitted model.
385
383
 
386
384
  Parameters:
387
- - fit_results: Dictionary with fitting results from fit_model()
385
+ - fit_results: DataFrame with fitting results from fit_model()
388
386
  - option_chain: Optional market data for comparison
389
- - moneyness_params: Grid of log-moneyness values
387
+ - log_moneyness_params: Grid of log-moneyness values
388
+ - return_domain: Domain for x-axis values ('log_moneyness', 'moneyness', 'strikes', 'delta')
390
389
 
391
390
  Returns:
392
391
  - Dictionary of plot figures
393
392
  """
394
393
  plots = {}
395
394
 
396
- moneyness_array, iv_surface = get_iv_surface(fit_results, moneyness_params)
397
-
398
- # Extract data from fit results
399
- raw_param_matrix = fit_results['raw_param_matrix']
400
- jw_param_matrix = fit_results['jw_param_matrix']
401
- fit_performance = fit_results['fit_performance']
395
+ # Generate IV surface and domain
396
+ iv_surface, x_surface = get_iv_surface(fit_results, log_moneyness_params, return_domain)
402
397
 
403
398
  # Plot volatility smiles
404
- plots['smiles'] = plot_all_smiles(moneyness_array, iv_surface, option_chain)
405
-
406
- # Plot 3D surface
407
- plots['surface_3d'] = plot_3d_surface(moneyness_array, iv_surface)
399
+ plots['smiles'] = plot_all_smiles(
400
+ x_surface=x_surface,
401
+ iv_surface=iv_surface,
402
+ option_chain=option_chain,
403
+ domain_type=return_domain
404
+ )
408
405
 
409
406
  # Plot parameters
410
- plots['raw_params'], plots['jw_params'] = plot_parameters(raw_param_matrix, jw_param_matrix)
407
+ plots['raw_params'] = plot_parameters(fit_results)
408
+ plots['jw_params'] = plot_jw_parameters(fit_results)
409
+
410
+ # Plot fit statistics
411
+ plots['fit_performance'] = plot_fit_performance(fit_results)
411
412
 
412
- # Plot fit statistics if available
413
- plots['fit_performance'] = plot_fit_performance(fit_performance)
413
+ # Plot 3D surface
414
+ plots['surface_3d'] = plot_3d_surface(
415
+ x_surface=x_surface,
416
+ iv_surface=iv_surface,
417
+ fit_results=fit_results,
418
+ domain_type=return_domain
419
+ )
414
420
 
415
421
  return plots
416
422
 
@@ -0,0 +1,520 @@
1
+ """
2
+ Visualization module for the Voly package.
3
+
4
+ This module provides visualization functions for volatility surfaces,
5
+ risk-neutral densities, and model fitting results.
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ from typing import Dict, List, Tuple, Optional, Union, Any
11
+ from voly.utils.logger import logger, catch_exception
12
+ from voly.models import SVIModel
13
+ import plotly.graph_objects as go
14
+ from plotly.subplots import make_subplots
15
+ import plotly.io as pio
16
+
17
+ # Set default renderer to browser for interactive plots
18
+ pio.renderers.default = "browser"
19
+
20
+
21
+ @catch_exception
22
+ def plot_volatility_smile(x_domain: np.ndarray,
23
+ iv_array: np.ndarray,
24
+ option_chain: pd.DataFrame = None,
25
+ maturity: Optional[str] = None,
26
+ domain_type: str = 'log_moneyness') -> go.Figure:
27
+ """
28
+ Plot volatility smile for a single expiry.
29
+
30
+ Parameters:
31
+ - x_domain: Array of x-axis values (log_moneyness, moneyness, strikes, delta)
32
+ - iv_array: Implied volatility values
33
+ - option_chain: Optional market data for comparison
34
+ - maturity: Maturity name for filtering market data
35
+ - domain_type: Type of x-domain ('log_moneyness', 'moneyness', 'strikes', 'delta')
36
+
37
+ Returns:
38
+ - Plotly figure
39
+ """
40
+ fig = go.Figure()
41
+
42
+ # Map domain types to axis labels
43
+ domain_labels = {
44
+ 'log_moneyness': 'Log Moneyness',
45
+ 'moneyness': 'Moneyness (S/K)',
46
+ 'strikes': 'Strike Price',
47
+ 'delta': 'Call Delta'
48
+ }
49
+
50
+ # Add model curve
51
+ fig.add_trace(
52
+ go.Scatter(
53
+ x=x_domain,
54
+ y=iv_array * 100, # Convert to percentage
55
+ mode='lines',
56
+ name='Model',
57
+ line=dict(color='#0080FF', width=2)
58
+ )
59
+ )
60
+
61
+ # Add market data if provided
62
+ if option_chain is not None and maturity is not None:
63
+ maturity_data = option_chain[option_chain['maturity_name'] == maturity]
64
+
65
+ if not maturity_data.empty:
66
+ # For market data, use log_moneyness by default as x-axis
67
+ market_x = maturity_data['log_moneyness']
68
+
69
+ # If domain is not log_moneyness, convert market data to match the domain
70
+ if domain_type == 'moneyness':
71
+ market_x = np.exp(market_x)
72
+ elif domain_type == 'strikes':
73
+ s = maturity_data['underlying_price'].iloc[0]
74
+ market_x = s / np.exp(market_x)
75
+ elif domain_type == 'delta':
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
+ )
91
+ )
92
+
93
+ dte_value = maturity_data['dtm'].iloc[0]
94
+
95
+ # Update layout with DTE
96
+ title = f'Vol Smile for {maturity} (DTE: {dte_value:.1f})'
97
+ else:
98
+ title = f'Vol Smile for {maturity}'
99
+ else:
100
+ title = 'Volatility Smile'
101
+
102
+ # Update layout
103
+ fig.update_layout(
104
+ title=title,
105
+ xaxis_title=domain_labels.get(domain_type, 'X Domain'),
106
+ yaxis_title='Implied Volatility (%)',
107
+ template='plotly_dark',
108
+ legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
109
+ )
110
+
111
+ return fig
112
+
113
+
114
+ @catch_exception
115
+ def plot_all_smiles(x_surface: Dict[str, np.ndarray],
116
+ iv_surface: Dict[str, np.ndarray],
117
+ option_chain: Optional[pd.DataFrame] = None,
118
+ domain_type: str = 'log_moneyness') -> List[go.Figure]:
119
+ """
120
+ Plot volatility smiles for all expiries.
121
+
122
+ Parameters:
123
+ - x_surface: Dictionary mapping maturity names to x-domain arrays
124
+ - iv_surface: Dictionary mapping maturity names to IV arrays
125
+ - option_chain: Optional market data for comparison
126
+ - domain_type: Type of x-domain ('log_moneyness', 'moneyness', 'strikes', 'delta')
127
+
128
+ Returns:
129
+ - List of Plotly figures
130
+ """
131
+ return [
132
+ plot_volatility_smile(
133
+ x_domain=x_surface[maturity],
134
+ iv_array=iv_surface[maturity],
135
+ option_chain=option_chain,
136
+ maturity=maturity,
137
+ domain_type=domain_type
138
+ )
139
+ for maturity in iv_surface.keys()
140
+ ]
141
+
142
+
143
+ @catch_exception
144
+ def plot_parameters(fit_results: pd.DataFrame) -> go.Figure:
145
+ """
146
+ Plot raw SVI parameters across different expiries.
147
+
148
+ Parameters:
149
+ - fit_results: DataFrame from fit_model() with maturity names as index
150
+
151
+ Returns:
152
+ - Plotly figure
153
+ """
154
+ # Select parameters to plot
155
+ param_names = ['a', 'b', 'sigma', 'rho', 'm']
156
+
157
+ # Create subplots
158
+ fig = make_subplots(
159
+ rows=3, cols=2,
160
+ subplot_titles=[f"Parameter {p}: {SVIModel.PARAM_DESCRIPTIONS.get(p, '')}"
161
+ for p in param_names] + ['']
162
+ )
163
+
164
+ # Get maturity names from index
165
+ maturity_names = fit_results.index
166
+
167
+ # Create hover text with maturity info
168
+ tick_labels = [f"{m} (DTE: {fit_results.loc[m, 'dtm']:.1f}, YTE: {fit_results.loc[m, 'ytm']:.4f})"
169
+ for m in maturity_names]
170
+
171
+ # Plot each parameter
172
+ for i, param in enumerate(param_names):
173
+ row, col = (i // 2) + 1, (i % 2) + 1
174
+
175
+ fig.add_trace(
176
+ go.Scatter(
177
+ x=list(range(len(maturity_names))),
178
+ y=fit_results[param],
179
+ mode='lines+markers',
180
+ name=param,
181
+ line=dict(width=2),
182
+ marker=dict(size=8),
183
+ text=tick_labels,
184
+ hovertemplate="%{text}<br>%{y:.4f}"
185
+ ),
186
+ row=row, col=col
187
+ )
188
+
189
+ # Set x-axis labels
190
+ fig.update_xaxes(
191
+ tickvals=list(range(len(maturity_names))),
192
+ ticktext=maturity_names,
193
+ tickangle=45,
194
+ row=row, col=col
195
+ )
196
+
197
+ # Update layout
198
+ fig.update_layout(
199
+ title='Raw SVI Parameters Across Expiries',
200
+ template='plotly_dark',
201
+ showlegend=False,
202
+ height=800
203
+ )
204
+
205
+ return fig
206
+
207
+
208
+ @catch_exception
209
+ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
210
+ """
211
+ Plot Jump-Wing parameters across different expiries.
212
+
213
+ Parameters:
214
+ - fit_results: DataFrame from fit_model() with maturity names as index
215
+
216
+ Returns:
217
+ - Plotly figure
218
+ """
219
+ # Select parameters to plot
220
+ param_names = ['nu', 'psi', 'p', 'c', 'nu_tilde']
221
+
222
+ # Create subplots
223
+ fig = make_subplots(
224
+ rows=3, cols=2,
225
+ subplot_titles=[f"Parameter {p}: {SVIModel.PARAM_DESCRIPTIONS.get(p, '')}"
226
+ for p in param_names] + ['']
227
+ )
228
+
229
+ # Get maturity names from index
230
+ maturity_names = fit_results.index
231
+
232
+ # Create hover text with maturity info
233
+ tick_labels = [f"{m} (DTE: {fit_results.loc[m, 'dtm']:.1f}, YTE: {fit_results.loc[m, 'ytm']:.4f})"
234
+ for m in maturity_names]
235
+
236
+ # Plot each parameter
237
+ for i, param in enumerate(param_names):
238
+ row, col = (i // 2) + 1, (i % 2) + 1
239
+
240
+ fig.add_trace(
241
+ go.Scatter(
242
+ x=list(range(len(maturity_names))),
243
+ y=fit_results[param],
244
+ mode='lines+markers',
245
+ name=param,
246
+ line=dict(width=2, color='rgb(0, 180, 180)'),
247
+ marker=dict(size=8),
248
+ text=tick_labels,
249
+ hovertemplate="%{text}<br>%{y:.4f}"
250
+ ),
251
+ row=row, col=col
252
+ )
253
+
254
+ # Set x-axis labels
255
+ fig.update_xaxes(
256
+ tickvals=list(range(len(maturity_names))),
257
+ ticktext=maturity_names,
258
+ tickangle=45,
259
+ row=row, col=col
260
+ )
261
+
262
+ # Update layout
263
+ fig.update_layout(
264
+ title='Jump-Wing Parameters Across Expiries',
265
+ template='plotly_dark',
266
+ showlegend=False,
267
+ height=800
268
+ )
269
+
270
+ return fig
271
+
272
+
273
+ @catch_exception
274
+ def plot_fit_performance(fit_results: pd.DataFrame) -> go.Figure:
275
+ """
276
+ Plot the fitting accuracy statistics.
277
+
278
+ Parameters:
279
+ - fit_results: DataFrame from fit_model() with maturity names as index
280
+
281
+ Returns:
282
+ - Plotly figure
283
+ """
284
+ # Define metrics to plot
285
+ metrics = {
286
+ 'rmse': {'title': 'RMSE by Expiry', 'row': 1, 'col': 1, 'ylabel': 'RMSE (%)', 'scale': 100},
287
+ 'mae': {'title': 'MAE by Expiry', 'row': 1, 'col': 2, 'ylabel': 'MAE (%)', 'scale': 100},
288
+ 'r2': {'title': 'R² by Expiry', 'row': 2, 'col': 1, 'ylabel': 'R²', 'scale': 1},
289
+ 'max_error': {'title': 'Max Error by Expiry', 'row': 2, 'col': 2, 'ylabel': 'Max Error (%)', 'scale': 100}
290
+ }
291
+
292
+ # Create subplots
293
+ fig = make_subplots(
294
+ rows=2, cols=2,
295
+ subplot_titles=[metrics[m]['title'] for m in metrics]
296
+ )
297
+
298
+ # Get maturity names from index and create x-axis indices
299
+ maturity_names = fit_results.index
300
+ x_indices = list(range(len(maturity_names)))
301
+
302
+ # Create hover labels
303
+ hover_labels = [f"{m} (DTE: {fit_results.loc[m, 'dtm']:.1f})" for m in maturity_names]
304
+
305
+ # Plot each metric
306
+ for metric, config in metrics.items():
307
+ fig.add_trace(
308
+ go.Scatter(
309
+ x=x_indices,
310
+ y=fit_results[metric] * config['scale'],
311
+ mode='lines+markers',
312
+ name=metric.upper(),
313
+ line=dict(width=2),
314
+ marker=dict(size=8),
315
+ text=hover_labels,
316
+ hovertemplate="%{text}<br>%{y:.4f}"
317
+ ),
318
+ row=config['row'], col=config['col']
319
+ )
320
+
321
+ # Update axes
322
+ fig.update_yaxes(title_text=config['ylabel'], row=config['row'], col=config['col'])
323
+
324
+ # Set x-axis labels for all subplots
325
+ for row in range(1, 3):
326
+ for col in range(1, 3):
327
+ fig.update_xaxes(
328
+ tickvals=x_indices,
329
+ ticktext=maturity_names,
330
+ tickangle=45,
331
+ row=row, col=col
332
+ )
333
+
334
+ # Update layout
335
+ fig.update_layout(
336
+ title='Model Fitting Accuracy Statistics',
337
+ template='plotly_dark',
338
+ showlegend=False,
339
+ height=700
340
+ )
341
+
342
+ return fig
343
+
344
+
345
+ @catch_exception
346
+ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
347
+ iv_surface: Dict[str, np.ndarray],
348
+ fit_results: pd.DataFrame = None,
349
+ domain_type: str = 'log_moneyness') -> go.Figure:
350
+ """
351
+ Plot 3D implied volatility surface.
352
+
353
+ Parameters:
354
+ - x_surface: Dictionary mapping maturity names to x-domain arrays
355
+ - iv_surface: Dictionary mapping maturity names to IV arrays
356
+ - fit_results: Optional DataFrame with maturity information
357
+ - domain_type: Type of x-domain ('log_moneyness', 'moneyness', 'strikes', 'delta')
358
+
359
+ Returns:
360
+ - Plotly figure
361
+ """
362
+ # Map domain types to axis labels
363
+ domain_labels = {
364
+ 'log_moneyness': 'Log Moneyness',
365
+ 'moneyness': 'Moneyness (S/K)',
366
+ 'strikes': 'Strike Price',
367
+ 'delta': 'Call Delta'
368
+ }
369
+
370
+ # Define custom colorscale
371
+ custom_blue_scale = [[0, '#60AEFC'], [1, '#002040']]
372
+
373
+ # Get maturity names
374
+ maturity_names = list(iv_surface.keys())
375
+
376
+ # Get z-axis values (days to expiry)
377
+ if fit_results is not None:
378
+ # Use DTM values from fit_results
379
+ maturity_values = [fit_results.loc[name, 'dtm'] for name in maturity_names]
380
+ else:
381
+ # Default to sequential values
382
+ maturity_values = list(range(len(maturity_names)))
383
+
384
+ # For domains with uniform x values across maturities (like log_moneyness, moneyness)
385
+ # we can use standard surface plot
386
+ if domain_type in ['log_moneyness', 'moneyness']:
387
+ # Create mesh grid
388
+ X, Y = np.meshgrid(list(x_surface.values())[0], maturity_values)
389
+ Z = np.array([iv_surface[m] * 100 for m in maturity_names]) # Convert to percentage
390
+
391
+ # Create figure
392
+ fig = go.Figure(data=[
393
+ go.Surface(
394
+ z=Z,
395
+ x=X,
396
+ y=Y,
397
+ colorscale=custom_blue_scale,
398
+ contours_z=dict(
399
+ show=True,
400
+ usecolormap=True,
401
+ highlightcolor="#0080FF",
402
+ project_z=True
403
+ )
404
+ )
405
+ ])
406
+
407
+ # For domains that might have different x values per maturity (like strikes, delta)
408
+ # we need to use a different approach
409
+ else:
410
+ # Create a 3D scatter plot with lines
411
+ fig = go.Figure()
412
+
413
+ # For each maturity, create a curve
414
+ for i, maturity in enumerate(maturity_names):
415
+ x_values = x_surface[maturity]
416
+ z_values = iv_surface[maturity] * 100 # Convert to percentage
417
+ y_values = np.full_like(x_values, maturity_values[i])
418
+
419
+ # Add a line for this maturity
420
+ fig.add_trace(go.Scatter3d(
421
+ x=x_values,
422
+ y=y_values,
423
+ z=z_values,
424
+ mode='lines',
425
+ line=dict(
426
+ color=f'rgb({30 + 225 * (i / len(maturity_names))}, {30 + 150 * (i / len(maturity_names))}, {200 - 170 * (i / len(maturity_names))})',
427
+ width=5
428
+ ),
429
+ name=maturity
430
+ ))
431
+
432
+ # Update layout
433
+ fig.update_layout(
434
+ title='Implied Volatility 3D Surface',
435
+ template='plotly_dark',
436
+ scene=dict(
437
+ xaxis_title=domain_labels.get(domain_type, 'X Domain'),
438
+ yaxis_title='Days to Expiry',
439
+ zaxis_title='Implied Volatility (%)',
440
+ aspectmode='manual',
441
+ aspectratio=dict(x=1.5, y=1, z=1),
442
+ camera=dict(
443
+ eye=dict(x=1.5, y=-1.5, z=1)
444
+ )
445
+ ),
446
+ margin=dict(l=65, r=50, b=65, t=90)
447
+ )
448
+
449
+ return fig
450
+
451
+
452
+ @catch_exception
453
+ def plot_rnd(x_domain: np.ndarray,
454
+ rnd_values: np.ndarray,
455
+ spot_price: float = 1.0,
456
+ title: str = 'Risk-Neutral Density') -> go.Figure:
457
+ """
458
+ Plot risk-neutral density (RND).
459
+
460
+ Parameters:
461
+ - x_domain: Grid of domain values
462
+ - rnd_values: RND values
463
+ - spot_price: Spot price for reference
464
+ - title: Plot title
465
+
466
+ Returns:
467
+ - Plotly figure
468
+ """
469
+ # Create figure
470
+ fig = go.Figure()
471
+
472
+ # Convert x_domain to prices (assuming it's in log_moneyness)
473
+ # This may need adjustment if the domain is not log_moneyness
474
+ prices = spot_price * np.exp(x_domain)
475
+
476
+ # Normalize the RND to integrate to 1
477
+ dx = x_domain[1] - x_domain[0]
478
+ total_density = np.sum(rnd_values) * dx
479
+ normalized_rnd = rnd_values / total_density if total_density > 0 else rnd_values
480
+
481
+ # Add trace
482
+ fig.add_trace(
483
+ go.Scatter(
484
+ x=prices,
485
+ y=normalized_rnd,
486
+ mode='lines',
487
+ name='RND',
488
+ line=dict(color='#00FFC1', width=2),
489
+ fill='tozeroy',
490
+ fillcolor='rgba(0, 255, 193, 0.2)'
491
+ )
492
+ )
493
+
494
+ # Add vertical line at spot price
495
+ fig.add_shape(
496
+ type='line',
497
+ x0=spot_price, y0=0,
498
+ x1=spot_price, y1=max(normalized_rnd) * 1.1,
499
+ line=dict(color='red', width=2, dash='dash')
500
+ )
501
+
502
+ # Add annotation for spot price
503
+ fig.add_annotation(
504
+ x=spot_price,
505
+ y=max(normalized_rnd) * 1.15,
506
+ text=f"Spot: {spot_price}",
507
+ showarrow=False,
508
+ font=dict(color='red')
509
+ )
510
+
511
+ # Update layout
512
+ fig.update_layout(
513
+ title=title,
514
+ xaxis_title='Price',
515
+ yaxis_title='Density',
516
+ template='plotly_dark',
517
+ showlegend=False
518
+ )
519
+
520
+ return fig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voly
3
- Version: 0.0.68
3
+ Version: 0.0.70
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT