import streamlit as st
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from scipy.optimize import minimize_scalar
def calculate_net_worth_scenario(initial_net_worth, investment_return, investment_allocation, salary, savings_rate, savings_rate_growth, expenses, taxes, years, emergency_fund_target, retirement_age, current_age, social_security):
net_worth = initial_net_worth
net_worth_history = []
emergency_fund = 0
inflation_rate = 2.0 # Assume a 2% annual inflation rate
for year in range(1, years + 1):
age = current_age + year
if age >= retirement_age:
salary = 0 # Stop salary income after retirement
annual_expenses = expenses # Exclude taxes after retirement
annual_income = social_security # Include Social Security income
else:
annual_expenses = expenses + taxes # Include taxes before retirement
annual_income = salary # Use salary as income before retirement
annual_savings = annual_income * (savings_rate / 100)
leftover_income = annual_income - annual_expenses
if emergency_fund < emergency_fund_target:
emergency_fund_contribution = min(annual_savings, emergency_fund_target - emergency_fund)
emergency_fund += emergency_fund_contribution
annual_savings -= emergency_fund_contribution
net_worth += annual_savings
net_worth += leftover_income
# Calculate investment return on the allocated portion of net worth
investment_portion = net_worth * (investment_allocation / 100)
investment_return_amount = investment_portion * (investment_return / 100)
net_worth += investment_return_amount
savings_rate = min(savings_rate + savings_rate_growth, 100)
# Apply inflation to expenses
expenses *= (1 + inflation_rate / 100)
net_worth_history.append(net_worth)
return net_worth_history
def monte_carlo_simulation(initial_net_worth, base_investment_return, investment_allocation, salary, savings_rate, savings_rate_growth, expenses, taxes, years, emergency_fund_target, retirement_age, current_age, social_security, num_simulations=1000):
simulation_results = []
for _ in range(num_simulations):
random_return = np.random.normal(base_investment_return, 2) # Assuming a standard deviation of 2%
net_worth_history = calculate_net_worth_scenario(initial_net_worth, random_return, investment_allocation, salary, savings_rate, savings_rate_growth, expenses, taxes, years, emergency_fund_target, retirement_age, current_age, social_security)
simulation_results.append(net_worth_history[-1]) # Append the final net worth
return simulation_results
def optimize_for_goal(target_net_worth, max_years, *args):
# First, calculate the net worth with the current savings rate
current_net_worth_history = calculate_net_worth_scenario(*args, years=max_years)
final_net_worth = current_net_worth_history[-1]
if final_net_worth >= target_net_worth:
return args[5] # Return the current savings rate if the goal is already achievable
# If the goal isn't achievable with the current savings rate, optimize
def objective(savings_rate):
new_args = list(args)
savings_rate_index = 5 # Assuming savings_rate is the 6th argument (index 5)
years_index = -4 # Assuming years is the 4th to last argument
new_args[savings_rate_index] = savings_rate
new_args[years_index] = max_years
net_worth_history = calculate_net_worth_scenario(*new_args)
return abs(net_worth_history[-1] - target_net_worth)
# Calculate the maximum possible savings rate based on income and expenses
salary = args[3] # Assuming salary is the 4th argument
expenses = args[6] # Assuming expenses is the 7th argument
max_possible_savings_rate = max(0, min(100, (salary - expenses) / salary * 100))
# Use the calculated max_possible_savings_rate as the upper bound
current_savings_rate = args[5]
result = minimize_scalar(objective, bounds=(current_savings_rate, max_possible_savings_rate), method='bounded')
return result.x
def perform_sensitivity_analysis(base_scenario, parameter_ranges):
results = {}
for param, range_values in parameter_ranges.items():
param_results = []
for value in range_values:
scenario = base_scenario.copy()
scenario[param] = value
net_worth_history = calculate_net_worth_scenario(**scenario)
param_results.append(net_worth_history[-1])
results[param] = param_results
return results
def optimize_retirement_age(target_net_worth, max_age, *args):
def objective(retirement_age):
unpacked_args = list(args)
unpacked_args[-2] = retirement_age # Replace the original retirement_age
net_worth_history = calculate_net_worth_scenario(*unpacked_args)
return abs(net_worth_history[-1] - target_net_worth)
current_age = args[-2] # Assuming current_age is the second-to-last argument
result = minimize_scalar(objective, bounds=(current_age, max_age), method='bounded')
return int(result.x)
def suggest_tax_optimization(salary, current_tax):
tax_rate = current_tax / salary * 100
suggestions = []
if tax_rate > 30:
suggestions.append("Consider maximizing contributions to tax-advantaged accounts like 401(k) or IRA.")
if tax_rate > 25:
suggestions.append("Look into tax-loss harvesting strategies for your investments.")
if tax_rate > 20:
suggestions.append("Explore potential deductions you might be missing, such as charitable contributions or business expenses.")
return suggestions
def optimize_investment_allocation(risk_tolerance, time_horizon, current_age, retirement_age):
# Simple allocation strategy based on risk tolerance and time horizon
years_to_retirement = retirement_age - current_age
stock_allocation = min(110 - current_age, 90) # Basic age-based rule
# Adjust based on risk tolerance (1-10 scale)
stock_allocation += (risk_tolerance - 5) * 5
# Adjust based on time horizon
if time_horizon < 5:
stock_allocation = max(stock_allocation - 20, 20)
elif time_horizon > 15:
stock_allocation = min(stock_allocation + 10, 90)
bond_allocation = 100 - stock_allocation
return {"Stocks": stock_allocation, "Bonds": bond_allocation}
def main():
st.title("Net Worth Calculator")
st.sidebar.header("Adjustable Parameters")
# User Inputs
initial_net_worth = st.sidebar.number_input("Initial Net Worth ($)", min_value=0, value=300000, step=10000)
salary = st.sidebar.number_input("Annual Salary ($)", min_value=0, value=100000, step=1000)
taxes = st.sidebar.number_input("Annual Taxes ($)", min_value=0, value=55000, step=500)
savings_rate = st.sidebar.slider("Savings Rate (%)", min_value=0.0, max_value=100.0, value=70.0, step=0.5)
savings_rate_growth = st.sidebar.slider("Savings Return (%)", min_value=0.0, max_value=10.0, value=2.0, step=0.1)
base_investment_return = st.sidebar.slider("Base Investment Return (%)", min_value=0.0, max_value=100.0, value=5.0, step=5.0)
investment_allocation = st.sidebar.slider("Current Investment Allocation (% in Stocks)", min_value=0.0, max_value=100.0, value=80.0, step=1.0)
current_age = st.sidebar.number_input("Current Age", min_value=0, value=25, step=1)
retirement_age = st.sidebar.number_input("Retirement Age", min_value=0, value=70, step=1)
social_security = st.sidebar.number_input("Annual Social Security Benefit ($)", min_value=0, value=20000, step=1000)
# Monthly Expenses
home_expenses = st.sidebar.number_input("Home/Rent Expenses ($ per month)", min_value=0, value=1000, step=50)
food_expenses = st.sidebar.number_input("Food Expenses ($ per month)", min_value=0, value=250, step=50)
gas_expenses = st.sidebar.number_input("Gas Expenses ($ per month)", min_value=0, value=180, step=10)
utilities_expenses = st.sidebar.number_input("Utilities ($ per month)", min_value=0, value=100, step=10)
healthcare_expenses = st.sidebar.number_input("Healthcare Expenses ($ per month)", min_value=0, value=0, step=10)
insurance_expenses = st.sidebar.number_input("Insurance ($ per month)", min_value=0, value=190, step=10)
entertainment_expenses = st.sidebar.number_input("Entertainment ($ per month)", min_value=0, value=167, step=10)
other_expenses = st.sidebar.number_input("Other Expenses ($ per month)", min_value=0, value=250, step=10)
years = st.sidebar.slider("Number of Years", min_value=1, max_value=50, value=10, step=1)
emergency_fund_target = st.sidebar.number_input("Emergency Fund Target ($)", min_value=0, value=5000, step=500)
# Total Monthly Expenses
total_monthly_expenses = (home_expenses + food_expenses + gas_expenses + utilities_expenses +
healthcare_expenses + insurance_expenses + entertainment_expenses + other_expenses)
# Convert monthly expenses to annual
total_annual_expenses = total_monthly_expenses * 12
# Calculate Net Worth for different scenarios
conservative_history = calculate_net_worth_scenario(initial_net_worth, base_investment_return * 0.70, investment_allocation, salary, savings_rate, savings_rate_growth, total_annual_expenses, taxes, years, emergency_fund_target=5000, retirement_age=retirement_age, current_age=current_age, social_security=social_security)
moderate_history = calculate_net_worth_scenario(initial_net_worth, base_investment_return, investment_allocation, salary, savings_rate, savings_rate_growth, total_annual_expenses, taxes, years, emergency_fund_target=5000, retirement_age=retirement_age, current_age=current_age, social_security=social_security)
risky_history = calculate_net_worth_scenario(initial_net_worth, base_investment_return * 1.3, investment_allocation, salary, savings_rate, savings_rate_growth, total_annual_expenses, taxes, years, emergency_fund_target=5000, retirement_age=retirement_age, current_age=current_age, social_security=social_security)
# Display the final net worth from the moderate scenario
final_moderate_net_worth = moderate_history[-1]
st.metric(label="Final Net Worth (Moderate Strategy)", value=f"${final_moderate_net_worth:,.2f}")
# Estimate annual spending based on the 4% rule
annual_spending_4_percent_rule = final_moderate_net_worth * 0.04
st.write(f"Estimated Annual Spending (4% Rule): ${annual_spending_4_percent_rule:,.2f}")
# Create a DataFrame for net worth history
net_worth_df = pd.DataFrame({
'Year': np.arange(1, years + 1),
'Conservative': conservative_history,
'Moderate': moderate_history,
'Risky': risky_history
})
# Display the DataFrame as a table
st.write("### Projected Net Worth Over Time:")
st.dataframe(net_worth_df.style.format({
'Conservative': "${:,.2f}",
'Moderate': "${:,.2f}",
'Risky': "${:,.2f}"
}))
# Plot Net Worth History using Plotly as a line chart
st.write("### Net Worth Growth Over Time:")
fig = px.line(
net_worth_df,
x='Year',
y=['Conservative', 'Moderate', 'Risky'],
title='Net Worth Growth Over Time',
labels={'value': 'Net Worth ($)', 'variable': 'Strategy'},
markers=True # Add markers for better visibility
)
# Update layout for a transparent background
fig.update_layout(
xaxis_title='Year',
yaxis_title='Net Worth ($)',
hovermode='x unified',
plot_bgcolor='rgba(0,0,0,0)', # Transparent background for the plot area
paper_bgcolor='rgba(0,0,0,0)', # Transparent paper background
font=dict(size=12, color='white'),
title_font=dict(size=16, color='white'),
xaxis=dict(
showgrid=True, # Gridlines for better readability
gridcolor='lightgrey',
zeroline=True, # Show zero line
color='white', # White axis labels
showline=True, # Show x-axis line
linewidth=1,
linecolor='white'
),
yaxis=dict(
showgrid=True, # Gridlines for better readability
gridcolor='lightgrey',
zeroline=True, # Show zero line
color='white', # White axis labels
showline=True, # Show y-axis line
linewidth=1,
linecolor='white',
type='linear'
)
)
# Update traces for different strategies
fig.update_traces(
line=dict(width=2),
selector=dict(name='Conservative'),
line_color='green',
opacity=0.75 # Set opacity for transparency
)
fig.update_traces(
line=dict(width=2),
selector=dict(name='Moderate'),
line_color='blue',
opacity=0.75 # Set opacity for transparency
)
fig.update_traces(
line=dict(width=2),
selector=dict(name='Risky'),
line_color='red',
opacity=0.75 # Set opacity for transparency
)
st.plotly_chart(fig)
# Monte Carlo Simulation
st.write("### Monte Carlo Simulation of Final Net Worth")
simulation_results = monte_carlo_simulation(initial_net_worth, base_investment_return, investment_allocation, salary, savings_rate, savings_rate_growth, total_annual_expenses, taxes, years, 5000, retirement_age, current_age, social_security)
counts, bins = np.histogram(simulation_results, bins=100)
st.write(f"Simulated {len(simulation_results)} scenarios.")
st.write(f"Mean Final Net Worth: ${np.mean(simulation_results):,.2f}")
st.write(f"Median Final Net Worth: ${np.median(simulation_results):,.2f}")
st.write(f"5th Percentile: ${np.percentile(simulation_results, 5):,.2f}")
st.write(f"95th Percentile: ${np.percentile(simulation_results, 95):,.2f}")
# Plot Monte Carlo Simulation Results
st.write("### Distribution of Final Net Worth from Monte Carlo Simulation")
# Create a histogram with custom colors and bin size
fig_monte_carlo = go.Figure(data=[go.Bar(
x=bins[:-1], # Bin start values
y=counts, # Bin counts
marker=dict(
color=counts, # Use the bin counts to color the bars
colorscale='Cividis', # Use a colormap for multi-colored bins
colorbar=dict(title='Frequency'),
showscale=True
),
opacity=0.75
)])
fig_monte_carlo.update_layout(
title='Monte Carlo Simulation Results',
xaxis_title='Final Net Worth ($)',
yaxis_title='Frequency',
plot_bgcolor='rgba(0,0,0,0)', # Transparent background for the plot area
paper_bgcolor='rgba(0,0,0,0)', # Transparent paper background
font=dict(size=12, color='white'), # Black font for better contrast
title_font=dict(size=16, color='white'), # Black title font
xaxis=dict(
showgrid=True,
gridcolor='lightgrey',
zeroline=True,
color='white',
showline=True,
linewidth=1,
linecolor='white'
),
yaxis=dict(
showgrid=True,
gridcolor='lightgrey',
zeroline=True,
color='white',
showline=True,
linewidth=1,
linecolor='white'
)
)
st.plotly_chart(fig_monte_carlo)
# Goal-based optimization
st.sidebar.subheader("Goal Optimization")
target_net_worth = st.sidebar.number_input("Target Net Worth ($)", min_value=0, value=1000000, step=10000)
max_years = st.sidebar.number_input("Maximum Years to Reach Goal", min_value=1, value=30, step=1)
if st.sidebar.button("Optimize for Goal"):
current_net_worth_history = calculate_net_worth_scenario(
initial_net_worth, base_investment_return, investment_allocation,
salary, savings_rate, savings_rate_growth, total_annual_expenses,
taxes, years, emergency_fund_target, retirement_age, current_age, social_security
)
final_net_worth = current_net_worth_history[-1]
st.write(f"Projected net worth with current savings rate: ${final_net_worth:,.2f}")
st.write(f"Target net worth: ${target_net_worth:,.2f}")
if final_net_worth >= target_net_worth:
st.success(f"Your current savings rate of {savings_rate:.2f}% is sufficient to reach your goal!")
else:
optimal_savings_rate = optimize_for_goal(
target_net_worth, max_years,
initial_net_worth, base_investment_return, investment_allocation,
salary, savings_rate, savings_rate_growth, total_annual_expenses,
taxes, years, emergency_fund_target, retirement_age, current_age, social_security
)
st.write(f"Current savings rate: {savings_rate:.2f}%")
st.write(f"Optimal savings rate to reach your goal: {optimal_savings_rate:.2f}%")
# Calculate the maximum possible savings rate for comparison
max_possible_savings_rate = max(0, min(100, (salary - total_annual_expenses) / salary * 100))
st.write(f"Maximum possible savings rate based on current income and expenses: {max_possible_savings_rate:.2f}%")
if optimal_savings_rate >= max_possible_savings_rate:
st.warning("The optimal savings rate is at or above the maximum possible rate given your current income and expenses. Consider adjusting your goal, timeline, or finding ways to increase income or reduce expenses.")
elif optimal_savings_rate > max_possible_savings_rate * 0.9:
st.warning("The optimal savings rate is very close to the maximum possible rate. This may be challenging to achieve and maintain.")
else:
st.success("The optimal savings rate appears achievable based on your current income and expenses.")
# Retirement age optimization
st.sidebar.subheader("Retirement Age Optimization")
retirement_target = st.sidebar.number_input("Target Retirement Net Worth ($)", min_value=0, value=2000000, step=10000)
max_retirement_age = st.sidebar.number_input("Maximum Retirement Age", min_value=current_age, value=70, step=1)
if st.sidebar.button("Optimize Retirement Age"):
current_net_worth_history = calculate_net_worth_scenario(
initial_net_worth, base_investment_return, investment_allocation,
salary, savings_rate, savings_rate_growth, total_annual_expenses,
taxes, years, emergency_fund_target, retirement_age, current_age, social_security
)
final_net_worth = current_net_worth_history[-1]
st.write(f"Projected net worth at current retirement age ({retirement_age}): ${final_net_worth:,.2f}")
st.write(f"Target retirement net worth: ${retirement_target:,.2f}")
if final_net_worth >= retirement_target:
st.success(f"Your current retirement age of {retirement_age} is sufficient to reach your goal!")
else:
optimal_retirement_age = optimize_retirement_age(
retirement_target, max_retirement_age,
initial_net_worth, base_investment_return, investment_allocation,
salary, savings_rate, savings_rate_growth, total_annual_expenses,
taxes, years, emergency_fund_target, retirement_age, current_age, social_security
)
st.info(f"Current retirement age: {retirement_age}")
st.info(f"Optimal retirement age to reach your goal: {optimal_retirement_age}")
if optimal_retirement_age >= max_retirement_age:
st.warning("The optimal retirement age is at or above your specified maximum. Consider adjusting your goal, savings rate, or finding ways to increase income or reduce expenses.")
elif optimal_retirement_age > retirement_age + 5:
st.warning(f"The optimal retirement age is significantly later than your current plan. You may want to consider increasing your savings rate or adjusting your retirement goals.")
else:
st.success(f"Adjusting your retirement age from {retirement_age} to {optimal_retirement_age} should allow you to reach your target net worth!")
# Tax optimization suggestions
if st.sidebar.button("Get Tax Optimization Suggestions"):
tax_suggestions = suggest_tax_optimization(salary, taxes)
st.write("### Tax Optimization Suggestions")
if tax_suggestions:
for suggestion in tax_suggestions:
st.info(suggestion)
else:
st.success("Based on your current tax rate, you're already in a good position. Keep up the good work!")
# Investment allocation optimization
st.sidebar.subheader("Investment Allocation Optimization")
risk_tolerance = st.sidebar.slider("Risk Tolerance", 1, 10, 5)
time_horizon = st.sidebar.number_input("Investment Time Horizon (years)", min_value=1, value=20, step=1)
if st.sidebar.button("Optimize Investment Allocation"):
current_allocation = {"Stocks": investment_allocation, "Bonds": 100 - investment_allocation}
st.write("Current asset allocation:")
st.info(f"Stocks: {current_allocation['Stocks']:.2f}%")
st.info(f"Bonds: {current_allocation['Bonds']:.2f}%")
optimal_allocation = optimize_investment_allocation(risk_tolerance, time_horizon, current_age, retirement_age)
st.write("Suggested optimal asset allocation:")
st.info(f"Stocks: {optimal_allocation['Stocks']:.2f}%")
st.info(f"Bonds: {optimal_allocation['Bonds']:.2f}%")
stock_difference = optimal_allocation["Stocks"] - current_allocation["Stocks"]
if abs(stock_difference) <= 5:
st.success("Your current asset allocation is close to the suggested optimal allocation!")
else:
if stock_difference > 0:
st.warning(f"Consider increasing your stock allocation by {stock_difference:.2f}%")
else:
st.warning(f"Consider decreasing your stock allocation by {abs(stock_difference):.2f}%")
st.write(f"This allocation is based on your risk tolerance ({risk_tolerance}/10) and time horizon ({time_horizon} years).")
# Sensitivity Analysis
if st.sidebar.button("Perform Sensitivity Analysis"):
st.write("### Sensitivity Analysis")
base_scenario = {
"initial_net_worth": initial_net_worth,
"investment_return": base_investment_return,
"investment_allocation": investment_allocation,
"salary": salary,
"savings_rate": savings_rate,
"savings_rate_growth": savings_rate_growth,
"expenses": total_annual_expenses,
"taxes": taxes,
"years": years,
"emergency_fund_target": emergency_fund_target,
"retirement_age": retirement_age,
"current_age": current_age,
"social_security": social_security
}
parameter_ranges = {
"investment_return": np.arange(1, 10, 0.5),
"savings_rate": np.arange(10, 80, 5),
"investment_allocation": np.arange(0, 100, 5)
}
sensitivity_results = perform_sensitivity_analysis(base_scenario, parameter_ranges)
for param, results in sensitivity_results.items():
fig = px.line(x=parameter_ranges[param], y=results, title=f"Sensitivity to {param}")
fig.update_layout(xaxis_title=param, yaxis_title="Final Net Worth ($)")
st.plotly_chart(fig)
st.info("The sensitivity analysis shows how changes in key parameters affect your final net worth. Steeper lines indicate that the parameter has a larger impact on the outcome.")
st.success("Use this information to focus on the factors that have the biggest influence on your financial future!")
if __name__ == "__main__":
main()