voly 0.0.68__py3-none-any.whl → 0.0.70__py3-none-any.whl

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/client.py CHANGED
@@ -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
 
voly/core/charts.py CHANGED
@@ -73,11 +73,11 @@ def plot_volatility_smile(x_domain: np.ndarray,
73
73
  s = maturity_data['underlying_price'].iloc[0]
74
74
  market_x = s / np.exp(market_x)
75
75
  elif domain_type == 'delta':
76
- # For delta, we'd need more complex conversion - only show model curve
77
- pass
76
+ # For delta, we'd need more complex conversion - skip market data for this domain
77
+ market_x = None
78
78
 
79
79
  # Add bid and ask IVs if the domain type allows
80
- if domain_type != 'delta':
80
+ if domain_type != 'delta' and market_x is not None:
81
81
  for iv_type in ['bid_iv', 'ask_iv']:
82
82
  if iv_type in maturity_data.columns:
83
83
  fig.add_trace(
@@ -92,14 +92,21 @@ def plot_volatility_smile(x_domain: np.ndarray,
92
92
 
93
93
  dte_value = maturity_data['dtm'].iloc[0]
94
94
 
95
- # Update layout
96
- fig.update_layout(
97
- title=f'Vol Smile for {maturity} (DTE: {dte_value:.1f})',
98
- xaxis_title=domain_labels.get(domain_type, 'X Domain'),
99
- yaxis_title='Implied Volatility (%)',
100
- template='plotly_dark',
101
- legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
102
- )
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
+ )
103
110
 
104
111
  return fig
105
112
 
@@ -191,7 +198,8 @@ def plot_parameters(fit_results: pd.DataFrame) -> go.Figure:
191
198
  fig.update_layout(
192
199
  title='Raw SVI Parameters Across Expiries',
193
200
  template='plotly_dark',
194
- showlegend=False
201
+ showlegend=False,
202
+ height=800
195
203
  )
196
204
 
197
205
  return fig
@@ -222,7 +230,7 @@ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
222
230
  maturity_names = fit_results.index
223
231
 
224
232
  # Create hover text with maturity info
225
- tick_labels = [f"{m} (DTE: {fit_results.loc[m, 'dtm']:.1f})"
233
+ tick_labels = [f"{m} (DTE: {fit_results.loc[m, 'dtm']:.1f}, YTE: {fit_results.loc[m, 'ytm']:.4f})"
226
234
  for m in maturity_names]
227
235
 
228
236
  # Plot each parameter
@@ -255,7 +263,8 @@ def plot_jw_parameters(fit_results: pd.DataFrame) -> go.Figure:
255
263
  fig.update_layout(
256
264
  title='Jump-Wing Parameters Across Expiries',
257
265
  template='plotly_dark',
258
- showlegend=False
266
+ showlegend=False,
267
+ height=800
259
268
  )
260
269
 
261
270
  return fig
@@ -326,7 +335,8 @@ def plot_fit_performance(fit_results: pd.DataFrame) -> go.Figure:
326
335
  fig.update_layout(
327
336
  title='Model Fitting Accuracy Statistics',
328
337
  template='plotly_dark',
329
- showlegend=False
338
+ showlegend=False,
339
+ height=700
330
340
  )
331
341
 
332
342
  return fig
@@ -354,7 +364,7 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
354
364
  'log_moneyness': 'Log Moneyness',
355
365
  'moneyness': 'Moneyness (S/K)',
356
366
  'strikes': 'Strike Price',
357
- 'delta': 'Delta'
367
+ 'delta': 'Call Delta'
358
368
  }
359
369
 
360
370
  # Define custom colorscale
@@ -371,57 +381,54 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
371
381
  # Default to sequential values
372
382
  maturity_values = list(range(len(maturity_names)))
373
383
 
374
- # Create a mesh grid for each maturity to handle different x domains
375
- X = []
376
- Y = []
377
- Z = []
378
-
379
- for i, m in enumerate(maturity_names):
380
- x_values = x_surface[m]
381
- z_values = iv_surface[m] * 100 # Convert to percentage
382
- y_value = maturity_values[i]
383
-
384
- # Add to point lists
385
- for j in range(len(x_values)):
386
- X.append(x_values[j])
387
- Y.append(y_value)
388
- Z.append(z_values[j])
389
-
390
- # Create 3D scatter plot with lines connecting points within each maturity
391
- fig = go.Figure()
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
+ ])
392
406
 
