Skip to content

Order Management Guide

This guide covers comprehensive order management using ProjectX Python SDK v3.3.4+. All order operations are fully asynchronous and provide real-time tracking capabilities.

Overview

The OrderManager provides complete lifecycle management for all order types including market, limit, stop, OCO (One Cancels Other), and bracket orders. All operations are async-first for optimal performance in trading applications.

Key Features

  • Multiple Order Types: Market, limit, stop, OCO, and bracket orders
  • Real-time Tracking: Live order status updates via WebSocket
  • Error Recovery: Automatic retry logic and comprehensive error handling
  • Price Precision: Automatic tick size alignment using Decimal arithmetic
  • Concurrent Operations: Place and manage multiple orders simultaneously
  • Risk Integration: Built-in integration with RiskManager when enabled

Getting Started

Basic Setup

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite


async def main():
    # Initialize with order management capabilities
    suite = await TradingSuite.create("MNQ")

    mnq_context = suite["MNQ"]
    # Order manager is automatically available
    order_manager = mnq_context.orders

    # Get instrument information for proper pricing
    instrument = mnq_context.instrument_info
    print(f"Tick size: ${instrument.tickSize}")


asyncio.run(main())

Safety First

** WARNING**: Order examples in this guide place real orders on the market. Always:

  • Use micro contracts (MNQ, MES) for testing
  • Set small position sizes
  • Have exit strategies ready
  • Test in paper trading environments when available

Order Types

Market Orders

Market orders execute immediately at the current market price with guaranteed fills but no price control.

import asyncio

from project_x_py import TradingSuite


async def place_market_order():
    suite = await TradingSuite.create("MNQ")

    try:
        # Place buy market order
        response = await suite["MNQ"].orders.place_market_order(
            contract_id=suite["MNQ"].instrument_info.id,  # Or use suite.instrument_info.id
            side=0,  # 0 = Buy, 1 = Sell
            size=1,  # Number of contracts
        )

        print(f"Market order placed: {response.orderId}")
        print(f"Status: {response.success}")

        # Wait for fill confirmation
        await asyncio.sleep(2)
        order_status = await suite["MNQ"].orders.get_order_statistics_async()
        print(f"Final status: {order_status}")

    except Exception as e:
        print(f"Order failed: {e}")
    finally:
        await suite.disconnect()


asyncio.run(place_market_order())

Limit Orders

Limit orders execute only at specified price or better, providing price control but no fill guarantee.

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite


async def place_limit_order():
    suite = await TradingSuite.create("MNQ")

    # Get current market price for context
    current_price = await suite["MNQ"].data.get_current_price()

    # Place buy limit order below market
    limit_price = Decimal(str(current_price)) - Decimal("2.00")  # $2.00 below market

    response = await suite["MNQ"].orders.place_limit_order(
        contract_id=suite["MNQ"].instrument_info.id,
        side=0,  # Buy
        size=1,
        limit_price=float(limit_price),
    )

    print(f"Limit order placed at ${limit_price}")
    print(f"Order ID: {response.orderId}")

    # Monitor order status
    while True:
        status = await suite["MNQ"].orders.get_order_by_id(response.orderId)
        if status is None:
            continue

        if status.status in [2, 3, 4]:
            print(
                f"Status: {'FILLED' if status.status == 2 else 'CANCELLED' if status.status == 3 else 'EXPIRED' if status.status == 4 else 'PENDING'}"
            )
            break

        await asyncio.sleep(5)  # Check every 5 seconds


asyncio.run(place_limit_order())

Stop Orders

Stop orders become market orders when the stop price is reached, useful for exits and breakout entries.

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite


async def place_stop_order():
    suite = await TradingSuite.create("MNQ")

    current_price = await suite["MNQ"].data.get_current_price()

    # Stop loss order (sell stop below current price)
    stop_price = Decimal(str(current_price)) - Decimal("2.00")  # $2.00 below market

    response = await suite["MNQ"].orders.place_stop_order(
        contract_id=suite["MNQ"].instrument_info.id,
        side=1,  # Sell (for stop loss)
        size=1,
        stop_price=float(stop_price),
    )

    print(f"Stop order placed at ${stop_price}")

    # Or stop entry order (buy stop above current price for breakouts)
    breakout_price = Decimal(str(current_price)) + Decimal("2.00")

    breakout_response = await suite["MNQ"].orders.place_stop_order(
        contract_id=suite["MNQ"].instrument_info.id,
        side=0,  # Buy
        size=1,
        stop_price=float(breakout_price),
    )

    print(f"Breakout order placed at ${breakout_price}")


