Educational project — not investment advice.
An interactive Streamlit application for portfolio risk analysis, built to demonstrate quantitative risk management skills relevant to risk management, asset management, and commodity trading roles in the Swiss financial market.
Users can select any S&P 500 constituents, build an equal-weight portfolio, and instantly visualize volatility, tail risk (VaR/CVaR), drawdowns, correlations, and stress test scenarios.
Overview — KPIs and portfolio wealth curve

Tail Risk — return distribution with VaR/CVaR

Stress Tests — shock scenarios

- Annualized portfolio volatility
- Historical Value at Risk (VaR) at configurable confidence levels (90% / 95% / 99%)
- Conditional VaR (CVaR / Expected Shortfall)
- Drawdown series and Maximum Drawdown
- Rolling volatility and Rolling VaR (63 / 126 / 252-day windows)
- Dynamic asset selection from the full S&P 500 universe
- Equal-weight portfolio construction
- Cumulative wealth index (base 1.0)
- Asset correlation heatmap
- One-day shock scenarios: −5%, −10%, −20%
- Estimated portfolio loss and residual value per scenario
- Live S&P 500 ticker search (scraped from Wikipedia)
- Configurable lookback period, confidence level, and rolling window
- Five dedicated tabs: Overview, Drawdown & Vol, Tail Risk, Correlation, Stress Tests
| Layer | Library |
|---|---|
| UI | Streamlit |
| Data | yfinance, requests, Pandas |
| Computation | NumPy |
| Visualisation | Matplotlib |
| Tests | Pytest |
risk-management-dashboard/
├── app.py
├── requirements.txt
├── .gitattributes
├── .gitignore
├── README.md
├── src/
│ ├── data.py <- Market data ingestion (yfinance + S&P 500 scraping)
│ ├── preprocessing.py <- Returns, annualisation, covariance, correlation
│ ├── portfolio.py <- Weights, portfolio returns, wealth index
│ ├── risk_metrics.py <- VaR, CVaR, drawdown, rolling metrics
│ ├── stress_testing.py <- Shock scenarios
│ └── plotting.py <- All matplotlib figures
├── tests/
│ ├── test_portfolio.py
│ ├── test_preprocessing.py
│ └── test_risk_metrics.py
└── notebook/
└── 01_exploration.ipynb <- Full pipeline walkthrough
| Tab | Content |
|---|---|
| Overview | KPI row (volatility, VaR, CVaR, max drawdown) + wealth curve |
| Drawdown & Vol | Underwater curve + rolling volatility |
| Tail Risk | Return histogram with VaR/CVaR markers + rolling VaR |
| Correlation | Asset correlation heatmap |
| Stress Tests | Shock scenario table |
- Risk metric implementation from scratch — building VaR, CVaR, and drawdown calculations manually (rather than relying on a library) reinforced their exact statistical definitions and the difference between historical and parametric approaches.
- Annualization pitfalls — I ran into a double-annualization bug when combining an already-annualized covariance matrix with a function that annualized again. This highlighted the importance of being explicit about units (daily vs. annualized) at every function boundary.
- Separation of concerns — splitting the codebase into
data,preprocessing,portfolio,risk_metrics,stress_testing, andplottingmodules made testing and debugging significantly easier than working in a single script. - Streamlit state management — handling persistent multiselect state (Select all / Clear buttons) required understanding
st.session_state, which isn't intuitive coming from a plain scripting mindset.
- Portfolio is equal-weight only in V1 (no custom weight allocation yet).
- VaR/CVaR are historical (non-parametric) — they assume the future resembles the historical sample.
- Data availability depends on yfinance; some tickers may have incomplete history for longer lookback periods.
This project is for educational purposes only. It does not constitute investment advice or a recommendation to buy or sell any financial instrument.
