akshay7 commited on
Commit
24b2b45
Β·
1 Parent(s): 6679075

add: comparison of multiple exit scenarios

Browse files
Files changed (1) hide show
  1. equity_calculator.py +404 -297
equity_calculator.py CHANGED
@@ -4,201 +4,173 @@ import plotly.graph_objects as go
4
  import plotly.express as px
5
  from plotly.subplots import make_subplots
6
 
7
- def calculate_equity_value(
8
- # Cap table inputs
9
- total_shares, your_options, strike_price,
10
- # Seed round
11
  seed_shares, seed_capital, seed_multiple, seed_participating,
12
- # Series A
13
  series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
14
- # Series B
15
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
16
- # Exit
17
- exit_valuation
18
  ):
19
- """Calculate startup equity value with liquidation preferences"""
20
-
21
- # Handle None values and provide defaults
22
- total_shares = total_shares or 10000000
23
- your_options = your_options or 0
24
- strike_price = strike_price or 0
25
- seed_shares = seed_shares or 0
26
- seed_capital = seed_capital or 0
27
- seed_multiple = seed_multiple or 1.0
28
- seed_participating = seed_participating or False
29
- series_a_shares = series_a_shares or 0
30
- series_a_capital = series_a_capital or 0
31
- series_a_multiple = series_a_multiple or 1.0
32
- series_a_participating = series_a_participating or False
33
- series_b_shares = series_b_shares or 0
34
- series_b_capital = series_b_capital or 0
35
- series_b_multiple = series_b_multiple or 1.0
36
- series_b_participating = series_b_participating or False
37
- exit_valuation = exit_valuation or 0
38
-
39
- # Input validation
40
- if total_shares <= 0 or your_options < 0 or exit_valuation < 0:
41
- return "Invalid inputs - please check your values", None, None
42
 
43
- # Calculate common shares (total - preferred shares issued)
44
  total_preferred_shares = seed_shares + series_a_shares + series_b_shares
45
  common_shares = total_shares - total_preferred_shares
46
 
47
  if common_shares <= 0:
48
- return "Error: Preferred shares exceed total shares", None, None
 
 
 
 
 
 
49
 
50
- # Build liquidation waterfall with participating/non-participating logic
51
  remaining_proceeds = exit_valuation
52
- waterfall_data = []
53
- participating_shareholders = [] # Track participating preferred for later distribution
54
-
55
- # Phase 1: Pay liquidation preferences first
56
 
57
- # Series B gets paid first (most recent round)
58
  series_b_preference_payout = 0
59
  if series_b_shares > 0 and series_b_capital > 0:
60
  series_b_preference = series_b_capital * series_b_multiple
61
  series_b_preference_payout = min(remaining_proceeds, series_b_preference)
62
  remaining_proceeds -= series_b_preference_payout
63
-
64
  if series_b_participating:
65
- participating_shareholders.append({
66
- 'round': 'Series B',
67
- 'shares': series_b_shares,
68
- 'preference_paid': series_b_preference_payout
69
- })
70
-
71
- waterfall_data.append({
72
- 'Round': 'Series B (Pref)',
73
- 'Preference': series_b_preference,
74
- 'Payout': series_b_preference_payout,
75
- 'Remaining': remaining_proceeds,
76
- 'Type': 'Preference'
77
- })
78
 
79
- # Series A gets paid next
80
  series_a_preference_payout = 0
81
  if series_a_shares > 0 and series_a_capital > 0:
82
  series_a_preference = series_a_capital * series_a_multiple
83
  series_a_preference_payout = min(remaining_proceeds, series_a_preference)
84
  remaining_proceeds -= series_a_preference_payout
85
-
86
  if series_a_participating:
87
- participating_shareholders.append({
88
- 'round': 'Series A',
89
- 'shares': series_a_shares,
90
- 'preference_paid': series_a_preference_payout
91
- })
92
-
93
- waterfall_data.append({
94
- 'Round': 'Series A (Pref)',
95
- 'Preference': series_a_preference,
96
- 'Payout': series_a_preference_payout,
97
- 'Remaining': remaining_proceeds,
98
- 'Type': 'Preference'
99
- })
100
 
101
- # Seed gets paid last among preferred
102
  seed_preference_payout = 0
103
  if seed_shares > 0 and seed_capital > 0:
104
  seed_preference = seed_capital * seed_multiple
105
  seed_preference_payout = min(remaining_proceeds, seed_preference)
106
  remaining_proceeds -= seed_preference_payout
107
-
108
  if seed_participating:
109
- participating_shareholders.append({
110
- 'round': 'Seed',
111
- 'shares': seed_shares,
112
- 'preference_paid': seed_preference_payout
113
- })
114
-
115
- waterfall_data.append({
116
- 'Round': 'Seed (Pref)',
117
- 'Preference': seed_preference,
118
- 'Payout': seed_preference_payout,
119
- 'Remaining': remaining_proceeds,
120
- 'Type': 'Preference'
121
- })
122
-
123
- # Phase 2: Distribute remaining proceeds
124
 
125
- # Calculate total shares eligible for remaining distribution
126
- # This includes: common shares + participating preferred shares
127
  participating_preferred_shares = sum([p['shares'] for p in participating_shareholders])
128
  total_participating_shares = common_shares + participating_preferred_shares
129
 
130
- # For non-participating preferred, we need to check if conversion is better
131
- # than taking the liquidation preference
132
-
133
- # Series B non-participating conversion check
134
  if series_b_shares > 0 and series_b_capital > 0 and not series_b_participating:
135
- # Calculate what they'd get if they converted to common
136
  conversion_value = (series_b_shares / total_shares) * exit_valuation
