Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import yfinance as yf
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
from datetime import datetime, timedelta
|
| 6 |
+
|
| 7 |
+
def fetch_stock_data(symbol, start_date, end_date):
|
| 8 |
+
return yf.download(symbol, start=start_date, end=end_date)
|
| 9 |
+
|
| 10 |
+
def backtest_mixed_investment(data, start_date, end_date, monthly_investment, stock_allocation, savings_rate):
|
| 11 |
+
investment_dates = []
|
| 12 |
+
stock_shares = 0
|
| 13 |
+
savings_balance = 0
|
| 14 |
+
total_invested = 0
|
| 15 |
+
stock_value = []
|
| 16 |
+
savings_value = []
|
| 17 |
+
|
| 18 |
+
stock_investment = monthly_investment * stock_allocation
|
| 19 |
+
savings_investment = monthly_investment * (1 - stock_allocation)
|
| 20 |
+
daily_rate = (1 + savings_rate) ** (1/365) - 1
|
| 21 |
+
|
| 22 |
+
current_date = pd.to_datetime(start_date)
|
| 23 |
+
prev_date = current_date
|
| 24 |
+
while current_date <= pd.to_datetime(end_date):
|
| 25 |
+
if current_date in data.index:
|
| 26 |
+
price = data.loc[current_date, 'Adj Close']
|
| 27 |
+
new_shares = stock_investment / price
|
| 28 |
+
stock_shares += new_shares
|
| 29 |
+
|
| 30 |
+
days_passed = (current_date - prev_date).days
|
| 31 |
+
savings_balance *= (1 + daily_rate) ** days_passed
|
| 32 |
+
savings_balance += savings_investment
|
| 33 |
+
|
| 34 |
+
total_invested += monthly_investment
|
| 35 |
+
|
| 36 |
+
investment_dates.append(current_date)
|
| 37 |
+
stock_value.append(stock_shares * price)
|
| 38 |
+
savings_value.append(savings_balance)
|
| 39 |
+
|
| 40 |
+
prev_date = current_date
|
| 41 |
+
|
| 42 |
+
current_date += pd.DateOffset(months=1)
|
| 43 |
+
|
| 44 |
+
stock_value_series = pd.Series(stock_value, index=investment_dates).reindex(data.index, method='ffill')
|
| 45 |
+
savings_value_series = pd.Series(savings_value, index=investment_dates).reindex(data.index, method='ffill')
|
| 46 |
+
portfolio_value = stock_value_series + savings_value_series
|
| 47 |
+
|
| 48 |
+
return portfolio_value, stock_value_series, savings_value_series, total_invested
|
| 49 |
+
|
| 50 |
+
def backtest_stock_only(data, start_date, end_date, monthly_investment):
|
| 51 |
+
investment_dates = []
|
| 52 |
+
total_shares = 0
|
| 53 |
+
total_invested = 0
|
| 54 |
+
|
| 55 |
+
current_date = pd.to_datetime(start_date)
|
| 56 |
+
while current_date <= pd.to_datetime(end_date):
|
| 57 |
+
if current_date in data.index:
|
| 58 |
+
price = data.loc[current_date, 'Adj Close']
|
| 59 |
+
shares = monthly_investment / price
|
| 60 |
+
total_shares += shares
|
| 61 |
+
total_invested += monthly_investment
|
| 62 |
+
|
| 63 |
+
investment_dates.append(current_date)
|
| 64 |
+
|
| 65 |
+
current_date += pd.DateOffset(months=1)
|
| 66 |
+
|
| 67 |
+
total_shares_series = pd.Series([total_shares] * len(investment_dates), index=investment_dates).reindex(data.index, method='ffill')
|
| 68 |
+
portfolio_value = data['Adj Close'] * total_shares_series
|
| 69 |
+
|
| 70 |
+
return portfolio_value, pd.Series([total_invested] * len(data.index), index=data.index), total_invested
|
| 71 |
+
|
| 72 |
+
def plot_results(portfolio_value, stock_value, savings_value, total_invested, symbol, start_date, end_date, stock_only=False):
|
| 73 |
+
fig, ax = plt.subplots(figsize=(12, 6))
|
| 74 |
+
ax.plot(portfolio_value.index, portfolio_value, label='Portfolio Value')
|
| 75 |
+
if not stock_only:
|
| 76 |
+
ax.plot(stock_value.index, stock_value, label='Stock Investment Value')
|
| 77 |
+
ax.plot(savings_value.index, savings_value, label='Savings Account Value')
|
| 78 |
+
ax.plot(portfolio_value.index, total_invested, label='Total Cash Invested', linestyle='--')
|
| 79 |
+
|
| 80 |
+
ax.set_title(f'Monthly Investment Analysis: {symbol} ({start_date} to {end_date})')
|
| 81 |
+
ax.set_xlabel('Date')
|
| 82 |
+
ax.set_ylabel('Value ($)')
|
| 83 |
+
ax.legend()
|
| 84 |
+
ax.grid(True)
|
| 85 |
+
|
| 86 |
+
total_return = (portfolio_value[-1] - total_invested[-1]) / total_invested[-1] * 100
|
| 87 |
+
|
| 88 |
+
ax.annotate(f'Total Return: {total_return:.2f}%', xy=(0.05, 0.95), xycoords='axes fraction', fontsize=10, ha='left', va='top')
|
| 89 |
+
ax.annotate(f'Final Portfolio Value: ${portfolio_value[-1]:.2f}', xy=(0.05, 0.90), xycoords='axes fraction', fontsize=10, ha='left', va='top')
|
| 90 |
+
if not stock_only:
|
| 91 |
+
ax.annotate(f'Final Stock Value: ${stock_value[-1]:.2f}', xy=(0.05, 0.85), xycoords='axes fraction', fontsize=10, ha='left', va='top')
|
| 92 |
+
ax.annotate(f'Final Savings Value: ${savings_value[-1]:.2f}', xy=(0.05, 0.80), xycoords='axes fraction', fontsize=10, ha='left', va='top')
|
| 93 |
+
ax.annotate(f'Total Invested: ${total_invested[-1]:.2f}', xy=(0.05, 0.75), xycoords='axes fraction', fontsize=10, ha='left', va='top')
|
| 94 |
+
|
| 95 |
+
return fig
|
| 96 |
+
|
| 97 |
+
def main():
|
| 98 |
+
st.title("InvestSim: Stock & Savings Portfolio Analyzer")
|
| 99 |
+
st.write("Simulate and analyze your investment strategy with stocks and high-yield savings accounts.")
|
| 100 |
+
|
| 101 |
+
# Sidebar inputs
|
| 102 |
+
st.sidebar.header('Input Parameters')
|
| 103 |
+
symbol = st.sidebar.text_input('Stock Symbol', 'AAPL')
|
| 104 |
+
start_date = st.sidebar.date_input('Start Date', datetime(2000, 1, 1))
|
| 105 |
+
end_date = st.sidebar.date_input('End Date', datetime.now())
|
| 106 |
+
monthly_investment = st.sidebar.number_input('Monthly Investment ($)', min_value=1, value=100)
|
| 107 |
+
investment_type = st.sidebar.radio("Investment Type", ("Stock Only", "Mixed (Stock + Savings)"))
|
| 108 |
+
|
| 109 |
+
if investment_type == "Mixed (Stock + Savings)":
|
| 110 |
+
stock_allocation = st.sidebar.slider('Stock Allocation (%)', 0, 100, 60) / 100
|
| 111 |
+
savings_rate = st.sidebar.number_input('HYSA Annual Interest Rate (%)', min_value=0.0, max_value=20.0, value=4.5) / 100
|
| 112 |
+
else:
|
| 113 |
+
stock_allocation = 1.0
|
| 114 |
+
savings_rate = 0.0
|
| 115 |
+
|
| 116 |
+
if st.sidebar.button('Run Analysis'):
|
| 117 |
+
# Fetch stock data
|
| 118 |
+
data = fetch_stock_data(symbol, start_date, end_date)
|
| 119 |
+
|
| 120 |
+
if data.empty:
|
| 121 |
+
st.error(f"No data available for {symbol}. Please check the stock symbol and date range.")
|
| 122 |
+
return
|
| 123 |
+
|
| 124 |
+
# Run backtest
|
| 125 |
+
if investment_type == "Mixed (Stock + Savings)":
|
| 126 |
+
portfolio_value, stock_value, savings_value, total_invested = backtest_mixed_investment(
|
| 127 |
+
data, start_date, end_date, monthly_investment, stock_allocation, savings_rate
|
| 128 |
+
)
|
| 129 |
+
else:
|
| 130 |
+
portfolio_value, total_invested_series, total_invested = backtest_stock_only(
|
| 131 |
+
data, start_date, end_date, monthly_investment
|
| 132 |
+
)
|
| 133 |
+
stock_value = portfolio_value
|
| 134 |
+
savings_value = pd.Series([0] * len(portfolio_value), index=portfolio_value.index)
|
| 135 |
+
|
| 136 |
+
# Plot results
|
| 137 |
+
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"))
|
| 138 |
+
st.pyplot(fig)
|
| 139 |
+
|
| 140 |
+
# Display summary statistics
|
| 141 |
+
st.subheader('Investment Summary')
|
| 142 |
+
st.write(f"Total Return: {((portfolio_value[-1] - total_invested) / total_invested * 100):.2f}%")
|
| 143 |
+
st.write(f"Final Portfolio Value: ${portfolio_value[-1]:.2f}")
|
| 144 |
+
if investment_type == "Mixed (Stock + Savings)":
|
| 145 |
+
st.write(f"Final Stock Value: ${stock_value[-1]:.2f}")
|
| 146 |
+
st.write(f"Final Savings Value: ${savings_value[-1]:.2f}")
|
| 147 |
+
st.write(f"Total Invested: ${total_invested:.2f}")
|
| 148 |
+
|
| 149 |
+
if __name__ == '__main__':
|
| 150 |
+
main()
|