Quantitative Portfolio Research Pipeline
A reproducible ETF research workflow using Python, pandas, SQL, constrained optimization, transaction-cost-aware walk-forward testing, and automated validation.
Benchmark (SPY) earned more in raw dollars. The optimizer did not beat it on return. 14.9% a year against 12.3% for the optimized portfolio. That is the number that matters if the only question is which grew faster, and it is stated here directly rather than left for a reader to work out from a table.
But raw return is not the only way to keep score. Per unit of risk taken (the Sharpe ratio, defined below), the optimized portfolio did better: 0.79 against 0.69 for Benchmark (SPY). It also fell less along the way: a maximum drawdown of -18.4% against -24.5% for Benchmark (SPY). Whether that trade-off is the right one depends on what matters more to the reader: growing the account as fast as possible, or growing it with a smaller worst-case fall along the way. This report reports both rather than picking one.
Out-of-sample performance
Weights are estimated from the previous 36 months, then held until the next quarterly rebalance. Costs are charged when allocations change.
This chart tracks $1 invested on day one through every rebalance to today. It is out-of-sample: at each point, the weights being tested were chosen using only data from before that date, so the line shows what an investor would actually have experienced, not a fit to history. A steeper climb means faster growth; a shallower dip during a downturn means the portfolio lost less when markets fell.
Risk and allocation
The strategy caps every asset at 30% and remains fully invested without leverage or short positions.
Drawdown is how far a portfolio has fallen from its highest-ever value, at every point in time. The chart below is always at or below 0%. It matters more than volatility alone because losses and gains are not symmetric: a 20% drawdown needs a 25% gain just to recover, and a 25% drawdown needs 33%. A shallower drawdown line means less ground to make up. The panel on the right shows how the optimizer is currently positioned across the six ETFs, based on the most recent rebalance.
Latest optimized weights
Cross-asset structure
Daily return correlations are estimated from the frozen research dataset.
Diversification only works between assets that do not move together. A correlation of 1.0 means two assets move in lockstep, so combining them provides no protection. A correlation near 0, or negative, means one can be falling while the other holds up or rises, which is what actually reduces portfolio-level risk. The heatmap below is why the optimizer leans on the assets it does: whichever pair is darkest is providing the least diversification benefit, and the lightest (or most negative) pairs are doing the most work.
Strategy comparison
| Strategy | Return | Volatility | Sharpe | Max drawdown | Daily VaR |
|---|---|---|---|---|---|
| Optimized portfolio | 12.3% | 11.7% | 0.79 | -18.4% | 1.1% |
| Benchmark (SPY) | 14.9% | 17.1% | 0.69 | -24.5% | 1.7% |
Did optimizing beat not optimizing?
Every rule is run on identical rebalance dates and identical training windows, so a difference between two of them comes from the rule rather than from a luckier schedule.
Comparing the optimizer only with Benchmark (SPY) asks whether this particular asset mix was a good choice. It does not say whether solving for weights was worth the trouble, because a portfolio manager could have picked any fixed split of the same six ETFs without running an optimization at all. The rows below labelled equal weight, inverse volatility, and risk parity are rules that use no return or covariance forecast whatsoever. They estimate nothing. If the optimizer cannot beat those, the optimization itself added no value.
| Annual Return | Annual Volatility | Sharpe Ratio | Maximum Drawdown | Daily Var 95 | Annual Turnover | |
|---|---|---|---|---|---|---|
| strategy | ||||||
| Maximum Sharpe | 12.30% | 11.74% | 0.79 | -18.39% | 1.14% | 0.89 |
| Minimum variance | 10.45% | 9.43% | 0.79 | -18.64% | 0.91% | 0.38 |
| Risk parity | 9.07% | 9.76% | 0.62 | -19.69% | 0.95% | 0.25 |
| Inverse volatility | 8.04% | 9.20% | 0.55 | -19.45% | 0.91% | 0.28 |
| Equal weight | 10.35% | 11.69% | 0.63 | -21.99% | 1.16% | 0.20 |
| Benchmark (SPY) | 14.87% | 17.13% | 0.69 | -24.50% | 1.65% | 0.00 |
Maximum Sharpe reached a Sharpe ratio of 0.79 against 0.63 for Equal weight, the best rule that estimates nothing. Optimizing helped in this sample, which is not guaranteed and is worth testing rather than assuming: on a more correlated single-country universe the same code produces the opposite result.
Does the conclusion survive its assumptions?
One assumption is varied at a time. Compare strategies within a row; a longer lookback also shortens the evaluation window, which is why the benchmark column moves too.
A backtest reports one number per configuration, and somebody chose the configuration. Varying the lookback, the weight cap, the transaction cost, and the covariance estimator one at a time and re-running the walk-forward backtest each time, Maximum Sharpe ranks first in 9 of 11 configurations tried and Minimum variance the other 2, so the finding below is not an artifact of one arbitrary setting.
| strategy | Benchmark (SPY) | Equal weight | Maximum Sharpe | Minimum variance | Risk parity | |
|---|---|---|---|---|---|---|
| assumption | value | |||||
| covariance_estimator | ledoit_wolf | 0.69 | 0.63 | 0.79 | 0.79 | 0.62 |
| sample | 0.69 | 0.63 | 0.79 | 0.79 | 0.62 | |
| lookback_months | 24 | 0.59 | 0.57 | 0.73 | 0.69 | 0.56 |
| 36 | 0.69 | 0.63 | 0.79 | 0.79 | 0.62 | |
| 60 | 1.18 | 1.34 | 1.78 | 1.64 | 1.44 | |
| maximum_weight | 0.2 | 0.69 | 0.63 | 0.67 | 0.70 | 0.66 |
| 0.3 | 0.69 | 0.63 | 0.79 | 0.79 | 0.62 | |
| 0.5 | 0.69 | 0.63 | 0.87 | 0.60 | 0.54 | |
| transaction_cost_bps | 0.0 | 0.69 | 0.63 | 0.80 | 0.79 | 0.62 |
| 10.0 | 0.69 | 0.63 | 0.79 | 0.79 | 0.62 | |
| 50.0 | 0.69 | 0.62 | 0.76 | 0.77 | 0.61 |
Sharpe ratio under each assumption. The ranking within a row is a fair comparison; levels across rows are not, because the evaluation window changes.
Research controls
The configuration file controls the experiment, so changing an assumption reruns the complete pipeline and report.
Frozen inputs
Six ETF histories from 2018-01-01 through 2025-12-31 are stored locally and loaded into SQLite.
No look-ahead
Each rebalance uses only observations available on or before that date.
Trading friction
10 basis points are charged against portfolio turnover at every rebalance.
Reproducible checks
Pytest verifies returns, weights, drawdowns, constraints, and backtest chronology.
Terms used in this report
- Sharpe ratio
- Return earned above the risk-free rate, divided by volatility. It answers "how much return per unit of risk," not "how much return." A higher Sharpe ratio can come with a lower raw return if the risk taken to get there was low enough.
- Volatility
- How much a portfolio's daily returns swing around their average, expressed as an annualized percentage. Higher volatility means a bumpier ride, independent of whether the overall trend is up or down.
- Drawdown
- The percentage fall from a portfolio's highest-ever value to its current value. Unlike volatility, it captures the worst single experience an investor actually lived through, not an average.
- Walk-forward testing
- Re-estimating the strategy's weights at every rebalance using only data available up to that date, then scoring the following period. This is what stops the backtest from quietly using future information it would not have had in real time.
- Value at Risk (VaR)
- The daily loss that historical returns were exceeded only 5% of the time. It describes a bad-but-not-worst-case day, not the maximum possible loss.
- Turnover
- How much of the portfolio is bought and sold at each rebalance. Higher turnover means more transaction costs eating into returns, even if the resulting weights are "better" on paper.
- Shrinkage
- Blending a noisy sample estimate (of covariance or expected returns) toward a more stable, simplified target. A few years of daily data is not enough to pin down expected returns precisely, so shrinkage trades a little bias for a lot less noise.
- Naive baseline
- A rule (equal weight, inverse volatility, risk parity) that requires no return or covariance forecast. It exists to answer whether an optimizer's added complexity is actually earning its keep.