Interpreting Backtest Costs When Configuration Fields Are Inert
Summary
This configuration note distinguishes settings that appear in a backtest identity from settings that actually affect simulated trading costs. In the described case study, all registered runs use a vectorized, return-to-expiry path. The configured execution-price, commission-rate, and slippage-rate fields are not passed into that path, and quote-side fills belong to an event-driven broker that the vectorized runs do not construct. The settings remain in the configuration because removing them would change the hashed identity of existing runs.
Actual costs are read separately from a setup file: option entry and exit spreads, estimated hedge spreads at each rebalance, and per-contract or per-share commissions. The note reports that the recorded total-slippage field is null across the stated population, despite costs being charged, because that path does not record cost totals. This is a useful audit lesson: a configuration value or missing cost metric alone does not establish what a backtest paid. The findings apply to the specified implementation and should be rechecked if its runner or cost plumbing changes.
Key ideas
- Configuration fields can affect a run's hashed identity even when the execution path does not consume them.
- The vectorized return-to-expiry path described here does not use the configured rate-based commission and slippage fields.
- Actual simulated costs come from separate option, hedge, and liquidation cost components.
- A null aggregate slippage field does not prove that no costs were charged.
- Cost interpretation depends on tracing the specific runner and its data flow.
Tags
Full text
# base.yaml ```yaml # Three fields below are inert on the path this case study runs: `execution.execution_price`, # `commission.rate` and `slippage.rate`. Every registered backtest here is `ret_to_expiry`, # which `case_studies/utils/backtest_runner.py` routes to `_run_htm_daily_mtm`, and that call # is not passed `cost_spec` - the only thing that carries these rates into a run. The # `quote_side` fill is read by the event-driven broker, which a `rebalance.mode: vectorized` # case study never constructs. They still reach the registered `backtest_config` and so the # hashed identity, which is why they stay: removing them would re-key 879 backtests to delete # values nothing reads. # # The costs this case study actually pays come from `_htm_backtest.py`, which reads # `config/setup.yaml::costs.components` directly - per-leg entry spread from the real option # quotes, `hedge_spread.estimate_bps_of_notional` on every hedge rebalance, # `commission.option_per_contract` and `commission.equity_per_share`, plus an exit spread on # liquidation. Verified 2026-09-12; `total_slippage` is NULL on all 879 rows because the # vectorized path records no cost totals, not because nothing was charged. account: allow_short_selling: true execution: execution_price: quote_side mark_price: price execution_mode: next_bar commission: model: percentage rate: 0.0005 slippage: model: percentage rate: 0.0002 calendar: calendar: NYSE timezone: UTC data_frequency: daily feed: timestamp_col: timestamp entity_col: symbol price_col: instr_mid close_col: close bid_col: instr_bid ask_col: instr_ask mid_col: instr_mid metadata: case_study: sp500_options ```
Shown in full with attribution under the source's licence. Licence: MIT
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.