This repository contains the code used to reproduce the results in Simple Dynamic Stock/Bond/Gold Portfolios.
-
Install uv.
-
Clone this repository and enter it:
git clone https://github.com/cvxgrp/simple-portfolio-code.git cd simple-portfolio-code -
Install the dependencies:
uv sync
-
Create
keys/fred_api.keycontaining your FRED API key. -
Run the scripts in order:
uv run python scripts/1_download_data.py uv run python scripts/2_download_evaluation_data.py uv run python scripts/3_download_distributions.py uv run python scripts/4_generate_alphas.py uv run python scripts/5_run_portfolios.py uv run python scripts/6_make_results.py uv run python scripts/7_hyperparameter_sensitivity.py uv run python scripts/8_walkforward_sensitivity.py uv run python scripts/9_statistical_inference.py uv run python scripts/10_lagged_information.py uv run python scripts/11_risk_based_benchmarks.py uv run python scripts/12_covariance_sensitivity.py uv run python scripts/13_black_litterman.py uv run python scripts/14_cost_sensitivity.py
Most scripts should finish in less than a minute, although
12_covariance_sensitivity.py may take a couple of minutes. Running the above
sequence should take roughly 10 minutes or less in total.
If you encounter an error while reproducing the results, please open a GitHub issue.
Generated tables are written to output/tables/ and figures to output/plots/.
After steps 1–5, run the additional, computationally expensive robustness check:
uv run python scripts/15_end_to_end_bootstrap.pyThis reproduces the end-to-end bootstrap methodology added to the paper in commit
8d1a96e: 100 stationary-bootstrap replications, mean block length 300 trading
days, and seed 0. Each replication uses a common index for daily asset returns,
volumes, raw macro series, Fama–French factors, and the daily federal funds rate.
Sampled returns are chained into continuous synthetic prices; features, 100-day
return forecasts, covariance estimates, and all six strategies are recomputed.
The source-return interval is 2005–2026 and performance is evaluated over
2006–2026, matching the paper's reference implementation.
This is separate from step 9, which resamples realized portfolio returns. It is excluded from the runtime estimate above. It uses the local raw parquet files from step 1 and observed backtests from step 5; it does not download data. Step 7's sweep is optional and enables the supplementary deflated-Sharpe report.
Outputs go to output/tables/end_to_end_inference/. In sharpe_differences.csv,
use the geo rows: mean_difference, lo, hi, and p_le_0 correspond to the
new paper table. difference is the observed-sample difference, not the
bootstrap mean. Other outputs are bootstrap_draws.csv (checkpoint),
bootstrap_metrics.csv, sharpe_definitions.csv, subperiods.csv, and, when
step 7 is available, deflated_sharpe.csv.
Completed replications are checkpointed every 10 runs and on interruption. Resume with the same inputs, seed, and block length:
uv run python scripts/15_end_to_end_bootstrap.py --resumeFor a shorter exploratory run, use --n-boot 2 --output-dir scratch/bootstrap-smoke.
--mean-block, --seed, and --output-dir are configurable. Resume can extend
a smaller run because each replication has its own deterministic random stream.
Use a new output directory after changing input data; checkpoints validate method,
seed, and block length but do not fingerprint raw files. With only 100 replications,
the paper treats these results as a robustness check, not a precise tail estimate.
Run the offline unit tests with uv run python -m unittest discover -s tests.