Project 01 / Quantitative finance
Quantitative Portfolio Research Pipeline
A six-ETF portfolio, re-optimized every quarter using only the data available on that date, then tested on the five years of returns it had never seen.
- Test window
- Feb 2021 – Dec 2025
out of sample - Universe
- 6 ETFs · 20 quarterly rebalances
- Data
- 12,066 ETF price records
- Stack
- Python · pandas · SciPy
SQL · MATLAB · pytest
Net of 10 bps transaction costs charged on every weight change. Walk-forward, out of sample, Feb 2021 – Dec 2025. Sharpe at a 3% risk-free rate.
The thirty-second version
It bought risk reduction, not return
SPY finished ahead in dollars: $19,740 against $17,661 on a $10,000 start. The optimized path got most of the way there through a much smaller hole — −18.4% at the October 2022 trough against SPY's −24.5%. Per unit of risk taken, it won.
It beat the rules that estimate nothing
Equal weight, inverse volatility and risk parity need no forecast, so they are the honest control group. Maximum Sharpe reached 0.79 against 0.63 for the best of them. That is the comparison that tests whether the optimization earned its keep.
The result survives being checked
44 pytest cases, no look-ahead in the rebalance loop, and an independent MATLAB implementation re-solved from scratch in CI that agrees to 6×10−7. Across 11 sensitivity configurations, maximum Sharpe ranked first in 9.
Out-of-sample performance
What $10,000 would have become
Every weight on this chart was chosen before the returns that scored it. The optimizer sees a 36-month lookback, commits to those weights for a quarter, pays a cost to trade, and is then marked against whatever actually happened next.
How it works
Four stages, one config file, fully reproducible
Changing the lookback window, the weight cap, the rebalance frequency or the cost assumption reruns the entire experiment and regenerates every chart, table and output on this page. Nothing here is hand-assembled.
Frozen price data
Adjusted daily closes for six ETFs are stored in SQLite and validated for gaps, duplicates and survivorship before any analysis runs.
Risk and return analytics
Returns, covariance, volatility, drawdown, Sharpe and VaR are computed from documented formulas, with Ledoit-Wolf covariance shrinkage and James-Stein shrinkage of expected returns.
Constrained optimizer
Weights maximize expected Sharpe subject to long-only, fully invested, and a 30% cap per asset. They are frozen for the next quarter and charged transaction costs when they change.
Validation layer
An independent MATLAB implementation and 44 pytest cases guard the calculations, the constraints, the cost accounting and the chronology on every push.
The control group
Did optimizing beat not optimizing?
Comparing an optimizer with SPY only asks whether the asset mix was better. Comparing it with equal weight asks whether the estimation was worth doing at all. Every rule below runs on identical rebalance dates and identical training windows, so a difference between two rows comes from the rule and not from one of them trading on luckier days.
Up and to the left is better. Dashed line: the capital allocation line through the maximum-Sharpe portfolio at a 3% risk-free rate.
| Allocation rule | Annual return | Volatility | Sharpe | Max drawdown | Turnover / yr |
|---|---|---|---|---|---|
| Maximum SharpeThis strategy | 12.30% | 11.74% | 0.79 | −18.4% | 0.89 |
| Minimum variance | 10.45% | 9.43% | 0.79 | −18.6% | 0.38 |
| Equal weight | 10.35% | 11.69% | 0.63 | −22.0% | 0.20 |
| Risk parity | 9.07% | 9.76% | 0.62 | −19.7% | 0.25 |
| Inverse volatility | 8.04% | 9.20% | 0.55 | −19.5% | 0.28 |
| Benchmark (SPY) | 14.87% | 17.13% | 0.69 | −24.5% | n/a |
Inside the optimizer
What it actually held, quarter by quarter
Twenty rebalances, each re-weighing the same six ETFs against a fresh 36-month lookback. Hover any band for its average weight across the full period.
Why you can trust the number
A backtest is a claim. This one carries its own audit trail.
Sensitivity
Does the conclusion survive its own assumptions?
A backtest reports one number per configuration, and somebody chose that configuration. A 36-month lookback, a 30% cap and 10 basis points are all defensible and all arbitrary. Varying each one at a time, maximum Sharpe ranks first in 9 of the 11 configurations tried and minimum variance in the other 2.
One caveat is built into the output rather than left for the reader to spot: a longer lookback needs more history before the first rebalance, so it also starts the evaluation later and scores a shorter period. That lifts every strategy's Sharpe including the benchmark's. Rows are comparable within themselves; levels across rows are not, and the benchmark column exists to make that shift visible.
Sensitivity grid → · Ranking →Independent cross-check
The same problem, solved twice, in two languages
Minimum variance and risk parity are re-solved in MATLAB with no shared solver: accelerated projected gradient and closed-form coordinate descent, written out rather than delegated to a toolbox. The script asserts agreement with the Python answer instead of printing numbers for a reader to eyeball. It runs in GNU Octave, so a reviewer without a MATLAB licence can check it, and CI runs it on every push.
Setting this up properly surfaced a real defect. The exported covariance was the full-sample matrix while the published weights were fitted to the latest 36-month window, so the cross-check had been solving a different problem from the one it claimed to verify. The comparison now uses the matrix the weights were actually solved from.
Agreement: 6×10−7 · 6 significant figuresEngineering controls
- No look-ahead anywhere in the rebalance loop
- Naive baselines run on identical rebalance dates, so comparisons are paired
- Transaction costs charged whenever allocations change
- Constraints asserted in tests, not assumed to hold
- Config-driven lookback, frequency, caps, costs and benchmark
- Prices frozen in SQLite so results are byte-reproducible
- Ledoit-Wolf covariance and James-Stein return shrinkage
- 44 pytest cases covering returns, drawdowns, costs and chronology
Why it matters
Investment decisions need evidence people can check
Clients entrust investment teams with substantial capital. When a portfolio gains or loses value they want to understand the decisions behind it, the risks taken, and whether the outcome is consistent with the stated approach. Unexplained results erode trust even when performance is positive.
This pipeline makes the research traceable end to end: historical prices feed documented calculations, allocation constraints make the investment rules explicit, and walk-forward testing scores those weights on subsequent returns with modeled trading costs. A reviewer can inspect the allocation history, compare return and drawdown against SPY, and rerun the analysis under different assumptions in one command.
This is deliberately not presented as a trading product. It is a research workflow, and the point of building it was the reliable connection between an investment idea and evidence a portfolio manager can examine and defend. Measuring each asset's exact contribution to gains and losses would require a dedicated performance-attribution analysis, which this project does not claim to provide.
What this project demonstrates
Quantitative analysis
Constrained optimization, covariance estimation and shrinkage, risk metrics, and an honest read of what a backtest can and cannot support.
Data engineering
A frozen, validated price store queried with real SQL, and a config-driven pipeline that regenerates every published artifact from source.
Research engineering
Tests around the maths, an independent second implementation, CI on every push, and a sensitivity sweep instead of a single flattering number.
Explore the evidence
Open the report or inspect the notebook
The notebook shows the working code, the SQL, the optimization functions, the visual checks, the MATLAB comparison and the calculation tests.