File size: 8,542 Bytes
2aaf2a2 04cf5a7 f2ba8bf c2b76ba 04cf5a7 c2b76ba 2aaf2a2 15ca7f4 2aaf2a2 a71b954 2aaf2a2 a71b954 2aaf2a2 2b4eab3 15ca7f4 2aaf2a2 a71b954 2aaf2a2 a71b954 2aaf2a2 2b4eab3 15ca7f4 2aaf2a2 2b4eab3 2aaf2a2 a71b954 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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
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
print("Starting the app ...")
# Sanity check on the yfinance_utils
print("Sanity check on the yfinance_utils ...")
# print(fetch_yfinance_daily("MSFT", "2020-01-01", "2020-01-03"))
data = yf.download("MSFT", "2020-01-01", "2020-01-03")
print(data.head())
# 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*25))
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=True)
# 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",
"Gold",
"US Treasury Bonds", "SGS Bonds",
"S&P 500", "Dow Jones", "NASDAQ Composite", #"NASDAQ Large Cap", "NASDAQ 100",
"Microsoft", "Google", "Nvidia",
"Bitcoin"
]
)
# 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": "A35.SI", # Nikko AM SGD Investment Grade Corporate Bond ETF
"US Treasury Bonds": "TLT", # iShares 20+ Year Treasury Bond ETF
"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")
# Calculate and display yearly return statistics
st.subheader("Yearly Return Statistics")
for asset in selected_assets:
valid_series = returns_data[asset].dropna()
if len(valid_series) > 1:
# Resample to yearly data
yearly_data = valid_series.resample('Y').first()
# Calculate yearly returns
yearly_returns = yearly_data.pct_change().dropna()
# Count positive and negative years
positive_years = (yearly_returns > 0).sum()
total_years = len(yearly_returns)
positive_percentage = (positive_years / total_years) * 100
st.write(f"{asset}: {positive_years} out of {total_years} years ({positive_percentage:.1f}%) had positive returns")
else:
st.write(f"{asset}: Insufficient data for yearly analysis")
# 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)}")
|