akshay7's picture
add: broken single code file into logical modules
1e4f9fd
"""
Chart generation for equity analysis visualization
"""
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from typing import List, Optional
from models import ScenarioResult, CapTable, EquityCalculator
class EquityCharts:
"""Handles all chart generation for equity analysis"""
@staticmethod
def create_multi_scenario_comparison(results: List[ScenarioResult]) -> Optional[go.Figure]:
"""Create comparison chart showing option values and exit valuations"""
if not results:
return None
scenario_names = [r.scenario_name for r in results]
option_values = [r.option_value for r in results]
exit_values = [r.exit_valuation for r in results]
fig = make_subplots(
rows=2, cols=1,
subplot_titles=("Your Option Value by Scenario", "Exit Valuation by Scenario"),
vertical_spacing=0.20,
specs=[[{"secondary_y": False}], [{"secondary_y": False}]]
)
# Option values bar chart
fig.add_trace(
go.Bar(
x=scenario_names,
y=option_values,
name="Option Value",
marker_color='#2E86AB',
text=[f"${val:,.0f}" for val in option_values],
textposition='outside'
),
row=1, col=1
)
# Exit valuations bar chart
fig.add_trace(
go.Bar(
x=scenario_names,
y=exit_values,
name="Exit Valuation",
marker_color='#F18F01',
text=[f"${val:,.0f}" for val in exit_values],
textposition='outside',
showlegend=False
),
row=2, col=1
)
fig.update_layout(
title="Multi-Scenario Equity Analysis",
height=650,
showlegend=True,
margin=dict(t=80, b=50, l=80, r=50)
)
# Add extra space for text labels above bars
if option_values:
fig.update_yaxes(title_text="Your Option Value ($)", row=1, col=1, range=[0, max(option_values) * 1.15])
if exit_values:
fig.update_yaxes(title_text="Company Valuation ($)", row=2, col=1, range=[0, max(exit_values) * 1.15])
return fig
@staticmethod
def create_liquidation_waterfall(
cap_table: CapTable,
exit_valuation: float,
scenario_name: str = "Best Scenario"
) -> go.Figure:
"""Create detailed liquidation waterfall chart for a specific exit value"""
calculator = EquityCalculator(cap_table)
remaining_proceeds = exit_valuation
waterfall_data = []
participating_shareholders = []
# Sort funding rounds (newest first for liquidation preferences)
sorted_rounds = sorted(cap_table.funding_rounds,
key=lambda x: ['Seed', 'Series A', 'Series B', 'Series C'].index(x.name)
if x.name in ['Seed', 'Series A', 'Series B', 'Series C'] else 999,
reverse=True)
# Phase 1: Liquidation preferences
for round in sorted_rounds:
if round.shares_issued > 0 and round.capital_raised > 0:
preference_payout = min(remaining_proceeds, round.liquidation_preference)
remaining_proceeds -= preference_payout
if round.is_participating:
participating_shareholders.append({
'round': round.name,
'shares': round.shares_issued
})
waterfall_data.append({
'Round': f'{round.name} (Pref)',
'Payout': preference_payout,
'Type': 'Preference'
})
# Phase 2: Participating preferred and common distribution
participating_preferred_shares = sum(p['shares'] for p in participating_shareholders)
total_participating_shares = cap_table.common_shares + participating_preferred_shares
if total_participating_shares > 0:
price_per_share = remaining_proceeds / total_participating_shares
common_proceeds = price_per_share * cap_table.common_shares
# Add participating preferred distributions
for participant in participating_shareholders:
participating_payout = price_per_share * participant['shares']
waterfall_data.append({
'Round': f"{participant['round']} (Part.)",
'Payout': participating_payout,
'Type': 'Participation'
})
else:
common_proceeds = remaining_proceeds
# Add common stock
waterfall_data.append({
'Round': 'Common Stock',
'Payout': common_proceeds,
'Type': 'Common'
})
# Create the chart
fig = go.Figure()
color_map = {
'Preference': '#FF6B6B', # Red for liquidation preferences
'Participation': '#4ECDC4', # Teal for participating preferred
'Common': '#F7DC6F' # Yellow for common stock
}
for item in waterfall_data:
if item['Payout'] > 0:
color = color_map.get(item['Type'], '#96CEB4')
fig.add_trace(go.Bar(
x=[item['Round']],
y=[item['Payout']],
name=f"{item['Round']} (${item['Payout']:,.0f})",
marker_color=color,
text=f"${item['Payout']:,.0f}",
textposition='outside'
))
fig.update_layout(
title=f"Liquidation Waterfall - ${exit_valuation:,.0f} Exit",
xaxis_title="Stakeholder",
yaxis_title="Payout ($)",
height=450,
showlegend=True,
margin=dict(t=60, b=50, l=80, r=50)
)
return fig
@staticmethod
def create_roi_analysis(results: List[ScenarioResult], investment_cost: float) -> Optional[go.Figure]:
"""Create ROI analysis chart"""
if not results:
return None
roi_data = []
for result in results:
roi = result.roi_percentage(investment_cost)
# Cap very high ROI for display purposes
display_roi = roi if roi < 999999 else 999999
roi_data.append({
'scenario': result.scenario_name,
'roi': display_roi,
'absolute_gain': result.option_value - investment_cost
})
fig = go.Figure()
fig.add_trace(go.Bar(
x=[d['scenario'] for d in roi_data],
y=[d['roi'] for d in roi_data],
name="ROI %",
marker_color='#28A745',
text=[f"{d['roi']:.0f}%" if d['roi'] < 999999 else "∞%" for d in roi_data],
textposition='outside'
))
fig.update_layout(
title="Return on Investment (ROI) by Scenario",
xaxis_title="Scenario",
yaxis_title="ROI (%)",
height=450,
margin=dict(t=60, b=50, l=60, r=50)
)
return fig
def format_results_table(results: List[ScenarioResult]) -> str:
"""Format scenario results as a markdown table"""
if not results:
return "No scenarios to display"
table = "## πŸ“Š Exit Scenario Comparison\n\n"
table += "| Scenario | Exit Value | Your Option Value | Value per Option | Common Proceeds |\n"
table += "|----------|------------|-------------------|------------------|------------------|\n"
for result in results:
table += f"| **{result.scenario_name}** | ${result.exit_valuation:,.0f} | "
table += f"${result.option_value:,.2f} | ${result.value_per_option:.4f} | "
table += f"${result.common_proceeds:,.0f} |\n"
return table
def format_equity_summary(summary: dict, results: List[ScenarioResult]) -> str:
"""Format complete equity analysis summary"""
results_table = format_results_table(results)
summary_text = f"""
## πŸ’° Your Equity Summary
**Your Option Grant:** {summary['your_options']:,} options
**Strike Price:** ${summary['strike_price']:.4f} per share
**Your Equity Stake:** {summary['your_equity_percentage']:.3f}%
{results_table}
## πŸ—οΈ Cap Table Summary
**Total Shares:** {summary['total_shares']:,}
**Common Shares:** {summary['common_shares']:,}
**Preferred Shares:** {summary['preferred_shares']:,}
**Liquidation Terms:** {' | '.join(summary['participating_status']) if summary['participating_status'] else 'No preferred rounds'}
**Break-even Price per Share:** ${summary['break_even_price']:.4f}
*(Price needed for your options to have positive value)*
"""
return summary_text