137
  if conversion_value > series_b_preference_payout:
138
- # They convert - add back their preference and include them in common distribution
139
  remaining_proceeds += series_b_preference_payout
140
  total_participating_shares += series_b_shares
141
- # Update waterfall data
142
- for item in waterfall_data:
143
- if item['Round'] == 'Series B (Pref)':
144
- item['Round'] = 'Series B (Converted)'
145
- item['Payout'] = 0 # They'll get paid in common distribution
146
- item['Type'] = 'Conversion'
147
-
148
- # Series A non-participating conversion check
149
  if series_a_shares > 0 and series_a_capital > 0 and not series_a_participating:
150
  conversion_value = (series_a_shares / total_shares) * exit_valuation
151
  if conversion_value > series_a_preference_payout:
152
  remaining_proceeds += series_a_preference_payout
153
  total_participating_shares += series_a_shares
154
- for item in waterfall_data:
155
- if item['Round'] == 'Series A (Pref)':
156
- item['Round'] = 'Series A (Converted)'
157
- item['Payout'] = 0
158
- item['Type'] = 'Conversion'
159
 
160
- # Seed non-participating conversion check
161
  if seed_shares > 0 and seed_capital > 0 and not seed_participating:
162
  conversion_value = (seed_shares / total_shares) * exit_valuation
163
  if conversion_value > seed_preference_payout:
164
  remaining_proceeds += seed_preference_payout
165
  total_participating_shares += seed_shares
166
- for item in waterfall_data:
167
- if item['Round'] == 'Seed (Pref)':
168
- item['Round'] = 'Seed (Converted)'
169
- item['Payout'] = 0
170
- item['Type'] = 'Conversion'
171
 
172
- # Final distribution to common + participating preferred
173
  if total_participating_shares > 0:
174
  price_per_participating_share = remaining_proceeds / total_participating_shares
175
  common_proceeds = price_per_participating_share * common_shares
176
-
177
- # Add participating preferred payouts to waterfall
178
- for participant in participating_shareholders:
179
- participating_payout = price_per_participating_share * participant['shares']
180
- waterfall_data.append({
181
- 'Round': f"{participant['round']} (Participating)",
182
- 'Preference': 0,
183
- 'Payout': participating_payout,
184
- 'Remaining': remaining_proceeds - participating_payout,
185
- 'Type': 'Participation'
186
- })
187
  else:
188
- price_per_participating_share = 0
189
  common_proceeds = remaining_proceeds
190
 
191
- # Calculate price per common share
192
- if common_shares > 0:
193
- price_per_common_share = common_proceeds / common_shares
194
- else:
195
- price_per_common_share = 0
196
-
197
- # Calculate your option value
198
  option_value_per_share = max(0, price_per_common_share - strike_price)
199
  total_option_value = option_value_per_share * your_options
200
 
201
- # Calculate your equity percentage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  your_equity_percentage = (your_options / total_shares) * 100 if total_shares > 0 else 0
203
 
204
  # Build results summary