393
- # Add data as a 3D scatter plot
394
- fig.add_trace(go.Scatter3d(
395
- x=X, y=Y, z=Z,
396
- mode='markers',
397
- marker=dict(
398
- size=3,
399
- color=Z,
400
- colorscale=custom_blue_scale,
401
- opacity=0.8
402
- ),
403
- hovertemplate="<b>%{y:.1f} days</b><br>X: %{x:.4f}<br>IV: %{z:.2f}%"
404
- ))
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()
405
412
 
406
- # Add lines connecting points for each maturity
407
- cumulative_index = 0
408
- for i, m in enumerate(maturity_names):
409
- points_count = len(x_surface[m])
410
- indices = list(range(cumulative_index, cumulative_index + points_count))
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])
411
418
 
412
- if len(indices) > 1:
419
+ # Add a line for this maturity
413
420
  fig.add_trace(go.Scatter3d(
414
- x=[X[j] for j in indices],
415
- y=[Y[j] for j in indices],
416
- z=[Z[j] for j in indices],
421
+ x=x_values,
422
+ y=y_values,
423
+ z=z_values,
417
424
  mode='lines',
418
- line=dict(color='blue', width=3),
419
- showlegend=False,
420
- hoverinfo='none'
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
421
430
  ))
422
431
 
423
- cumulative_index += points_count
424
-
425
432
  # Update layout
426
433
  fig.update_layout(
427
434
  title='Implied Volatility 3D Surface',
@@ -431,7 +438,10 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
431
438
  yaxis_title='Days to Expiry',
432
439
  zaxis_title='Implied Volatility (%)',
433
440
  aspectmode='manual',
434
- aspectratio=dict(x=1.5, y=1, z=1)
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
+ )
435
445
  ),
436
446
  margin=dict(l=65, r=50, b=65, t=90)
437
447
  )
