therickglenn's picture
Update app.py
69f7d13 verified
from datetime import date
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
import yfinance as yf
# -------------------------------------------------
# Streamlit Configuration
# -------------------------------------------------
st.set_page_config(
page_title="Financial Dashboard",
layout="wide",
page_icon="๐Ÿค‘"
)
# -------------------------------------------------
# Initialize Session State for Navigation and Carousel
# -------------------------------------------------
if "selected_tab" not in st.session_state:
st.session_state.selected_tab = "Home" # Default tab
if 'carousel_slide' not in st.session_state:
st.session_state.carousel_slide = 0 # Initialize carousel slide index
# -------------------------------------------------
# Define All Possible Tabs
# -------------------------------------------------
tabs = [
("๐Ÿ  Home", "Home"),
("๐Ÿ’ฑ Forex", "Forex"),
("๐Ÿ“ˆ Stocks", "Stocks"),
("๐Ÿ’ฐ Crypto", "Crypto"),
("๐Ÿ”ง Commodities", "Commodities"),
("๐ŸŒ Markets", "Markets"),
("๐Ÿ“Š ETFs/Mutual Funds", "ETFs/Mutual Funds"),
("๐Ÿ’น Investments", "Investments")
]
# -------------------------------------------------
# Sidebar with Navigation
# -------------------------------------------------
st.sidebar.title("๐Ÿค‘ Financial Dashboard")
def sidebar_navigation(tabs):
for display_name, internal_name in tabs:
if st.sidebar.button(display_name):
st.session_state.selected_tab = internal_name
sidebar_navigation(tabs)
# -------------------------------------------------
# Date Range & Timeframe Selection
# -------------------------------------------------
st.sidebar.markdown("---")
st.sidebar.markdown("#### Date Range & Timeframe")
start_date = st.sidebar.date_input("Start Date", date(2023, 1, 1))
end_date = st.sidebar.date_input("End Date", date.today())
time_increments = ["Daily", "Weekly", "Monthly"]
selected_time_increment = st.sidebar.selectbox("Time Increment", time_increments)
time_increment_mapping = {"Daily": "1d", "Weekly": "1wk", "Monthly": "1mo"}
time_increment_for_yahoo = time_increment_mapping[selected_time_increment]
# -------------------------------------------------
# Tickers Map
# -------------------------------------------------
tickers_map = {
"Stocks": ["AAPL", "MSFT", "AMZN", "GOOGL", "NVDA", "TSLA", "META"],
"Forex": ["EURUSD=X", "USDJPY=X", "GBPUSD=X", "AUDUSD=X", "USDCAD=X", "NZDUSD=X", "USDCHF=X"],
"Crypto": ["BTC-USD", "ETH-USD", "USDT-USD", "BNB-USD", "XRP-USD", "ADA-USD", "DOGE-USD"],
"Commodities": ["GLD", "SLV", "USO", "UNG", "PPLT", "COPX", "GDX"],
"Markets": ["^GSPC", "^DJI", "^IXIC", "^FTSE", "^N225", "^HSI", "^GDAXI"],
"ETFs/Mutual Funds": ["SPY", "VTI", "VOO", "QQQ", "ARKK", "IWM", "XLF"]
}
# -------------------------------------------------
# 1) Fetch Data (matching your snippet)
# -------------------------------------------------
@st.cache_data(ttl=3600)
def fetch_data(tickers, start_date, end_date, time_increment):
"""
Downloads data using yfinance, then resets index and ensures
each of Open, High, Low, Close, Volume is 1D by calling squeeze().
"""
data = {}
for ticker in tickers:
try:
df = yf.download(
ticker,
start=start_date,
end=end_date,
interval=time_increment,
progress=False
)
if not df.empty:
df = df.reset_index()
# Flatten columns if necessary
for col in ["Open", "High", "Low", "Close", "Volume"]:
if col in df.columns:
df[col] = df[col].squeeze() # from your working snippet
data[ticker] = df
else:
st.warning(f"No data available for {ticker}.")
except Exception as e:
st.error(f"Error fetching data for {ticker}: {e}")
return data
# -------------------------------------------------
# 2) Plot Combined Data (matching your snippet)
# -------------------------------------------------
def plot_combined_data(data, title):
combined_df = pd.DataFrame()
# Gather all unique dates
all_dates = pd.concat([df["Date"] for df in data.values() if not df.empty]).drop_duplicates().sort_values()
combined_df["Date"] = all_dates
# Align "Close" data for each ticker
for ticker, df in data.items():
if not df.empty:
df = df.set_index("Date").reindex(all_dates).reset_index()
combined_df[ticker] = df["Close"].values.flatten()
# Melt and plot
if not combined_df.empty:
combined_df = combined_df.melt(id_vars=["Date"], var_name="Symbol", value_name="Price")
fig = px.line(combined_df, x="Date", y="Price", color="Symbol", title=title)
return fig
else:
st.warning("No data available for the selected symbols.")
return None
# -------------------------------------------------
# 3) Plot Individual Data (matching your snippet)
# -------------------------------------------------
def plot_individual_data(data, ticker):
df = data.get(ticker)
if df is not None and not df.empty:
close_prices = df["Close"].values.flatten() # from your snippet
fig = px.line(
df,
x="Date",
y=close_prices,
title=f"{ticker} Prices",
labels={"x": "Date", "y": "Price"}
)
return fig
else:
st.warning(f"No data available for {ticker}.")
return None
# -------------------------------------------------
# 4) Historical Data for Investments (same as before)
# -------------------------------------------------
@st.cache_data(ttl=3600)
def fetch_historical_data(df, default_start_date, default_end_date):
historical_data = {}
for asset in df['Asset']:
try:
hist_df = yf.download(asset, start=default_start_date, end=default_end_date, progress=False)
if not hist_df.empty:
hist_df = hist_df['Close'].rename(asset)
historical_data[asset] = hist_df
else:
st.warning(f"No historical data available for {asset}.")
except Exception as e:
st.error(f"Error fetching historical data for {asset}: {e}")
if historical_data:
portfolio_df = pd.DataFrame(historical_data)
portfolio_df.fillna(method='ffill', inplace=True)
portfolio_df.fillna(method='bfill', inplace=True)
return portfolio_df
else:
return None
# -------------------------------------------------
# 5) Display Title
# -------------------------------------------------
def display_title(title_text):
st.markdown(
f"<h2 style='text-align: left; color: white;'>{title_text}</h2>",
unsafe_allow_html=True
)
# -------------------------------------------------
# 6) Display Investments Portfolio with Carousel
# -------------------------------------------------
def display_investments_portfolio(default_start_date, default_end_date):
uploader_col, sample_col = st.columns(2)
with uploader_col:
st.markdown("#### ๐Ÿ“ฅ Upload Your Investment Data")
uploaded_file = st.file_uploader("Upload your investment data (CSV)", type=["csv"])
st.info("Ensure your CSV contains: Asset, Quantity, Purchase Price, Current Price.")
with sample_col:
st.markdown("#### ๐Ÿ“„ Sample Investment Data")
sample_data = {
'Asset': ['AAPL', 'MSFT', 'BTC-USD', 'EURUSD=X', 'GLD'],
'Quantity': [10, 5, 0.5, 1000, 2],
'Purchase Price': [150, 250, 30000, 1.10, 1800],
'Current Price': [200, 300, 45000, 1.20, 1900]
}
sample_df = pd.DataFrame(sample_data)
sample_df['Total Invested'] = sample_df['Quantity'] * sample_df['Purchase Price']
sample_df['Current Value'] = sample_df['Quantity'] * sample_df['Current Price']
sample_df['Profit/Loss'] = sample_df['Current Value'] - sample_df['Total Invested']
sample_df['Profit/Loss (%)'] = (sample_df['Profit/Loss'] / sample_df['Total Invested']) * 100
st.dataframe(sample_df)
if uploaded_file is not None:
try:
df = pd.read_csv(uploaded_file)
st.subheader("Uploaded Investment Data")
st.dataframe(df)
required_columns = {'Asset', 'Quantity', 'Purchase Price', 'Current Price'}
if not required_columns.issubset(df.columns):
missing = required_columns - set(df.columns)
st.error(f"Uploaded CSV is missing the following required columns: {', '.join(missing)}")
return
numeric_columns = ['Quantity', 'Purchase Price', 'Current Price']
for col in numeric_columns:
df[col] = pd.to_numeric(df[col], errors='coerce')
if df[numeric_columns].isnull().any().any():
st.error("Some entries in your CSV have invalid numerical data. Please correct them and try again.")
return
df['Total Invested'] = df['Quantity'] * df['Purchase Price']
df['Current Value'] = df['Quantity'] * df['Current Price']
df['Profit/Loss'] = df['Current Value'] - df['Total Invested']
df['Profit/Loss (%)'] = (df['Profit/Loss'] / df['Total Invested']) * 100
st.subheader("Investment Portfolio Summary")
total_invested = df['Total Invested'].sum()
total_current = df['Current Value'].sum()
total_profit_loss = df['Profit/Loss'].sum()
total_profit_loss_pct = (total_profit_loss / total_invested) * 100
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Invested", f"${total_invested:,.2f}")
col2.metric("Current Value", f"${total_current:,.2f}")
col3.metric("Total Profit/Loss", f"${total_profit_loss:,.2f}",
delta=f"{total_profit_loss_pct:.2f}%")
col4.metric("Number of Assets", len(df))
st.subheader("Investment Portfolio Visualizations")
fig1 = px.pie(
df,
names='Asset',
values='Current Value',
title='Portfolio Allocation by Asset',
hole=0.4,
color='Asset',
color_discrete_sequence=px.colors.qualitative.Pastel
)
fig1.update_traces(textposition='inside', textinfo='percent+label')
fig2 = px.treemap(
df,
path=['Asset'],
values='Current Value',
title='Portfolio Allocation Treemap',
color='Profit/Loss',
color_continuous_scale='RdYlGn'
)
fig3 = px.scatter(
df,
x='Current Value',
y='Profit/Loss',
text='Asset',
title='Profit/Loss vs. Current Value by Asset',
hover_data=['Quantity', 'Purchase Price'],
color='Profit/Loss',
color_continuous_scale='RdYlGn'
)
fig3.update_traces(textposition='top center')
fig4 = go.Figure(go.Indicator(
mode="gauge+number",
value=total_profit_loss_pct,
title={'text': "Total Profit/Loss (%)"},
gauge={
'axis': {'range': [-100, 100]},
'bar': {'color': "darkblue"},
'steps': [
{'range': [-100, 0], 'color': "red"},
{'range': [0, 100], 'color': "green"}
],
'threshold': {
'line': {'color': "black", 'width': 4},
'thickness': 0.75,
'value': 0
}
}
))
fig5 = px.bar(
df,
x='Asset',
y='Profit/Loss',
color='Profit/Loss',
title='Profit/Loss by Asset',
color_continuous_scale='RdYlGn',
labels={'Profit/Loss': 'Profit/Loss ($)'},
hover_data=['Quantity', 'Purchase Price', 'Current Price']
)
fig5.update_layout(showlegend=False)
# Mock portfolio growth from historical data
historical_df = fetch_historical_data(df, default_start_date, default_end_date)
if historical_df is not None:
weights = df['Current Value'] / df['Current Value'].sum()
portfolio_growth = historical_df.multiply(weights, axis=1).sum(axis=1)
portfolio_growth_df = portfolio_growth.reset_index()
portfolio_growth_df.columns = ['Date', 'Portfolio Value']
fig6 = px.line(
portfolio_growth_df,
x='Date',
y='Portfolio Value',
title='Portfolio Growth Over Time',
labels={'Portfolio Value': 'Value ($)', 'Date': 'Date'},
color_discrete_sequence=['#636EFA']
)
else:
fig6 = go.Figure()
fig6.add_annotation(text="No historical data available.", x=0.5, y=0.5, showarrow=False)
fig6.update_layout(title='Portfolio Growth Over Time')
carousel_figs = [fig1, fig2, fig3, fig4, fig5, fig6]
prev_col, next_col = st.columns([1, 1])
with prev_col:
if st.button("โฎ๏ธ Prev", key="prev"):
st.session_state.carousel_slide = (st.session_state.carousel_slide - 1) % len(carousel_figs)
with next_col:
if st.button("โญ๏ธ Next", key="next"):
st.session_state.carousel_slide = (st.session_state.carousel_slide + 1) % len(carousel_figs)
st.plotly_chart(carousel_figs[st.session_state.carousel_slide], use_container_width=True)
st.markdown(f"**Slide {st.session_state.carousel_slide + 1} of {len(carousel_figs)}**")
st.info(
"""
**Navigate Through Visualizations:**
Use the Previous and Next buttons above to browse through different portfolio visualizations.
**Expand Plots:**
Click the full-screen icon on the top-right of each plot to view it in full screen.
"""
)
except Exception as e:
st.error(f"Error processing the uploaded file: {e}")
else:
st.subheader("Sample Portfolio Visualizations")
sample_data = {
'Asset': ['AAPL', 'MSFT', 'BTC-USD', 'EURUSD=X', 'GLD'],
'Quantity': [10, 5, 0.5, 1000, 2],
'Purchase Price': [150, 250, 30000, 1.10, 1800],
'Current Price': [200, 300, 45000, 1.20, 1900]
}
sample_df = pd.DataFrame(sample_data)
sample_df['Total Invested'] = sample_df['Quantity'] * sample_df['Purchase Price']
sample_df['Current Value'] = sample_df['Quantity'] * sample_df['Current Price']
sample_df['Profit/Loss'] = sample_df['Current Value'] - sample_df['Total Invested']
sample_df['Profit/Loss (%)'] = (sample_df['Profit/Loss'] / sample_df['Total Invested']) * 100
fig1 = px.pie(
sample_df,
names='Asset',
values='Current Value',
title='Sample Portfolio Allocation by Asset',
hole=0.4,
color='Asset',
color_discrete_sequence=px.colors.qualitative.Pastel
)
fig1.update_traces(textposition='inside', textinfo='percent+label')
fig2 = px.treemap(
sample_df,
path=['Asset'],
values='Current Value',
title='Sample Portfolio Allocation Treemap',
color='Profit/Loss',
color_continuous_scale='RdYlGn'
)
fig3 = px.scatter(
sample_df,
x='Current Value',
y='Profit/Loss',
text='Asset',
title='Sample Profit/Loss vs. Current Value by Asset',
hover_data=['Quantity', 'Purchase Price'],
color='Profit/Loss',
color_continuous_scale='RdYlGn'
)
fig3.update_traces(textposition='top center')
fig4 = go.Figure(go.Indicator(
mode="gauge+number",
value=sample_df['Profit/Loss (%)'].sum(),
title={'text': "Sample Total Profit/Loss (%)"},
gauge={
'axis': {'range': [-100, 100]},
'bar': {'color': "darkblue"},
'steps': [
{'range': [-100, 0], 'color': "red"},
{'range': [0, 100], 'color': "green"}
],
'threshold': {
'line': {'color': "black", 'width': 4},
'thickness': 0.75,
'value': 0
}
}
))
fig5 = px.bar(
sample_df,
x='Asset',
y='Profit/Loss',
color='Profit/Loss',
title='Sample Profit/Loss by Asset',
color_continuous_scale='RdYlGn',
labels={'Profit/Loss': 'Profit/Loss ($)'},
hover_data=['Quantity', 'Purchase Price', 'Current Price']
)
fig5.update_layout(showlegend=False)
# Mock portfolio growth data
sample_growth = pd.DataFrame({
'Date': pd.date_range(start=start_date, end=end_date, periods=100),
'Portfolio Value': (sample_df['Current Value'].sum()) * (1 + 0.01 * np.arange(100))
})
fig6 = px.line(
sample_growth,
x='Date',
y='Portfolio Value',
title='Sample Portfolio Growth Over Time',
labels={'Portfolio Value': 'Value ($)', 'Date': 'Date'},
color_discrete_sequence=['#636EFA']
)
carousel_figs = [fig1, fig2, fig3, fig4, fig5, fig6]
left_space, prev_col, next_col, right_space = st.columns([2.5, .5, .5, 2.5])
with prev_col:
if st.button("โฎ๏ธ Prev", key="prev"):
st.session_state.carousel_slide = (
st.session_state.carousel_slide - 1
) % len(carousel_figs)
with next_col:
if st.button("โญ๏ธ Next", key="next"):
st.session_state.carousel_slide = (
st.session_state.carousel_slide + 1
) % len(carousel_figs)
st.plotly_chart(carousel_figs[st.session_state.carousel_slide], use_container_width=True)
st.markdown(f"**Slide {st.session_state.carousel_slide + 1} of {len(carousel_figs)}**")
st.info(
"""
**Navigate Through Visualizations:**
Use the Previous and Next buttons above to browse through different portfolio visualizations.
**Expand Plots:**
Click the full-screen icon on the top-right of each plot to view it in full screen.
"""
)
# -------------------------------------------------
# Main Logic for Each Tab
# -------------------------------------------------
if st.session_state.selected_tab == "Home":
display_title("Home")
st.title("Financial Dashboard")
st.markdown(
"""
Welcome to the **Financial Dashboard**! This platform provides comprehensive insights into various financial instruments.
### ๐Ÿ“Š Features:
- **Stocks:** Analyze major stock performance.
- **Forex:** Monitor currency exchange rates.
- **Crypto:** Track cryptocurrency trends.
- **Commodities:** Observe commodity market movements.
- **Markets:** Overview of major market indices.
- **ETFs/Mutual Funds:** Evaluate ETF and mutual fund performances.
- **Investments:** Upload and track your personal investment portfolio.
### ๐Ÿ” How to Use:
1. **Navigate:** Use the sidebar buttons to select a financial category.
2. **Customize:** Choose your desired date range and time increment.
3. **Visualize:** View combined and individual plots for selected tickers.
4. **Investments:** Upload your investment CSV file above to track your portfolio.
"""
)
elif st.session_state.selected_tab in tickers_map:
display_title(st.session_state.selected_tab)
# Let user input custom tickers
st.sidebar.markdown("---")
st.sidebar.markdown(f"#### Enter Custom {st.session_state.selected_tab} Symbols")
default_symbols = ",".join(tickers_map[st.session_state.selected_tab])
user_input = st.sidebar.text_input(
f"Enter {st.session_state.selected_tab} symbols (comma-separated):",
default_symbols
)
user_tickers = [ticker.strip().upper() for ticker in user_input.split(",") if ticker.strip()]
if not user_tickers:
st.warning("Please enter at least one ticker symbol.")
else:
data = fetch_data(user_tickers, start_date, end_date, time_increment_for_yahoo)
if data:
tab_names = ["Combined"] + user_tickers
plot_tabs = st.tabs(tab_names)
# Combined Data Tab
with plot_tabs[0]:
fig = plot_combined_data(data, f"{st.session_state.selected_tab} Combined Prices")
if fig:
st.plotly_chart(fig, use_container_width=True)
# Individual Data Tabs
for i, ticker in enumerate(user_tickers):
with plot_tabs[i + 1]:
fig = plot_individual_data(data, ticker)
if fig:
st.plotly_chart(fig, use_container_width=True)
else:
st.warning("No data fetched. Please adjust your ticker symbols or date range.")
else:
# Investments Tab
if st.session_state.selected_tab == "Investments":
display_title("Investments")
st.markdown(
"""
### ๐Ÿ“ฅ Upload Your Investment Data
Use the uploader above to upload a CSV file containing your investment portfolio details.
**Required Columns:**
- **Asset:** Name or symbol of the investment.
- **Quantity:** Number of units held.
- **Purchase Price:** Price at which each unit was purchased.
- **Current Price:** Current market price of each unit.
"""
)
display_investments_portfolio(default_start_date=start_date, default_end_date=end_date)