Created
August 11, 2025 11:40
-
-
Save MaxGhenis/d04a7e2e1aac155606847888ddb486e7 to your computer and use it in GitHub Desktop.
Personal Injury Settlement Analysis using Stacked Simulations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Personal Injury Settlement Analysis - Stacked Simulation\n", | |
| "\n", | |
| "Analyzing retirement options for a $677,530 personal injury settlement using efficient stacked simulations." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "import numpy as np\n", | |
| "import pandas as pd\n", | |
| "import matplotlib.pyplot as plt\n", | |
| "import seaborn as sns\n", | |
| "from finsim.stacked_simulation import (\n", | |
| " create_scenario_config,\n", | |
| " simulate_stacked_scenarios,\n", | |
| " analyze_confidence_thresholds,\n", | |
| " summarize_confidence_thresholds\n", | |
| ")\n", | |
| "\n", | |
| "# Set style\n", | |
| "sns.set_style(\"whitegrid\")\n", | |
| "plt.rcParams['figure.figsize'] = (12, 8)\n", | |
| "\n", | |
| "print(\"Loaded finsim stacked simulation functionality\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Settlement Details\n", | |
| "\n", | |
| "- **Total Settlement**: $677,530\n", | |
| "- **Annuity Cost**: $527,530\n", | |
| "- **Immediate Cash**: $150,000\n", | |
| "\n", | |
| "### Annuity Options\n", | |
| "1. **Annuity A**: Life with 15-year guarantee - $3,516.29/month ($42,195/year)\n", | |
| "2. **Annuity B**: 15 years guaranteed - $4,057.78/month ($48,693/year)\n", | |
| "3. **Annuity C**: 10 years guaranteed - $5,397.12/month ($64,765/year)\n", | |
| "\n", | |
| "**Important**: Personal injury annuities are tax-free." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Settlement parameters\n", | |
| "TOTAL_SETTLEMENT = 677_530\n", | |
| "ANNUITY_COST = 527_530\n", | |
| "IMMEDIATE_CASH = TOTAL_SETTLEMENT - ANNUITY_COST\n", | |
| "\n", | |
| "# Annuity annual payments\n", | |
| "ANNUITY_A_ANNUAL = 3_516.29 * 12 # $42,195\n", | |
| "ANNUITY_B_ANNUAL = 4_057.78 * 12 # $48,693\n", | |
| "ANNUITY_C_ANNUAL = 5_397.12 * 12 # $64,765\n", | |
| "\n", | |
| "print(f\"Total Settlement: ${TOTAL_SETTLEMENT:,}\")\n", | |
| "print(f\"Annuity Cost: ${ANNUITY_COST:,}\")\n", | |
| "print(f\"Immediate Cash: ${IMMEDIATE_CASH:,}\")\n", | |
| "print(f\"\\nAnnuity A: ${ANNUITY_A_ANNUAL:,.0f}/year (life with 15-yr guarantee)\")\n", | |
| "print(f\"Annuity B: ${ANNUITY_B_ANNUAL:,.0f}/year (15 years)\")\n", | |
| "print(f\"Annuity C: ${ANNUITY_C_ANNUAL:,.0f}/year (10 years)\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Define Scenarios" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Create scenario configurations\n", | |
| "scenarios = [\n", | |
| " create_scenario_config(\n", | |
| " name=\"100% Stocks (VT)\",\n", | |
| " initial_portfolio=TOTAL_SETTLEMENT,\n", | |
| " has_annuity=False\n", | |
| " ),\n", | |
| " create_scenario_config(\n", | |
| " name=\"Annuity A + Stocks\",\n", | |
| " initial_portfolio=IMMEDIATE_CASH,\n", | |
| " has_annuity=True,\n", | |
| " annuity_type=\"Life Contingent with Guarantee\",\n", | |
| " annuity_annual=ANNUITY_A_ANNUAL,\n", | |
| " annuity_guarantee_years=15\n", | |
| " ),\n", | |
| " create_scenario_config(\n", | |
| " name=\"Annuity B + Stocks\",\n", | |
| " initial_portfolio=IMMEDIATE_CASH,\n", | |
| " has_annuity=True,\n", | |
| " annuity_type=\"Fixed Period\",\n", | |
| " annuity_annual=ANNUITY_B_ANNUAL,\n", | |
| " annuity_guarantee_years=15\n", | |
| " ),\n", | |
| " create_scenario_config(\n", | |
| " name=\"Annuity C + Stocks\",\n", | |
| " initial_portfolio=IMMEDIATE_CASH,\n", | |
| " has_annuity=True,\n", | |
| " annuity_type=\"Fixed Period\",\n", | |
| " annuity_annual=ANNUITY_C_ANNUAL,\n", | |
| " annuity_guarantee_years=10\n", | |
| " )\n", | |
| "]\n", | |
| "\n", | |
| "# Display scenario details\n", | |
| "for scenario in scenarios:\n", | |
| " print(f\"\\n{scenario['name']}:\")\n", | |
| " print(f\" Initial portfolio: ${scenario['initial_portfolio']:,}\")\n", | |
| " if scenario['has_annuity']:\n", | |
| " print(f\" Annuity income: ${scenario['annuity_annual']:,.0f}/year\")\n", | |
| " print(f\" Guarantee period: {scenario['annuity_guarantee_years']} years\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Base Parameters" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Base parameters for all scenarios\n", | |
| "base_params = {\n", | |
| " \"current_age\": 65,\n", | |
| " \"gender\": \"Male\",\n", | |
| " \"social_security\": 24_000,\n", | |
| " \"pension\": 0,\n", | |
| " \"employment_income\": 0,\n", | |
| " \"retirement_age\": 65,\n", | |
| " \"expected_return\": 7.0, # 7% expected return\n", | |
| " \"return_volatility\": 18.0, # 18% volatility\n", | |
| " \"dividend_yield\": 1.8, # 1.8% dividend yield\n", | |
| " \"state\": \"CA\",\n", | |
| " \"include_mortality\": True,\n", | |
| "}\n", | |
| "\n", | |
| "# Display parameters\n", | |
| "print(\"Base Parameters:\")\n", | |
| "print(f\" Age: {base_params['current_age']}\")\n", | |
| "print(f\" Gender: {base_params['gender']}\")\n", | |
| "print(f\" Social Security: ${base_params['social_security']:,}/year\")\n", | |
| "print(f\" Expected Return: {base_params['expected_return']}%\")\n", | |
| "print(f\" Volatility: {base_params['return_volatility']}%\")\n", | |
| "print(f\" State: {base_params['state']}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Run Stacked Simulations\n", | |
| "\n", | |
| "Using the efficient stacked approach - all scenarios run together with shared tax calculations." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Define spending levels to test\n", | |
| "spending_levels = list(range(30_000, 105_000, 5_000))\n", | |
| "\n", | |
| "print(f\"Testing {len(spending_levels)} spending levels: ${min(spending_levels):,} to ${max(spending_levels):,}\")\n", | |
| "print(f\"Running {len(scenarios)} scenarios stacked together\")\n", | |
| "print(f\"Simulations per scenario: 2,000\")\n", | |
| "print(f\"\\nThis uses only {len(spending_levels) * 30} tax calculations instead of {len(scenarios) * 2000 * 30 * len(spending_levels):,}\")\n", | |
| "print(f\"Speedup: {(len(scenarios) * 2000):,}x\")\n", | |
| "print(\"\\nRunning simulations...\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "%%time\n", | |
| "# Run the stacked simulations\n", | |
| "results = simulate_stacked_scenarios(\n", | |
| " scenarios=scenarios,\n", | |
| " spending_levels=spending_levels,\n", | |
| " n_simulations=2000,\n", | |
| " n_years=30,\n", | |
| " base_params=base_params,\n", | |
| " include_percentiles=True,\n", | |
| " random_seed=42\n", | |
| ")\n", | |
| "\n", | |
| "print(f\"\\nCompleted {len(results)} scenario-spending combinations\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Analyze Results" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Convert results to DataFrame\n", | |
| "df = pd.DataFrame(results)\n", | |
| "\n", | |
| "# Display sample results\n", | |
| "print(\"Sample results:\")\n", | |
| "display(df.head(10))" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Find Sustainable Spending at Various Confidence Levels" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Analyze confidence thresholds\n", | |
| "confidence_levels = [90, 75, 50, 25, 10]\n", | |
| "scenario_names = [s[\"name\"] for s in scenarios]\n", | |
| "\n", | |
| "# Create summary table\n", | |
| "summary_df = summarize_confidence_thresholds(\n", | |
| " results=results,\n", | |
| " scenarios=scenario_names,\n", | |
| " confidence_levels=confidence_levels\n", | |
| ")\n", | |
| "\n", | |
| "# Format the table for display\n", | |
| "formatted_summary = summary_df.copy()\n", | |
| "for col in formatted_summary.columns[1:]:\n", | |
| " formatted_summary[col] = formatted_summary[col].apply(lambda x: f\"${x:,.0f}\")\n", | |
| "\n", | |
| "print(\"\\nSUSTAINABLE SPENDING AT VARIOUS CONFIDENCE LEVELS\")\n", | |
| "print(\"=\" * 70)\n", | |
| "print(\"(All amounts in 2025 dollars)\\n\")\n", | |
| "display(formatted_summary)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Visualize Success Rates" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Create visualization\n", | |
| "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))\n", | |
| "\n", | |
| "# Left plot: All scenarios on one chart\n", | |
| "colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']\n", | |
| "for idx, scenario_name in enumerate(scenario_names):\n", | |
| " scenario_df = df[df[\"scenario\"] == scenario_name]\n", | |
| " ax1.plot(scenario_df[\"spending\"] / 1000, \n", | |
| " scenario_df[\"success_rate\"] * 100,\n", | |
| " color=colors[idx], linewidth=2.5, \n", | |
| " marker='o', markersize=5,\n", | |
| " label=scenario_name, alpha=0.8)\n", | |
| "\n", | |
| "# Add confidence level lines\n", | |
| "for confidence in [90, 75, 50, 25]:\n", | |
| " ax1.axhline(y=confidence, color='gray', linestyle='--', alpha=0.3)\n", | |
| " ax1.text(102, confidence, f'{confidence}%', fontsize=9, va='center')\n", | |
| "\n", | |
| "ax1.set_xlabel('Annual Spending ($1000s)', fontsize=12)\n", | |
| "ax1.set_ylabel('Success Rate (%)', fontsize=12)\n", | |
| "ax1.set_title('Success Rate vs Annual Spending', fontsize=14, fontweight='bold')\n", | |
| "ax1.grid(True, alpha=0.3)\n", | |
| "ax1.set_ylim(0, 105)\n", | |
| "ax1.set_xlim(28, 102)\n", | |
| "ax1.legend(loc='upper right', fontsize=10)\n", | |
| "\n", | |
| "# Right plot: 90% confidence spending comparison\n", | |
| "confidence_90 = []\n", | |
| "for scenario_name in scenario_names:\n", | |
| " thresholds = analyze_confidence_thresholds(results, scenario_name, [90])\n", | |
| " confidence_90.append(thresholds[90])\n", | |
| "\n", | |
| "bars = ax2.bar(range(len(scenario_names)), confidence_90, color=colors, alpha=0.7)\n", | |
| "ax2.set_xticks(range(len(scenario_names)))\n", | |
| "ax2.set_xticklabels([s.replace(' + Stocks', '\\n+ Stocks') for s in scenario_names], \n", | |
| " rotation=0, ha='center')\n", | |
| "ax2.set_ylabel('Sustainable Spending ($)', fontsize=12)\n", | |
| "ax2.set_title('90% Confidence Sustainable Spending', fontsize=14, fontweight='bold')\n", | |
| "ax2.grid(True, alpha=0.3, axis='y')\n", | |
| "\n", | |
| "# Add value labels on bars\n", | |
| "for bar, value in zip(bars, confidence_90):\n", | |
| " height = bar.get_height()\n", | |
| " ax2.text(bar.get_x() + bar.get_width()/2., height + 1000,\n", | |
| " f'${value:,.0f}', ha='center', va='bottom', fontsize=10)\n", | |
| "\n", | |
| "plt.tight_layout()\n", | |
| "plt.show()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Detailed Analysis at Key Confidence Levels" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Analyze key confidence levels\n", | |
| "print(\"\\nKEY FINDINGS\")\n", | |
| "print(\"=\" * 70)\n", | |
| "\n", | |
| "for confidence in [90, 75, 50]:\n", | |
| " print(f\"\\nAt {confidence}% confidence level:\")\n", | |
| " best_spending = 0\n", | |
| " best_scenario = \"\"\n", | |
| " \n", | |
| " for scenario_name in scenario_names:\n", | |
| " thresholds = analyze_confidence_thresholds(results, scenario_name, [confidence])\n", | |
| " spending = thresholds[confidence]\n", | |
| " print(f\" {scenario_name}: ${spending:,.0f}/year\")\n", | |
| " \n", | |
| " if spending > best_spending:\n", | |
| " best_spending = spending\n", | |
| " best_scenario = scenario_name\n", | |
| " \n", | |
| " print(f\" → Best option: {best_scenario}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Portfolio Percentiles Analysis" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Analyze portfolio outcomes at 90% confidence spending levels\n", | |
| "print(\"\\nPORTFOLIO OUTCOMES AT 90% CONFIDENCE SPENDING\")\n", | |
| "print(\"=\" * 70)\n", | |
| "\n", | |
| "for scenario_name in scenario_names:\n", | |
| " # Find 90% confidence spending\n", | |
| " thresholds = analyze_confidence_thresholds(results, scenario_name, [90])\n", | |
| " target_spending = thresholds[90]\n", | |
| " \n", | |
| " # Find closest result\n", | |
| " scenario_results = df[df[\"scenario\"] == scenario_name]\n", | |
| " closest_idx = (scenario_results[\"spending\"] - target_spending).abs().idxmin()\n", | |
| " result = scenario_results.loc[closest_idx]\n", | |
| " \n", | |
| " print(f\"\\n{scenario_name} at ${target_spending:,.0f}/year:\")\n", | |
| " print(f\" Success rate: {result['success_rate']:.1%}\")\n", | |
| " print(f\" Median final portfolio: ${result['median_final']:,.0f}\")\n", | |
| " print(f\" 10th percentile: ${result['p10_final']:,.0f}\")\n", | |
| " print(f\" 90th percentile: ${result['p90_final']:,.0f}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Risk-Return Tradeoff" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Create risk-return plot\n", | |
| "fig, ax = plt.subplots(figsize=(10, 6))\n", | |
| "\n", | |
| "# For each scenario, plot confidence curve\n", | |
| "confidence_range = [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10]\n", | |
| "\n", | |
| "for idx, scenario_name in enumerate(scenario_names):\n", | |
| " thresholds = analyze_confidence_thresholds(results, scenario_name, confidence_range)\n", | |
| " spending_at_confidence = [thresholds[c] for c in confidence_range]\n", | |
| " \n", | |
| " ax.plot(confidence_range, np.array(spending_at_confidence) / 1000,\n", | |
| " color=colors[idx], linewidth=2.5,\n", | |
| " marker='o', markersize=4,\n", | |
| " label=scenario_name, alpha=0.8)\n", | |
| "\n", | |
| "ax.set_xlabel('Confidence Level (%)', fontsize=12)\n", | |
| "ax.set_ylabel('Sustainable Spending ($1000s/year)', fontsize=12)\n", | |
| "ax.set_title('Risk-Return Tradeoff: Confidence vs Sustainable Spending', \n", | |
| " fontsize=14, fontweight='bold')\n", | |
| "ax.grid(True, alpha=0.3)\n", | |
| "ax.legend(loc='upper right', fontsize=10)\n", | |
| "ax.invert_xaxis() # Higher confidence on the left\n", | |
| "\n", | |
| "# Add vertical lines at key confidence levels\n", | |
| "for conf in [90, 75, 50]:\n", | |
| " ax.axvline(x=conf, color='gray', linestyle='--', alpha=0.3)\n", | |
| "\n", | |
| "plt.tight_layout()\n", | |
| "plt.show()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Summary and Recommendations" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "print(\"\\nSUMMARY AND RECOMMENDATIONS\")\n", | |
| "print(\"=\" * 70)\n", | |
| "\n", | |
| "print(\"\\n1. CONSERVATIVE APPROACH (90% confidence):\")\n", | |
| "print(\" → Annuity A (Life with 15-yr guarantee) provides highest sustainable spending\")\n", | |
| "print(\" → Can sustainably spend ~$61,000/year\")\n", | |
| "print(\" → Provides lifetime income protection\")\n", | |
| "\n", | |
| "print(\"\\n2. MODERATE APPROACH (75% confidence):\")\n", | |
| "print(\" → Annuity A still optimal at ~$66,000/year\")\n", | |
| "print(\" → 100% Stocks catching up at ~$59,000/year\")\n", | |
| "\n", | |
| "print(\"\\n3. BALANCED APPROACH (50% confidence):\")\n", | |
| "print(\" → 100% Stocks becomes optimal at ~$73,000/year\")\n", | |
| "print(\" → Higher potential but more volatility\")\n", | |
| "\n", | |
| "print(\"\\n4. KEY CONSIDERATIONS:\")\n", | |
| "print(\" • Personal injury annuities are TAX-FREE (major advantage)\")\n", | |
| "print(\" • Annuity A provides lifetime protection against longevity risk\")\n", | |
| "print(\" • 100% Stocks offers more flexibility and potential upside\")\n", | |
| "print(\" • Health status and life expectancy are critical factors\")\n", | |
| "print(\" • Consider partial strategies (e.g., 50% annuity, 50% stocks)\")\n", | |
| "\n", | |
| "print(\"\\n\" + \"=\" * 70)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Save Results" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# Save detailed results\n", | |
| "df.to_csv('settlement_analysis_results.csv', index=False)\n", | |
| "print(\"Results saved to settlement_analysis_results.csv\")\n", | |
| "\n", | |
| "# Save summary table\n", | |
| "summary_df.to_csv('settlement_confidence_summary.csv', index=False)\n", | |
| "print(\"Summary saved to settlement_confidence_summary.csv\")" | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "Python 3", | |
| "language": "python", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.11.0" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 4 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment