Spaces:
Sleeping
Sleeping
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") | |
st.write("Note: Cryptocurrencies (BTC, ETH, SOL, DOGE) are highly volatile and should be considered high-risk investments") | |
# 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 | |
use_log_scale = st.sidebar.checkbox("Use Log Scale", value=False) | |
# 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", | |
"Bitcoin", | |
"Ethereum", | |
"Solana", | |
"Dogecoin", | |
], | |
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", | |
"Bitcoin": "BTC-USD", | |
"Ethereum": "ETH-USD", | |
"Solana": "SOL-USD", | |
"Dogecoin": "DOGE-USD", | |
} | |
# 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, | |
yaxis_type="log" if use_log_scale else "linear" | |
) | |
# 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)}") | |