Nathan Gomes

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.

The verdict: it gave up 2.6 points of annual return to SPY and bought a crash a quarter shallower, a third less volatility, and a better Sharpe ratio than SPY or any allocation rule that estimates nothing.
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
Sharpe ratio 0.79
SPY 0.69 · equal weight 0.63
Best of all six rules
Worst drawdown −18.4%
SPY −24.5%
6.1 pts shallower
Volatility 11.7%
SPY 17.1%
32% lower
Annual return 12.3%
SPY 14.9%
2.6 pts given up

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

1

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.

2

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.

3

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.

02

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.

$8,000 $10,000 $12,500 $15,000 $17,500 $20,000 2021 2022 2023 2024 2025 Optimized portfolio trough: $8,929 on 2022-10-17, -18.4% drawdown $17,661 $19,740
Optimized portfolio  $17,661 Benchmark SPY  $19,740 Worst drawdown, Oct 2022
Read the gap, not just the endpoint. SPY's line is higher at the end and lower in the middle. The October 2022 trough is the point a client actually feels, and the optimized portfolio was down 18.4% there against SPY's 24.5%. Ending at 89% of SPY’s balance for three quarters of the pain is the trade this strategy is making, and it is a deliberate one rather than an accident of the sample.
03

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.

STAGE 01

Frozen price data

Adjusted daily closes for six ETFs are stored in SQLite and validated for gaps, duplicates and survivorship before any analysis runs.

SQLiteSQL CTEsWindow functions
STAGE 02

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.

pandasNumPyLedoit-Wolf
STAGE 03

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.

SciPy SLSQPWalk-forward10 bps costs
STAGE 04

Validation layer

An independent MATLAB implementation and 44 pytest cases guard the calculations, the constraints, the cost accounting and the chronology on every push.

pytestMATLAB / OctaveCI
04

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.

0% 4% 8% 12% 16% 0% 5% 10% 15% ANNUALIZED VOLATILITY ANNUALIZED RETURN Maximum Sharpe, 12.30% return, 11.74% volatility, Sharpe 0.79 Minimum variance, 10.45% return, 9.43% volatility, Sharpe 0.79 Risk parity, 9.07% return, 9.76% volatility, Sharpe 0.62 Inverse volatility, 8.04% return, 9.20% volatility, Sharpe 0.55 Equal weight, 10.35% return, 11.69% volatility, Sharpe 0.63 Benchmark (SPY), 14.87% return, 17.13% volatility, Sharpe 0.69 Maximum Sharpe Sharpe 0.79 Minimum variance Sharpe 0.79 Risk parity Sharpe 0.62 Inverse volatility Sharpe 0.55 Equal weight Sharpe 0.63 Benchmark (SPY) Sharpe 0.69 Maximum Sharpethis strategyBenchmark (SPY)more return, much more risk

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 ruleAnnual returnVolatilitySharpeMax drawdownTurnover / yr
Maximum SharpeThis strategy12.30%11.74%0.79−18.4%0.89
Minimum variance10.45%9.43%0.79−18.6%0.38
Equal weight10.35%11.69%0.63−22.0%0.20
Risk parity9.07%9.76%0.62−19.7%0.25
Inverse volatility8.04%9.20%0.55−19.5%0.28
Benchmark (SPY)14.87%17.13%0.69−24.5%n/a
Optimizing helped here, but that is not a general law. Six ETFs spanning bonds, gold and four equity regions have genuinely different risk profiles, so the covariance matrix carries structure worth exploiting. On a more correlated single-country stock universe the same class of optimizer loses to equal weight — the DeMiguel, Garlappi and Uppal (2009) result, which my other project reproduces. Minimum variance matched the Sharpe ratio at less than half the turnover, which in a fee-sensitive mandate may well be the better answer.
05

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.

0% 25% 50% 75% 100% AGG · US bonds: 13.5% average weight across 20 rebalances EEM · Emerging markets: 0.6% average weight across 20 rebalances EFA · Developed intl.: 10.5% average weight across 20 rebalances EWC · Canadian equities: 18.0% average weight across 20 rebalances GLD · Gold: 27.5% average weight across 20 rebalances SPY · US equities: 30.0% average weight across 20 rebalances 2021-01 2022-01 2023-01 2024-01 2025-01 2025-10 AGG 14% EEM 1% EFA 10% EWC 18% GLD 28% SPY 30%
The 30% cap is doing real work. Gold and US equities sit pinned near the cap for almost the entire period and emerging markets is held at essentially zero. That is the solver reading the covariance matrix, not a hand-picked allocation — and the cap is the only thing stopping it from concentrating further. It is also the clearest illustration of the risk in this approach: an optimizer will happily bet the whole book on whatever looked best in the lookback window unless you constrain it.
06

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 figures

Engineering 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
07

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.

SciPyNumPyPortfolio theoryVaRSharpe

Data engineering

A frozen, validated price store queried with real SQL, and a config-driven pipeline that regenerates every published artifact from source.

pandasSQLiteSQL CTEsWindow functions

Research engineering

Tests around the maths, an independent second implementation, CI on every push, and a sensitivity sweep instead of a single flattering number.

pytestMATLAB / OctaveCIJupyter

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.