A vectorized pandas/NumPy library for portfolio risk and performance analytics — returns, risk-adjusted performance metrics, portfolio math, factor regression, and Monte Carlo simulation. Everything is written without explicit Python loops over time steps or assets; operations are expressed as vector/matrix math, which is both the idiomatic way to write this in pandas/NumPy and dramatically faster at realistic data sizes.
A for-loop over rows in pandas is almost always a sign of fighting the
library rather than using it — and at scale, the difference is not
cosmetic. Computing rolling beta across 500 assets with a Python loop
re-fitting a regression at every window position is orders of magnitude
slower than the pandas rolling().cov()/.var() approach used here. Being
able to say why a vectorized approach is faster (single compiled
numpy/pandas operation over contiguous memory vs. per-element Python
interpreter overhead) is itself a common quant dev interview thread.
quant-analytics-toolkit/
├── analytics/
│ ├── returns.py # simple/log returns, cumulative returns, annualized return
│ ├── risk_metrics.py # volatility, Sharpe, Sortino, Calmar, max drawdown, VaR, CVaR
│ ├── portfolio.py # portfolio returns, covariance/correlation, vol, diversification, risk contribution
│ ├── regression.py # CAPM alpha/beta, rolling beta, multi-factor regression
│ └── monte_carlo.py # vectorized GBM simulation, simulated VaR
├── tests/ # 44 tests across all 5 modules
└── examples/
└── run_analysis.py # end-to-end demo on synthetic multi-asset data
Returns — simple vs. log returns (and why log returns are preferred for statistical work: they're additive across time, simple returns aren't), cumulative growth, annualized (geometric) return.
Risk metrics — annualized volatility, Sharpe ratio, Sortino ratio (penalizes only downside volatility), max drawdown, Calmar ratio, historical VaR, parametric (variance-covariance) VaR, and Conditional VaR/Expected Shortfall. The README-worthy nuance: parametric VaR assumes normality and understates tail risk on real (fat-tailed) return distributions — the code says this explicitly rather than hiding the limitation.
Portfolio — combining asset returns into portfolio returns via
matrix multiplication, the sqrt(w^T Cov w) portfolio volatility formula,
diversification ratio, and per-asset risk contribution (which can differ a
lot from position weight — a concentrated, highly-correlated small
position can contribute more risk than its weight suggests).
Regression — CAPM alpha/beta both as a full-sample fit (via
scipy.stats.linregress, which gives R², p-value, and standard error for
free) and as a rolling series computed via vectorized rolling
covariance/variance, plus a general multi-factor OLS regression via
numpy.linalg.lstsq.
Monte Carlo — vectorized Geometric Brownian Motion path simulation (the entire n_simulations × n_days matrix generated and evolved in one shot, no loop over days or simulations) and a simulation-based VaR estimate that serves as a cross-check against the analytical VaR figures.
pip install -r requirements.txt
python -m pytest tests/ -v44 tests, all passing, including statistical sanity checks (e.g. beta recovered from a regression with known true beta lands within tolerance; CVaR is always ≥ VaR; diversification ratio is always ≥ 1).
python examples/run_analysis.pyGenerates 3 correlated synthetic assets plus a market benchmark, then walks through individual and portfolio risk metrics, CAPM regression against the benchmark, rolling beta, and a Monte Carlo VaR cross-check — all the way from raw prices to a full risk report.
Worth stating directly, since it's the honest version of "I wrote tests":
the first version of sharpe_ratio checked if std == 0 to guard against
division by zero, and sortino_ratio computed downside deviation as the
std of only the negative returns. Both broke in practice — the Sharpe
check failed on a constant-return series because summing floating-point
values left a tiny nonzero epsilon instead of an exact zero, and Sortino
returned NaN whenever there was only one negative return in the sample
(sample standard deviation of a single point is 0/0). The tests caught
both before this ever reached a demo. Fixes: a < 1e-12 tolerance instead
of == 0, and the standard downside-deviation formula
(sqrt(mean(min(excess, 0)^2)) over all periods, not just the negative
ones).
- Assumes returns are the unit of analysis throughout; a production system would also need consistent handling of dividends, corporate actions, and multiple currencies.
- Fixed-weight portfolio returns assume rebalancing every period; no transaction-cost-aware rebalancing logic.
- Monte Carlo uses plain GBM (no jumps, no stochastic volatility) — a natural extension is a jump-diffusion or Heston-model simulator for more realistic tail behavior.
- No support yet for a full backtesting loop with these metrics wired in
(see the separate
quant-backtesterproject for that piece).
MIT