asyncio.run(place_stop_order())

Bracket Orders

Bracket orders are the most sophisticated order type, combining entry, stop loss, and take profit in one operation.

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite
from project_x_py.models import BracketOrderResponse


async def place_bracket_order():
    suite = await TradingSuite.create("MNQ")

    current_price = await suite["MNQ"].data.get_current_price()

    # Complete bracket order setup
    response = await suite["MNQ"].orders.place_bracket_order(
        contract_id=suite["MNQ"].instrument_info.id,
        side=0,  # Buy entry
        size=1,
        # Entry order (optional - if None, uses market order)
        entry_type="market",
        entry_price=None,
        # Risk management
        stop_loss_price=float(
            Decimal(str(current_price)) - Decimal("4")
        ),  # Stop loss $4 from entry
        take_profit_price=float(
            Decimal(str(current_price)) + Decimal("8")
        ),  # Take profit $8 from entry
        # Order timing
    )


    print("Bracket order placed:")
    print(f"  Entry: {response.entry_order_id}")
    print(f"  Stop Loss: {response.stop_order_id}")
    print(f"  Take Profit: {response.target_order_id}")

    # Monitor bracket order progress
    await monitor_bracket_order(suite, response)


async def monitor_bracket_order(
    suite: TradingSuite, bracket_response: BracketOrderResponse
):
    """Monitor all three orders in a bracket."""

    while True:
        if bracket_response.entry_order_id is None:
            continue
        # Check main order status
        main_status = await suite["MNQ"].orders.get_order_by_id(
            bracket_response.entry_order_id
        )

        print(f"Entry order: {main_status}")

        if main_status == "FILLED":
            print("Entry filled! Monitoring exit orders...")

            # Now monitor the exit orders
            while True:
                if bracket_response.stop_order_id is None:
                    continue
                stop_status = await suite["MNQ"].orders.get_order_by_id(
                    bracket_response.stop_order_id
                )
                if bracket_response.target_order_id is None:
                    continue
                target_status = await suite["MNQ"].orders.get_order_by_id(
                    bracket_response.target_order_id
                )

                if stop_status is None:
                    continue
                if target_status is None:
                    continue
                if stop_status.status == 2:
                    print("Stop loss triggered!")
                    break
                elif target_status.status == 2:
                    print("Take profit hit!")
                    break

                await asyncio.sleep(2)
            break

        elif main_status in ["CANCELLED", "REJECTED"]:
            print(f"Entry order {main_status}")
            break

        await asyncio.sleep(5)


asyncio.run(place_bracket_order())

Order Lifecycle and Tracking

Real-time Order Status

Track order status changes in real-time using events or polling:

import asyncio
from decimal import Decimal

from project_x_py import EventType, TradingSuite
from project_x_py.event_bus import Event


def on_order_update(event: Event):
    order_data = event.data
    print(f"Order {order_data['order_id']} status: {order_data['status']}")

    if order_data["status"] == "FILLED":
        print(f"  Filled at ${order_data['fill_price']}")
        print(f"  Quantity: {order_data['filled_quantity']}")


async def setup_order_tracking():
    suite = await TradingSuite.create("MNQ")

    # Event-driven tracking (recommended)
    async def on_order_update(event: Event):
        order_data = event.data

        print(order_data)
        print(f"Order {order_data['order_id']} status: {order_data['new_status']}")

        if order_data["status"] == "FILLED":
            print(f"  Filled at ${order_data['filledPrice']}")
            print(f"  Quantity: {order_data['filled_quantity']}")
        if order_data["status"] == "MODIFIED":
            print(f"  Modified at ${order_data['limitPrice']}")
            print(f"  Quantity: {order_data['size']}")

    current_price = await suite["MNQ"].data.get_current_price()
    if current_price:
        limit_price = Decimal(str(current_price)) - Decimal("2.0")
    else:
        return

    # Place an order to demonstrate tracking
    response = await suite["MNQ"].orders.place_limit_order(
        contract_id=suite["MNQ"].instrument_info.id,
        side=0,
        size=1,
        limit_price=float(limit_price),
    )
    print(f"Tracking order: {response.orderId}")

    await suite.on(EventType.ORDER_FILLED, on_order_update)
    await suite.on(EventType.ORDER_MODIFIED, on_order_update)

    # Keep connection alive for events
    await asyncio.sleep(30)


