← all research

Status: DONE (2026-08-15) · Suite: 281 passed, 2 skipped (+11 tests in tests/test_alpaca_backtest.py)

What was built

data/alpaca/backtest.py — long/flat OOS backtester for the Phase-3/4 bar system. CLI: python -m data.alpaca.backtest.

Execution model (matches the executable label)

signal read  ->  bar t close   (feature row = bar t close)
entry fill   ->  bar t+1 OPEN:  fill = open[t+1] * (1 + slip + fee)
exit fill    ->  bar t+1+h OPEN: fill = open[t+1+h] * (1 - slip - fee)
  • Long/flat only; a signal holds exactly h bars (fixed duration — no decision drift). Position taken only when the prediction ranking clears a fixed threshold (signal_quantile, default top-quintile).
  • Fills are priced on the OPEN grid; pred frames lacking open are joined as-of against the raw bar store (Phase-4 OOS files cost zero extra IO).
  • No trade can reference bars past its own exit: the last h rows can never signal (engine assertion in tests).

Cost model — itemized, never hidden

modeentry slipexit slipfees (round trip, from config.risk)
maker002 × fee_maker_pct = 30 bps
taker+half_spread-half_spread2 × fee_taker_pct = 50 bps
  • Half-spread default 0.6 bps (Phase-2 measured ETH profile), configurable.
  • Ledger keeps entry_px/exit_px/entry_cost/exit_cost/ret_gross/ret_net/ fees_bps/spread_bps/pnl_dollar — gross and net are separable, costs auditable per trade.

Stats & persistence

  • Ledger → net_cum_bps/gross_cum_bps/fees_tot_bps/spread_tot_bps, net-cumulative %, max drawdown, annualized Sharpe, hit rate, n.
  • Saved to processed_dir/alpaca/backtests/{SYM}/{TF}/h{h}.json + h{h}_ledger.parquet (stats/rule/sanity + full per-trade ledger).

Honest-economics battery (tests)

  • injected IC≈0.76 signal: gross edge positive; net below gross by exactly the itemized round trip; default 30 bps maker costs can swallow the edge → a 1 bp/side override nets positive (cost hurdle arbitrates);
  • pure-noise predictions: gross ≈ 0, net ≈ exactly −(fees+spread) — the "no edge" cost floor benchmark;
  • fill times/prices reproduce the open→open label exactly (entry t+1, exit t+1+h); maker vs taker spread itemization; bars join; bad exec mode rejected; persistence round trip.

Finding worth highlighting

With the repo's conservative fee constants (15/25 bps per side), round-trip costs (30/50 bps) dominate the 1-minute crypto label noise (typical |y| ~ 10-20 bps). The backtester's job — rejecting edges that cannot clear the cost hurdle — will decide which (symbol, timeframe, horizon) cells are tradeable at all. 5Min/1Hour cells are the likely survivors.