fills.bar_fill¶
The Tier-0 fill model and its one pessimistic intrabar path — the assumption every fill price in a report rests on.
bar_fill
¶
Tier-0 bar-based fill model — the pessimistic execution realism layer.
Anti-optimism is the whole point: whenever a bar leaves the fill ambiguous, this model resolves it AGAINST the trader. Concretely:
- Participation firewall (no look-ahead): an order interacts with a bar
only if it was accepted at/before the bar's OPEN (
accepted_ts <= bar.ts_event). An order created on a bar's close signal therefore first participates in the NEXT bar — the classic backtest look-ahead bug is structurally impossible. - Limits need trade-THROUGH: by default a resting limit does not fill on
an exact touch of its level — the bar must trade at least one tick past it
(an exact-touch extreme is exactly where real queues don't get filled).
This applies at the OPEN too: a bar opening exactly at the level is a touch,
not price improvement, so it does not fill at the open under the default;
the order can still fill at its level if the bar later trades through.
fill_limit_on_touch=Truerelaxes both for sensitivity analysis. - Stops always pay slippage: a triggered stop fills
stop_slippage_ticksADVERSE of its trigger, and a gap through the stop fills at the (worse) open plus slippage — never at the stop price itself. The slipped price MAY exceed the bar's high/low: real stop slippage escapes the bar's printed range, and clamping it back inside would be optimistic. That is intentional. - Marketable-at-open limits DO take the improvement: an open STRICTLY better than the level is genuine price improvement, not optimism — a resting buy limit above the open would fill at the open in reality too.
- Honest timestamps: open-instant fills are stamped with the bar's OPEN
time (
bar.ts_event); every intrabar fill is stamped with the bar's CLOSE time (bar.ts_init) — the honest upper bound for an unknown intrabar time. - Trigger levels: every fill carries
trigger_price— the LEVEL that triggered it (stop level, limit level, or the open), never the slippage-adjusted price. The broker orders intrabar events by trigger.
All prices are produced by integer-tick arithmetic on the instrument grid; a fill price can never land off-grid. Fills are for the FULL remaining quantity (Tier-0 models no partials).
BarFillConfig
¶
Bases: Struct
Knobs of the Tier-0 pessimism model (all default to the conservative side).
stop_slippage_ticks
class-attribute
instance-attribute
¶
Adverse ticks added to EVERY triggered stop fill (gap or intrabar).
market_slippage_ticks
class-attribute
instance-attribute
¶
Adverse ticks on market fills (0: the modeled micros are liquid enough).
fill_limit_on_touch
class-attribute
instance-attribute
¶
False (default) = a limit fills only if the bar trades >= 1 tick THROUGH its level; True = an exact touch of the level suffices.
BarFillModel
¶
BarFillModel(config: BarFillConfig | None = None)
Tier-0 FillModel: decides fills from OHLC bars + the shared path.
Conforms structurally to protocols.FillModel. Deterministic — no
randomness at all at this tier.
Source code in src/topstep_backtest/fills/bar_fill.py
try_fill
¶
try_fill(order: WorkingOrder, ctx: MarketContext, path: PricePath) -> list[Fill]