@@ -440,16 +450,17 @@ def plot_3d_surface(x_surface: Dict[str, np.ndarray],
440
450
 
441
451
 
442
452
  @catch_exception
443
- def plot_rnd(moneyness_array: np.ndarray,
453
+ def plot_rnd(x_domain: np.ndarray,
444
454
  rnd_values: np.ndarray,
445
- spot_price: float = 1.0) -> go.Figure:
455
+ spot_price: float = 1.0,
456
+ title: str = 'Risk-Neutral Density') -> go.Figure:
446
457
  """
447
458
  Plot risk-neutral density (RND).
448
459
 
449
460
  Parameters:
450
- - moneyness_array: Grid of log-moneyness values
461
+ - x_domain: Grid of domain values
451
462
  - rnd_values: RND values
452
- - spot_price: Spot price for converting to absolute prices
463
+ - spot_price: Spot price for reference
453
464
  - title: Plot title
454
465
 
455
466
  Returns:
@@ -458,11 +469,12 @@ def plot_rnd(moneyness_array: np.ndarray,
458
469
  # Create figure
459
470
  fig = go.Figure()
460
471
 
461
- # Convert to prices and normalize RND
462
- prices = spot_price * np.exp(moneyness_array)
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)
463
475
 
464
476
  # Normalize the RND to integrate to 1
465
- dx = moneyness_array[1] - moneyness_array[0]
477
+ dx = x_domain[1] - x_domain[0]
466
478
  total_density = np.sum(rnd_values) * dx
467
479
  normalized_rnd = rnd_values / total_density if total_density > 0 else rnd_values
468
480
 
@@ -496,453 +508,13 @@ def plot_rnd(moneyness_array: np.ndarray,
496
508
  font=dict(color='red')
497
509
  )
498
510
 
499
- # Update layout
500
- fig.update_layout(
501
- title='Risk-Neutral Density',
502
- xaxis_title='Price',
503
- yaxis_title='Density',
504
- template='plotly_dark',
505
- showlegend=False
506
- )
507
-
508
- return fig
509
-
510
-
511
- @catch_exception
512
- def plot_rnd_all_expiries(moneyness_array: np.ndarray,
513
- rnd_surface: Dict[str, np.ndarray],
514
- fit_results: Dict[str, Any],
515
- spot_price: float = 1.0) -> go.Figure:
516
- """
517
- Plot risk-neutral densities for all expiries.
518
-
519
- Parameters:
520
- - moneyness_array: Grid of log-moneyness values
521
- - rnd_surface: Dictionary mapping maturity names to RND arrays
522
- - param_matrix: Matrix containing model parameters with maturity info
523
- - spot_price: Spot price for converting to absolute prices
524
-
525
- Returns:
526
- - Plotly figure
527
- """
528
- # Get maturity information
529
- dte_values = fit_results['fit_performance']['DTE']
530
-
531
- # Create figure
532
- fig = go.Figure()
533
-
534
- # Get maturity names in order by DTE
535
- maturity_names = sorted(rnd_surface.keys(), key=lambda x: dte_values[x])
536
-
537
- # Create color scale from purple to green
538
- n_maturities = len(maturity_names)
539
- colors = [f'rgb({int(255 - i * 255 / n_maturities)}, {int(i * 255 / n_maturities)}, 255)'
540
- for i in range(n_maturities)]
541
-
542
- # Convert to prices
543
- prices = spot_price * np.exp(moneyness_array)
544
-
545
- # Add traces for each expiry
546
- for i, maturity_name in enumerate(maturity_names):
547
- rnd = rnd_surface[maturity_name]
548
- dte = dte_values[maturity_name]
549
-
550
- # Normalize the RND
551
- dx = moneyness_array[1] - moneyness_array[0]
552
- total_density = np.sum(rnd) * dx
553
- normalized_rnd = rnd / total_density if total_density > 0 else rnd
554
-
555
- # Add trace
556
- fig.add_trace(
557
- go.Scatter(
558
- x=prices,
559
- y=normalized_rnd,
560
- mode='lines',
561
- name=f"{maturity_name} (DTE: {dte:.1f})",
562
- line=dict(color=colors[i], width=2),
563
- )
564
- )
565
-
566
- # Add vertical line at spot price
567
- fig.add_shape(
568
- type='line',
569
- x0=spot_price, y0=0,
570
- x1=spot_price, y1=1, # Will be scaled automatically
571
- line=dict(color='red', width=2, dash='dash')
572
- )
573
-
574
- # Update layout
575
- fig.update_layout(
576
- title="Risk-Neutral Densities Across Expiries",
577
- xaxis_title='Price',
578
- yaxis_title='Density',
579
- template='plotly_dark',
580
- legend=dict(
581
- yanchor="top",
582
- y=0.99,
583
- xanchor="left",
584
- x=0.01
585
- )
586
- )
587
-
588
- return fig
589
-
590
-
591
- @catch_exception
592
- def plot_rnd_3d(moneyness_array: np.ndarray,
593
- rnd_surface: Dict[str, np.ndarray],
594
- param_matrix: pd.DataFrame,
595
- spot_price: float = 1.0) -> go.Figure:
596
- """
597
- Plot 3D surface of risk-neutral densities.
598
-
599
- Parameters:
600
- - moneyness_array: Grid of log-moneyness values
601
- - rnd_surface: Dictionary mapping maturity names to RND arrays
602
- - param_matrix: Matrix containing model parameters with maturity info
603
- - spot_price: Spot price for converting to absolute prices
604
-
605
- Returns:
606
- - Plotly figure
607
- """
608
- # Get maturity information
609
- dte_values = param_matrix.attrs['dte_values']
610
-
611
- # Get maturity names in order by DTE
612
- maturity_names = sorted(rnd_surface.keys(), key=lambda x: dte_values[x])
613
-
614
- # Extract DTE values for z-axis
615
- dte_list = [dte_values[name] for name in maturity_names]
616
-
617
- # Convert to prices
618
- prices = spot_price * np.exp(moneyness_array)
619
-
620
- # Create z-data matrix and normalize RNDs
621
- z_data = np.zeros((len(maturity_names), len(prices)))
622
-
623
- for i, name in enumerate(maturity_names):
624
- rnd = rnd_surface[name]
625
-
626
- # Normalize the RND
627
- dx = moneyness_array[1] - moneyness_array[0]
628
- total_density = np.sum(rnd) * dx
629
- normalized_rnd = rnd / total_density if total_density > 0 else rnd
630
-
631
- z_data[i] = normalized_rnd
632
-
633
- # Create mesh grid
634
- X, Y = np.meshgrid(prices, dte_list)
635
-
636
- # Create 3D surface
637
- fig = go.Figure(data=[
638
- go.Surface(
639
- z=z_data,
640
- x=X,
641
- y=Y,
642
- colorscale='Viridis',
643
- showscale=True
644
- )
645
- ])
646
-
647
- # Update layout
648
- fig.update_layout(
649
- title="3D Risk-Neutral Density Surface",
650
- scene=dict(
651
- xaxis_title="Price",
652
- yaxis_title="Days to Expiry",
653
- zaxis_title="Density"
654
- ),
655
- margin=dict(l=65, r=50, b=65, t=90),
656
- template="plotly_dark"
657
- )
658
-
659
- return fig
660
-
661
-
662
- @catch_exception
663
- def plot_rnd_statistics(rnd_statistics: pd.DataFrame,
664
- rnd_probabilities: pd.DataFrame) -> Tuple[go.Figure, go.Figure]:
665
- """
666
- Plot RND statistics and probabilities.
667
-
668
- Parameters:
669
- - rnd_statistics: DataFrame with RND statistics
670
- - rnd_probabilities: DataFrame with RND probabilities
671
-
672
- Returns:
673
- - Tuple of (statistics_fig, probabilities_fig)
674
- """
675
- # Create subplot figure for key statistics
676
- stats_fig = make_subplots(
677
- rows=1, cols=3,
678
- subplot_titles=("Standard Deviation (%) vs. DTE",
679
- "Skewness vs. DTE",
680
- "Excess Kurtosis vs. DTE")
681
- )
682
-
683
- # Add traces for each statistic
684
- stats_fig.add_trace(
685
- go.Scatter(
686
- x=rnd_statistics["dte"],
687
- y=rnd_statistics["std_dev_pct"],
688
- mode="lines+markers",
689
- name="Standard Deviation (%)",
690
- hovertemplate="DTE: %{x:.1f}<br>Std Dev: %{y:.2f}%"
691
- ),
692
- row=1, col=1
693
- )
694
-
695
- stats_fig.add_trace(
696
- go.Scatter(
697
- x=rnd_statistics["dte"],
698
- y=rnd_statistics["skewness"],
699
- mode="lines+markers",
700
- name="Skewness",
701
- hovertemplate="DTE: %{x:.1f}<br>Skewness: %{y:.4f}"
702
- ),
703
- row=1, col=2
704
- )
705
-
706
- stats_fig.add_trace(
707
- go.Scatter(
708
- x=rnd_statistics["dte"],
709
- y=rnd_statistics["excess_kurtosis"],
710
- mode="lines+markers",
711
- name="Excess Kurtosis",
712
- hovertemplate="DTE: %{x:.1f}<br>Excess Kurtosis: %{y:.4f}"
713
- ),
714
- row=1, col=3
715
- )
716
-
717
- # Update layout
718
- stats_fig.update_layout(
719
- title="Risk-Neutral Density Statistics Across Expiries",
720
- template="plotly_dark",
721
- height=500,
722
- showlegend=False
723
- )
724
-
725
- # Update axes
726
- stats_fig.update_xaxes(title_text="Days to Expiry", row=1, col=1)
727
- stats_fig.update_xaxes(title_text="Days to Expiry", row=1, col=2)
728
- stats_fig.update_xaxes(title_text="Days to Expiry", row=1, col=3)
729
-
730
- stats_fig.update_yaxes(title_text="Standard Deviation (%)", row=1, col=1)
731
- stats_fig.update_yaxes(title_text="Skewness", row=1, col=2)
732
- stats_fig.update_yaxes(title_text="Excess Kurtosis", row=1, col=3)
733
-
734
- # Create a second figure for probability thresholds
735
- prob_fig = go.Figure()
736
-
737
- # Get probability columns (those starting with "p_")
738
- prob_cols = [col for col in rnd_probabilities.columns if col.startswith("p_")]
739
-
740
- # Sort the columns to ensure they're in order by threshold value
741
- prob_cols_above = sorted([col for col in prob_cols if "above" in col],
742
- key=lambda x: float(x.split("_")[2]))
743
- prob_cols_below = sorted([col for col in prob_cols if "below" in col],
744
- key=lambda x: float(x.split("_")[2]))
745
-
746
- # Color gradients
747
- green_colors = [
748
- 'rgba(144, 238, 144, 1)', # Light green
749
- 'rgba(50, 205, 50, 1)', # Lime green
750
- 'rgba(34, 139, 34, 1)', # Forest green
751
- 'rgba(0, 100, 0, 1)' # Dark green
752
- ]
753
-
754
- red_colors = [
755
- 'rgba(139, 0, 0, 1)', # Dark red
756
- 'rgba(220, 20, 60, 1)', # Crimson
757
- 'rgba(240, 128, 128, 1)', # Light coral
758
- 'rgba(255, 182, 193, 1)' # Light pink/red
759
- ]
760
-
761
- # Add lines for upside probabilities (green)
762
- for i, col in enumerate(prob_cols_above):
763
- threshold = float(col.split("_")[2])
764
- label = f"P(X > {threshold})"
765
-
766
- # Select color based on how far OTM
767
- color_idx = min(i, len(green_colors) - 1)
768
-
769
- prob_fig.add_trace(
770
- go.Scatter(
771
- x=rnd_probabilities["dte"],
772
- y=rnd_probabilities[col] * 100, # Convert to percentage
773
- mode="lines+markers",
774
- name=label,
775
- line=dict(color=green_colors[color_idx], width=3),
776
- marker=dict(size=8, color=green_colors[color_idx]),
777
- hovertemplate="DTE: %{x:.1f}<br>" + label + ": %{y:.2f}%"
778
- )
779
- )
780
-
781
- # Add lines for downside probabilities (red)
782
- for i, col in enumerate(prob_cols_below):
783
- threshold = float(col.split("_")[2])
784
- label = f"P(X < {threshold})"
785
-
786
- # Select color based on how far OTM
787
- color_idx = min(i, len(red_colors) - 1)
788
-
789
- prob_fig.add_trace(
790
- go.Scatter(
791
- x=rnd_probabilities["dte"],
792
- y=rnd_probabilities[col] * 100, # Convert to percentage
793
- mode="lines+markers",
794
- name=label,
795
- line=dict(color=red_colors[color_idx], width=3),
796
- marker=dict(size=8, color=red_colors[color_idx]),
797
- hovertemplate="DTE: %{x:.1f}<br>" + label + ": %{y:.2f}%"
798
- )
799
- )
800
-
801
- # Update layout
802
- prob_fig.update_layout(
803
- title="Probability Thresholds Across Expiries",
804
- xaxis_title="Days to Expiry",
805
- yaxis_title="Probability (%)",
806
- template="plotly_dark",
807
- legend=dict(
808
- yanchor="top",
809
- y=0.99,
810
- xanchor="right",
811
- x=0.99
812
- )
813
- )
814
-
815
- return stats_fig, prob_fig
816
-
817
-
818
- @catch_exception
819
- def plot_cdf(moneyness_array: np.ndarray,
820
- rnd_values: np.ndarray,
821
- spot_price: float = 1.0,
822
- title: str = 'Cumulative Distribution Function') -> go.Figure:
823
- """
824
- Plot the cumulative distribution function (CDF) from RND values.
825
-
826
- Parameters:
827
- - moneyness_array: Grid of log-moneyness values
828
- - rnd_values: RND values
829
- - spot_price: Spot price for converting to absolute prices
830
- - title: Plot title
831
-
832
- Returns:
833
- - Plotly figure
834
- """
835
- # Convert to prices and normalize RND
836
- prices = spot_price * np.exp(moneyness_array)
837
-
838
- # Normalize the RND
839
- dx = moneyness_array[1] - moneyness_array[0]
840
- total_density = np.sum(rnd_values) * dx
841
- normalized_rnd = rnd_values / total_density if total_density > 0 else rnd_values
842
-
843
- # Calculate CDF
844
- cdf = np.cumsum(normalized_rnd) * dx
845
-
846
- # Create figure
847
- fig = go.Figure()
848
-
849
- # Add CDF trace
850
- fig.add_trace(
851
- go.Scatter(
852
- x=prices,
853
- y=cdf,
854
- mode='lines',
855
- name='CDF',
856
- line=dict(color='#00FFC1', width=2)
857
- )
858
- )
859
-
860
- # Add vertical line at spot price
861
- fig.add_shape(
862
- type='line',
863
- x0=spot_price, y0=0,
864
- x1=spot_price, y1=1,
865
- line=dict(color='red', width=2, dash='dash')
866
- )
867
-
868
- # Add horizontal line at CDF=0.5 (median)
869
- fig.add_shape(
870
- type='line',
871
- x0=prices[0], y0=0.5,
872
- x1=prices[-1], y1=0.5,
873
- line=dict(color='orange', width=2, dash='dash')
874
- )
875
-
876
- # Add annotation for spot price
877
- fig.add_annotation(
878
- x=spot_price,
879
- y=1.05,
880
- text=f"Spot: {spot_price}",
881
- showarrow=False,
882
- font=dict(color='red')
883
- )
884
-
885
511
  # Update layout
886
512
  fig.update_layout(
887
513
  title=title,
888
514
  xaxis_title='Price',
889
- yaxis_title='Cumulative Probability',
515
+ yaxis_title='Density',
890
516
  template='plotly_dark',
891
- yaxis=dict(range=[0, 1.1]),
892
517
  showlegend=False
893
518
  )
894
519
 
895
520
  return fig
896
-
897
-
898
- @catch_exception
899
- def plot_pdf(moneyness_array: np.ndarray,
900
- rnd_values: np.ndarray,
901
- spot_price: float = 1.0,
902
- title: str = 'Probability Density Function') -> go.Figure:
903
- """
904
- Plot the probability density function (PDF) from RND values.
905
-
906
- Parameters:
907
- - moneyness_array: Grid of log-moneyness values
908
- - rnd_values: RND values
909
- - spot_price: Spot price for converting to absolute prices
910
- - title: Plot title
911
-
912
- Returns:
913
- - Plotly figure
914
- """
915
- # This is essentially the same as plot_rnd but with a different title
916
- return plot_rnd(moneyness_array, rnd_values, spot_price, title)
917
-
918
-
919
- @catch_exception
920
- def plot_interpolated_surface(
921
- interp_results: Dict[str, Any],
922
- title: str = 'Interpolated Implied Volatility Surface'
923
- ) -> go.Figure:
924
- """
925
- Plot interpolated implied volatility surface.
926
-
927
- Parameters:
928
- - interp_results: Dictionary with interpolation results
929
- - title: Plot title
930
-
931
- Returns:
932
- - Plotly figure
933
- """
934
- # Extract data from interpolation results
935
- moneyness_array = interp_results['moneyness_array']
936
- target_expiries_years = interp_results['target_expiries_years']
937
- iv_surface = interp_results['iv_surface']
938
-
939
- # Create a 3D surface plot
940
- fig = plot_3d_surface(
941
- moneyness=moneyness_array,
942
- expiries=target_expiries_years,
943
- iv_surface=iv_surface,
944
- title=title
945
- )
946
-
947
- return fig
948
-
@@ -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
@@ -1,18 +1,18 @@
1
1
  voly/__init__.py,sha256=8xyDk7rFCn_MOD5hxuv5cxxKZvBVRiSIM7TgaMPpwpw,211
2
- voly/client.py,sha256=fXcS856ZYyJrhHjxUhGnth4N4BQbR-ExMYd25CTpR64,20539
2
+ voly/client.py,sha256=-5-eMMYZwyRZcyupkTyNDUDhCnUwCv4NJzqb20_IRy4,20725
3
3
  voly/exceptions.py,sha256=PBsbn1vNMvKcCJwwJ4lBO6glD85jo1h2qiEmD7ArAjs,92
4
4
  voly/formulas.py,sha256=wSbGAH6GuQThT9QyQY4Ud2eUf9fo1YFHglUmP6fNris,11871
5
5
  voly/models.py,sha256=LXXIlpXZQEfXTuCngxC8Hd3bWtw6wdXDCSGxTLmHM-c,3659
6
6
  voly/core/__init__.py,sha256=bu6fS2I1Pj9fPPnl-zY3L7NqrZSY5Zy6NY2uMUvdhKs,183
7
- voly/core/charts.py,sha256=rsAkVddQVwAwCle9NQ-_zDO-29U7gPF_Zx8n4OjK-X8,28467
7
+ voly/core/charts.py,sha256=4tl-FpaWOKhDT6tPNDYtFLAOjekmwu-pV1vDxIIPZ8c,16134
8
8
  voly/core/data.py,sha256=e8qBArubNqPkrfuIYB_q2WhRf7TKzA4Z3FhMC-xyLEE,8862
9
9
  voly/core/fit.py,sha256=JOr2XjM-I9HtfbyEN0tdGuNCZimQ2ttm4lNUpF-tKb4,9226
10
10
  voly/core/interpolate.py,sha256=ztVIePJZOh-CIbn69wkh1JW2rKywNe2FEewRN0zcSAo,8185
11
11
  voly/core/rnd.py,sha256=8FTU-Qp9epW9yE4XSOdiFGIRXrGyXqF6mVgZn1NMvxk,11813
12
12
  voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
13
13
  voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
14
- voly-0.0.68.dist-info/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
15
- voly-0.0.68.dist-info/METADATA,sha256=GcwW-KAqS4hbQ3ARYxkdDYKqC4U7aef7kQYZbeVeY4U,4092
16
- voly-0.0.68.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
17
- voly-0.0.68.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
18
- voly-0.0.68.dist-info/RECORD,,
14
+ voly-0.0.70.dist-info/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
15
+ voly-0.0.70.dist-info/METADATA,sha256=dqgqKzVmRO4xaFLzzgyfZuCZfZgK-SKQy6rOx9sIIUo,4092
16
+ voly-0.0.70.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
17
+ voly-0.0.70.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
18
+ voly-0.0.70.dist-info/RECORD,,
File without changes
File without changes