# Backtesting Engine Loop

The engine loop is the causality contract of the framework. It decides what data the strategy can see, when a signal exists, when an order can be filled, and how the exit is resolved.

## Daily replay shape

For intraday options strategies, a clear loop usually looks like this:

```python
for day in trading_days:
    session = load_underlying_session(ticker, day)
    daily_context = load_daily_context(ticker, day)
    option_context = load_contract_context(ticker, day)

    setup = strategy.find_setup(session, daily_context)
    if setup is None:
        record_skip(day, "no_setup")
        continue

    exit_plan = strategy.resolve_exit(session, setup)
    trade = option_engine.try_express_setup(
        ticker=ticker,
        day=day,
        setup=setup,
        exit_plan=exit_plan,
        option_context=option_context,
    )
    record_trade_or_rejection(trade)
```

The exact implementation can be more complex, but the direction should stay the same: load the causal state, find a setup, choose an instrument, simulate execution, then log the result.

## Signal time and entry time

The most important rule is that a completed bar can create a signal, but it cannot also give the strategy an intrabar fill before the signal existed. If a signal is generated on bar `t`, the conservative default is entry on the next observable bar or quote after `t`.

For example, an opening-range breakout can:

1. Build the opening range from completed bars.
2. Detect a breakout after the range is known.
3. Set `signal_ts` to the completed signal bar.
4. Set `entry_ts` to the next bar open or the next quote that passes the execution policy.

This rule is not cosmetic. Same-bar entry is one of the easiest ways to overstate a momentum strategy.

## Engine boundaries

Keep these records separate in the code:

| Record | Should contain |
|---|---|
| Config | Strategy parameters, option filters, risk limits, and fill rules. |
| Session state | Underlying bars and context available for the day. |
| Setup | Direction, signal timestamp, entry timestamp, underlying entry price, and feature metadata. |
| Exit plan | Exit timestamp, stop/target result, and reason. |
| Option attempt | Selected or rejected contract, structure, quote evidence, and rejection counters. |
| Trade | Final quantity, entry price, exit price, PnL, return, and audit metadata. |

When a skip happens, log it. No-trade days are part of the result set because they explain coverage, density, and feasibility.

## Strategy families

The public `cutebacktests` runtime can support different signal families as long as they share the same replay contract:

- opening-range breakout and continuation
- VWAP and z-score mean reversion
- relative-strength or proxy-relative logic
- daily forecast profiles expressed through options

The strategy family changes the setup. It should not change the framework's causal discipline.

## Failure modes to test

Engine tests should cover:

- no bars for a day
- setup exists but no eligible contract exists
- contract exists but no quote window exists near entry
- signal timestamp after allowed entry cutoff
- entry timestamp at or after exit timestamp
- exit target observable only after the relevant timestamp
- multiple symbols trading on the same calendar day

Read next: [Backtesting Execution Realism](/docs/backtesting-execution-realism) and [Backtesting Test Plan](/docs/backtesting-test-plan).
