# Backtesting Execution Realism

Execution realism is the difference between a signal backtest and a tradable options backtest. Options do not trade continuously across every strike. Last price can be stale, midpoint can be optimistic, and a few cents of spread can dominate a short-dated trade.

## Bars, quotes, and trades

Use each data object for the job it answers:

| Data | Best use |
|---|---|
| Underlying bars | Signal features and underlying stop/target logic. |
| Option bars | Option price path and fallback valuation. |
| Option quotes | Executable bid/ask context and spread quality. |
| Option trades | Activity evidence and last-sale context. |

A serious framework often uses bars for path and quotes for fills. For a long option, entry near the ask and exit near the bid is more conservative than assuming midpoint both ways. More advanced models can allow midpoint or price improvement only when quote freshness, spread width, and trade evidence support it.

## Fill policy

Make the fill model explicit:

```python
def long_option_entry_fill(bid: float, ask: float, mode: str) -> float:
    if ask <= 0 or bid < 0 or ask < bid:
        raise ValueError("invalid_quote")
    if mode == "marketable_limit":
        return ask
    if mode == "mid_with_haircut":
        return (bid + ask) / 2 + 0.25 * (ask - bid)
    raise ValueError(f"unsupported_fill_mode: {mode}")
```

The important part is not this exact formula. It is that the simulator records what side of the market was used and rejects quotes that do not pass the policy.

## Stops and exits

Stops and targets must be observable. If a premium stop uses option quotes, the framework should check quotes after entry and apply the stop only when a quote pair proves the stop level was reachable. If an underlying stop uses underlying bars, the option exit still needs a corresponding option quote or option bar at the exit time.

Common exit reasons:

- profit target
- premium stop
- underlying stop
- time stop
- end-of-day exit
- max-hold exit
- invalid or missing quote
- forced rejection because entry would occur after exit

Every exit should preserve the timestamp and price source.

## Spread and slippage

Track cost in dollars, not only percentages. A `0.10` option spread is `10` dollars per contract before commissions. For short-dated options, that may be larger than the expected edge.

Useful execution metrics:

- entry spread percentage
- exit spread percentage
- spread as a share of premium
- expected move to spread ratio
- entry quote age
- exit quote age
- rejected trade count by reason
- fill source: quote, bar fallback, or proxy

## Rejecting trades is success

A realistic simulator should reject trades when the market is not good enough. That can reduce trade count and make performance less exciting, but it improves the scientific value of the result.

Good rejection reasons include:

- no quote near entry
- quote crossed or invalid
- spread too wide
- ask below minimum premium
- entry after effective exit
- stop/target cannot be priced
- quantity below one after risk caps

Read next: [Quotes](/docs/quotes), [Trades](/docs/trades), [Backtesting Robustness](/docs/backtesting-robustness), and [Options Backtest Realism Checker](/options-backtest-realism-checker).