asyncio.run(setup_order_tracking())

Order Modification and Cancellation

Modifying Orders

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite


async def modify_orders():
    suite = await TradingSuite.create("MNQ")

    # Place initial limit order
    current_price = await suite["MNQ"].data.get_current_price()
    initial_price = Decimal(str(current_price)) - Decimal("50")

    response = await suite["MNQ"].orders.place_limit_order(
        contract_id=suite["MNQ"].instrument_info.id,
        side=0,
        size=1,
        limit_price=float(initial_price),
    )

    order_id = response.orderId
    print(f"Initial order at ${initial_price}")

    # Wait a moment
    await asyncio.sleep(5)

    # Modify the order price (move closer to market)
    new_price = Decimal(str(current_price)) - Decimal("25")

    modify_response = await suite["MNQ"].orders.modify_order(
        order_id=order_id,
        limit_price=float(new_price),
        size=2,  # Also increase size
    )

    print(f"Order modified to ${new_price}, size: 2")

    # Modify only specific fields
    await suite["MNQ"].orders.modify_order(
        order_id=order_id,
        size=3,  # Only change size
    )


asyncio.run(modify_orders())

Cancelling Orders

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite


async def cancel_orders():
    suite = await TradingSuite.create("MNQ")

    # Place multiple orders
    orders: list[int] = []
    current_price = await suite["MNQ"].data.get_current_price()

    for i in range(3):
        price = Decimal(str(current_price)) - Decimal(str(10 * (i + 1)))
        response = await suite["MNQ"].orders.place_limit_order(
            contract_id=suite["MNQ"].instrument_info.id,
            side=0,
            size=1,
            limit_price=float(price),
        )
        orders.append(response.orderId)

    print(f"Placed {len(orders)} orders")

    # Cancel individual order
    await suite["MNQ"].orders.cancel_order(order_id=orders[0])
    print("Cancelled first order")

    # Cancel multiple orders
    for order in orders[1:]:
        await suite["MNQ"].orders.cancel_order(order_id=order)
    print("Cancelled remaining orders")

    # Cancel all open orders (nuclear option)
    await suite["MNQ"].orders.cancel_all_orders(contract_id=suite["MNQ"].instrument_info.id)
    print("All orders cancelled")


asyncio.run(cancel_orders())

Error Handling and Recovery

Comprehensive Error Handling

import asyncio
from decimal import Decimal

from project_x_py import TradingSuite
from project_x_py.exceptions import (
    ProjectXOrderError,
    ProjectXPositionError,
    ProjectXRateLimitError,
)


async def robust_order_placement():
    suite = await TradingSuite.create("MNQ")

    try:
        response = await suite["MNQ"].orders.place_bracket_order(
            contract_id=suite["MNQ"].instrument_info.id,
            side=0,
            size=1,
            entry_price=None,
            entry_type="market",
            stop_loss_price=float(Decimal("50")),
            take_profit_price=float(Decimal("100")),
        )

        print(f"Order 1 placed successfully: {response.entry_order_id}")

        # This will raise an error because if the entry_price is None, the entry_type must be "market"
        response = await suite["MNQ"].orders.place_bracket_order(
            contract_id=suite["MNQ"].instrument_info.id,
            side=1,
            size=1,
            entry_price=None,
            stop_loss_price=float(Decimal("50")),
            take_profit_price=float(Decimal("100")),
        )

    except ProjectXPositionError as e:
        print(f"Insufficient margin: {e}")
        # Reduce position size or add funds

    except ProjectXOrderError as e:
        print(f"Order error: {e}")
        if "invalid price" in str(e).lower():
            # Price alignment issue - check tick size
            instrument = suite["MNQ"].instrument_info
            print(f"Tick size: {instrument.tickSize}")

    except ProjectXRateLimitError as e:
        print(f"Rate limited: {e}")
        # Wait and retry
        await asyncio.sleep(1)
        # Retry logic here...

    except Exception as e:
        print(f"Unexpected error: {e}")

