Skip to content

Feature Proposal: Persistent Limit Order with Per-Order Timeout #205

@river-walras

Description

@river-walras

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

  1. Lost Trading Opportunities: A limit order that misses one K-bar cannot wait for subsequent price movement
  2. Lack of Flexibility: Cannot differentiate between aggressive orders (unlimited wait) and conservative orders (short timeout)
  3. Limited Utility of cancel_pending_order/cancel_all_pending_orders: These methods are only useful when buy_delay/sell_delay is large

Proposed Solution

Core Design

Two-layer order processing architecture:

  1. 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_scope for next processing cycle
  2. 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

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 indefinitely
  • timeout_bars=None: Persist indefinitely until filled or manually cancelled
  • timeout_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_bar and timeout_bars fields
  • PendingOrderScope: Add get() and all_orders() methods
  • PendingOrderScope.add(): Add created_bar and timeout_bars parameters

2. context.py

  • ExecResult: Add buy_timeout_bars and sell_timeout_bars fields
  • ExecContext: Add buy_timeout_bars and sell_timeout_bars properties
  • set_exec_ctx_data(): Reset timeout properties

3. strategy.py

  • _schedule_order(): Pass created_bar and timeout_bars to pending_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 fill

Example 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 bars

Example 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 = 5

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions