Status: DONE | Date: 2026-08-15 | Target branch of the 57-section spec: src/data/alpaca/
What this phase delivers
A production-grade ingestion layer for the two Phase-1 crypto assets
(ETH/USD, SOL/USD, Alpaca spot) covering:
- REST historical backfill (bars, trades, quotes) with sliding-start pagination
- Live websocket collector (trades/quotes/orderbook) with buffer+flush sinks
- Deep order-book snapshot capture (Alpaca's only real depth source)
- Canonical pyarrow schemas, UTC-day partitioning, dedup, DuckDB research views
- Dynamic asset precision (fractionable / min_order_size / increments — no hard-coding)
- Offline test suite (no network) + opt-in live tests
- Documented feed limitations (this page)
Layout
Spec's src/data/alpaca/ is mapped to the repo's existing data/ convention:
data/raw/alpaca/{SYM}/trades/{date}.parquet # T
data/raw/alpaca/{SYM}/quotes/{date}.parquet # Q
data/raw/alpaca/{SYM}/orderbook/{date}.parquet # O top-of-book rows
data/raw/alpaca/{SYM}/book_snapshots/{date}.parquet # O full-depth snapshots
data/raw/alpaca/{SYM}/bars/{1Min,5Min}/{date}.parquet # I (aggregated)
data/alpaca/ modules mirror the spec's src/data/alpaca/ names:
common.py, trades.py, quotes.py, orderbook.py, historical.py,
websocket.py, market_data.py.
Files created / modified
| File | Change | Purpose |
|---|---|---|
data/alpaca/__init__.py | new | package |
data/alpaca/common.py | new | symbol rules, schemas, dedup, PartitionSink, AlpacaDataDB, partition IO |
data/alpaca/historical.py | new | REST backfill + availability_report() + CLI (python -m data.alpaca.historical backfill|snapshots|report) |
data/alpaca/websocket.py | new | AlpacaCryptoStream T/Q/O collector (O→top + deep snapshots) |
data/alpaca/trades.py | new | trade normalizer + TradesSink |
data/alpaca/quotes.py | new | quote normalizer + QuotesSink |
data/alpaca/orderbook.py | new | orderbook (top + long-form deep) rows + sinks |
data/alpaca/market_data.py | new | MarketDataProvider ABC + AlpacaMarketDataProvider + asset_metadata() |
tests/test_alpaca_market_data.py | new | 15 offline + 2 live tests |
pytest.ini | modified | testpaths + live marker |
requirements.txt | modified | duckdb>=1.0.0 |
.env.example | modified | ALPACA_CRYPTO_SYMBOLS, ALPACA_CRYPTO_BAR_TIMEFRAMES |
Tests
241 passed, 2 skipped full suite. Phase-1 file: 15 passed, 2 skipped
(the 2 skips are the live-gated REST smoke tests; run with
RUN_LIVE_TESTS=1). Covered: symbol canonicalization (ETH/USD ok, ETHUSD /
ETH-USD rejected), schema round-trips, dedup on dual-write, partitioning
across UTC days + end-day boundary, long-form deep book levels, WS
reset→snapshot persistence, DuckDB views + bar timeframe extraction.
Measured feed profile (live, 2026-08-12 → 08-15)
Stored raw rows (3-day backfill + WS soaks):
| Kind | Rows | Date range | Per-day estimate |
|---|---|---|---|
| trades | 823 | 08-12→15 | ~275/day (ETH ~200, SOL ~75) |
| quotes | 15,619 | 08-12→15 | ~5,200/day (ETH ~3.3k, SOL ~3.0k) |
| bars 1Min | 2,872 | 08-12→15 | both symbols, full coverage |
| bars 5Min | 1,331 | 08-12→15 | both symbols, full coverage |
| orderbook (top) | 2 | 08-15 | 1 WS event / symbol |
| book_snapshots (deep) | 199 | 08-15 | ~100 levels / snapshot |
- ETH/USD
fractionable=True,min_order_size=0.000531067,min_trade_increment=1e-9,price_increment=1e-9(verified live). - 1Min/5Min bars available back to 2021-01-01 for both symbols; ETH trades back to 2021, but SOL trades/quotes effectively start ~Oct 2024.
- Avg observed spread: ETH ~11 bps, SOL ~45 bps.
- Latest (2026-08-15): ETH ~1,882, SOL ~75.4.
Feed limitations (explicit, not assumed away)
- Live websocket is essentially silent. Raw probe against
wss://stream.data.alpaca.markets/v1beta3/crypto/us(auth + subscribe + 60s dump): an initial full-depth orderbook snapshot per symbol, then zero trade and zero quote frames for 30s+. The 3-day stored tape confirms the sparse but real tick flow (~275 trades/day). Practical consequence: real-time tick-flow features from WS are NOT available on this feed today; the REST historical + latest-quote endpoints are the usable live path. Initial deep books are captured (book_snapshots) so depth is not lost. - No historical deep book.
CryptoLatestOrderbookRequestreturns only a current snapshot (~60/62 levels/side); no deep-book history. Any depth feature must start accumulating frombook_snapshotsgoing forward. - SOL trade/quote history starts ~Oct 2024 despite bar history to 2021. Any pre-Oct-2024 SOL feature work must use bars/quotes-only or ETH.
- Symbols: Alpaca crypto is
marginable=False,shortable=False— system constrains to long/flat (never short, no leverage), consistent with spec. - Deep book forward-only: partial half-asks / condition confirm reset from
the
rflag; persistence is append-only per day.
Decisions locked in
- Canonical symbols strictly
ETH/USD,SOL/USD. - Naive-UTC timestamps; UTC-day partitions via
timestamp.dt.normalize(). nextPageTokenunsupported in alpaca-py 0.43.5 → sliding-start pagination.- DuckDB views registered only when a glob matches (empty-glob guard).
- Model fees: maker 15 bps / taker 25 bps (config) — not yet wired into a backtest; spread/slippage/latency modelling deferred to the execution phase.
- Paper trading first; live orders require explicit confirmation flags.
Next steps
- Phase 2: feature construction on bars + sparse quotes (microprice/spread proxies), given the WS tick constraint measured here.
- Start accumulating
book_snapshotsdepth history (needs a persistent collector, e.g. periodic reconnect so reset snapshots fire). - If real-time trade/quotes ever required, evaluate a secondary venue or re-check Alpaca feed upgrades; document the decision.
- Wire fee model + paper-order layer (execution phase).