asyncio.run(robust_order_placement())

Performance Optimization

Concurrent Order Operations

async def concurrent_operations():
    suite = await TradingSuite.create("MNQ")

    current_price = await suite.data.get_current_price()

    # Place multiple orders concurrently
    tasks = []

    for i in range(5):
        price = Decimal(str(current_price)) - Decimal(str(10 * (i + 1)))

        task = suite.orders.place_limit_order(
            "MNQ", 0, 1, price, time_in_force="DAY"
        )
        tasks.append(task)

    # Execute all orders concurrently
    responses = await asyncio.gather(*tasks, return_exceptions=True)

    successful_orders = []
    for i, response in enumerate(responses):
        if isinstance(response, Exception):
            print(f"Order {i} failed: {response}")
        else:
            successful_orders.append(response.order_id)
            print(f"Order {i} placed: {response.order_id}")

    print(f"Successfully placed {len(successful_orders)} orders")

    # Monitor all orders concurrently
    status_tasks = [
        suite.orders.get_order_status(order_id)
        for order_id in successful_orders
    ]

    statuses = await asyncio.gather(*status_tasks)
    for order_id, status in zip(successful_orders, statuses):
        print(f"Order {order_id}: {status}")

Batch Operations

async def batch_operations():
    suite = await TradingSuite.create("MNQ")

    # Batch cancel multiple orders
    order_ids = ["order1", "order2", "order3"]  # Your order IDs

    results = await suite.orders.batch_cancel_orders(order_ids)

    for order_id, result in results.items():
        if result["success"]:
            print(f"Cancelled {order_id}")
        else:
            print(f"Failed to cancel {order_id}: {result['error']}")

    # Batch status check
    statuses = await suite.orders.batch_get_status(order_ids)

    for order_id, status in statuses.items():
        print(f"Order {order_id}: {status}")

Best Practices

1. Order Size and Risk Management

# Always calculate position sizes based on risk
async def calculate_position_size(suite, entry_price, stop_price, risk_percent=0.01):
    """Calculate position size based on risk tolerance."""

    account_info = suite.client.account_info
    risk_amount = account_info.balance * Decimal(str(risk_percent))

    price_risk = abs(entry_price - stop_price)

    # Account for contract multiplier
    instrument = await suite.client.get_instrument("MNQ")
    multiplier = instrument.contractSize or Decimal("20")  # MNQ multiplier

    position_size = int(risk_amount / (price_risk * multiplier))

    return max(1, position_size)  # Minimum 1 contract

2. Price Precision

# Always use Decimal for price calculations
from decimal import Decimal, ROUND_HALF_UP

async def align_to_tick_size(price: Decimal, tick_size: Decimal) -> Decimal:
    """Align price to instrument tick size."""

    return (price / tick_size).quantize(
        Decimal('1'), rounding=ROUND_HALF_UP
    ) * tick_size

# Usage
current_price = await suite.data.get_current_price()
instrument = await suite.client.get_instrument("MNQ")

entry_price = Decimal(str(current_price)) - Decimal("25")
aligned_price = await align_to_tick_size(
    entry_price,
    Decimal(str(instrument.tickSize))
)

3. Order Validation

async def validate_order_before_placement(suite, contract_id, side, size, price=None):
    """Validate order parameters before placement."""

    # Check account margin
    account_info = suite.client.account_info

    # Get instrument info
    instrument = await suite.client.get_instrument(contract_id)

    # Validate size
    if size <= 0:
        raise ValueError("Order size must be positive")

    # Validate price alignment
    if price:
        tick_size = Decimal(str(instrument.tickSize))
        if price % tick_size != 0:
            raise ValueError(f"Price {price} not aligned to tick size {tick_size}")

    # Check position limits (if risk manager enabled)
    if hasattr(suite, 'risk_manager') and suite.risk_manager:
        current_position = await suite.positions.get_position(contract_id)
        new_size = (current_position.size if current_position else 0)

        if side == 0:  # Buy
            new_size += size
        else:  # Sell
            new_size -= size

        # Check if new position exceeds limits
        max_position = 10  # Your risk limit
        if abs(new_size) > max_position:
            raise ValueError(f"New position {new_size} exceeds limit {max_position}")

    return True

