-
Notifications
You must be signed in to change notification settings - Fork 382
Open
Description
Feature Proposal: Persistent Limit Order with Per-Order Timeout
Summary
Propose improving PyBroker's handling of limit orders to allow unfilled orders to persist to subsequent K-bars, with support for per-order independent timeout settings. Currently, a limit order that fails to fill on its scheduled execution date is immediately cancelled, preventing the strategy from waiting for better fill opportunities.
Problem Statement
Current Behavior
- Limit orders are attempted exactly once on their scheduled execution date
- If the limit_price condition is not met, the order is immediately removed from
pending_order_scope - No opportunity to wait for better prices in subsequent bars
- No flexibility to set different timeout durations for different orders
Pain Points
- Lost Trading Opportunities: A limit order that misses one K-bar cannot wait for subsequent price movement
- Lack of Flexibility: Cannot differentiate between aggressive orders (unlimited wait) and conservative orders (short timeout)
- Limited Utility of cancel_pending_order/cancel_all_pending_orders: These methods are only useful when
buy_delay/sell_delayis large
Proposed Solution
Core Design
Two-layer order processing architecture:
-
New Order Processing (
_place_buy/sell_orders):- Processes orders from
buy_sched/sell_sched - Filled → Remove from
pending_order_scope - Unfilled → Keep in
pending_order_scopefor next processing cycle
- Processes orders from
-
Persistent Order Processing (
_process_persistent_orders- new method):- Each bar, iterate through all orders in
pending_order_scope - Check timeout → Remove if expired
- Attempt execution → Remove if filled, keep if unfilled
- Each bar, iterate through all orders in
Key Features
1. Per-Order Independent Timeout Settings
New properties in ExecContext:
ctx.buy_timeout_bars = 10 # Buy order persists for 10 bars
ctx.sell_timeout_bars = None # Sell order persists indefinitelytimeout_bars=None: Persist indefinitely until filled or manually cancelledtimeout_bars=N: Persist for N bars, then auto-cancel
2. Backward Compatibility
- Existing code requires no changes
- Default behavior unchanged (timeout=None means indefinite persistence)
3. Manual Control
Users can manage orders:
# View pending orders
pending = list(ctx.pending_orders(symbol))
# Cancel specific order
ctx.cancel_pending_order(order_id)
# Cancel all orders for symbol
ctx.cancel_all_pending_orders(symbol)Implementation Details
Files to Modify
1. scope.py
- PendingOrder: Add
created_barandtimeout_barsfields - PendingOrderScope: Add
get()andall_orders()methods - PendingOrderScope.add(): Add
created_barandtimeout_barsparameters
2. context.py
- ExecResult: Add
buy_timeout_barsandsell_timeout_barsfields - ExecContext: Add
buy_timeout_barsandsell_timeout_barsproperties - set_exec_ctx_data(): Reset timeout properties
3. strategy.py
- _schedule_order(): Pass
created_barandtimeout_barstopending_order_scope.add() - _place_buy_orders(): Simplify logic - only remove on fill, keep in scope if unfilled
- _place_sell_orders(): Same as above
- _process_persistent_orders() (new): Process all persistent orders
- Main loop: Call
_process_persistent_orders()each bar
4. log.py
- debug_timeout_order() (new): Log order timeout cancellation
Usage Examples
Example 1: Indefinite Limit Order Persistence (Aggressive Trading)
def my_strategy(ctx: ExecContext):
if buy_signal:
ctx.buy_shares = 100
ctx.buy_limit_price = 50.0
ctx.buy_fill_price = PriceType.LOW
# No buy_timeout_bars set, defaults to None (indefinite)
# Order will continuously attempt to fillExample 2: 10-Bar Timeout (Conservative Trading)
def my_strategy(ctx: ExecContext):
if buy_signal:
ctx.buy_shares = 100
ctx.buy_limit_price = 50.0
ctx.buy_fill_price = PriceType.LOW
ctx.buy_timeout_bars = 10 # Auto-cancel after 10 barsExample 3: Dynamic Order Management (High-Frequency Trading)
def my_strategy(ctx: ExecContext):
pending = list(ctx.pending_orders(ctx.symbol))
if market_crisis:
# Cancel all pending orders during crisis
ctx.cancel_all_pending_orders(ctx.symbol)
return
if buy_signal and not pending:
# Only place new order if no pending orders
ctx.buy_shares = 100
ctx.buy_limit_price = 50.0
ctx.buy_timeout_bars = 5Metadata
Metadata
Assignees
Labels
No labels