An order type specifies when an order becomes eligible and what prices it may accept. It does not guarantee arrival time, queue priority, available size, execution price, or a complete fill. A backtest must add those assumptions explicitly.
The right model depends on the strategy. Daily allocation research may need only a conservative next-bar convention and costs. A market-making or stop-sensitive strategy may need quotes, trades, depth, queue position, latency, and venue-specific trigger rules.
Core order types
| Order | Eligibility and price rule | Main execution risk |
|---|---|---|
| Market | Execute against available opposite-side liquidity after venue arrival | Price is not guaranteed, and thin or protected markets can delay, reject, or partially fill |
| Limit | Buy no higher than the limit or sell no lower than the limit | The market can touch or cross the price without filling the simulated order |
| Stop market | Remain dormant until the configured trigger condition, then submit or activate a market order | A gap can produce a fill far beyond the stop price |
| Stop limit | Trigger into a limit order with a separate limit price | Price protection can leave the order unfilled during a fast move |
| Market if touched | Trigger a market-style order when price moves toward a specified level | Trigger source and post-trigger market price determine the result |
| Limit if touched | Trigger a limit order when the touch condition occurs | Both trigger uncertainty and limit non-execution apply |
| Trailing stop | Move the trigger only in the favorable direction, then activate a market or limit order | Update frequency, reference price, gaps, and stop-limit behavior matter |
The SEC's stop-order bulletin emphasizes that a stop price is a trigger, not a guaranteed execution price, and that brokers can use different last-sale or quotation trigger standards. Trade execution guidance also notes that routing takes time and the displayed quote may cover only limited size.
Order names and rules vary by asset, broker, exchange, and jurisdiction. Simulate the actual venue contract rather than relying only on a generic label.
Time in force and order lifecycle
Time in force (TIF) answers how long eligible order quantity remains active:
- DAY: expire at the venue or broker's defined session boundary.
- GTC: remain active until filled or cancelled, often subject to broker expiration and corporate-action policies.
- GTD: remain active until a specified timestamp or session.
- IOC: execute immediately available quantity and cancel the remainder.
- FOK: execute the full quantity immediately or cancel all of it.
- Opening or closing auction instructions: participate only in the specified auction under its cutoff and imbalance rules.
A realistic lifecycle includes creation, client submission, broker and venue arrival, acceptance or rejection, activation, amendments, partial fills, cancellation requests, late fills racing a cancel, expiry, and final reconciliation. Backtests that jump directly from signal to one fill record omit failure modes that can matter in production.
When can the order first execute?
For every signal, write down:
data knowledge time <= decision time <= submit time <= venue arrival time <= fill time
A rule calculated from a completed daily close usually cannot receive that same close as an ordinary continuous-market fill. A next-session open is one possible convention, but not a universal fix. A signal known before a market-on-close auction cutoff might legitimately enter that auction, while a signal using the final auction price cannot have caused an order submitted before the cutoff.
The same-row convention used by many array backtests means only that the input signal and simulated price share an index. The researcher must shift the signal or supply an eligible order price when the decision becomes known later. See look-ahead bias for the knowledge-time test.
Bar data cannot reveal the intrabar path
An OHLC bar shows four values, not their sequence or executable liquidity. If a long position has a stop at 95 and target at 105 while the next bar ranges from 94 to 106, the bar alone cannot establish which order fired first. A deterministic simulator must choose and disclose a pessimistic, optimistic, fixed-path, or randomized rule, or use more granular data.
Gaps need separate treatment. If a sell stop is 100 and the next tradable market opens at 90, filling at 100 invents unavailable liquidity. Conversely, a buy limit at 100 can receive price improvement after a gap down, subject to routing, priority, and available quantity.
Bar volume does not identify volume available at one price, and a low equal to a buy limit does not reveal whether the simulated order had queue priority. Filling on touch is not categorically right or wrong. It is an assumption:
- Touch-fill can be reasonable for a small taker order that crosses displayed liquidity.
- A resting maker order may sit behind existing quantity even after trades print at its price.
- Requiring price to cross the limit is a conservative proxy, not proof of a fill.
- L2 aggregate depth lacks individual order priority. L3 history shows recorded orders but still does not show how the simulated order would have changed other participants' behavior.
Executed VectorBT PRO limit-order example
This example creates one synthetic close series and one entry signal. The market order uses the signal-row price. The limit order sets its buy limit 1% below that reference through limit_delta=0.01 and remains pending until the series makes it eligible. Fees, slippage, volume, spread, and queue position are all absent.
import numpy as np
import pandas as pd
import vectorbtpro as vbt
rng = np.random.default_rng(1)
price = pd.Series(
100 * np.exp(np.cumsum(rng.normal(-0.0005, 0.015, 100))),
index=pd.date_range("2024-01-01", periods=100),
)
entries = pd.Series(False, index=price.index)
entries.iloc[5] = True
exits = pd.Series(False, index=price.index)
pf_market = vbt.PF.from_signals(price, entries, exits, freq="1D")
pf_limit = vbt.PF.from_signals(
price,
entries,
exits,
order_type="limit",
limit_delta=0.01,
freq="1D",
)
cols = ["Signal Index", "Fill Index", "Price", "Type"]
print("Market")
print(pf_market.orders.records_readable[cols].to_string(index=False))
print("Limit, 1% below signal price")
print(pf_limit.orders.records_readable[cols].to_string(index=False))
Market
Signal Index Fill Index Price Type
2024-01-06 2024-01-06 102.039845 Market
Limit, 1% below signal price
Signal Index Fill Index Price Type
2024-01-06 2024-01-19 101.019447 Limit
The result demonstrates the API contract, not realistic fill probability. The limit price is 102.039845 * (1 - 0.01) = 101.019447. Its later fill assumes the available price series is sufficient evidence and that one unit can execute there. A live resting order would also depend on bid and ask, queue, quantity, latency, and venue rules.
Stops, limits, and costs must interact
Execution assumptions should not be added after the strategy return is calculated. Spread, fees, slippage, impact, borrow, and funding affect whether an order is worth sending and how much remains available for later orders. Partial fills change position size. Rejections and delayed cancels change risk. An OCO bracket requires one child fill to cancel the other, with an explicit rule for simultaneous eligibility.
Stop and target studies also need counterfactual re-simulation. Historical MAE and MFE do not establish how a stop would have changed later entries, portfolio cash, gaps, or execution costs.
Framework capabilities and boundaries
VectorBT PRO signal simulation supports market and limit orders, stop orders that can become market or limit orders, delta formats, pending-limit conflict rules, and TIF modes including DAY, GTC, GTD, LOO, and FOK. Its public limit-order example demonstrates parameterized deltas. These mechanisms make array-scale order experiments practical, while the supplied OHLC, spread, slippage, and liquidity assumptions still determine realism.
Community VectorBT supports market-style signal orders, stop-loss, take-profit, and trailing stops in Portfolio.from_signals(). It does not expose VectorBT PRO's general pending limit-order and TIF system. Custom from_order_func() callbacks can express more state, but the researcher must implement and validate the lifecycle.
NautilusTrader can replay bars, quotes, trades, and L1, L2, or L3 books through a matching engine with configurable latency, fees, fill probability, queue position, and liquidity consumption. Its fill and matching documentation explains that recorded books remain immutable unless separate consumption tracking is enabled and that consumption estimates size rather than priority. More granular data supports a more detailed model. It does not recreate the counterfactual market response to the simulated orders.
Minimum order-model audit
Before accepting a backtest result, record:
- Signal knowledge, submission, arrival, and earliest fill times.
- Order, trigger, TIF, session, amendment, and cancellation rules.
- Bid, ask, last-sale, midpoint, bar, or auction price used for each decision.
- Gap and same-bar stop-versus-target precedence.
- Partial-fill, volume, depth, queue, and liquidity-consumption assumptions.
- Spread, fees, slippage, market impact, borrow, and funding.
- Rejection, price-band, lot-size, tick-size, margin, and rate-limit rules.
- Differences between the simulated order contract and the live broker or venue.
An order simulator is credible when these assumptions are explicit, conservative where evidence is missing, and tested against more granular or live execution data. Architectural labels alone do not establish realism.