Advanced Order Management¶
Runtime: ~7 minutes
Level: Advanced
This notebook covers advanced order management techniques:
- Bracket Orders - Stop loss + take profit in one order
- OCO Orders - One-Cancels-Other order pairs
- Trailing Stops - Dynamic stop losses that follow price
- Order Status Tracking - Monitoring and managing order lifecycle
- Partial Fills - Handling incomplete executions
- Order Modification - Updating live orders
- Advanced Position Management - Scaling in/out, pyramiding
These techniques are essential for professional trading strategies with proper risk management.
📋 Notebook Information
- RustyBT Version: 0.1.2+
- Last Validated: 2025-11-07
- API Compatibility: Verified ✅
- Documentation: API Reference
# Setup
from rustybt.analytics import setup_notebook
setup_notebook()
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from rustybt import TradingAlgorithm
from rustybt.api import (
order,
order_target,
order_target_percent,
order_target_value,
get_open_orders,
cancel_order,
symbol,
record,
schedule_function,
)
from rustybt.finance.execution import (
ExecutionStyle,
MarketOrder,
LimitOrder,
StopOrder,
StopLimitOrder,
)
from rustybt.finance.order import Order
from rustybt.finance.execution import (
# Note: TrailingStopLimitOrder not available
BracketOrder,
# ⚠️ OCO order sibling linking not yet implemented
# OCOOrder,
TrailingStopOrder,
# TrailingStopLimitOrder, # Not available - only TrailingStopOrder exists
)
print("✓ Imports successful")
1. Bracket Orders¶
Bracket orders place a main order along with:
- Stop loss - Exit if price moves against you
- Take profit - Exit if target profit is reached
Both legs are submitted simultaneously and automatically cancel each other when one fills.
class BracketOrderStrategy(TradingAlgorithm):
"""
Example: Long entry with 2% stop loss and 5% take profit.
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.entry_price = None
context.position_size = 100
# Risk parameters
context.stop_loss_pct = 0.02 # 2% stop loss
context.take_profit_pct = 0.05 # 5% take profit
def handle_data(self, context, data):
# Entry logic (e.g., moving average crossover)
if context.portfolio.positions[context.asset].amount == 0:
current_price = data.current(context.asset, 'price')
# Calculate bracket prices
stop_price = current_price * (1 - context.stop_loss_pct)
profit_price = current_price * (1 + context.take_profit_pct)
# Place bracket order
bracket = BracketOrder(
entry_style=MarketOrder(), # Was: entry_price=current_price
stop_loss_price=stop_price,
take_profit_price=profit_price,
)
self.order(
context.asset,
context.position_size,
style=bracket
)
context.entry_price = current_price
self.record(
entry_style=MarketOrder(), # Was: entry_price=current_price
stop_price=stop_price,
profit_price=profit_price,
)
print("✓ Bracket order strategy defined")
Bracket Order Variations¶
# Long bracket with limit entry
def place_long_bracket_limit(context, asset, entry_price, shares):
stop_price = entry_price * 0.98 # 2% stop
profit_price = entry_price * 1.05 # 5% profit
bracket = BracketOrder(
entry_price=entry_price,
entry_style=LimitOrder(entry_price), # Wait for better price
stop_loss_price=stop_price,
take_profit_price=profit_price,
)
return context.order(asset, shares, style=bracket)
# Short bracket
def place_short_bracket(context, asset, entry_price, shares):
stop_price = entry_price * 1.02 # 2% stop (price goes UP)
profit_price = entry_price * 0.95 # 5% profit (price goes DOWN)
bracket = BracketOrder(
entry_price=entry_price,
stop_loss_price=stop_price,
take_profit_price=profit_price,
)
return context.order(asset, -shares, style=bracket) # Negative for short
# Asymmetric bracket (tighter stop, wider profit)
def place_asymmetric_bracket(context, asset, entry_price, shares):
"""1:3 risk/reward ratio"""
stop_price = entry_price * 0.99 # 1% stop
profit_price = entry_price * 1.03 # 3% profit
bracket = BracketOrder(
entry_price=entry_price,
stop_loss_price=stop_price,
take_profit_price=profit_price,
)
return context.order(asset, shares, style=bracket)
print("✓ Bracket order variations defined")
2. OCO Orders (One-Cancels-Other)¶
OCO orders are pairs of orders where filling one automatically cancels the other.
Use cases:
- Breakout strategies (buy stop above, buy stop below)
- Exit management (stop loss, take profit)
- Range trading (buy limit at support, sell limit at resistance)
class BreakoutOCOStrategy(TradingAlgorithm):
"""
Example: Breakout strategy using OCO
- Buy if price breaks above resistance
- Sell if price breaks below support
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.lookback = 20
self.schedule_function(
self.check_breakout,
date_rules=self.date_rules.every_day(),
time_rules=self.time_rules.market_open()
)
def check_breakout(self, context, data):
# Skip if we have a position
if context.portfolio.positions[context.asset].amount != 0:
return
# Get historical data
prices = data.history(
context.asset,
'close',
context.lookback,
'1d'
)
# Calculate support and resistance
resistance = prices.max()
support = prices.min()
current_price = data.current(context.asset, 'price')
# Place OCO order for breakout
# Buy stop above resistance
buy_trigger = resistance * 1.001 # 0.1% above
# Sell stop below support
sell_trigger = support * 0.999 # 0.1% below
# ⚠️ OCO order sibling linking not yet implemented
# oco = OCOOrder(
order1=StopOrder(buy_trigger),
order2=StopOrder(sell_trigger),
)
# The amount determines direction
# For OCO, we use two separate orders
buy_order = self.order(
context.asset,
100,
style=StopOrder(buy_trigger)
)
sell_order = self.order(
context.asset,
-100,
style=StopOrder(sell_trigger)
)
# Link orders as OCO
# ⚠️ OCO order sibling linking not yet implemented
# buy_order.add_oco_sibling(sell_order)
self.record(
resistance=resistance,
support=support,
current_price=current_price,
)
print("✓ OCO breakout strategy defined")
3. Trailing Stops¶
Trailing stops automatically adjust the stop price as the market price moves in your favor.
Benefits:
- Protect profits while letting winners run
- Automatically tighten stops as price moves
- Reduce emotional trading decisions
class TrailingStopStrategy(TradingAlgorithm):
"""
Example: Trend following with trailing stop.
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.trailing_pct = 0.05 # 5% trailing stop
context.trailing_order_id = None
def handle_data(self, context, data):
current_price = data.current(context.asset, 'price')
position = context.portfolio.positions[context.asset]
# Entry: Simple moving average crossover
if position.amount == 0:
prices = data.history(context.asset, 'close', 50, '1d')
sma_short = prices[-10:].mean()
sma_long = prices.mean()
if sma_short > sma_long:
# Enter long
shares = 100
self.order(context.asset, shares, style=MarketOrder())
# Place trailing stop
trailing_stop = TrailingStopOrder(
trail_percent=context.trailing_pct
)
context.trailing_order_id = self.order(
context.asset,
-shares, # Opposite direction to exit
style=trailing_stop
)
# Monitor trailing stop
elif context.trailing_order_id:
open_orders = self.get_open_orders(context.asset)
if context.trailing_order_id in open_orders:
trailing_order = open_orders[context.trailing_order_id]
stop_price = trailing_order.stop
self.record(
price=current_price,
trailing_stop=stop_price,
unrealized_pnl=position.last_sale_price * position.amount - position.cost_basis,
)
else:
# Trailing stop was filled (position exited)
context.trailing_order_id = None
print("✓ Trailing stop strategy defined")
Trailing Stop Variations¶
# Percentage trailing stop
def place_trailing_stop_percent(context, asset, shares, trail_pct):
"""Trail by percentage (e.g., 5% below highest price)"""
trailing_stop = TrailingStopOrder(trail_percent=trail_pct)
return context.order(asset, -shares, style=trailing_stop)
# Fixed dollar trailing stop
def place_trailing_stop_fixed(context, asset, shares, trail_amount):
"""Trail by fixed dollar amount (e.g., $5 below highest)"""
trailing_stop = TrailingStopOrder(trail_amount=trail_amount)
return context.order(asset, -shares, style=trailing_stop)
# Trailing stop limit (avoid slippage)
def place_trailing_stop_limit(context, asset, shares, trail_pct, limit_offset):
"""Trailing stop with limit order (control execution price)"""
trailing_stop_limit = TrailingStopLimitOrder(
trail_percent=trail_pct,
limit_offset=limit_offset # Limit price offset from stop
)
return context.order(asset, -shares, style=trailing_stop_limit)
# ATR-based trailing stop (volatility-adjusted)
def place_atr_trailing_stop(context, data, asset, shares, atr_multiplier=2.0):
"""Trail based on Average True Range (adapts to volatility)"""
# Calculate ATR
high = data.history(asset, 'high', 14, '1d')
low = data.history(asset, 'low', 14, '1d')
close = data.history(asset, 'close', 15, '1d')[:-1]
tr = np.maximum(
high - low,
np.maximum(
np.abs(high - close),
np.abs(low - close)
)
)
atr = tr.mean()
# Trailing stop based on ATR
trail_amount = atr * atr_multiplier
trailing_stop = TrailingStopOrder(trail_amount=trail_amount)
return context.order(asset, -shares, style=trailing_stop)
print("✓ Trailing stop variations defined")
4. Order Status Tracking¶
Monitor and manage order lifecycle programmatically.
from rustybt.finance.order import ORDER_STATUS
class OrderTrackingStrategy(TradingAlgorithm):
"""
Example: Track order status and handle different states.
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.pending_orders = {} # Track our orders
self.schedule_function(
self.manage_orders,
date_rules=self.date_rules.every_day(),
time_rules=self.time_rules.market_open(minutes=1)
)
def manage_orders(self, context, data):
# Get all open orders
open_orders = self.get_open_orders()
for asset, orders in open_orders.items():
for order_id, order_obj in orders.items():
self.log.info(
f"Order {order_id}: {order_obj.status} - "
f"{order_obj.amount} shares @ {order_obj.limit or 'market'}"
)
# Handle different order states
if order_obj.status == ORDER_STATUS.OPEN:
# Order submitted but not filled
self.handle_open_order(context, order_obj, data)
elif order_obj.status == ORDER_STATUS.HELD:
# Order held (e.g., waiting for market open)
self.log.info(f"Order {order_id} held")
elif order_obj.status == ORDER_STATUS.PARTIALLY_FILLED:
# Partially filled
self.handle_partial_fill(context, order_obj)
def handle_open_order(self, context, order_obj, data):
"""Handle unfilled orders (e.g., cancel stale limit orders)"""
# Example: Cancel limit orders that are more than 1 hour old
if order_obj.limit: # It's a limit order
age = context.get_datetime() - order_obj.created
if age > timedelta(hours=1):
self.log.info(f"Canceling stale order {order_obj.id}")
self.cancel_order(order_obj)
def handle_partial_fill(self, context, order_obj):
"""Handle partially filled orders"""
filled_pct = order_obj.filled / order_obj.amount
self.log.info(
f"Order {order_obj.id} partially filled: "
f"{filled_pct:.1%} ({order_obj.filled}/{order_obj.amount})"
)
# Optionally cancel and replace with market order
if filled_pct < 0.5: # Less than 50% filled
remaining = order_obj.amount - order_obj.filled
self.cancel_order(order_obj)
# Place market order for remainder
self.order(order_obj.asset, remaining, style=MarketOrder())
print("✓ Order tracking strategy defined")
Order Status Reference¶
# Order status values
order.status:
'open' # Submitted but not filled
'held' # Held (e.g., market closed)
'partial' # Partially filled
'filled' # Completely filled
'canceled' # Canceled
'rejected' # Rejected by broker
# Order attributes
order.id # Unique order ID
order.asset # Asset being traded
order.amount # Total shares (signed)
order.filled # Shares filled so far
order.limit # Limit price (if limit order)
order.stop # Stop price (if stop order)
order.created # Creation timestamp
order.commission # Commission paid
5. Partial Fill Handling¶
Strategies for dealing with partial order execution.
class PartialFillStrategy(TradingAlgorithm):
"""
Advanced partial fill handling strategies.
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.target_position = 0
context.partial_fill_timeout = timedelta(minutes=5)
def handle_data(self, context, data):
# Check for partial fills
open_orders = self.get_open_orders(context.asset)
for order_id, order_obj in open_orders.items():
if order_obj.status == 'partial':
self.handle_partial(
context,
order_obj,
data,
strategy='patient' # or 'aggressive', 'adaptive'
)
def handle_partial(self, context, order_obj, data, strategy='patient'):
age = context.get_datetime() - order_obj.created
filled_pct = order_obj.filled / order_obj.amount
remaining = order_obj.amount - order_obj.filled
if strategy == 'patient':
# Wait for full fill, only convert to market if timeout
if age > context.partial_fill_timeout:
self.cancel_order(order_obj)
self.order(order_obj.asset, remaining, style=MarketOrder())
self.log.info(f"Timeout: Converting to market order")
elif strategy == 'aggressive':
# Immediately convert to market if < 80% filled
if filled_pct < 0.8:
self.cancel_order(order_obj)
self.order(order_obj.asset, remaining, style=MarketOrder())
self.log.info(f"Low fill rate: Converting to market")
elif strategy == 'adaptive':
# Adjust limit price towards market
current_price = data.current(order_obj.asset, 'price')
if order_obj.limit:
# Move limit price 50% towards market
new_limit = (order_obj.limit + current_price) / 2
self.cancel_order(order_obj)
self.order(
order_obj.asset,
remaining,
style=LimitOrder(new_limit)
)
self.log.info(f"Adjusting limit: {order_obj.limit:.2f} -> {new_limit:.2f}")
print("✓ Partial fill handling defined")
6. Order Modification¶
Modify existing orders without canceling and resubmitting.
class OrderModificationStrategy(TradingAlgorithm):
"""
Example: Dynamically adjust stop loss as price moves.
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.stop_order_id = None
context.entry_price = None
context.highest_price = None
def handle_data(self, context, data):
current_price = data.current(context.asset, 'price')
position = context.portfolio.positions[context.asset]
if position.amount > 0:
# Update highest price
if context.highest_price is None:
context.highest_price = current_price
else:
context.highest_price = max(context.highest_price, current_price)
# Check if we need to update stop loss
self.update_stop_loss(context, data)
def update_stop_loss(self, context, data):
"""Update stop loss to lock in profits"""
if context.stop_order_id is None:
return
# Get current stop order
open_orders = self.get_open_orders(context.asset)
if context.stop_order_id not in open_orders:
return
stop_order = open_orders[context.stop_order_id]
current_stop = stop_order.stop
# Calculate new stop (5% below highest price)
new_stop = context.highest_price * 0.95
# Only raise stop, never lower it
if new_stop > current_stop:
# Modify the order
# ⚠️ Order modification not yet implemented in rustybt
# self.modify_order(
# TODO: This feature is planned for a future release
stop_order,
stop_price=new_stop
)
self.log.info(
f"Stop loss updated: ${current_stop:.2f} -> ${new_stop:.2f}"
)
self.record(
price=data.current(context.asset, 'price'),
stop_price=new_stop,
highest_price=context.highest_price,
)
print("✓ Order modification strategy defined")
class PyramidingStrategy(TradingAlgorithm):
"""
Scale into winning positions (add to winners).
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
# Pyramiding parameters
context.initial_shares = 100
context.max_adds = 3 # Max 3 additional entries
context.add_threshold = 0.02 # Add every 2% profit
context.position_history = [] # Track entry prices
def handle_data(self, context, data):
current_price = data.current(context.asset, 'price')
position = context.portfolio.positions[context.asset]
# Initial entry
if position.amount == 0:
self.order(context.asset, context.initial_shares)
context.position_history = [(current_price, context.initial_shares)]
self.log.info(f"Initial entry: {context.initial_shares} @ ${current_price:.2f}")
# Add to position
elif len(context.position_history) < context.max_adds + 1:
last_entry_price = context.position_history[-1][0]
profit_since_last = (current_price - last_entry_price) / last_entry_price
if profit_since_last >= context.add_threshold:
# Add smaller amount each time (risk management)
add_shares = int(context.initial_shares * 0.5)
self.order(context.asset, add_shares)
context.position_history.append((current_price, add_shares))
self.log.info(
f"Adding to position: {add_shares} @ ${current_price:.2f} "
f"(+{profit_since_last:.1%} profit)"
)
# Record position info
if position.amount > 0:
avg_entry = sum(p * s for p, s in context.position_history) / position.amount
self.record(
position_size=position.amount,
avg_entry_price=avg_entry,
current_price=current_price,
total_pnl=(current_price - avg_entry) * position.amount,
)
print("✓ Pyramiding strategy defined")
Scaling Out (Profit Taking)¶
class ScalingOutStrategy(TradingAlgorithm):
"""
Take partial profits at multiple levels.
"""
def initialize(self, context):
context.asset = self.symbol('AAPL')
context.entry_price = None
# Scaling out targets
context.profit_targets = [
(0.03, 0.25), # Take 25% at +3%
(0.05, 0.25), # Take 25% at +5%
(0.08, 0.25), # Take 25% at +8%
# Let remaining 25% run with trailing stop
]
context.targets_hit = [False] * len(context.profit_targets)
def handle_data(self, context, data):
current_price = data.current(context.asset, 'price')
position = context.portfolio.positions[context.asset]
if position.amount == 0:
return
if context.entry_price is None:
context.entry_price = position.cost_basis / position.amount
# Calculate current profit
profit_pct = (current_price - context.entry_price) / context.entry_price
# Check profit targets
for i, (target_profit, exit_pct) in enumerate(context.profit_targets):
if profit_pct >= target_profit and not context.targets_hit[i]:
# Take profit
shares_to_sell = int(position.amount * exit_pct)
self.order(context.asset, -shares_to_sell)
context.targets_hit[i] = True
self.log.info(
f"Profit target {i+1} hit: Taking {exit_pct:.0%} profit "
f"({shares_to_sell} shares) at +{profit_pct:.1%}"
)
# Record
self.record(
profit_pct=profit_pct,
position_size=position.amount,
targets_hit=sum(context.targets_hit),
)
print("✓ Scaling out strategy defined")
Summary¶
Key Takeaways¶
- Bracket orders simplify risk management with automatic stop/profit levels
- OCO orders enable breakout strategies and complex exit logic
- Trailing stops protect profits while letting winners run
- Order tracking provides fine-grained control over execution
- Partial fills require explicit handling strategies
- Order modification enables dynamic risk management
- Scaling in/out optimizes position sizing and profit taking
Best Practices¶
✅ Always use stop losses for risk management
✅ Monitor order status and handle failures gracefully
✅ Consider partial fills in your strategy logic
✅ Use trailing stops to lock in profits
✅ Scale into positions gradually to reduce risk
✅ Take partial profits at predefined levels
✅ Test order handling thoroughly before live trading
Next Steps¶
- Notebook 13: Portfolio Optimization + Walk Forward
- Notebook 14: Multi-Timeframe Strategies
- Notebook 15: Custom Indicator Development