File size: 6,756 Bytes
2aaf2a2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
import streamlit as st
import pandas as pd
import yfinance as yf
import plotly.graph_objects as go
from datetime import datetime, timedelta
import numpy as np
# Import utility functions
from utils.yfinance_utils import fetch_yfinance_daily
from utils.currency_utils import get_usd_sgd_rate
from utils.fd_utils import calculate_fd_returns
# Set page config
st.set_page_config(page_title="Asset Class Comparison", layout="wide")
# Title and description
st.title("Asset Class Performance Comparison")
st.write("Compare the performance of different asset classes over time")
# Sidebar for user inputs
st.sidebar.header("Investment Parameters")
currency = st.sidebar.selectbox("Display Currency", ["USD", "SGD"], index=0)
initial_investment = st.sidebar.number_input(f"Initial Investment Amount ({currency})", min_value=1000, value=10000, step=1000)
start_date = st.sidebar.date_input("Start Date", value=datetime.now() - timedelta(days=365*10))
user_end_date = st.sidebar.date_input("End Date", value=datetime.now())
fd_rate = st.sidebar.number_input("Fixed Deposit Rate (%)", min_value=0.0, value=2.9, step=0.1) / 100
# Asset selection
selected_assets = st.sidebar.multiselect(
"Select Assets to Compare",
[
"Fixed Deposit",
"Gold",
"SGS Bonds",
"US Treasury Bonds",
"NASDAQ Composite",
"NASDAQ Large Cap",
"NASDAQ 100",
"S&P 500",
"Dow Jones",
"Microsoft",
"Google",
"Nvidia",
"Apple",
"Amazon",
"Tesla",
"Netflix",
"Meta",
],
default=["Fixed Deposit", "Microsoft", "Google", "Nvidia"]
)
# Today's date for reference
today = datetime.now().date()
usd_to_sgd = get_usd_sgd_rate() if currency == "SGD" else 1.0
currency_symbol = "$" if currency == "USD" else "S$"
# Create a dictionary of tickers for yfinance
tickers = {
"Gold": "GC=F",
"SGS Bonds": "^TNX",
"US Treasury Bonds": "^TNX",
"NASDAQ Composite": "^IXIC",
"NASDAQ Large Cap": "^NDX",
"NASDAQ 100": "^NDX",
"S&P 500": "^GSPC",
"Dow Jones": "^DJI",
"Microsoft": "MSFT",
"Google": "GOOGL",
"Nvidia": "NVDA",
"Apple": "AAPL",
"Amazon": "AMZN",
"Tesla": "TSLA",
"Netflix": "NFLX",
"Meta": "META",
}
# Determine the effective end date for each asset
asset_end_dates = {}
for asset in selected_assets:
if asset == "Fixed Deposit":
asset_end_dates[asset] = user_end_date
else:
if user_end_date > today:
asset_end_dates[asset] = today
else:
asset_end_dates[asset] = user_end_date
# Warn the user if a future end date is selected for market assets
if any(user_end_date > today and asset != "Fixed Deposit" for asset in selected_assets):
st.warning(f"Market data is only available up to today ({today}). For market assets, the end date has been set to today.")
# Calculate returns for each selected asset
asset_series = {}
failed_assets = []
actual_start_dates = {}
for asset in selected_assets:
asset_start = start_date
asset_end = asset_end_dates[asset]
if asset == "Fixed Deposit":
fd_index = pd.date_range(start=asset_start, end=user_end_date)
daily_rate = (1 + fd_rate) ** (1/365) - 1
fd_values = initial_investment * (1 + daily_rate) ** np.arange(len(fd_index))
if currency == "SGD":
fd_values = fd_values * usd_to_sgd
asset_series[asset] = pd.Series(fd_values, index=fd_index)
actual_start_dates[asset] = asset_start
else:
price_data = fetch_yfinance_daily(tickers[asset], asset_start, asset_end)
if price_data is not None and not price_data.empty:
price_data = price_data.sort_index()
actual_start = price_data.index[0]
actual_start_dates[asset] = actual_start
aligned_index = pd.date_range(start=actual_start, end=asset_end)
price_data = price_data.reindex(aligned_index)
price_data = price_data.ffill()
asset_values = initial_investment * (price_data / price_data.iloc[0])
if currency == "SGD":
asset_values = asset_values * usd_to_sgd
asset_series[asset] = asset_values
else:
failed_assets.append(asset)
# Combine all asset series into a single DataFrame
if asset_series:
returns_data = pd.DataFrame(asset_series)
else:
returns_data = pd.DataFrame()
# Remove failed assets from selected_assets (except FD)
selected_assets = [asset for asset in selected_assets if asset not in failed_assets or asset == "Fixed Deposit"]
if not selected_assets:
st.error("No assets could be loaded. Please try different assets.")
st.stop()
# Create the plot
fig = go.Figure()
for asset in selected_assets:
fig.add_trace(go.Scatter(
x=returns_data.index,
y=returns_data[asset],
name=asset,
mode='lines'
))
fig.update_layout(
title="Asset Performance Comparison",
xaxis_title="Date",
yaxis_title=f"Investment Value ({currency_symbol})",
hovermode="x unified",
height=600
)
# Display the plot
st.plotly_chart(fig, use_container_width=True)
# Calculate and display final returns
st.subheader("Final Investment Values")
for asset in selected_assets:
valid_series = returns_data[asset].dropna()
if not valid_series.empty:
final_value = valid_series.iloc[-1]
st.write(f"{asset}: {currency_symbol}{final_value:,.2f}")
else:
st.write(f"{asset}: Data unavailable")
# Calculate and display annualized returns
st.subheader("Annualized Returns")
for asset in selected_assets:
valid_series = returns_data[asset].dropna()
if len(valid_series) > 1:
actual_start = actual_start_dates[asset]
days = (valid_series.index[-1] - valid_series.index[0]).days
years = days / 365
final_value = valid_series.iloc[-1]
annualized_return = ((final_value / initial_investment) ** (1/years) - 1) * 100
if pd.Timestamp(actual_start).date() > start_date:
st.write(f"{asset}: {annualized_return:.2f}% (Data available from {actual_start.strftime('%Y-%m-%d')})")
else:
st.write(f"{asset}: {annualized_return:.2f}%")
else:
st.write(f"{asset}: N/A")
# Show warnings for data availability
for asset in selected_assets:
if asset in actual_start_dates and pd.Timestamp(actual_start_dates[asset]).date() > start_date:
st.warning(f"Data for {asset} is only available from {actual_start_dates[asset].strftime('%Y-%m-%d')}. The analysis starts from this date.")
# Show warning for failed assets
if failed_assets:
st.warning(f"Could not load data for the following assets: {', '.join(failed_assets)}")
|