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_KEY exported 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
in references/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
type field; 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 matches factor-research. See
../massive-flat-files/SKILL.md
entitlement section.
- Pull splits and dividends per
references/corporate-action-adjustment.md.
GET /v3/reference/splits?ticker={T} and
GET /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_code and sector columns 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 (see references/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-files for the bulk daily
aggregates pull (S3 auth, path layout, parallelism).
massive-api-patterns for 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 reserves fundamentals_path for 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.md
but the v1 implementation uses price-only. The adjustment field 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 default top100/top500/top1000 seeds 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.
1---2name: backtest-data-prep3description: 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.4---56# backtest-data-prep78You hand over a universe definition, a date window, and an output directory.9The skill emits a clean OHLCV+volume parquet plus a manifest plus an10edge-case log. The downstream consumer is a backtester (your Python loop,11QuantConnect, vectorbt, zipline-reloaded, etc.), not a human reader.1213The skill's value is correctness. Quants spend 80% of their time on data14prep, and the most common backtest bugs come from five sources:15161. **Survivorship bias.** Backtesting against today's top-500 silently17 excludes 2022-2024 failures (FRC, SVB, BBBY, SI). The remaining18 cohort outperforms the true historical population because the19 losers are gone.202. **Look-ahead bias on fundamentals.** Using the latest revised21 consensus instead of point-in-time. The signal contains22 forward-looking information that wasn't available at the rebalance.233. **Corporate action mis-adjustment.** Pre-split prices not adjusted,24 or adjusted using the wrong factor.254. **Holiday and half-day handling.** Missing days treated as errors or26 filled wrong.275. **Inconsistent calendar alignment.** Calendar dates vs trading28 dates, US vs global market hours.2930The skill addresses all five and documents the treatment in the manifest.3132## When to invoke3334- A quant says "I need clean OHLCV for the top 500 from 2022 to today35 for my mean-reversion backtest"36- A researcher needs a survivorship-clean dataset for a paper37- A factor-research extension wants a longer window than the in-memory38 panel can carry39- The user says "prep a backtest dataset", "build me a clean OHLCV40 parquet", "I need point-in-time prices for a universe"4142## What you need4344- A universe (CLI flag: `top100`, `top500`, `top1000`, `sp500`,45 `custom:path/to/tickers.csv`)46- A window (`--window YYYY-MM-DD..YYYY-MM-DD`)47- An output directory (`--out path/to/dataset/`)48- `MASSIVE_API_KEY` exported in the environment49- Stocks Starter plan minimum (flat files included with any paid plan;50 flat-files entitlement probed and REST fallback used when not granted)5152## Expected runtime5354A 4-year top-100 run:55- Flat-files cold: ~5-10 minutes56- REST grouped cold: ~10-15 minutes57- Warm (parquet day-cache hit): under 60 seconds5859A 5-year top-500 run is roughly 5x the REST time and 2-3x the60flat-files time (the bottleneck shifts from day-bucket fetch to61per-ticker corp action calls).6263## What you get back6465Three files in the output directory, plus a rendered summary printed66to stdout.6768**Files:**69- `ohlcv.parquet`: one row per ticker x trading day. Columns documented70 in [`references/output-formats.md`](./references/output-formats.md).71- `manifest.md`: human-readable run record. Universe definition, window,72 survivorship treatment, corp action methodology, source endpoints,73 every parameter that affects reproducibility, run timestamp.74- `edge-cases.log`: line-delimited JSON. One entry per edge case75 detected: trading halt, IPO partial coverage, delisting during76 window, ticker change, data gap.7778**Layer 1: canonical JSON** matching [`output-schema.json`](./output-schema.json).79Emitted to stdout alongside the rendered summary so downstream tools80that wrap the CLI can parse the run metadata without re-reading the81manifest.8283**Layer 2: rendered dataset summary** in the style of a clean-data84report (think QuantConnect's data quality report or Bloomberg's BDH85audit). See [`references/rendering.md`](./references/rendering.md).86Files written, universe construction, corporate actions applied,87coverage, edge cases, schema, sources, take. The take answers the88question a quant asks at the top of every backtest: "is this dataset89actually clean enough to trust?"9091## How it works92931. **Construct the universe** per [`references/survivorship-handling.md`](./references/survivorship-handling.md).94 For the default top-N seeds, pull `/v3/reference/tickers` (active95 and delisted both), enrich with market cap from96 `/v3/reference/tickers/{ticker}`, rank, and keep the top N. The97 universe label records whether the seed is forward-looking98 biased (it usually is for current top-N).992. **Filter to common stock.** Reference-endpoint `type` field; drop100 ETFs, ETNs, ADRCs, units, warrants, rights. Documented in the101 manifest because the consumer may want to override.1023. **Pull daily aggregates** for the window. Flat-files preferred103 (`s3://flatfiles/us_stocks_sip/day_aggs_v1/{yyyy}/{mm}/{yyyy-mm-dd}.csv.gz`,104 parallelized 16 workers). REST fallback when entitlement is missing:105 `GET /v2/aggs/grouped/locale/us/market/stocks/{date}?adjusted=true`,106 one call per trading day. Pattern matches `factor-research`. See107 [`../massive-flat-files/SKILL.md`](../massive-flat-files/SKILL.md)108 entitlement section.1094. **Pull splits and dividends** per110 [`references/corporate-action-adjustment.md`](./references/corporate-action-adjustment.md).111 `GET /v3/reference/splits?ticker={T}` and112 `GET /v3/reference/dividends?ticker={T}`. Massive's grouped aggs113 are split-adjusted by default (`adjusted=true`), so the skill does114 NOT re-apply the split ratio to OHLC; it does emit a cumulative115 adjustment factor as a separate column so the consumer can116 un-adjust if their backtester wants raw prints.1175. **Pull ticker reference** for sector enrichment. One call per118 ticker, parallelized. Adds `sic_code` and `sector` columns to the119 parquet.1206. **Detect edge cases.** IPO partial coverage (window starts before121 `list_date`), delisting during window, ticker changes (the122 reference endpoint doesn't link old to new, so flag is best-effort),123 and trading halts (LULD pauses surface as session OHLC with124 continuous-trade range; not always detectable from daily aggs).125 Holidays and half-days are NOT edge cases; they're handled by the126 calendar (see [`references/calendar-alignment.md`](./references/calendar-alignment.md)).1277. **Emit parquet, manifest, edge-cases log.** Parquet uses Snappy128 compression by default (the right tradeoff for backtest IO; see129 [`references/output-formats.md`](./references/output-formats.md)).130 Manifest is markdown so it diffs cleanly in git.131132## Foundations used133134- [`massive-flat-files`](../massive-flat-files) for the bulk daily135 aggregates pull (S3 auth, path layout, parallelism).136- [`massive-api-patterns`](../massive-api-patterns) for REST auth, the137 grouped-daily fallback, and the throttle pattern when pulling138 per-ticker splits/dividends.139140## Output mode: dataset141142Dataset mode is new in this repo (this is the fifth output mode,143joining note, stream, table, exception-report). The defining property:144the primary artifact is a machine-readable file on disk, not a145rendered string. The rendered string is the operator-readable146companion that describes what was written.147148The format follows the convention QuantConnect and Bloomberg BDH have149converged on for data quality reports: file inventory, universe150construction, corporate actions, coverage, edge cases, schema,151sources, take. See [`references/rendering.md`](./references/rendering.md)152for the canonical format rules.153154## Endpoints used155156- `s3://flatfiles/us_stocks_sip/day_aggs_v1/{yyyy}/{mm}/{yyyy-mm-dd}.csv.gz`:157 one file per trading day; ~250 files per year. Parallelize 16158 workers. Flat-files first.159- `GET /v2/aggs/grouped/locale/us/market/stocks/{date}?adjusted=true`:160 REST fallback when flat-files returns 403. Same one-call-per-day161 pattern.162- `GET /v3/reference/tickers?market=stocks&active=true`: paginated163 candidate pool for top-N seeds.164- `GET /v3/reference/tickers?market=stocks&active=false`: delisted165 pool for survivorship-clean universes.166- `GET /v3/reference/tickers/{ticker}`: market_cap (for ranking),167 type, sic_code, sic_description, list_date, delisted_utc.168- `GET /v3/reference/splits?ticker={T}`: split history for the corp169 action adjustment factor.170- `GET /v3/reference/dividends?ticker={T}`: dividend history for the171 price-only adjustment factor (when `--adjustment total-return`).172173## Doesn't handle (yet)174175- **Fundamentals join.** This skill emits OHLCV only. For fundamentals176 joined point-in-time, use `earnings-drilldown` (which carries the177 8-K acceptance methodology) or chain factor-research downstream. The178 schema reserves `fundamentals_path` for a future fundamentals file179 in the same output directory.180- **Total-return adjustment.** Default adjustment is price-only181 (splits applied by Massive, cumulative factor for un-adjustment).182 Total-return adjustment (treating dividends as reinvested) is183 documented in184 [`references/corporate-action-adjustment.md`](./references/corporate-action-adjustment.md)185 but the v1 implementation uses price-only. The `adjustment` field in186 the manifest records the choice so the consumer knows.187- **Intraday data.** Daily aggregates only. Minute and tick aggregates188 are queued as a separate skill (the file sizes change the problem).189- **Spinoff cost-basis allocation.** Detected and flagged190 (`spinoffs[]` in the schema), but the basis split is not applied;191 the manifest recommends manual override for spinoff-heavy windows.192- **Non-US universes.** US common stock only in v1. Massive's flat193 files cover other locales but the survivorship machinery here is194 US-specific (CIK linkage, exchange filter, holiday calendar).195- **Point-in-time universe reconstruction.** Same caveat as196 `factor-research`. The default `top100/top500/top1000` seeds use197 current market cap; for a true point-in-time universe, reconstruct198 per period. Queued as a clean PR extension.199200These are clean PR extensions. The output schema reserves space for201each so adding them later doesn't break consumers.