Backtest data prep
Build a clean, point-in-time, ready-to-backtest OHLCV dataset for a US equity universe across an arbitrary date window. Emits parquet plus a manifest plus an edge-case log, with corporate actions reconciled, survivorship treatment documented, holidays and half-days preserved correctly, and any IPO partial coverage or trading halts flagged. The dataset is the artifact a downstream Python/R/Julia backtester consumes; the rendered summary is the operator-readable companion. First skill in the suite that ships dataset output mode.From its SKILL.md
npx -y skills add rgourley/quant-garage --skill backtest-data-prepAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.0 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
backtest-data-prep
You hand over a universe definition, a date window, and an output directory. The skill emits a clean OHLCV+volume parquet plus a manifest plus an edge-case log. The downstream consumer is a backtester (your Python loop, QuantConnect, vectorbt, zipline-reloaded, etc.), not a human reader.
The skill's value is correctness. Quants spend 80% of their time on data prep, and the most common backtest bugs come from five sources:
- Survivorship bias. Backtesting against today's top-500 silently excludes 2022-2024 failures (FRC, SVB, BBBY, SI). The remaining cohort outperforms the true historical population because the losers are gone.
- Look-ahead bias on fundamentals. Using the latest revised consensus instead of point-in-time. The signal contains forward-looking information that wasn't available at the rebalance.
- Corporate action mis-adjustment. Pre-split prices not adjusted, or adjusted using the wrong factor.
- Holiday and half-day handling. Missing days treated as errors or filled wrong.
- Inconsistent calendar alignment. Calendar dates vs trading dates, US vs global market hours.
The skill addresses all five and documents the treatment in the manifest.
When to invoke
- A quant says "I need clean OHLCV for the top 500 from 2022 to today for my mean-reversion backtest"
- A researcher needs a survivorship-clean dataset for a paper
- A factor-research extension wants a longer window than the in-memory panel can carry
- The user says "prep a backtest dataset", "build me a clean OHLCV parquet", "I need point-in-time prices for a universe"
What you need
- A universe (CLI flag:
top100,top500,top1000,sp500,custom:path/to/tickers.csv) - A window (
--window YYYY-MM-DD..YYYY-MM-DD) - An output directory (
--out path/to/dataset/) MASSIVE_API_KEYexported in the environment- Stocks Starter plan minimum (flat files included with any paid plan; flat-files entitlement probed and REST fallback used when not granted)
Expected runtime
A 4-year top-100 run:
- Flat-files cold: ~5-10 minutes
- REST grouped cold: ~10-15 minutes
- Warm (parquet day-cache hit): under 60 seconds
A 5-year top-500 run is roughly 5x the REST time and 2-3x the flat-files time (the bottleneck shifts from day-bucket fetch to per-ticker corp action calls).
What you get back
Three files in the output directory, plus a rendered summary printed to stdout.
Files:
ohlcv.parquet: one row per ticker x trading day. Columns documented inreferences/output-formats.md.manifest.md: human-readable run record. Universe definition, window, survivorship treatment, corp action methodology, source endpoints, every parameter that affects reproducibility, run timestamp.edge-cases.log: line-delimited JSON. One entry per edge case detected: trading halt, IPO partial coverage, delisting during window, ticker change, data gap.
Layer 1: canonical JSON matching output-schema.json.
Emitted to stdout alongside the rendered summary so downstream tools
that wrap the CLI can parse the run metadata without re-reading the
manifest.
Layer 2: rendered dataset summary in the style of a clean-data
report (think QuantConnect's data quality report or Bloomberg's BDH
audit). See references/rendering.md.
Files written, universe construction, corporate actions applied,
coverage, edge cases, schema, sources, take. The take answers the
question a quant asks at the top of every backtest: "is this dataset
actually clean enough to trust?"
How it works
- Construct the universe per
references/survivorship-handling.md. For the default top-N seeds, pull/v3/reference/tickers(active and delisted both), enrich with market cap from/v3/reference/tickers/{ticker}, rank, and keep the top N. The universe label records whether the seed is forward-looking biased (it usually is for current top-N). - Filter to common stock. Reference-endpoint
typefield; drop ETFs, ETNs, ADRCs, units, warrants, rights. Documented in the manifest because the consumer may want to override. - Pull daily aggregates for the window. Flat-files preferred
(
s3://flatfiles/us_stocks_sip/day_aggs_v1/{yyyy}/{mm}/{yyyy-mm-dd}.csv.gz, parallelized 16 workers). REST fallback when entitlement is missing:GET /v2/aggs/grouped/locale/us/market/stocks/{date}?adjusted=true, one call per trading day. Pattern matchesfactor-research. See../massive-flat-files/SKILL.mdentitlement section. - Pull splits and dividends per
references/corporate-action-adjustment.md.GET /v3/reference/splits?ticker={T}andGET /v3/reference/dividends?ticker={T}. Massive's grouped aggs are split-adjusted by default (adjusted=true), so the skill does NOT re-apply the split ratio to OHLC; it does emit a cumulative adjustment factor as a separate column so the consumer can un-adjust if their backtester wants raw prints. - Pull ticker reference for sector enrichment. One call per
ticker, parallelized. Adds
sic_codeandsectorcolumns to the parquet. - Detect edge cases. IPO partial coverage (window starts before
list_date), delisting during window, ticker changes (the reference endpoint doesn't link old to new, so flag is best-effort), and trading halts (LULD pauses surface as session OHLC with continuous-trade range; not always detectable from daily aggs). Holidays and half-days are NOT edge cases; they're handled by the calendar (seereferences/calendar-alignment.md). - Emit parquet, manifest, edge-cases log. Parquet uses Snappy
compression by default (the right tradeoff for backtest IO; see
references/output-formats.md). Manifest is markdown so it diffs cleanly in git.
Foundations used
massive-flat-filesfor the bulk daily aggregates pull (S3 auth, path layout, parallelism).massive-api-patternsfor REST auth, the grouped-daily fallback, and the throttle pattern when pulling per-ticker splits/dividends.
Output mode: dataset
Dataset mode is new in this repo (this is the fifth output mode, joining note, stream, table, exception-report). The defining property: the primary artifact is a machine-readable file on disk, not a rendered string. The rendered string is the operator-readable companion that describes what was written.
The format follows the convention QuantConnect and Bloomberg BDH have
converged on for data quality reports: file inventory, universe
construction, corporate actions, coverage, edge cases, schema,
sources, take. See references/rendering.md
for the canonical format rules.
Endpoints used
s3://flatfiles/us_stocks_sip/day_aggs_v1/{yyyy}/{mm}/{yyyy-mm-dd}.csv.gz: one file per trading day; ~250 files per year. Parallelize 16 workers. Flat-files first.GET /v2/aggs/grouped/locale/us/market/stocks/{date}?adjusted=true: REST fallback when flat-files returns 403. Same one-call-per-day pattern.GET /v3/reference/tickers?market=stocks&active=true: paginated candidate pool for top-N seeds.GET /v3/reference/tickers?market=stocks&active=false: delisted pool for survivorship-clean universes.GET /v3/reference/tickers/{ticker}: market_cap (for ranking), type, sic_code, sic_description, list_date, delisted_utc.GET /v3/reference/splits?ticker={T}: split history for the corp action adjustment factor.GET /v3/reference/dividends?ticker={T}: dividend history for the price-only adjustment factor (when--adjustment total-return).
Doesn't handle (yet)
- Fundamentals join. This skill emits OHLCV only. For fundamentals
joined point-in-time, use
earnings-drilldown(which carries the 8-K acceptance methodology) or chain factor-research downstream. The schema reservesfundamentals_pathfor a future fundamentals file in the same output directory. - Total-return adjustment. Default adjustment is price-only
(splits applied by Massive, cumulative factor for un-adjustment).
Total-return adjustment (treating dividends as reinvested) is
documented in
references/corporate-action-adjustment.mdbut the v1 implementation uses price-only. Theadjustmentfield in the manifest records the choice so the consumer knows. - Intraday data. Daily aggregates only. Minute and tick aggregates are queued as a separate skill (the file sizes change the problem).
- Spinoff cost-basis allocation. Detected and flagged
(
spinoffs[]in the schema), but the basis split is not applied; the manifest recommends manual override for spinoff-heavy windows. - Non-US universes. US common stock only in v1. Massive's flat files cover other locales but the survivorship machinery here is US-specific (CIK linkage, exchange filter, holiday calendar).
- Point-in-time universe reconstruction. Same caveat as
factor-research. The defaulttop100/top500/top1000seeds use current market cap; for a true point-in-time universe, reconstruct per period. Queued as a clean PR extension.
These are clean PR extensions. The output schema reserves space for each so adding them later doesn't break consumers.
What ships with it: 9 files
58.8 KB alongside SKILL.md
references/
- calendar-alignment.md6.4 KB
- corporate-action-adjustment.md7.5 KB
- output-formats.md7.3 KB
- point-in-time-fundamentals.md5.9 KB
- rendering.md9.6 KB
- survivorship-handling.md6.0 KB
- output-schema.json10.3 KB
- README.md3.0 KB
- requires.yml2.8 KB