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
hbars (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;
predframes lackingopenare 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
hrows can never signal (engine assertion in tests).
Cost model — itemized, never hidden
| mode | entry slip | exit slip | fees (round trip, from config.risk) |
|---|---|---|---|
| maker | 0 | 0 | 2 × fee_maker_pct = 30 bps |
| taker | +half_spread | -half_spread | 2 × 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.