Skip to content
Skip to content
← all research
STUDY

"Phase 2 — Alpaca ETH/USD + SOL/USD Feature Construction"

2026-08-15

·

Status: DONE | Date: 2026-08-15 | Stack: Phase 1 raw store -> data/alpaca/features.py

#Scope

Per-symbol feature frames over the raw bar + sparse-quote partitions, built exactly to the feed reality measured in Phase 1: ETH ~3.3k / SOL ~3k quotes per day (~2-3/minute), so most 1Min bars have no quote in-bar. Design choice: every quote feature is the last-known quote at-or-before the bar close, plus an explicit staleness column — we report liquidity honestly instead of backfilling fake freshness.

#Feature catalogue (24 columns)

  • Price/momentum: r_1/3/5/10/20/60, mom_20/60/120, hi_dist_20, lo_dist_20
  • Volatility/shape: ret_std_10/30, atr_14, range_pct, range_ratio_20, body_pct, upper/lower_wick_pct, mp_position
  • Volume/intensity: log_vol, vol_ratio_20, log_trade_count, tc_ratio_20, vwap_dev, vol_per_trade_ratio
  • Calendar (24/7): hour_sin, hour_cos, dow
  • Sparse-quote microstructure (as-of bar close): spread_bps, microprice_dev_bps, imb, log_bid_size, log_ask_size, quote_age_s, quotes_in_bar

Deliberately absent: aggressor-side / order-flow imbalance features — Alpaca crypto trades carry no aggressor side and there is no historical deep book (Phase-1 limitation). Documented, not assumed away.

#No-look-ahead guarantees

  • All rolling windows trailing with min_periods; atr_14, mom_*, ret_std_* etc. are NaN on the first rows (never shifted/centered).
  • Quotes merged with merge_asof(..., direction="backward"); a quote at t+1s after bar-close can only affect bars from the next close onwards (tested explicitly).
  • assert_no_future_leak() structural guard ships alongside; warmup rows are NaN by construction.

#Files

FilePurpose
data/alpaca/features.pybuilders, schema, persist/load, leak guard
tests/test_alpaca_features.py10 offline tests (synthetic bars/quotes injected)
data/processed/alpaca/features/{SYM}/{TF}/v1.parquetsaved feature frames

#Tests

10 passed new; full suite 251 passed, 2 skipped. Coverage: rolling first-row NaN, r_1 construction, spread/microprice/imb math, backward-asof no-future-leak, in-bar quote counting, staleness monotonicity, raw-store- driven build, persistence roundtrip, strict symbol rejection.

#Real-data smoke (3-day live store, 2026-08-12→15)

SymbolTFrowsspread covermean quote ageatr_14 meanr_1 NA
ETH/USD1Min1,41399.9%152s0.000631
ETH/USD5Min651100%203s0.001011
SOL/USD1Min1,45399.9%147s0.000901
SOL/USD5Min68099.9%182s0.001511

Row counts exactly reproduce the stored 2,872 1Min bars across both symbols.

#Findings / implications

  • Spread coverage near 100% even though the feed is sparse — the as-of design pays off — but the mean quote age of ~150-200s on 1Min bars is the feature that actually encodes the sparse feed. Models must learn that stale-quote microstructure ≠ fresh.
  • ETH 1Min ATR ~0.06% of price vs SOL ~0.09%; SOL 5Min ATR ~0.15% rounds out Phase-1's spread observation (SOL ~45bps spread). Entry costs dominate SOL at this scale — relevant to the backtest/position-sizing phases.

#Next steps

  • Phase 3: labels (forward open->open returns), dataset assembly with warmup/embargo trimming, walk-forward splits and a leakage guard.
  • Extend the historical store for training (bars 2021→, quotes SOL 2024-10→) — only a 3-day live window is stored so far.

All trading described is paper (simulated). Not investment advice.

← all research