Last active
May 23, 2025 00:23
-
-
Save thuliumsystems/5a9422f4269e94300c6101ff70ab7282 to your computer and use it in GitHub Desktop.
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": "code", | |
| "execution_count": 27, | |
| "id": "9612a570", | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "--- TSP Balance Over Time ---\n", | |
| " Age Aggressive (8% Annual) Conservative (4% Annual)\n", | |
| "Date \n", | |
| "2025-07 20 $1,503.15 $1,503.15\n", | |
| "2025-07 20 $3,015.97 $3,011.22\n", | |
| "2025-08 21 $4,538.53 $4,524.23\n", | |
| "2025-09 21 $6,070.88 $6,042.19\n", | |
| "2025-10 21 $7,613.09 $7,565.12\n", | |
| "2025-11 21 $9,165.22 $9,093.04\n", | |
| "2025-12 21 $10,727.34 $10,625.96\n", | |
| "2026 22 $31,023.12 $30,151.79\n", | |
| "2027 23 $39,997.37 $37,856.49\n", | |
| "2028 24 $51,724.57 $47,593.61\n", | |
| "2029 25 $63,746.32 $57,244.53\n", | |
| "2030 26 $77,001.66 $67,548.64\n", | |
| "2031 27 $90,330.31 $77,600.22\n", | |
| "2032 28 $107,615.77 $90,221.94\n", | |
| "2033 29 $125,196.23 $102,646.57\n", | |
| "2034 30 $144,454.98 $115,835.33\n", | |
| "2035 31 $163,707.74 $128,632.43\n", | |
| "2036 32 $188,555.15 $144,628.72\n", | |
| "2037 33 $213,698.18 $160,298.19\n", | |
| "2038 34 $241,124.50 $176,861.59\n", | |
| "2039 35 $268,436.96 $192,870.57\n", | |
| "2040 36 $303,572.31 $212,814.62\n", | |
| "2041 37 $339,004.13 $232,280.11\n", | |
| "2042 38 $377,542.35 $252,791.36\n", | |
| "2043 39 $415,819.93 $272,557.78\n", | |
| "2044 40 $464,951.92 $297,120.16\n", | |
| "2045 41 $514,381.52 $321,026.44\n", | |
| "2046 42 $568,037.35 $346,156.13\n", | |
| "2047 43 $621,232.86 $370,318.21\n", | |
| "2048 44 $689,407.12 $400,283.38\n", | |
| "2049 45 $757,880.56 $429,384.77\n", | |
| "2050 46 $832,103.73 $459,917.36\n", | |
| "2051 47 $905,594.91 $489,221.75\n", | |
| "2052 48 $999,675.96 $525,507.43\n", | |
| "2053 49 $1,094,058.33 $560,686.36\n", | |
| "2054 50 $1,196,263.14 $597,539.59\n", | |
| "2055 51 $1,297,366.37 $632,859.75\n", | |
| "2056 52 $1,426,693.33 $676,539.52\n", | |
| "2057 53 $1,556,324.51 $718,828.31\n", | |
| "2058 54 $1,696,598.03 $763,075.80\n", | |
| "2059 55 $1,835,267.14 $805,433.57\n", | |
| "2060 56 $2,012,545.78 $857,763.38\n", | |
| "2061 57 $2,190,132.56 $908,369.70\n", | |
| "2062 58 $2,382,198.15 $961,267.42\n", | |
| "2063 59 $2,571,975.23 $1,011,858.20\n", | |
| "\n", | |
| "--- Annual Salary Over Time ---\n", | |
| " Annual Salary\n", | |
| "Year \n", | |
| "2025 $32,796.00\n", | |
| "2026 $34,107.84\n", | |
| "2027 $35,419.68\n", | |
| "2028 $36,731.52\n", | |
| "2029 $38,043.36\n", | |
| "2030 $39,355.20\n", | |
| "2031 $40,667.04\n", | |
| "2032 $41,978.88\n", | |
| "2033 $43,290.72\n", | |
| "2034 $44,602.56\n", | |
| "2035 $45,914.40\n", | |
| "2036 $47,226.24\n", | |
| "2037 $48,538.08\n", | |
| "2038 $49,849.92\n", | |
| "2039 $51,161.76\n", | |
| "2040 $52,473.60\n", | |
| "2041 $53,785.44\n", | |
| "2042 $55,097.28\n", | |
| "2043 $56,409.12\n", | |
| "2044 $57,720.96\n", | |
| "2045 $59,032.80\n", | |
| "2046 $60,344.64\n", | |
| "2047 $61,656.48\n", | |
| "2048 $62,968.32\n", | |
| "2049 $64,280.16\n", | |
| "2050 $65,592.00\n", | |
| "2051 $66,903.84\n", | |
| "2052 $68,215.68\n", | |
| "2053 $69,527.52\n", | |
| "2054 $70,839.36\n", | |
| "2055 $72,151.20\n", | |
| "2056 $73,463.04\n", | |
| "2057 $74,774.88\n", | |
| "2058 $76,086.72\n", | |
| "2059 $77,398.56\n", | |
| "2060 $78,710.40\n", | |
| "2061 $80,022.24\n", | |
| "2062 $81,334.08\n", | |
| "2063 $82,645.92\n", | |
| "2064 $83,957.76\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "from datetime import datetime, timedelta\n", | |
| "import pandas as pd\n", | |
| "\n", | |
| "# Sets the current date for the simulation context\n", | |
| "current_date = datetime(2025, 7, 1)\n", | |
| "# Stores the user's birth date as a datetime object.\n", | |
| "birth_date = datetime.strptime(\"2004-08-02\", \"%Y-%m-%d\")\n", | |
| "\n", | |
| "# Specifies the number of initial years during which a higher contribution rate will apply.\n", | |
| "initial_years_high_contribution = 2\n", | |
| "# Sets the contribution rate (as a percentage) for the initial period.\n", | |
| "initial_contribution_rate = 0.5\n", | |
| "\n", | |
| "# Sets the contribution rate (as a percentage) for the period after the initial years, until retirement.\n", | |
| "later_contribution_rate = 0.15\n", | |
| "\n", | |
| "# Defines the target retirement age in years.\n", | |
| "retirement_age = 59.5\n", | |
| "# Sets the annual contribution limit for the Roth TSP in 2025.\n", | |
| "annual_limit = 23000\n", | |
| "# Defines the initial base monthly salary (e.g., E-3 salary as of April 2025).\n", | |
| "salary_start = 2733.00\n", | |
| "# Sets the employer (DoD) matching contribution rate as a percentage of salary.\n", | |
| "match_rate = 0.05\n", | |
| "# Annual real growth rate for salary (applied without compounding to the base salary).\n", | |
| "salary_growth_rate_annual = 0.04\n", | |
| "\n", | |
| "# Investment Parameters (adjusted for effective annual compounding)\n", | |
| "# Monthly growth rate equivalent to 8% annual growth, compounded monthly.\n", | |
| "growth_aggressive = (1 + 0.08)**(1/12) - 1\n", | |
| "# Monthly growth rate equivalent to 4% annual growth, compounded monthly.\n", | |
| "growth_conservative = (1 + 0.04)**(1/12) - 1\n", | |
| "\n", | |
| "# --- Date Calculations for Simulation ---\n", | |
| "# The simulation starts from the current_date.\n", | |
| "start_sim_date = current_date\n", | |
| "\n", | |
| "# Calculates the exact retirement date (59 years and 6 months after birth date).\n", | |
| "target_year = birth_date.year + int(retirement_age)\n", | |
| "target_month = birth_date.month + int((retirement_age - int(retirement_age)) * 12)\n", | |
| "if target_month > 12:\n", | |
| " target_year += (target_month - 1) // 12\n", | |
| " target_month = (target_month - 1) % 12 + 1\n", | |
| "retirement_date = datetime(target_year, target_month, birth_date.day)\n", | |
| "\n", | |
| "# Calculates the total number of months for the simulation.\n", | |
| "# The simulation runs from the current date until the retirement date.\n", | |
| "total_months_simulation = int((retirement_date - start_sim_date).days / 30.4375) + 1\n", | |
| "if total_months_simulation < 0:\n", | |
| " total_months_simulation = 0 # If retirement date has already passed\n", | |
| "\n", | |
| "# --- Function to Simulate TSP Growth ---\n", | |
| "def simulate_tsp_custom(initial_monthly_salary, growth_rate, initial_years_high_contribution, initial_rate, later_rate, total_months_sim, start_sim_date, birth_date, annual_limit, match_rate, salary_growth_rate_annual):\n", | |
| " balance = 0\n", | |
| " balances = []\n", | |
| " # Store the initial monthly salary for simple growth calculation\n", | |
| " base_monthly_salary = initial_monthly_salary\n", | |
| "\n", | |
| " for month_offset in range(total_months_sim):\n", | |
| " # Calculate the current date within the simulation loop\n", | |
| " current_sim_date = start_sim_date + timedelta(days=month_offset * 30.4375)\n", | |
| "\n", | |
| " # Calculate years passed since the simulation start for salary increase and contribution rate\n", | |
| " # Using full years passed since the start_sim_date to apply simple salary growth annually.\n", | |
| " sim_years_passed = (current_sim_date.year - start_sim_date.year)\n", | |
| "\n", | |
| " # Calculate current monthly salary with simple annual growth\n", | |
| " # Each year, the salary increases by a fixed amount based on the initial salary (not compounded).\n", | |
| " current_monthly_salary = base_monthly_salary + (sim_years_passed * (base_monthly_salary * salary_growth_rate_annual))\n", | |
| "\n", | |
| " if sim_years_passed < initial_years_high_contribution:\n", | |
| " personal_contribution = current_monthly_salary * initial_rate\n", | |
| " else:\n", | |
| " personal_contribution = min(current_monthly_salary * later_rate, annual_limit / 12)\n", | |
| "\n", | |
| " # Calculate DoD matching contribution\n", | |
| " dod_match = min(current_monthly_salary * match_rate, personal_contribution)\n", | |
| " # Total contribution for the month\n", | |
| " total_contribution = personal_contribution + dod_match\n", | |
| "\n", | |
| " # Update balance with growth and new contribution\n", | |
| " balance = balance * (1 + growth_rate) + total_contribution\n", | |
| " balances.append(balance)\n", | |
| " return balances\n", | |
| "\n", | |
| "# --- Simulate Annual Salary Growth (without compounding) ---\n", | |
| "def simulate_annual_salary_growth(start_salary_monthly, annual_growth_rate, start_date, end_date):\n", | |
| " salary_data = []\n", | |
| " base_monthly_salary = start_salary_monthly # Store the initial base salary for non-compounding growth\n", | |
| " \n", | |
| " # Iterate through years from the start_date's year up to the end_date's year\n", | |
| " for year in range(start_date.year, end_date.year + 1):\n", | |
| " years_passed = year - start_date.year\n", | |
| " # Calculate current monthly salary with simple annual growth\n", | |
| " # The increase is a fixed amount (base_monthly_salary * annual_growth_rate) added each year.\n", | |
| " current_monthly_salary = base_monthly_salary + (years_passed * (base_monthly_salary * annual_growth_rate))\n", | |
| " \n", | |
| " salary_data.append({\n", | |
| " \"Year\": year,\n", | |
| " \"Annual Salary\": current_monthly_salary * 12 # Annualized salary for the year\n", | |
| " })\n", | |
| " return pd.DataFrame(salary_data).set_index(\"Year\")\n", | |
| "\n", | |
| "# --- Run TSP Simulations ---\n", | |
| "balances_aggressive = simulate_tsp_custom(\n", | |
| " salary_start, growth_aggressive, initial_years_high_contribution, initial_contribution_rate,\n", | |
| " later_contribution_rate, total_months_simulation, start_sim_date, birth_date, annual_limit, match_rate, salary_growth_rate_annual\n", | |
| ")\n", | |
| "balances_conservative = simulate_tsp_custom(\n", | |
| " salary_start, growth_conservative, initial_years_high_contribution, initial_contribution_rate,\n", | |
| " later_contribution_rate, total_months_simulation, start_sim_date, birth_date, annual_limit, match_rate, salary_growth_rate_annual\n", | |
| ")\n", | |
| "\n", | |
| "# --- Prepare Data for TSP DataFrame ---\n", | |
| "data_for_df = []\n", | |
| "current_sim_month_index = 0\n", | |
| "while current_sim_month_index < total_months_simulation:\n", | |
| " sim_date = start_sim_date + timedelta(days=current_sim_month_index * 30.4375)\n", | |
| "\n", | |
| " if sim_date.year == 2025 and sim_date.month <= 12:\n", | |
| " age_at_month = (sim_date - birth_date).days / 365.25\n", | |
| " if current_sim_month_index < len(balances_aggressive):\n", | |
| " data_for_df.append({\n", | |
| " \"Date\": sim_date.strftime(\"%Y-%m\"),\n", | |
| " \"Age\": int(age_at_month),\n", | |
| " \"Aggressive (8% Annual)\": balances_aggressive[current_sim_month_index],\n", | |
| " \"Conservative (4% Annual)\": balances_conservative[current_sim_month_index]\n", | |
| " })\n", | |
| " current_sim_month_index += 1\n", | |
| " elif sim_date.year > 2025:\n", | |
| " break\n", | |
| "\n", | |
| "start_yearly_data_year = 2026\n", | |
| "for year_val in range(start_yearly_data_year, retirement_date.year + 1):\n", | |
| " target_date_for_year_end = datetime(year_val, 12, 31)\n", | |
| " months_offset_to_year_end = int((target_date_for_year_end - start_sim_date).days / 30.4375)\n", | |
| "\n", | |
| " if months_offset_to_year_end >= 0 and months_offset_to_year_end < total_months_simulation:\n", | |
| " age_at_year_end = (target_date_for_year_end - birth_date).days / 365.25\n", | |
| " data_for_df.append({\n", | |
| " \"Date\": str(year_val),\n", | |
| " \"Age\": int(age_at_year_end),\n", | |
| " \"Aggressive (8% Annual)\": balances_aggressive[months_offset_to_year_end],\n", | |
| " \"Conservative (4% Annual)\": balances_conservative[months_offset_to_year_end]\n", | |
| " })\n", | |
| "\n", | |
| "df_tsp = pd.DataFrame(data_for_df)\n", | |
| "df_tsp.set_index(\"Date\", inplace=True)\n", | |
| "df_tsp[\"Aggressive (8% Annual)\"] = df_tsp[\"Aggressive (8% Annual)\"].map(\"${:,.2f}\".format)\n", | |
| "df_tsp[\"Conservative (4% Annual)\"] = df_tsp[\"Conservative (4% Annual)\"].map(\"${:,.2f}\".format)\n", | |
| "\n", | |
| "print(\"\\n--- TSP Balance Over Time ---\")\n", | |
| "print(df_tsp)\n", | |
| "\n", | |
| "df_salary = simulate_annual_salary_growth(salary_start, salary_growth_rate_annual, start_sim_date, retirement_date)\n", | |
| "df_salary[\"Annual Salary\"] = df_salary[\"Annual Salary\"].map(\"${:,.2f}\".format)\n", | |
| "\n", | |
| "print(\"\\n--- Annual Salary Over Time ---\")\n", | |
| "print(df_salary)" | |
| ] | |
| } | |
| ], | |
| "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.9.6" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 5 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment