Order Management Examples¶
Practical examples of order management patterns for common trading scenarios.
Overview¶
This guide demonstrates real-world order management patterns including:
- Entry and exit strategies
- Risk management with stops
- Position sizing
- Order monitoring and adjustment
- Multi-asset order management
Basic Patterns¶
Simple Buy and Hold¶
from rustybt.algorithm import TradingAlgorithm
from rustybt.api import order, symbol
class BuyAndHold(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('SPY')
context.ordered = False
def handle_data(self, context, data):
if not context.ordered:
# Buy once at market price
order(context.asset, 100)
context.ordered = True
Mean Reversion with Limits¶
from rustybt.api import order, get_open_orders, cancel_order
from rustybt.finance.execution import LimitOrder
class MeanReversion(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.lookback = 20
def handle_data(self, context, data):
# Get price data
prices = data.history(context.asset, 'close', context.lookback, '1d')
current_price = data.current(context.asset, 'close')
# Calculate mean and std
mean_price = prices.mean()
std_price = prices.std()
position = context.portfolio.positions.get(context.asset)
# Entry: Price 2 std below mean
if current_price < mean_price - 2 * std_price:
if position is None or position.amount == 0:
# Buy at current price (limit order for better fill)
order(context.asset, 100, style=LimitOrder(current_price))
# Exit: Price returns to mean
elif current_price > mean_price:
if position and position.amount > 0:
# Sell at market to exit quickly
order(context.asset, -position.amount)
Risk Management Patterns¶
Entry with Stop-Loss¶
from rustybt.finance.execution import MarketOrder, StopOrder
class StopLossStrategy(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.entry_price = None
context.stop_loss_pct = 0.05 # 5% stop
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
position = context.portfolio.positions.get(context.asset)
# Entry logic
if position is None or position.amount == 0:
if self.should_enter(context, data):
# Enter at market
order(context.asset, 100, style=MarketOrder())
context.entry_price = current_price
# Place stop-loss immediately
stop_price = current_price * (1 - context.stop_loss_pct)
order(context.asset, -100, style=StopOrder(stop_price))
def should_enter(self, context, data):
# Example: Simple price momentum signal
prices = data.history(context.asset, 'close', 20, '1d')
return prices[-1] > prices.mean() # Price above 20-day MA
Bracket Order (Complete Risk Management)¶
from rustybt.finance.execution import BracketOrder, MarketOrder
class BracketStrategy(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('TSLA')
context.risk_reward_ratio = 2.0 # 2:1 reward:risk
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
position = context.portfolio.positions.get(context.asset)
if position is None or position.amount == 0:
if self.entry_signal(context, data):
# Calculate stop and target prices
risk_amount = current_price * 0.05 # 5% risk
stop_price = current_price - risk_amount
target_price = current_price + (risk_amount * context.risk_reward_ratio)
# Enter with automatic risk management
order(
context.asset,
100,
style=BracketOrder(
entry_style=MarketOrder(),
stop_loss_price=stop_price,
take_profit_price=target_price
)
)
self.log.info(
f"Bracket order: Entry ${current_price:.2f}, "
f"Stop ${stop_price:.2f}, Target ${target_price:.2f}"
)
def entry_signal(self, context, data):
# Example: RSI oversold signal
prices = data.history(context.asset, 'close', 14, '1d')
rsi = compute_rsi(prices, window=14) # Your RSI calculation
return rsi < 30 # Oversold condition
Trailing Stop for Profit Protection¶
from rustybt.finance.execution import TrailingStopOrder
from rustybt.api import get_open_orders, cancel_order
class TrailingStopStrategy(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.entry_price = None
context.profit_threshold = 0.05 # 5% profit to activate trailing
context.trail_percent = 0.03 # 3% trail
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
position = context.portfolio.positions.get(context.asset)
if position and position.amount > 0:
# Calculate profit percentage
profit_pct = (current_price - context.entry_price) / context.entry_price
if profit_pct >= context.profit_threshold:
# Cancel existing stop orders
open_orders = get_open_orders(context.asset)
for order in open_orders:
if order.stop is not None:
cancel_order(order)
# Place trailing stop
order(
context.asset,
-position.amount,
style=TrailingStopOrder(trail_percent=context.trail_percent)
)
self.log.info(f"Activated trailing stop at {profit_pct:.1%} profit")
elif position is None or position.amount == 0:
# Entry logic
if self.entry_signal(context, data):
order(context.asset, 100)
context.entry_price = current_price
Position Sizing Patterns¶
Fixed Dollar Amount¶
class FixedDollarPosition(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('SPY')
context.target_position_value = 10000 # $10,000 per position
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
# Calculate shares for target dollar amount
shares = int(context.target_position_value / current_price)
position = context.portfolio.positions.get(context.asset)
current_shares = position.amount if position else 0
if self.should_rebalance(context, data):
# Order difference to reach target
order(context.asset, shares - current_shares)
Percentage of Portfolio¶
class PercentagePosition(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('QQQ')
context.target_pct = 0.10 # 10% of portfolio
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
portfolio_value = context.portfolio.portfolio_value
# Calculate target position value
target_value = portfolio_value * context.target_pct
target_shares = int(target_value / current_price)
position = context.portfolio.positions.get(context.asset)
current_shares = position.amount if position else 0
# Rebalance to target
order(context.asset, target_shares - current_shares)
Kelly Criterion Position Sizing¶
import numpy as np
class KellyPosition(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.win_rate = 0.55 # 55% win rate
context.avg_win = 0.10 # 10% average win
context.avg_loss = 0.05 # 5% average loss
def calculate_kelly_fraction(self, context):
# Kelly formula: f = (p*b - q) / b
# p = win probability
# q = loss probability
# b = win/loss ratio
p = context.win_rate
q = 1 - p
b = context.avg_win / context.avg_loss
kelly = (p * b - q) / b
# Use fractional Kelly for safety
return kelly * 0.5 # Half Kelly
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
portfolio_value = context.portfolio.portfolio_value
# Calculate position size using Kelly
kelly_fraction = self.calculate_kelly_fraction(context)
target_value = portfolio_value * kelly_fraction
target_shares = int(target_value / current_price)
position = context.portfolio.positions.get(context.asset)
current_shares = position.amount if position else 0
order(context.asset, target_shares - current_shares)
Order Monitoring Patterns¶
Monitor and Adjust Orders¶
from rustybt.api import get_open_orders, cancel_order
from rustybt.finance.execution import LimitOrder
class MonitoringStrategy(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.order_timeout_bars = 5
context.order_timestamps = {}
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
open_orders = get_open_orders(context.asset)
for order in open_orders:
# Track order age
if order.id not in context.order_timestamps:
context.order_timestamps[order.id] = context.datetime
bars_since_order = (context.datetime - context.order_timestamps[order.id]).days
# Cancel stale limit orders and replace with market
if bars_since_order >= context.order_timeout_bars:
if order.limit is not None:
self.log.info(f"Cancelling stale limit order {order.id}")
cancel_order(order)
# Re-submit as market order
order(context.asset, order.open_amount)
Partial Fill Handling¶
from rustybt.finance.order import ORDER_STATUS
class PartialFillHandler(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.min_fill_pct = 0.75 # Require 75% fill
def handle_data(self, context, data):
open_orders = get_open_orders(context.asset)
for order in open_orders:
if order.status == ORDER_STATUS.PARTIALLY_FILLED:
fill_pct = order.filled / order.amount
if fill_pct < context.min_fill_pct:
# Not enough filled, cancel remainder
self.log.warning(
f"Order {order.id}: Only {fill_pct:.1%} filled, "
f"cancelling remainder"
)
cancel_order(order)
else:
# Acceptable fill, let it continue
self.log.info(
f"Order {order.id}: {fill_pct:.1%} filled, "
f"waiting for remainder"
)
Multi-Asset Patterns¶
Pairs Trading¶
class PairsTrading(TradingAlgorithm):
def initialize(self, context):
context.asset1 = self.symbol('PEP')
context.asset2 = self.symbol('KO')
context.lookback = 30
context.entry_zscore = 2.0
context.exit_zscore = 0.5
def handle_data(self, context, data):
# Get price history
prices1 = data.history(context.asset1, 'close', context.lookback, '1d')
prices2 = data.history(context.asset2, 'close', context.lookback, '1d')
# Calculate spread and z-score
spread = prices1 - prices2
zscore = (spread[-1] - spread.mean()) / spread.std()
pos1 = context.portfolio.positions.get(context.asset1)
pos2 = context.portfolio.positions.get(context.asset2)
# Entry logic
if abs(zscore) > context.entry_zscore:
if zscore > 0:
# Asset1 overpriced, asset2 underpriced
# Short asset1, long asset2
order(context.asset1, -100)
order(context.asset2, 100)
else:
# Asset1 underpriced, asset2 overpriced
# Long asset1, short asset2
order(context.asset1, 100)
order(context.asset2, -100)
# Exit logic
elif abs(zscore) < context.exit_zscore:
# Close positions
if pos1 and pos1.amount != 0:
order(context.asset1, -pos1.amount)
if pos2 and pos2.amount != 0:
order(context.asset2, -pos2.amount)
Portfolio Rebalancing¶
class Rebalancing(TradingAlgorithm):
def initialize(self, context):
# Define target portfolio weights
context.target_weights = {
self.symbol('SPY'): 0.40, # 40% S&P 500
self.symbol('QQQ'): 0.30, # 30% Nasdaq
self.symbol('IWM'): 0.20, # 20% Small cap
self.symbol('TLT'): 0.10, # 10% Bonds
}
context.rebalance_frequency = 21 # Monthly (trading days)
context.days_since_rebalance = 0
def handle_data(self, context, data):
context.days_since_rebalance += 1
if context.days_since_rebalance >= context.rebalance_frequency:
self.rebalance_portfolio(context, data)
context.days_since_rebalance = 0
def rebalance_portfolio(self, context, data):
portfolio_value = context.portfolio.portfolio_value
for asset, target_weight in context.target_weights.items():
current_price = data.current(asset, 'close')
# Calculate target position
target_value = portfolio_value * target_weight
target_shares = int(target_value / current_price)
# Get current position
position = context.portfolio.positions.get(asset)
current_shares = position.amount if position else 0
# Order difference
shares_to_order = target_shares - current_shares
if shares_to_order != 0:
order(asset, shares_to_order)
self.log.info(
f"Rebalancing {asset.symbol}: "
f"{current_shares} → {target_shares} shares"
)
Advanced Patterns¶
Scale-In Strategy¶
class ScaleIn(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.total_target = 300 # Total position target
context.scale_increments = 3 # Number of entries
context.scale_interval = 5 # Days between entries
context.entries_made = 0
context.last_entry_date = None
def handle_data(self, context, data):
position = context.portfolio.positions.get(context.asset)
current_shares = position.amount if position else 0
# Check if we should scale in
if context.entries_made < context.scale_increments:
# Check timing
if context.last_entry_date is None or \
(context.datetime - context.last_entry_date).days >= context.scale_interval:
# Calculate increment size
increment = context.total_target // context.scale_increments
order(context.asset, increment)
context.entries_made += 1
context.last_entry_date = context.datetime
self.log.info(
f"Scale-in entry {context.entries_made}/{context.scale_increments}: "
f"{increment} shares"
)
Scale-Out Strategy¶
class ScaleOut(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.profit_levels = [0.05, 0.10, 0.15] # 5%, 10%, 15% profit
context.scale_percentages = [0.33, 0.33, 0.34] # Scale out portions
context.entry_price = None
context.exits_made = 0
def handle_data(self, context, data):
current_price = data.current(context.asset, 'close')
position = context.portfolio.positions.get(context.asset)
# Entry logic
if position is None or position.amount == 0:
if self.entry_signal(context, data):
order(context.asset, 300)
context.entry_price = current_price
context.exits_made = 0
# Scale-out logic
elif context.entry_price is not None:
profit_pct = (current_price - context.entry_price) / context.entry_price
if context.exits_made < len(context.profit_levels):
target_profit = context.profit_levels[context.exits_made]
if profit_pct >= target_profit:
# Calculate shares to sell
total_shares = position.amount
exit_pct = context.scale_percentages[context.exits_made]
shares_to_sell = int(total_shares * exit_pct)
order(context.asset, -shares_to_sell)
context.exits_made += 1
self.log.info(
f"Scale-out exit {context.exits_made}: "
f"{shares_to_sell} shares at {profit_pct:.1%} profit"
)
Error Handling Patterns¶
Robust Order Placement¶
from rustybt.finance.order import ORDER_STATUS
class RobustOrdering(TradingAlgorithm):
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.max_retries = 3
context.retry_count = {}
def place_order_with_retry(self, context, asset, amount, style=None):
"""Place order with automatic retry on rejection."""
try:
order_id = order(asset, amount, style=style)
# Check if order was rejected
order_obj = context.blotter.orders.get(order_id)
if order_obj and order_obj.status == ORDER_STATUS.REJECTED:
retry_count = self.retry_count.get(asset, 0)
if retry_count < context.max_retries:
self.log.warning(
f"Order rejected: {order_obj.reason}, "
f"retrying ({retry_count + 1}/{context.max_retries})"
)
# Adjust order based on rejection reason
adjusted_amount = self.adjust_order(
order_obj.reason, amount, context
)
self.retry_count[asset] = retry_count + 1
return self.place_order_with_retry(
context, asset, adjusted_amount, style
)
else:
self.log.error("Max retries reached, giving up")
return None
else:
# Order accepted, reset retry count
self.retry_count[asset] = 0
return order_id
except Exception as e:
self.log.error(f"Order placement error: {e}")
return None
def adjust_order(self, rejection_reason, amount, context):
"""Adjust order based on rejection reason."""
if "INSUFFICIENT_FUNDS" in rejection_reason:
# Reduce order size by 50%
return amount // 2
elif "POSITION_LIMIT" in rejection_reason:
# Reduce to fit within limit
return amount // 2
else:
# No adjustment possible
return amount
Best Practices Summary¶
✅ DO¶
- Check Existing Positions: Before placing new orders
- Monitor Order Status: Track fills, partial fills, and rejections
- Use Appropriate Order Types: Match order type to strategy needs
- Implement Risk Management: Always use stops or position limits
- Handle Partial Fills: Account for incomplete order fills
- Log Order Activity: Track order placement and fills for debugging
❌ DON'T¶
- Place Duplicate Orders: Check for open orders first
- Ignore Rejections: Handle and log rejection reasons
- Assume Instant Fills: Even market orders may take time
- Over-Leverage: Use position sizing appropriate to account size
- Forget Transaction Costs: Include commissions and slippage in backtests
Related Documentation¶
- Order Types - Complete order types reference
- Order Lifecycle - Order states and transitions
- Blotter Architecture - Order management system
- Transaction Costs - Cost modeling
Next Steps¶
- Study Order Lifecycle for state management
- Review Transaction Costs for realistic modeling
- Explore Portfolio Management for position tracking