@@ -207,166 +179,264 @@ def calculate_equity_value(
207
  if series_a_shares > 0: participating_status.append(f"Series A: {'Participating' if series_a_participating else 'Non-Participating'}")
208
  if series_b_shares > 0: participating_status.append(f"Series B: {'Participating' if series_b_participating else 'Non-Participating'}")
209
 
 
 
 
 
 
 
 
 
 
 
210
  results = f"""
211
- ## πŸ’° Your Equity Value
212
 
213
- **Total Option Value:** ${total_option_value:,.2f}
214
- **Value per Option:** ${option_value_per_share:.4f}
215
  **Your Equity Stake:** {your_equity_percentage:.3f}%
216
 
217
- ## πŸ“Š Liquidation Analysis
218
-
219
- **Exit Valuation:** ${exit_valuation:,.2f}
220
- **Common Stock Proceeds:** ${common_proceeds:,.2f}
221
- **Price per Common Share:** ${price_per_common_share:.4f}
222
-
223
- **Liquidation Terms:** {' | '.join(participating_status) if participating_status else 'No preferred rounds'}
224
-
225
- **Break-even Exit Value:** ${(strike_price * total_shares):,.2f}
226
- *(Exit value needed for your options to have value)*
227
 
228
  ## πŸ—οΈ Cap Table Summary
229
 
230
  **Total Shares:** {total_shares:,}
231
  **Common Shares:** {common_shares:,}
232
  **Preferred Shares:** {total_preferred_shares:,}
 
 
 
 
 
233
  """
234
-
235
- # Create waterfall chart
236
- if waterfall_data:
237
- waterfall_df = pd.DataFrame(waterfall_data)
238
-
239
- fig = go.Figure()
240
 
241
- # Color coding for different types
242
- color_map = {
243
- 'Preference': '#FF6B6B', # Red for liquidation preferences
244
- 'Participation': '#4ECDC4', # Teal for participating preferred
245
- 'Conversion': '#45B7D1' # Blue for converted preferred
246
- }
247
 
248
- for i, row in waterfall_df.iterrows():
249
- color = color_map.get(row.get('Type', 'Preference'), '#96CEB4')
250
- fig.add_trace(go.Bar(
251
- x=[row['Round']],
252
- y=[row['Payout']],
253
- name=f"{row['Round']} (${row['Payout']:,.0f})",
254
- marker_color=color,
255
- text=f"${row['Payout']:,.0f}" if row['Payout'] > 0 else "Converted",
256
- textposition='inside'
257
- ))
 
 
258
 
259
- # Add common stock proceeds
260
- if common_proceeds > 0:
261
- fig.add_trace(go.Bar(
262
- x=['Common Stock'],
263
- y=[common_proceeds],
264
- name=f"Common Stock (${common_proceeds:,.0f})",
265
- marker_color='#F7DC6F',
266
- text=f"${common_proceeds:,.0f}",
267
- textposition='inside'
268
- ))
 
 
 
269
 
270
  fig.update_layout(
271
- title="Liquidation Waterfall (Preferences β†’ Participation β†’ Common)",
272
- xaxis_title="Stakeholder",
273
- yaxis_title="Payout ($)",
274
  showlegend=True,
275
- height=400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  )
277
 
278
- waterfall_chart = fig
279
  else:
280
- # Simple pie chart if no preferred rounds
281
- fig = go.Figure(data=[go.Pie(
282
- labels=['Your Options', 'Other Common'],
283
- values=[your_options, common_shares - your_options],
284
- hole=0.3
285
- )])
286
- fig.update_layout(title="Your Share of Common Stock")
287
- waterfall_chart = fig
288
-
289
- # Create sensitivity analysis
290
- exit_values = [exit_valuation * i for i in [0.5, 0.75, 1.0, 1.25, 1.5, 2.0]]
291
- option_values = []
292
-
293
- for ev in exit_values:
294
- # Recalculate for each exit value with participating logic
295
- temp_remaining = ev
296
- temp_participating_shareholders = []
 
297
 
298
- # Pay preferences first
299
- if series_b_shares > 0 and series_b_capital > 0:
300
- temp_payout = min(temp_remaining, series_b_capital * series_b_multiple)
301
- temp_remaining -= temp_payout
302
- if series_b_participating:
303
- temp_participating_shareholders.append({'shares': series_b_shares})
 
 
 
304
 
305
- if series_a_shares > 0 and series_a_capital > 0:
306
- temp_payout = min(temp_remaining, series_a_capital * series_a_multiple)
307
- temp_remaining -= temp_payout
308
- if series_a_participating:
309
- temp_participating_shareholders.append({'shares': series_a_shares})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- if seed_shares > 0 and seed_capital > 0:
312
- temp_payout = min(temp_remaining, seed_capital * seed_multiple)
313
- temp_remaining -= temp_payout
314
- if seed_participating:
315
- temp_participating_shareholders.append({'shares': seed_shares})
316
 
317
- # Handle non-participating conversion decisions
318
- temp_total_participating = common_shares + sum([p['shares'] for p in temp_participating_shareholders])
 
 
 
 
 
 
 
 
319
 
320
- # Check non-participating conversions (simplified for sensitivity)
321
- if series_b_shares > 0 and not series_b_participating:
322
- conversion_value = (series_b_shares / total_shares) * ev
323
- preference_value = series_b_capital * series_b_multiple
324
- if conversion_value > preference_value:
325
- temp_remaining += min(temp_remaining + preference_value, preference_value)
326
- temp_total_participating += series_b_shares
327
 
328
- if series_a_shares > 0 and not series_a_participating:
329
- conversion_value = (series_a_shares / total_shares) * ev
330
- preference_value = series_a_capital * series_a_multiple
331
- if conversion_value > preference_value:
332
- temp_remaining += min(temp_remaining + preference_value, preference_value)
333
- temp_total_participating += series_a_shares
 
 
 
 
334
 
335
- if seed_shares > 0 and not seed_participating:
336
- conversion_value = (seed_shares / total_shares) * ev
337
- preference_value = seed_capital * seed_multiple
338
- if conversion_value > preference_value:
339
- temp_remaining += min(temp_remaining + preference_value, preference_value)
340
- temp_total_participating += seed_shares
341
 
342
- # Calculate common proceeds
343
- if temp_total_participating > 0:
344
- temp_common_proceeds = (common_shares / temp_total_participating) * temp_remaining
345
- else:
346
- temp_common_proceeds = temp_remaining
 
 
 
 
 
 
 
 
 
347
 
348
- temp_price_per_share = temp_common_proceeds / common_shares if common_shares > 0 else 0
349
- temp_option_value = max(0, temp_price_per_share - strike_price) * your_options
350
- option_values.append(temp_option_value)
351
-
352
- sensitivity_fig = go.Figure()
353
- sensitivity_fig.add_trace(go.Scatter(
354
- x=exit_values,
355
- y=option_values,
356
- mode='lines+markers',
357
- name='Option Value',
358
- line=dict(color='#2E86AB', width=3),
359
- marker=dict(size=8)
360
- ))
361
-
362
- sensitivity_fig.update_layout(
363
- title="Sensitivity Analysis: Exit Value vs Option Value",
364
- xaxis_title="Exit Valuation ($)",
365
- yaxis_title="Your Option Value ($)",
366
- height=400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  )
368
 
369
- return results, waterfall_chart, sensitivity_fig
370
 
371
  # Create Gradio interface
372
  with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app:
@@ -377,20 +447,20 @@ with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app
377
  with gr.Column():
378
  gr.Markdown("## Cap Table Structure")
379
  total_shares = gr.Number(label="Total Fully Diluted Shares", value=10000000, precision=0)
380
- your_options = gr.Number(label="Your Option Grant", value=50000, precision=0)
381
  strike_price = gr.Number(label="Strike Price per Share ($)", value=0.10, precision=4)
382
 
383
  gr.Markdown("## Funding Rounds")
384
 
385
- with gr.Accordion("Seed Round", open=True):
386
- seed_shares = gr.Number(label="Seed Shares Issued", value=2000000, precision=0)
387
- seed_capital = gr.Number(label="Seed Capital Raised ($)", value=2000000, precision=0)
388
  seed_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
389
  seed_participating = gr.Checkbox(label="Participating Preferred", value=False)
390
 
391
- with gr.Accordion("Series A", open=True):
392
- series_a_shares = gr.Number(label="Series A Shares Issued", value=1500000, precision=0)
393
- series_a_capital = gr.Number(label="Series A Capital Raised ($)", value=10000000, precision=0)
394
  series_a_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
395
  series_a_participating = gr.Checkbox(label="Participating Preferred", value=False)
396
 
@@ -401,16 +471,42 @@ with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app
401
  series_b_participating = gr.Checkbox(label="Participating Preferred", value=False)
402
 
403
  with gr.Column():
404
- gr.Markdown("## Exit Scenario")
405
- exit_valuation = gr.Number(label="Exit Valuation ($)", value=50000000, precision=0)
406
 
407
- calculate_btn = gr.Button("Calculate Equity Value", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
 
409
  results_text = gr.Markdown()
410
 
411
  with gr.Row():
412
- waterfall_plot = gr.Plot(label="Liquidation Waterfall")
413
- sensitivity_plot = gr.Plot(label="Sensitivity Analysis")
 
 
 
414
 
415
  # Set up the calculation trigger
416
  inputs = [
@@ -418,10 +514,14 @@ with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app
418
  seed_shares, seed_capital, seed_multiple, seed_participating,
419
  series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
420
  series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
421
- exit_valuation
 
 
 
 
422
  ]
423
 
424
- outputs = [results_text, waterfall_plot, sensitivity_plot]
425
 
426
  calculate_btn.click(calculate_equity_value, inputs=inputs, outputs=outputs)
427
 
@@ -430,26 +530,33 @@ with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app
430
  input_component.change(calculate_equity_value, inputs=inputs, outputs=outputs)
431
 
432
  gr.Markdown("""
433
- ## πŸ“š How it Works
434
-
435
- ### Liquidation Waterfall
436
- 1. **Liquidation Preferences**: Preferred shareholders get their money back first (with multipliers)
437
- 2. **Participating vs Non-Participating**:
438
- - **Non-Participating**: Investors choose either their preference OR convert to common (whichever is better)
439
- - **Participating**: Investors get their preference AND participate in remaining proceeds ("double dipping")
440
- 3. **Conversion Decision**: Non-participating preferred will convert if their pro-rata share of total exit > liquidation preference
441
- 4. **Your Options**: Get value from whatever is left for common shareholders
442
-
443
- ### Visual Guide
444
- - **Red bars**: Liquidation preferences paid first
445
- - **Teal bars**: Participating preferred getting their share of remaining proceeds
446
- - **Blue bars**: Non-participating preferred that chose to convert to common
447
- - **Yellow bars**: Final proceeds available to common stock (including your options)
448
-
449
- ### Key Insight
450
- **Participating preferred significantly reduces common stock value** because investors get both their money back AND a share of the upside. Toggle the participating checkboxes to see the dramatic difference in your option value!
451
-
452
- **Note**: This calculator assumes basic liquidation preferences. Real cap tables may include anti-dilution provisions, participation caps, and other complex terms.
 
 
 
 
 
 
 
453
  """)
454
 
455
  if __name__ == "__main__":
 
4
  import plotly.express as px
5
  from plotly.subplots import make_subplots
6
 
7
+ def calculate_single_scenario(
8
+ exit_valuation, total_shares, your_options, strike_price,
 
 
9
  seed_shares, seed_capital, seed_multiple, seed_participating,
 
10
  series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
11
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating
 
 
 
12
  ):
13
+ """Calculate equity value for a single exit scenario"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # Calculate common shares
16
  total_preferred_shares = seed_shares + series_a_shares + series_b_shares
17
  common_shares = total_shares - total_preferred_shares
18
 
19
  if common_shares <= 0:
20
+ return {
21
+ 'exit_valuation': exit_valuation,
22
+ 'option_value': 0,
23
+ 'price_per_share': 0,
24
+ 'common_proceeds': 0,
25
+ 'error': 'Preferred shares exceed total shares'
26
+ }
27
 
28
+ # Phase 1: Pay liquidation preferences
29
  remaining_proceeds = exit_valuation
30
+ participating_shareholders = []
 
 
 
31
 
32
+ # Series B (most recent)
33
  series_b_preference_payout = 0
34
  if series_b_shares > 0 and series_b_capital > 0:
35
  series_b_preference = series_b_capital * series_b_multiple
36
  series_b_preference_payout = min(remaining_proceeds, series_b_preference)
37
  remaining_proceeds -= series_b_preference_payout
 
38
  if series_b_participating:
39
+ participating_shareholders.append({'shares': series_b_shares})
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ # Series A
42
  series_a_preference_payout = 0
43
  if series_a_shares > 0 and series_a_capital > 0:
44
  series_a_preference = series_a_capital * series_a_multiple
45
  series_a_preference_payout = min(remaining_proceeds, series_a_preference)
46
  remaining_proceeds -= series_a_preference_payout
 
47
  if series_a_participating:
48
+ participating_shareholders.append({'shares': series_a_shares})
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # Seed
51
  seed_preference_payout = 0
52
  if seed_shares > 0 and seed_capital > 0:
53
  seed_preference = seed_capital * seed_multiple
54
  seed_preference_payout = min(remaining_proceeds, seed_preference)
55
  remaining_proceeds -= seed_preference_payout
 
56
  if seed_participating:
57
+ participating_shareholders.append({'shares': seed_shares})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ # Phase 2: Handle non-participating conversions
 
60
  participating_preferred_shares = sum([p['shares'] for p in participating_shareholders])
61
  total_participating_shares = common_shares + participating_preferred_shares
62
 
63
+ # Check conversions for non-participating preferred
 
 
 
64
  if series_b_shares > 0 and series_b_capital > 0 and not series_b_participating:
 
65
  conversion_value = (series_b_shares / total_shares) * exit_valuation
66
  if conversion_value > series_b_preference_payout:
 
67
  remaining_proceeds += series_b_preference_payout
68
  total_participating_shares += series_b_shares
69
+
 
 
 
 
 
 
 
70
  if series_a_shares > 0 and series_a_capital > 0 and not series_a_participating:
71
  conversion_value = (series_a_shares / total_shares) * exit_valuation
72
  if conversion_value > series_a_preference_payout:
73
  remaining_proceeds += series_a_preference_payout
74
  total_participating_shares += series_a_shares
 
 
 
 
 
75
 
 
76
  if seed_shares > 0 and seed_capital > 0 and not seed_participating:
77
  conversion_value = (seed_shares / total_shares) * exit_valuation
78
  if conversion_value > seed_preference_payout:
79
  remaining_proceeds += seed_preference_payout
80
  total_participating_shares += seed_shares
 
 
 
 
 
81
 
82
+ # Final distribution
83
  if total_participating_shares > 0:
84
  price_per_participating_share = remaining_proceeds / total_participating_shares
85
  common_proceeds = price_per_participating_share * common_shares
 
 
 
 
 
 
 
 
 
 
 
86
  else:
 
87
  common_proceeds = remaining_proceeds
88
 
89
+ price_per_common_share = common_proceeds / common_shares if common_shares > 0 else 0
 
 
 
 
 
 
90
  option_value_per_share = max(0, price_per_common_share - strike_price)
91
  total_option_value = option_value_per_share * your_options
92
 
93
+ return {
94
+ 'exit_valuation': exit_valuation,
95
+ 'option_value': total_option_value,
96
+ 'price_per_share': price_per_common_share,
97
+ 'common_proceeds': common_proceeds,
98
+ 'error': None
99
+ }
100
+
101
+ def calculate_equity_value(
102
+ # Cap table inputs
103
+ total_shares, your_options, strike_price,
104
+ # Seed round
105
+ seed_shares, seed_capital, seed_multiple, seed_participating,
106
+ # Series A
107
+ series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
108
+ # Series B
109
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
110
+ # Multiple exit scenarios
111
+ exit_scenario_1, scenario_1_name,
112
+ exit_scenario_2, scenario_2_name,
113
+ exit_scenario_3, scenario_3_name,
114
+ exit_scenario_4, scenario_4_name,
115
+ exit_scenario_5, scenario_5_name
116
+ ):
117
+ """Calculate startup equity value with liquidation preferences for multiple exit scenarios"""
118
+
119
+ # Handle None values and provide defaults
120
+ total_shares = total_shares or 10000000
121
+ your_options = your_options or 0
122
+ strike_price = strike_price or 0
123
+ seed_shares = seed_shares or 0
124
+ seed_capital = seed_capital or 0
125
+ seed_multiple = seed_multiple or 1.0
126
+ seed_participating = seed_participating or False
127
+ series_a_shares = series_a_shares or 0
128
+ series_a_capital = series_a_capital or 0
129
+ series_a_multiple = series_a_multiple or 1.0
130
+ series_a_participating = series_a_participating or False
131
+ series_b_shares = series_b_shares or 0
132
+ series_b_capital = series_b_capital or 0
133
+ series_b_multiple = series_b_multiple or 1.0
134
+ series_b_participating = series_b_participating or False
135
+
136
+ # Handle exit scenarios
137
+ exit_scenario_1 = exit_scenario_1 or 0
138
+ exit_scenario_2 = exit_scenario_2 or 0
139
+ exit_scenario_3 = exit_scenario_3 or 0
140
+ exit_scenario_4 = exit_scenario_4 or 0
141
+ exit_scenario_5 = exit_scenario_5 or 0
142
+ scenario_1_name = scenario_1_name or "Scenario 1"
143
+ scenario_2_name = scenario_2_name or "Scenario 2"
144
+ scenario_3_name = scenario_3_name or "Scenario 3"
145
+ scenario_4_name = scenario_4_name or "Scenario 4"
146
+ scenario_5_name = scenario_5_name or "Scenario 5"
147
+
148
+ # Input validation
149
+ if total_shares <= 0:
150
+ return "Invalid inputs - please check your values", None, None, None
151
+
152
+ # Calculate scenarios
153
+ scenarios = []
154
+ exit_values = [exit_scenario_1, exit_scenario_2, exit_scenario_3, exit_scenario_4, exit_scenario_5]
155
+ scenario_names = [scenario_1_name, scenario_2_name, scenario_3_name, scenario_4_name, scenario_5_name]
156
+
157
+ for exit_val, name in zip(exit_values, scenario_names):
158
+ if exit_val > 0: # Only calculate scenarios with positive exit values
159
+ scenario_result = calculate_single_scenario(
160
+ exit_val, total_shares, your_options, strike_price,
161
+ seed_shares, seed_capital, seed_multiple, seed_participating,
162
+ series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
163
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating
164
+ )
165
+ scenario_result['name'] = name
166
+ scenarios.append(scenario_result)
167
+
168
+ if not scenarios:
169
+ return "Please enter at least one exit scenario with a positive value", None, None, None
170
+
171
+ # Calculate common shares and basic info
172
+ total_preferred_shares = seed_shares + series_a_shares + series_b_shares
173
+ common_shares = total_shares - total_preferred_shares
174
  your_equity_percentage = (your_options / total_shares) * 100 if total_shares > 0 else 0
175
 
176
  # Build results summary
 
179
  if series_a_shares > 0: participating_status.append(f"Series A: {'Participating' if series_a_participating else 'Non-Participating'}")
180
  if series_b_shares > 0: participating_status.append(f"Series B: {'Participating' if series_b_participating else 'Non-Participating'}")
181
 
182
+ # Create scenario comparison table
183
+ scenario_table = "## πŸ“Š Exit Scenario Comparison\n\n"
184
+ scenario_table += "| Scenario | Exit Value | Your Option Value | Value per Option | Common Proceeds |\n"
185
+ scenario_table += "|----------|------------|-------------------|------------------|------------------|\n"
186
+
187
+ for scenario in scenarios:
188
+ scenario_table += f"| **{scenario['name']}** | ${scenario['exit_valuation']:,.0f} | "
189
+ scenario_table += f"${scenario['option_value']:,.2f} | ${scenario['option_value']/your_options if your_options > 0 else 0:.4f} | "
190
+ scenario_table += f"${scenario['common_proceeds']:,.0f} |\n"
191
+
192
  results = f"""
193
+ ## πŸ’° Your Equity Summary
194
 
195
+ **Your Option Grant:** {your_options:,} options
196
+ **Strike Price:** ${strike_price:.4f} per share
197
  **Your Equity Stake:** {your_equity_percentage:.3f}%
198
 
199
+ {scenario_table}
 
 
 
 
 
 
 
 
 
200
 
201
  ## πŸ—οΈ Cap Table Summary
202
 
203
  **Total Shares:** {total_shares:,}
204
  **Common Shares:** {common_shares:,}
205
  **Preferred Shares:** {total_preferred_shares:,}
206
+
207
+ **Liquidation Terms:** {' | '.join(participating_status) if participating_status else 'No preferred rounds'}
208
+
209
+ **Break-even Price per Share:** ${strike_price:.4f}
210
+ *(Price needed for your options to have positive value)*
211
  """
212
+
213
+ # Create comparison bar chart
214
+ if scenarios:
215
+ scenario_names = [s['name'] for s in scenarios]
216
+ option_values = [s['option_value'] for s in scenarios]
217
+ exit_values = [s['exit_valuation'] for s in scenarios]
218
 
219
+ fig = make_subplots(
220
+ rows=2, cols=1,
221
+ subplot_titles=("Your Option Value by Scenario", "Exit Valuation by Scenario"),
222
+ vertical_spacing=0.20,
223
+ specs=[[{"secondary_y": False}], [{"secondary_y": False}]]
224
+ )
225
 
226
+ # Option values bar chart
227
+ fig.add_trace(
228
+ go.Bar(
229
+ x=scenario_names,
230
+ y=option_values,
231
+ name="Option Value",
232
+ marker_color='#2E86AB',
233
+ text=[f"${val:,.0f}" for val in option_values],
234
+ textposition='outside'
235
+ ),
236
+ row=1, col=1
237
+ )
238
 
239
+ # Exit valuations bar chart
240
+ fig.add_trace(
241
+ go.Bar(
242
+ x=scenario_names,
243
+ y=exit_values,
244
+ name="Exit Valuation",
245
+ marker_color='#F18F01',
246
+ text=[f"${val:,.0f}" for val in exit_values],
247
+ textposition='outside',
248
+ showlegend=False
249
+ ),
250
+ row=2, col=1
251
+ )
252
 
253
  fig.update_layout(
254
+ title="Multi-Scenario Equity Analysis",
255
+ height=650,
 
256
  showlegend=True,
257
+ margin=dict(t=80, b=50, l=80, r=50)
258
+ )
259
+
260
+ # Add extra space for text labels above bars
261
+ fig.update_yaxes(title_text="Your Option Value ($)", row=1, col=1, range=[0, max(option_values) * 1.15])
262
+ fig.update_yaxes(title_text="Company Valuation ($)", row=2, col=1, range=[0, max(exit_values) * 1.15])
263
+
264
+ comparison_chart = fig
265
+ else:
266
+ comparison_chart = None
267
+
268
+ # Create detailed breakdown chart for the highest scenario
269
+ if scenarios:
270
+ # Find the scenario with highest option value for detailed analysis
271
+ best_scenario = max(scenarios, key=lambda x: x['option_value'])
272
+
273
+ # Create a detailed waterfall for this scenario
274
+ detailed_fig = calculate_single_scenario_waterfall(
275
+ best_scenario['exit_valuation'], total_shares, your_options, strike_price,
276
+ seed_shares, seed_capital, seed_multiple, seed_participating,
277
+ series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
278
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating
279
  )
280
 
281
+ detailed_chart = detailed_fig
282
  else:
283
+ detailed_chart = None
284
+
285
+ # Create ROI comparison
286
+ if scenarios and your_options > 0:
287
+ roi_data = []
288
+ investment_cost = your_options * strike_price
289
+
290
+ for scenario in scenarios:
291
+ if investment_cost > 0:
292
+ roi = ((scenario['option_value'] - investment_cost) / investment_cost) * 100
293
+ else:
294
+ roi = float('inf') if scenario['option_value'] > 0 else 0
295
+
296
+ roi_data.append({
297
+ 'scenario': scenario['name'],
298
+ 'roi': roi if roi != float('inf') else 999999, # Cap at very high number for display
299
+ 'absolute_gain': scenario['option_value'] - investment_cost
300
+ })
301
 
302
+ roi_fig = go.Figure()
303
+ roi_fig.add_trace(go.Bar(
304
+ x=[d['scenario'] for d in roi_data],
305
+ y=[d['roi'] for d in roi_data],
306
+ name="ROI %",
307
+ marker_color='#28A745',
308
+ text=[f"{d['roi']:.0f}%" if d['roi'] < 999999 else "∞%" for d in roi_data],
309
+ textposition='outside'
310
+ ))
311
 
312
+ roi_fig.update_layout(
313
+ title="Return on Investment (ROI) by Scenario",
314
+ xaxis_title="Scenario",
315
+ yaxis_title="ROI (%)",
316
+ height=450,
317
+ margin=dict(t=60, b=50, l=60, r=50)
318
+ )
319
+
320
+ roi_chart = roi_fig
321
+ else:
322
+ roi_chart = None
323
+
324
+ return results, comparison_chart, detailed_chart, roi_chart
325
+
326
+ def calculate_single_scenario_waterfall(
327
+ exit_valuation, total_shares, your_options, strike_price,
328
+ seed_shares, seed_capital, seed_multiple, seed_participating,
329
+ series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
330
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating
331
+ ):
332
+ """Create detailed waterfall chart for a single scenario"""
333
+
334
+ common_shares = total_shares - (seed_shares + series_a_shares + series_b_shares)
335
+ remaining_proceeds = exit_valuation
336
+ waterfall_data = []
337
+ participating_shareholders = []
338
+
339
+ # Phase 1: Liquidation preferences
340
+ if series_b_shares > 0 and series_b_capital > 0:
341
+ series_b_preference = series_b_capital * series_b_multiple
342
+ series_b_payout = min(remaining_proceeds, series_b_preference)
343
+ remaining_proceeds -= series_b_payout
344
 
345
+ if series_b_participating:
346
+ participating_shareholders.append({'round': 'Series B', 'shares': series_b_shares})
 
 
 
347
 
348
+ waterfall_data.append({
349
+ 'Round': 'Series B (Pref)',
350
+ 'Payout': series_b_payout,
351
+ 'Type': 'Preference'
352
+ })
353
+
354
+ if series_a_shares > 0 and series_a_capital > 0:
355
+ series_a_preference = series_a_capital * series_a_multiple
356
+ series_a_payout = min(remaining_proceeds, series_a_preference)
357
+ remaining_proceeds -= series_a_payout
358
 
359
+ if series_a_participating:
360
+ participating_shareholders.append({'round': 'Series A', 'shares': series_a_shares})
 
 
 
 
 
361
 
362
+ waterfall_data.append({
363
+ 'Round': 'Series A (Pref)',
364
+ 'Payout': series_a_payout,
365
+ 'Type': 'Preference'
366
+ })
367
+
368
+ if seed_shares > 0 and seed_capital > 0:
369
+ seed_preference = seed_capital * seed_multiple
370
+ seed_payout = min(remaining_proceeds, seed_preference)
371
+ remaining_proceeds -= seed_payout
372
 
373
+ if seed_participating:
374
+ participating_shareholders.append({'round': 'Seed', 'shares': seed_shares})
 
 
 
 
375
 
376
+ waterfall_data.append({
377
+ 'Round': 'Seed (Pref)',
378
+ 'Payout': seed_payout,
379
+ 'Type': 'Preference'
380
+ })
381
+
382
+ # Phase 2: Check conversions and final distribution
383
+ participating_preferred_shares = sum([p['shares'] for p in participating_shareholders])
384
+ total_participating_shares = common_shares + participating_preferred_shares
385
+
386
+ # Simplified conversion logic for visualization
387
+ if total_participating_shares > 0:
388
+ price_per_share = remaining_proceeds / total_participating_shares
389
+ common_proceeds = price_per_share * common_shares
390
 
391
+ # Add participating preferred distributions
392
+ for participant in participating_shareholders:
393
+ participating_payout = price_per_share * participant['shares']
394
+ waterfall_data.append({
395
+ 'Round': f"{participant['round']} (Part.)",
396
+ 'Payout': participating_payout,
397
+ 'Type': 'Participation'
398
+ })
399
+ else:
400
+ common_proceeds = remaining_proceeds
401
+
402
+ # Add common stock
403
+ waterfall_data.append({
404
+ 'Round': 'Common Stock',
405
+ 'Payout': common_proceeds,
406
+ 'Type': 'Common'
407
+ })
408
+
409
+ # Create the chart
410
+ fig = go.Figure()
411
+
412
+ color_map = {
413
+ 'Preference': '#FF6B6B',
414
+ 'Participation': '#4ECDC4',
415
+ 'Common': '#F7DC6F'
416
+ }
417
+
418
+ for item in waterfall_data:
419
+ if item['Payout'] > 0:
420
+ color = color_map.get(item['Type'], '#96CEB4')
421
+ fig.add_trace(go.Bar(
422
+ x=[item['Round']],
423
+ y=[item['Payout']],
424
+ name=f"{item['Round']} (${item['Payout']:,.0f})",
425
+ marker_color=color,
426
+ text=f"${item['Payout']:,.0f}",
427
+ textposition='outside'
428
+ ))
429
+
430
+ fig.update_layout(
431
+ title=f"Liquidation Waterfall - ${exit_valuation:,.0f} Exit",
432
+ xaxis_title="Stakeholder",
433
+ yaxis_title="Payout ($)",
434
+ height=450,
435
+ showlegend=True,
436
+ margin=dict(t=60, b=50, l=80, r=50)
437
  )
438
 
439
+ return fig
440
 
441
  # Create Gradio interface
442
  with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app:
 
447
  with gr.Column():
448
  gr.Markdown("## Cap Table Structure")
449
  total_shares = gr.Number(label="Total Fully Diluted Shares", value=10000000, precision=0)
450
+ your_options = gr.Number(label="Your Option Grant", value=0, precision=0)
451
  strike_price = gr.Number(label="Strike Price per Share ($)", value=0.10, precision=4)
452
 
453
  gr.Markdown("## Funding Rounds")
454
 
455
+ with gr.Accordion("Seed Round", open=False):
456
+ seed_shares = gr.Number(label="Seed Shares Issued", value=0, precision=0)
457
+ seed_capital = gr.Number(label="Seed Capital Raised ($)", value=0, precision=0)
458
  seed_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
459
  seed_participating = gr.Checkbox(label="Participating Preferred", value=False)
460
 
461
+ with gr.Accordion("Series A", open=False):
462
+ series_a_shares = gr.Number(label="Series A Shares Issued", value=0, precision=0)
463
+ series_a_capital = gr.Number(label="Series A Capital Raised ($)", value=0, precision=0)
464
  series_a_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
465
  series_a_participating = gr.Checkbox(label="Participating Preferred", value=False)
466
 
 
471
  series_b_participating = gr.Checkbox(label="Participating Preferred", value=False)
472
 
473
  with gr.Column():
474
+ gr.Markdown("## Exit Scenarios")
475
+ gr.Markdown("*Define multiple exit scenarios to compare side-by-side*")
476
 
477
+ with gr.Row():
478
+ with gr.Column():
479
+ scenario_1_name = gr.Textbox(label="Scenario 1 Name", value="Conservative", placeholder="e.g., Conservative")
480
+ exit_scenario_1 = gr.Number(label="Exit Valuation ($)", value=25000000, precision=0)
481
+
482
+ with gr.Column():
483
+ scenario_2_name = gr.Textbox(label="Scenario 2 Name", value="Base Case", placeholder="e.g., Base Case")
484
+ exit_scenario_2 = gr.Number(label="Exit Valuation ($)", value=50000000, precision=0)
485
+
486
+ with gr.Row():
487
+ with gr.Column():
488
+ scenario_3_name = gr.Textbox(label="Scenario 3 Name", value="Optimistic", placeholder="e.g., Optimistic")
489
+ exit_scenario_3 = gr.Number(label="Exit Valuation ($)", value=100000000, precision=0)
490
+
491
+ with gr.Column():
492
+ scenario_4_name = gr.Textbox(label="Scenario 4 Name", value="", placeholder="e.g., Moon Shot")
493
+ exit_scenario_4 = gr.Number(label="Exit Valuation ($)", value=0, precision=0)
494
+
495
+ with gr.Row():
496
+ with gr.Column():
497
+ scenario_5_name = gr.Textbox(label="Scenario 5 Name", value="", placeholder="e.g., IPO")
498
+ exit_scenario_5 = gr.Number(label="Exit Valuation ($)", value=0, precision=0)
499
+
500
+ calculate_btn = gr.Button("πŸš€ Calculate All Scenarios", variant="primary", size="lg")
501
 
502
  results_text = gr.Markdown()
503
 
504
  with gr.Row():
505
+ comparison_plot = gr.Plot(label="Multi-Scenario Comparison")
506
+
507
+ with gr.Row():
508
+ waterfall_plot = gr.Plot(label="Detailed Waterfall (Best Scenario)")
509
+ roi_plot = gr.Plot(label="Return on Investment")
510
 
511
  # Set up the calculation trigger
512
  inputs = [
 
514
  seed_shares, seed_capital, seed_multiple, seed_participating,
515
  series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
516
  series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
517
+ exit_scenario_1, scenario_1_name,
518
+ exit_scenario_2, scenario_2_name,
519
+ exit_scenario_3, scenario_3_name,
520
+ exit_scenario_4, scenario_4_name,
521
+ exit_scenario_5, scenario_5_name
522
  ]
523
 
524
+ outputs = [results_text, comparison_plot, waterfall_plot, roi_plot]
525
 
526
  calculate_btn.click(calculate_equity_value, inputs=inputs, outputs=outputs)
527
 
 
530
  input_component.change(calculate_equity_value, inputs=inputs, outputs=outputs)
531
 
532
  gr.Markdown("""
533
+ ## πŸ“š How to Use This Calculator
534
+
535
+ ### 🎯 Multi-Scenario Analysis
536
+ **This is where the real value lies!** Instead of guessing one exit value, define multiple realistic scenarios:
537
+ - **Conservative**: What if growth is slower than expected?
538
+ - **Base Case**: Most likely scenario based on current trajectory
539
+ - **Optimistic**: If everything goes right
540
+ - **Moon Shot**: Best case scenario (10x+ returns)
541
+
542
+ ### πŸ“Š Key Outputs
543
+ 1. **Comparison Table**: Side-by-side option values across all scenarios
544
+ 2. **Visual Charts**: See how your returns scale with different exits
545
+ 3. **ROI Analysis**: Understand your return on investment potential
546
+ 4. **Detailed Waterfall**: How liquidation preferences affect distributions
547
+
548
+ ### πŸ’‘ Decision Framework
549
+ Use this to evaluate:
550
+ - **Risk vs Reward**: How much upside vs downside?
551
+ - **Opportunity Cost**: Compare to other job offers or investments
552
+ - **Negotiation Power**: Understanding your equity's potential value range
553
+
554
+ ### πŸ”§ Liquidation Preferences
555
+ - **Non-Participating**: Investors choose preference OR convert to common (better for employees)
556
+ - **Participating**: Investors get preference AND share upside (worse for employees)
557
+ - **Multiples**: How many times their investment investors get back first
558
+
559
+ **Pro Tip**: Try toggling participating preferred on/off to see the dramatic impact on your equity value!
560
  """)
561
 
562
  if __name__ == "__main__":