Spaces:
Running
Running
File size: 11,804 Bytes
f72b4bb 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c e3daab3 dbe247c 85698ed dbe247c e3daab3 dbe247c e3daab3 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca dbe247c 3e124ca f72b4bb dbe247c | 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 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import requests
import os
# ββ Page config ββββββββββββββββββββββββββββββββββββββββββββββ
st.set_page_config(
page_title="Portfolio Monitoring Dashboard",
page_icon="π",
layout="wide"
)
# ββ Hugging Face AI (FinBERT sentiment) ββββββββββββββββββββββ
HF_API_KEY = os.environ.get("HF_API_KEY", "")
def analyze_sentiment(text: str) -> str:
"""Calls FinBERT on Hugging Face to get financial sentiment."""
if not HF_API_KEY:
return "β οΈ No API key"
url = "https://api-inference.huggingface.co/models/ProsusAI/finbert"
headers = {"Authorization": f"Bearer {HF_API_KEY}"}
try:
r = requests.post(url, headers=headers,
json={"inputs": text}, timeout=10)
result = r.json()
if isinstance(result, list) and result:
top = max(result[0], key=lambda x: x["score"])
emoji = {"positive": "π’", "negative": "π΄",
"neutral": "π‘"}.get(top["label"].lower(), "βͺ")
return f"{emoji} {top['label'].capitalize()} ({top['score']:.0%})"
except Exception:
return "β Error"
return "β Unknown"
# ββ Load data βββββββββββββββββββββββββββββββββββββββββββββββββ
@st.cache_data
def load_data():
portfolio = pd.read_csv("portfolio_output.csv")
risk_metrics = pd.read_csv("risk_metrics_output.csv")
daily_returns = pd.read_csv("portfolio_daily_returns_output.csv",
parse_dates=["date"])
return portfolio, risk_metrics, daily_returns
try:
portfolio, risk_metrics, daily_returns = load_data()
data_loaded = True
except FileNotFoundError:
data_loaded = False
# ββ Sidebar βββββββββββββββββββββββββββββββββββββββββββββββββββ
st.sidebar.title("Portfolio Monitor")
st.sidebar.caption("ESCP β Applied Data Science Workshop")
# ββ Main title ββββββββββββββββββββββββββββββββββββββββββββββββ
st.title("π Portfolio Monitoring Dashboard")
st.caption("Real-time portfolio performance, risk alerts & AI-powered news sentiment")
st.markdown("""
<div style="
background-color: #1A3C6E;
padding: 10px 20px;
border-radius: 8px;
margin-bottom: 10px;
">
<p style="color: white; font-size: 13px; margin: 0; text-align: center;">
π₯ <b>Group Project</b> β
ClΓ©ment De Ceukeleire Β· Laure Dumont Β· MatΓ©o FranΓ§ois Β· Romain Prudhon
</p>
<p style="color: #A9CCE3; font-size: 12px; margin: 4px 0 0 0; text-align: center;">
ESCP Business School β Applied Data Science Workshop
</p>
</div>
""", unsafe_allow_html=True)
st.divider()
# ββ Demo mode if no CSV βββββββββββββββββββββββββββββββββββββββ
if not data_loaded:
st.warning(
"β οΈ No data files found. Showing demo data. "
"Upload your CSV files to see real results."
)
np.random.seed(42)
portfolio = pd.DataFrame({
"Ticker": ["AAPL", "MSFT", "NVDA", "GOOGL", "AMZN"],
"Friendly name": ["Apple", "Microsoft", "Nvidia",
"Alphabet", "Amazon"],
"market_value": [12000, 9500, 8200, 6100, 5400],
"invested_amount": [10000, 8000, 5000, 5500, 6000],
"unrealized_pnl": [2000, 1500, 3200, 600, -600],
"cumulative_realized_pnl":[500, 300, 200, 100, 50],
"total_pnl": [2500, 1800, 3400, 700, -550],
"weight": [0.29, 0.23, 0.20, 0.15, 0.13],
"asset_concentration_flag": [False, False, False, False, False],
"stressed_value": [10200, 8075, 6970, 5185, 4590],
"stress_test_loss": [-1800,-1425,-1230, -915, -810],
"alert_level": ["Normal","Normal","Normal",
"Normal","Warning loss"],
})
portfolio["unrealized_return_pct"] = (
portfolio["unrealized_pnl"] / portfolio["invested_amount"]
)
dates = pd.date_range(end=pd.Timestamp.today(), periods=120, freq="B")
daily_returns = pd.DataFrame({
"date": dates,
"portfolio_daily_returns": np.random.normal(0.0005, 0.012, 120)
})
risk_metrics = pd.DataFrame({
"Metric": ["Mean daily return", "Daily volatility",
"Annualized volatility", "Worst daily return",
"Best daily return", "Sharpe ratio"],
"Value": [0.0005, 0.012, 0.190, -0.032, 0.028, 0.66]
})
# ββ Ticker filter βββββββββββββββββββββββββββββββββββββββββββββ
ticker_list = ["All"] + sorted(portfolio["Ticker"].dropna().unique().tolist())
selected = st.sidebar.selectbox("Filter by asset", ticker_list)
pv = portfolio if selected == "All" else portfolio[portfolio["Ticker"] == selected]
# ββ KPI Cards βββββββββββββββββββββββββββββββββββββββββββββββββ
st.subheader("Portfolio Summary")
c1, c2, c3, c4 = st.columns(4)
c1.metric("π° Invested", f"{pv['invested_amount'].sum():,.0f} β¬")
c2.metric("π Market Value", f"{pv['market_value'].sum():,.0f} β¬")
c3.metric("π Total P&L", f"{pv['total_pnl'].sum():,.0f} β¬")
c4.metric("π¦ Positions", int(pv["Ticker"].nunique()))
st.divider()
# ββ Charts row 1 ββββββββββββββββββββββββββββββββββββββββββββββ
col_l, col_r = st.columns(2)
with col_l:
st.subheader("π₯§ Portfolio Allocation")
fig_pie = px.pie(pv, names="Ticker", values="market_value",
hole=0.35)
fig_pie.update_traces(textinfo="percent+label")
st.plotly_chart(fig_pie, use_container_width=True)
with col_r:
st.subheader("π Market Value by Asset")
color_col = "alert_level" if "alert_level" in pv.columns else "Ticker"
fig_bar = px.bar(
pv.sort_values("market_value", ascending=False),
x="Ticker", y="market_value", color=color_col,
color_discrete_map={
"Normal": "#2ecc71",
"Warning loss": "#f39c12",
"Critical loss": "#e74c3c"
}
)
st.plotly_chart(fig_bar, use_container_width=True)
# ββ Cumulative return βββββββββββββββββββββββββββββββββββββββββ
st.subheader("π Cumulative Portfolio Return")
daily_returns["cumulative_return"] = (
(1 + daily_returns["portfolio_daily_returns"]).cumprod() - 1
)
fig_line = px.line(daily_returns, x="date", y="cumulative_return",
labels={"cumulative_return": "Cumulative Return",
"date": "Date"})
fig_line.add_hline(y=0, line_dash="dash", line_color="black")
fig_line.update_traces(line_color="#3498db")
st.plotly_chart(fig_line, use_container_width=True)
# ββ Unrealized return scatter ββββββββββββββββββββββββββββββββββ
st.subheader("β‘ Unrealized Return by Asset")
if "unrealized_return_pct" in pv.columns:
fig_ret = px.bar(
pv.sort_values("unrealized_return_pct"),
x="Ticker", y="unrealized_return_pct",
color=pv["unrealized_return_pct"].apply(
lambda x: "Gain" if x >= 0 else "Loss"
),
color_discrete_map={"Gain": "#2ecc71", "Loss": "#e74c3c"},
labels={"unrealized_return_pct": "Unrealized Return %"}
)
fig_ret.add_hline(y=0, line_dash="dash", line_color="black")
st.plotly_chart(fig_ret, use_container_width=True)
# ββ Risk metrics ββββββββββββββββββββββββββββββββββββββββββββββ
st.subheader("π¬ Risk Metrics")
st.dataframe(risk_metrics, use_container_width=True, hide_index=True)
# ββ Stress test βββββββββββββββββββββββββββββββββββββββββββββββ
if "stressed_value" in pv.columns:
st.subheader("π₯ Stress Test (β15% market shock)")
sk1, sk2, sk3 = st.columns(3)
sk1.metric("Current Value", f"{pv['market_value'].sum():,.0f} β¬")
sk2.metric("Stressed Value", f"{pv['stressed_value'].sum():,.0f} β¬",
delta=f"{pv['stress_test_loss'].sum():,.0f} β¬")
sk3.metric("Estimated Loss", f"{pv['stress_test_loss'].sum():,.0f} β¬")
# ββ Alert table βββββββββββββββββββββββββββββββββββββββββββββββ
st.subheader("π¨ Risk Alert Table")
alert_cols = [c for c in ["Ticker", "market_value", "weight",
"unrealized_return_pct", "alert_level",
"asset_concentration_flag"] if c in pv.columns]
st.dataframe(pv[alert_cols].sort_values("market_value", ascending=False),
use_container_width=True, hide_index=True)
st.divider()
# ββ AI Sentiment Analysis βββββββββββββββββββββββββββββββββββββ
st.subheader("π€ AI Sentiment Analysis (FinBERT)")
st.caption("Powered by Hugging Face β ProsusAI/finbert")
news_examples = {
"AAPL": "Apple reports record quarterly earnings driven by iPhone sales",
"MSFT": "Microsoft faces antitrust investigation in European markets",
"NVDA": "Nvidia surges on strong AI chip demand forecast",
"GOOGL": "Alphabet announces major layoffs amid cost-cutting efforts",
"AMZN": "Amazon expands logistics network with new warehouse openings",
}
st.info(
"Enter a news headline below and click Analyze to get "
"an AI-powered sentiment score using FinBERT, "
"a model trained specifically on financial text."
)
col_input, col_btn = st.columns([4, 1])
with col_input:
headline = st.text_input(
"News headline",
value="Apple reports record quarterly earnings driven by iPhone sales"
)
with col_btn:
st.write("")
st.write("")
run_sentiment = st.button("π Analyze")
if run_sentiment and headline:
with st.spinner("Calling FinBERT model..."):
sentiment = analyze_sentiment(headline)
st.success(f"**Sentiment result:** {sentiment}")
# Pre-loaded examples
if st.checkbox("Show sentiment for example headlines"):
results = []
for ticker, text in news_examples.items():
with st.spinner(f"Analyzing {ticker}..."):
sent = analyze_sentiment(text)
results.append({"Ticker": ticker, "Headline": text, "Sentiment": sent})
st.dataframe(pd.DataFrame(results), use_container_width=True, hide_index=True)
st.divider()
# ββ Download ββββββββββββββββββββββββββββββββββββββββββββββββββ
st.download_button(
label="β¬οΈ Download Portfolio Table (CSV)",
data=portfolio.to_csv(index=False).encode("utf-8"),
file_name="portfolio_monitoring_output.csv",
mime="text/csv"
)
st.caption("ESCP Business School β Applied Data Science Workshop | Group Project") |