4. Event-Driven Order Management

class OrderEventHandler:
    def __init__(self, suite):
        self.suite = suite
        self.active_orders = {}

    async def setup_event_handlers(self):
        """Setup comprehensive order event handling."""

        await self.suite.on(EventType.ORDER_PLACED, self.on_order_placed)
        await self.suite.on(EventType.ORDER_FILLED, self.on_order_filled)
        await self.suite.on(EventType.ORDER_CANCELLED, self.on_order_cancelled)
        await self.suite.on(EventType.ORDER_REJECTED, self.on_order_rejected)

    async def on_order_placed(self, event):
        """Handle order placement confirmation."""
        order_data = event.data
        self.active_orders[order_data['order_id']] = order_data
        print(f" Order placed: {order_data['order_id']}")

    async def on_order_filled(self, event):
        """Handle order fills."""
        order_data = event.data
        order_id = order_data['order_id']

        print(f"< Order filled: {order_id}")
        print(f"   Price: ${order_data['fill_price']}")
        print(f"   Quantity: {order_data['filled_quantity']}")

        # Remove from active orders
        if order_id in self.active_orders:
            del self.active_orders[order_id]

        # Handle bracket order logic
        if 'bracket_group_id' in order_data:
            await self.handle_bracket_fill(order_data)

    async def handle_bracket_fill(self, order_data):
        """Handle bracket order fills specially."""
        group_id = order_data['bracket_group_id']

        if order_data['order_type'] == 'entry':
            print(f"Bracket entry filled - monitoring exits for group {group_id}")
        elif order_data['order_type'] in ['stop', 'target']:
            print(f"Bracket exit filled - group {group_id} complete")
            # Cancel remaining exit order if needed

# Usage
async def run_with_event_handling():
    suite = await TradingSuite.create("MNQ")

    handler = OrderEventHandler(suite)
    await handler.setup_event_handlers()

    # Your trading logic here...
    response = await suite.orders.place_bracket_order(
        "MNQ", 0, 1,
        stop_offset=Decimal("50"),
        target_offset=Decimal("100")
    )

    # Events will be handled automatically
    await asyncio.sleep(60)  # Keep running for events

Integration with Risk Management

When the RiskManager feature is enabled, order placement is automatically validated:

async def risk_managed_trading():
    # Enable risk management
    suite = await TradingSuite.create("MNQ", features=["risk_manager"])

    # Risk manager validates all orders automatically
    try:
        response = await suite.orders.place_bracket_order(
            contract_id="MNQ",
            side=0, size=5,  # Large size
            stop_offset=Decimal("25"),
            target_offset=Decimal("75")
        )

    except ProjectXRiskViolationError as e:
        print(f"Risk check failed: {e}")
        # Order was rejected due to risk limits

    # Set custom risk parameters
    await suite.risk_manager.set_position_limit("MNQ", max_contracts=3)
    await suite.risk_manager.set_daily_loss_limit(Decimal("1000"))

    # Now retry with smaller size
    response = await suite.orders.place_bracket_order(
        contract_id="MNQ",
        side=0, size=2,  # Safer size
        stop_offset=Decimal("50"),
        target_offset=Decimal("100")
    )

Summary

The ProjectX OrderManager provides comprehensive order management capabilities:

  • Multiple order types with full async support
  • Real-time tracking via WebSocket events
  • Error recovery with automatic retry logic
  • Price precision handling with tick alignment
  • Risk integration when RiskManager is enabled
  • Concurrent operations for high-performance trading
  • Event-driven architecture for responsive applications

All order operations are designed for production trading environments with proper error handling, logging, and performance optimization. Always test thoroughly with small positions before deploying live trading strategies.


Next: Position Management Guide | Previous: Trading Suite Guide