|
import streamlit as st |
|
import yfinance as yf |
|
import pandas as pd |
|
import matplotlib.pyplot as plt |
|
from datetime import datetime, timedelta |
|
|
|
def fetch_stock_data(symbol, start_date, end_date): |
|
return yf.download(symbol, start=start_date, end=end_date) |
|
|
|
def backtest_mixed_investment(data, start_date, end_date, monthly_investment, stock_allocation, savings_rate): |
|
investment_dates = [] |
|
stock_shares = 0 |
|
savings_balance = 0 |
|
total_invested = 0 |
|
stock_value = [] |
|
savings_value = [] |
|
|
|
stock_investment = monthly_investment * stock_allocation |
|
savings_investment = monthly_investment * (1 - stock_allocation) |
|
daily_rate = (1 + savings_rate) ** (1/365) - 1 |
|
|
|
current_date = pd.to_datetime(start_date) |
|
prev_date = current_date |
|
while current_date <= pd.to_datetime(end_date): |
|
if current_date in data.index: |
|
price = data.loc[current_date, 'Adj Close'] |
|
new_shares = stock_investment / price |
|
stock_shares += new_shares |
|
|
|
days_passed = (current_date - prev_date).days |
|
savings_balance *= (1 + daily_rate) ** days_passed |
|
savings_balance += savings_investment |
|
|
|
total_invested += monthly_investment |
|
|
|
investment_dates.append(current_date) |
|
stock_value.append(stock_shares * price) |
|
savings_value.append(savings_balance) |
|
|
|
prev_date = current_date |
|
|
|
current_date += pd.DateOffset(months=1) |
|
|
|
stock_value_series = pd.Series(stock_value, index=investment_dates).reindex(data.index, method='ffill') |
|
savings_value_series = pd.Series(savings_value, index=investment_dates).reindex(data.index, method='ffill') |
|
portfolio_value = stock_value_series + savings_value_series |
|
|
|
return portfolio_value, stock_value_series, savings_value_series, total_invested |
|
|
|
def backtest_stock_only(data, start_date, end_date, monthly_investment): |
|
investment_dates = [] |
|
total_shares = 0 |
|
total_invested = 0 |
|
|
|
current_date = pd.to_datetime(start_date) |
|
while current_date <= pd.to_datetime(end_date): |
|
if current_date in data.index: |
|
price = data.loc[current_date, 'Adj Close'] |
|
shares = monthly_investment / price |
|
total_shares += shares |
|
total_invested += monthly_investment |
|
|
|
investment_dates.append(current_date) |
|
|
|
current_date += pd.DateOffset(months=1) |
|
|
|
total_shares_series = pd.Series([total_shares] * len(investment_dates), index=investment_dates).reindex(data.index, method='ffill') |
|
portfolio_value = data['Adj Close'] * total_shares_series |
|
|
|
return portfolio_value, pd.Series([total_invested] * len(data.index), index=data.index), total_invested |
|
|
|
def plot_results(portfolio_value, stock_value, savings_value, total_invested, symbol, start_date, end_date, stock_only=False): |
|
fig, ax = plt.subplots(figsize=(12, 6)) |
|
ax.plot(portfolio_value.index, portfolio_value, label='Portfolio Value') |
|
if not stock_only: |
|
ax.plot(stock_value.index, stock_value, label='Stock Investment Value') |
|
ax.plot(savings_value.index, savings_value, label='Savings Account Value') |
|
ax.plot(portfolio_value.index, total_invested, label='Total Cash Invested', linestyle='--') |
|
|
|
ax.set_title(f'Monthly Investment Analysis: {symbol} ({start_date} to {end_date})') |
|
ax.set_xlabel('Date') |
|
ax.set_ylabel('Value ($)') |
|
ax.legend() |
|
ax.grid(True) |
|
|
|
total_return = (portfolio_value[-1] - total_invested[-1]) / total_invested[-1] * 100 |
|
|
|
ax.annotate(f'Total Return: {total_return:.2f}%', xy=(0.05, 0.95), xycoords='axes fraction', fontsize=10, ha='left', va='top') |
|
ax.annotate(f'Final Portfolio Value: ${portfolio_value[-1]:.2f}', xy=(0.05, 0.90), xycoords='axes fraction', fontsize=10, ha='left', va='top') |
|
if not stock_only: |
|
ax.annotate(f'Final Stock Value: ${stock_value[-1]:.2f}', xy=(0.05, 0.85), xycoords='axes fraction', fontsize=10, ha='left', va='top') |
|
ax.annotate(f'Final Savings Value: ${savings_value[-1]:.2f}', xy=(0.05, 0.80), xycoords='axes fraction', fontsize=10, ha='left', va='top') |
|
ax.annotate(f'Total Invested: ${total_invested[-1]:.2f}', xy=(0.05, 0.75), xycoords='axes fraction', fontsize=10, ha='left', va='top') |
|
|
|
return fig |
|
|
|
def main(): |
|
st.title("InvestSim: Stock & Savings Portfolio Analyzer") |
|
st.write("Simulate and analyze your investment strategy with stocks and high-yield savings accounts.") |
|
|
|
|
|
st.sidebar.header('Input Parameters') |
|
symbol = st.sidebar.text_input('Stock Symbol', 'AAPL') |
|
start_date = st.sidebar.date_input('Start Date', datetime(2000, 1, 1)) |
|
end_date = st.sidebar.date_input('End Date', datetime.now()) |
|
monthly_investment = st.sidebar.number_input('Monthly Investment ($)', min_value=1, value=100) |
|
investment_type = st.sidebar.radio("Investment Type", ("Stock Only", "Mixed (Stock + Savings)")) |
|
|
|
if investment_type == "Mixed (Stock + Savings)": |
|
stock_allocation = st.sidebar.slider('Stock Allocation (%)', 0, 100, 60) / 100 |
|
savings_rate = st.sidebar.number_input('HYSA Annual Interest Rate (%)', min_value=0.0, max_value=20.0, value=4.5) / 100 |
|
else: |
|
stock_allocation = 1.0 |
|
savings_rate = 0.0 |
|
|
|
if st.sidebar.button('Run Analysis'): |
|
|
|
data = fetch_stock_data(symbol, start_date, end_date) |
|
|
|
if data.empty: |
|
st.error(f"No data available for {symbol}. Please check the stock symbol and date range.") |
|
return |
|
|
|
|
|
if investment_type == "Mixed (Stock + Savings)": |
|
portfolio_value, stock_value, savings_value, total_invested = backtest_mixed_investment( |
|
data, start_date, end_date, monthly_investment, stock_allocation, savings_rate |
|
) |
|
else: |
|
portfolio_value, total_invested_series, total_invested = backtest_stock_only( |
|
data, start_date, end_date, monthly_investment |
|
) |
|
stock_value = portfolio_value |
|
savings_value = pd.Series([0] * len(portfolio_value), index=portfolio_value.index) |
|
|
|
|
|
fig = plot_results(portfolio_value, stock_value, savings_value, total_invested_series if investment_type == "Stock Only" else pd.Series([total_invested] * len(portfolio_value), index=portfolio_value.index), symbol, start_date, end_date, stock_only=(investment_type == "Stock Only")) |
|
st.pyplot(fig) |
|
|
|
|
|
st.subheader('Investment Summary') |
|
st.write(f"Total Return: {((portfolio_value[-1] - total_invested) / total_invested * 100):.2f}%") |
|
st.write(f"Final Portfolio Value: ${portfolio_value[-1]:.2f}") |
|
if investment_type == "Mixed (Stock + Savings)": |
|
st.write(f"Final Stock Value: ${stock_value[-1]:.2f}") |
|
st.write(f"Final Savings Value: ${savings_value[-1]:.2f}") |
|
st.write(f"Total Invested: ${total_invested:.2f}") |
|
|
|
if __name__ == '__main__': |
|
main() |