Skip to content
All library documents

Interpreting Backtest Costs When Configuration Fields Are Inert

Code Machine Learning for Trading

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.