""" Probability table components for displaying probability data in structured format. """ import numpy as np import pandas as pd import plotly.graph_objects as go from typing import Dict, List, Optional from src.visualization.themes import DARK_THEME def create_probability_table( probabilities: Dict[str, float], spot_price: float, title: str = "Probability Analysis" ) -> go.Figure: """ Create formatted table of probabilities. Args: probabilities: Dictionary of probability labels and values spot_price: Current spot price title: Table title Returns: Plotly table figure """ # Prepare data labels = [] values = [] colors = [] for label, prob in probabilities.items(): labels.append(label) values.append(f"{prob*100:.2f}%") # Color code by probability level if prob > 0.3: colors.append('rgba(0, 255, 136, 0.3)') # Green for high prob elif prob > 0.15: colors.append('rgba(255, 215, 0, 0.3)') # Yellow for medium prob else: colors.append('rgba(255, 68, 68, 0.3)') # Red for low prob # Create table fig = go.Figure(data=[go.Table( header=dict( values=['Scenario', 'Probability'], fill_color=DARK_THEME['plot_bg'], align='left', font=dict(color=DARK_THEME['text'], size=14), height=40 ), cells=dict( values=[labels, values], fill_color=[colors, colors], align='left', font=dict(color=DARK_THEME['text'], size=12), height=30 ) )]) fig.update_layout( title=title, paper_bgcolor=DARK_THEME['background'], font=dict(color=DARK_THEME['text']), height=min(400, 100 + len(labels) * 35), margin=dict(l=10, r=10, t=50, b=10) ) return fig def create_strikes_table( strikes: np.ndarray, probabilities: np.ndarray, spot_price: float, num_strikes: int = 10, title: str = "Strike Probabilities" ) -> go.Figure: """ Create table showing probabilities at specific strike levels. Args: strikes: Strike prices probabilities: Probability values (CDF - cumulative) spot_price: Current spot price num_strikes: Number of strikes to display title: Table title Returns: Plotly table figure """ # Select strikes around spot price spot_idx = np.argmin(np.abs(strikes - spot_price)) # Get range of strikes start_idx = max(0, spot_idx - num_strikes // 2) end_idx = min(len(strikes), start_idx + num_strikes) start_idx = max(0, end_idx - num_strikes) # Adjust if at end selected_strikes = strikes[start_idx:end_idx] selected_probs = probabilities[start_idx:end_idx] # Create data strike_labels = [f"${s:.2f}" for s in selected_strikes] prob_below = [f"{p*100:.2f}%" for p in selected_probs] prob_above = [f"{(1-p)*100:.2f}%" for p in selected_probs] # Color code strikes relative to spot colors = [] for strike in selected_strikes: if abs(strike - spot_price) < spot_price * 0.01: # Within 1% of spot colors.append('rgba(0, 255, 136, 0.3)') # Green for ATM elif strike < spot_price: colors.append('rgba(0, 217, 255, 0.2)') # Cyan for ITM else: colors.append('rgba(255, 0, 255, 0.2)') # Magenta for OTM # Create table fig = go.Figure(data=[go.Table( header=dict( values=['Strike', 'P(S < K)', 'P(S > K)'], fill_color=DARK_THEME['plot_bg'], align='center', font=dict(color=DARK_THEME['text'], size=14), height=40 ), cells=dict( values=[strike_labels, prob_below, prob_above], fill_color=[colors, colors, colors], align='center', font=dict(color=DARK_THEME['text'], size=12), height=30 ) )]) fig.update_layout( title=title, paper_bgcolor=DARK_THEME['background'], font=dict(color=DARK_THEME['text']), height=min(500, 100 + num_strikes * 35), margin=dict(l=10, r=10, t=50, b=10) ) return fig def create_statistics_table( stats: Dict[str, float], spot_price: float, title: str = "PDF Statistics" ) -> go.Figure: """ Create formatted table of PDF statistics. Args: stats: Dictionary of statistics from PDFStatistics spot_price: Current spot price title: Table title Returns: Plotly table figure """ # Organize statistics into categories categories = [] metrics = [] values = [] # Central Tendency categories.extend(['Central Tendency'] * 3) metrics.extend(['Expected Price (Mean)', 'Median', 'Mode']) values.extend([ f"${stats['mean']:.2f}", f"${stats['median']:.2f}", f"${stats['mode']:.2f}" ]) # Dispersion categories.extend(['Dispersion'] * 3) metrics.extend(['Standard Deviation', 'Implied Move', 'Implied Volatility']) values.extend([ f"${stats['std']:.2f}", f"±{stats['implied_move_pct']:.2f}%", f"{stats['implied_volatility']*100:.2f}%" ]) # Shape categories.extend(['Shape'] * 2) metrics.extend(['Skewness', 'Excess Kurtosis']) values.extend([ f"{stats['skewness']:.3f}", f"{stats['excess_kurtosis']:.3f}" ]) # Tail Probabilities categories.extend(['Tail Risk'] * 4) metrics.extend(['P(Down >5%)', 'P(Up >5%)', 'P(Down >10%)', 'P(Up >10%)']) values.extend([ f"{stats['prob_down_5pct']*100:.2f}%", f"{stats['prob_up_5pct']*100:.2f}%", f"{stats['prob_down_10pct']*100:.2f}%", f"{stats['prob_up_10pct']*100:.2f}%" ]) # Confidence Intervals categories.extend(['Confidence Intervals'] * 2) metrics.extend(['68% CI', '95% CI']) values.extend([ f"${stats['ci_68_lower']:.2f} - ${stats['ci_68_upper']:.2f}", f"${stats['ci_95_lower']:.2f} - ${stats['ci_95_upper']:.2f}" ]) # Current Price categories.append('Reference') metrics.append('Current Spot Price') values.append(f"${spot_price:.2f}") # Color code by category category_colors = { 'Central Tendency': 'rgba(0, 217, 255, 0.2)', 'Dispersion': 'rgba(0, 255, 136, 0.2)', 'Shape': 'rgba(255, 215, 0, 0.2)', 'Tail Risk': 'rgba(255, 68, 68, 0.2)', 'Confidence Intervals': 'rgba(255, 0, 255, 0.2)', 'Reference': 'rgba(150, 150, 150, 0.2)' } colors = [category_colors[cat] for cat in categories] # Create table fig = go.Figure(data=[go.Table( header=dict( values=['Category', 'Metric', 'Value'], fill_color=DARK_THEME['plot_bg'], align='left', font=dict(color=DARK_THEME['text'], size=14), height=40 ), cells=dict( values=[categories, metrics, values], fill_color=[colors, colors, colors], align=['left', 'left', 'right'], font=dict(color=DARK_THEME['text'], size=12), height=30 ) )]) fig.update_layout( title=title, paper_bgcolor=DARK_THEME['background'], font=dict(color=DARK_THEME['text']), height=min(800, 100 + len(categories) * 30), margin=dict(l=10, r=10, t=50, b=10) ) return fig def create_comparison_table( comparison_data: Dict[str, Dict[str, float]], title: str = "Multi-Expiration Comparison" ) -> go.Figure: """ Create comparison table across multiple expirations. Args: comparison_data: Dictionary with format: { 'expiration_label': { 'mean': float, 'std': float, 'skewness': float, ... } } title: Table title Returns: Plotly table figure """ if not comparison_data: raise ValueError("No data provided for comparison") # Get all expirations expirations = list(comparison_data.keys()) # Metrics to compare metrics = ['mean', 'std', 'implied_move_pct', 'skewness', 'excess_kurtosis'] metric_labels = ['Mean ($)', 'Std Dev ($)', 'Implied Move (%)', 'Skewness', 'Kurtosis'] # Build table data table_data = [metric_labels] for exp in expirations: data = comparison_data[exp] row = [] for metric in metrics: value = data.get(metric, 0) if metric in ['mean', 'std']: row.append(f"${value:.2f}") elif metric == 'implied_move_pct': row.append(f"±{value:.2f}%") else: row.append(f"{value:.3f}") table_data.append(row) # Create table fig = go.Figure(data=[go.Table( header=dict( values=['Metric'] + [f'{exp}' for exp in expirations], fill_color=DARK_THEME['plot_bg'], align='center', font=dict(color=DARK_THEME['text'], size=14), height=40 ), cells=dict( values=table_data, fill_color=DARK_THEME['background'], align='center', font=dict(color=DARK_THEME['text'], size=12), height=30 ) )]) fig.update_layout( title=title, paper_bgcolor=DARK_THEME['background'], font=dict(color=DARK_THEME['text']), height=300, margin=dict(l=10, r=10, t=50, b=10) ) return fig if __name__ == "__main__": # Test probability tables print("Testing probability tables...") spot = 450.0 # Test 1: Simple probability table probabilities = { 'P(Price < $440)': 0.25, 'P($440 < Price < $450)': 0.25, 'P($450 < Price < $460)': 0.30, 'P(Price > $460)': 0.20 } fig1 = create_probability_table(probabilities, spot) fig1.write_html("test_prob_table.html") print("✅ Probability table saved to test_prob_table.html") # Test 2: Strikes table strikes = np.linspace(430, 470, 20) cdf = np.linspace(0.1, 0.9, 20) # Synthetic CDF fig2 = create_strikes_table(strikes, cdf, spot, num_strikes=10) fig2.write_html("test_strikes_table.html") print("✅ Strikes table saved to test_strikes_table.html") # Test 3: Statistics table stats = { 'mean': 451.5, 'median': 450.8, 'mode': 450.0, 'std': 15.2, 'implied_move_pct': 3.38, 'implied_volatility': 0.20, 'skewness': -0.15, 'excess_kurtosis': 0.5, 'prob_down_5pct': 0.18, 'prob_up_5pct': 0.22, 'prob_down_10pct': 0.08, 'prob_up_10pct': 0.10, 'ci_68_lower': 436.3, 'ci_68_upper': 466.7, 'ci_95_lower': 421.1, 'ci_95_upper': 481.9 } fig3 = create_statistics_table(stats, spot) fig3.write_html("test_stats_table.html") print("✅ Statistics table saved to test_stats_table.html") # Test 4: Comparison table comparison = { '15D': { 'mean': 450.5, 'std': 10.2, 'implied_move_pct': 2.27, 'skewness': -0.10, 'excess_kurtosis': 0.3 }, '30D': { 'mean': 451.2, 'std': 15.0, 'implied_move_pct': 3.33, 'skewness': -0.15, 'excess_kurtosis': 0.5 }, '60D': { 'mean': 452.0, 'std': 22.5, 'implied_move_pct': 4.98, 'skewness': -0.20, 'excess_kurtosis': 0.7 } } fig4 = create_comparison_table(comparison) fig4.write_html("test_comparison_table.html") print("✅ Comparison table saved to test_comparison_table.html") print("\n✅ All probability table tests passed!")