Signal Timing¶
The T / T+1 Rule¶
WRONG (look-ahead bias):
Signal at bar T close → Entry at bar T close
CORRECT (realistic):
Signal at bar T close → Entry at bar T+1 open
When on_bar() returns an Order, the engine does not fill it immediately. The order becomes pending and fills at the next bar's open with adverse slippage applied.
How Pending Orders Work¶
class MyStrategy(Strategy):
def on_bar(self, bar, indicators, positions):
if some_condition:
# This order is NOT filled now.
# It becomes pending and fills at the NEXT bar's open.
return MarketOrder(side=Side.LONG, take_profit_pct=0.05)
return None
Internally:
on_bar()returns aMarketOrderduring Phase 4 of bar T- The engine stores it as a pending order
- At the start of bar T+1 (Phase 1), the pending order fills at
bar_T1.open * (1 + slippage)
Multiple Orders¶
on_bar() can return a single order or a list:
def on_bar(self, bar, indicators, positions):
orders = []
# Last MarketOrder wins (overwrites previous pending market order)
orders.append(MarketOrder(side=Side.LONG))
# LimitOrders stack (all are added to the pending limit queue)
orders.append(LimitOrder(side=Side.LONG, limit_price=2300.0))
orders.append(LimitOrder(side=Side.LONG, limit_price=2250.0))
return orders
Rules:
- MarketOrder: the last one returned replaces any previous pending market order
- LimitOrder: each one is appended to the pending limit queue
- StopOrder: each one is appended to the pending stop queue
skip_signal_on_close¶
When a position closes during Phase 2 or 3 of a bar, should on_bar() still run?
config = {
"skip_signal_on_close": True, # default — skip on_bar after exit
# "skip_signal_on_close": False, # for mean-reversion re-entry
}
- True (default): if a position closes this bar,
on_bar()is skipped. Prevents same-bar re-entry after a stop or target hit. Good for trend-following. - False:
on_bar()always runs. Allows immediate re-entry after an exit. Good for mean-reversion strategies where you want to flip direction on exit.
same_direction_only¶
When True, if you have a LONG position open and on_bar() returns a SHORT order, the order is silently rejected. Set False to allow hedging.
Verifying Your Timing¶
Use the DelayTest to add +1 bar latency. If PnL drops dramatically, your strategy may have hidden timing sensitivity: