book
Checkout our new book! Hands on AI Trading with Python, QuantConnect, and AWS Learn More arrow

Order Management

Transaction Manager

Introduction

The algorithm transactions manager (SecurityTransactionManager) contains a collection of helper methods to quickly access all your orders. To access the transaction manager, use the Transactionstransactions property of your algorithm. If you save a reference to your order tickets, you shouldn't need to use the transaction manager. LEAN updates the order tickets as the brokerage processes your orders.

Get a Single Order Ticket

If you didn't save a reference to the order ticket when you created an order, you can call the GetOrderTicketget_order_ticket method to get it. You need to pass the order ID to the method. If you don't have the order ID, you can use the LastOrderIdlast_order_id property to get the order ID of the most recent order.

var orderId = Transactions.LastOrderId;
var ticket = Transactions.GetOrderTicket(orderId);
order_id = self.transactions.last_order_id
ticket = self.transactions.get_order_ticket(order_id)

Get Order Tickets

To get order tickets, call the GetOrderTicketsget_order_tickets or GetOpenOrderTicketsget_open_order_tickets method. You can pass in a filter function to filter all of the order tickets or pass a Symbol to get the order tickets for a specific asset.

// Get all order tickets
var orderTickets = Transactions.GetOrderTickets();

// Get order tickets that pass a filter
var filteredOrderTickets = Transactions.GetOrderTickets(orderTicket => orderTicket.Symbol == symbol);

// Get all open order tickets
var openOrderTickets = Transactions.GetOpenOrderTickets();

// Get all open order tickets for a symbol
var symbolOpenOrderTickets = Transactions.GetOpenOrderTickets(symbol);

// Get open order tickets that pass a filter
var filteredOpenOrderTickets = Transactions.GetOpenOrderTickets(orderTicket => orderTicket.Quantity > 10);
# Get all order tickets
order_tickets = self.transactions.get_order_tickets()

# Get order tickets that pass a filter
filtered_order_tickets = self.transactions.get_order_tickets(lambda order_ticket: order_ticket.symbol == symbol)

# Get all open order tickets
open_order_tickets = self.transactions.get_open_order_tickets()

# Get all open order tickets for a symbol
symbol_open_order_tickets = self.transactions.get_open_order_tickets(symbol)

# Get open order tickets that pass a filter
filtered_open_order_tickets = self.transactions.get_open_order_tickets(lambda order_ticket: order_ticket.quantity > 10)

Get a Single Order

To get a clone of a specific order, call the GetOrderByIdget_order_by_id method with the order Id. To get the order Id, use the OrderIdorder_id property of the order ticket or use the LastOrderID property if you want the most recent order.

var orderId = Transactions.LastOrderID;
var order = Transactions.GetOrderById(orderId);
order_id = self.transactions.last_order_id
order = self.transactions.get_order_by_id(order_id)

Order objects are immutable and changes to the order object will not impact the trade. To make an update to an order you must use Order Tickets. Order objects have the following attributes:

Get Orders

To get a list of orders, call the GetOrdersget_orders, GetOpenOrdersget_open_orders, or GetOrdersByBrokerageIdget_orders_by_brokerage_id method. These method returns a list of Order objects.

// Get all completed orders
var completedOrders = Transactions.GetOrders();

// Get all completed orders that pass a filter
var filteredCompletedOrders = Transactions.GetOrders(x => x.Quantity > 10);

// Get a list of all completed orders for a symbol
var symbolCompletedOrders = Transactions.GetOrders(x => x.Symbol == symbol);

// Get all open orders
var openOrders = Transactions.GetOpenOrders();

// Get all open orders that pass a filter
var filteredOpenOrders = Transactions.GetOpenOrders(x => x.Quantity > 10);

// Get a list of all open orders for a symbol
var symbolOpenOrders = Transactions.GetOpenOrders(symbol);

// Get all open and completed orders that correspond to an Id that the brokerage assigned in live trading
var ordersByBrokerageId = Transactions.GetOrdersByBrokerageId(brokerageId);
# Get all completed orders
completed_Orders = self.transactions.get_orders()

# Get all completed orders that pass a filter
filtered_completed_orders = self.transactions.get_orders(lambda x: x.quantity > 10)

# Retrieve a list of all completed orders for a symbol
symbol_completed_orders = self.transactions.get_orders(lambda x: x.symbol == symbol)

# Get all open orders
open_orders = self.transactions.get_open_orders()

# Get all open orders that pass a filter
filtered_open_orders = self.transactions.get_open_orders(lambda x: x.quantity > 10)

# Retrieve a list of all open orders for a symbol
symbol_open_orders = self.transactions.get_open_orders(symbol)

# Get all open and completed orders that correspond to an Id that the brokerage assigned in live trading
orders_by_brokerage_id = self.transactions.get_orders_by_brokerage_id(brokerageId)

Order objects have the following attributes:

The OrdersCountorders_count property gets the current number of orders that have been processed.

Get Remaining Order Quantity

To get the unfilled quantity of open orders, call the GetOpenOrdersRemainingQuantityget_open_orders_remaining_quantity method.

// Get the quantity of all open orders
var allOpenQuantity = Transactions.GetOpenOrdersRemainingQuantity();

// Get the quantity of open orders that pass a filter
var filteredOpenQuantity = Transactions.GetOpenOrdersRemainingQuantity(
    orderTicket => orderTicket.Quantity > 10
);

// Get the quantity of open orders for a symbol
var symbolOpenQuantity = Transactions.GetOpenOrdersRemainingQuantity(symbol);
# Get the quantity of all open orders
all_open_quantity = self.transactions.get_open_orders_remaining_quantity()

# Get the quantity of open orders that pass a filter
filtered_open_quantity = self.transactions.get_open_orders_remaining_quantity(
    lambda order_ticket: order_ticket.quantity > 10
)

# Get the quantity of open orders for a symbol
symbol_open_quantity = self.transactions.get_open_orders_remaining_quantity(symbol)

Cancel Orders

To cancel open orders, call the CancelOpenOrderscancel_open_orders method. This method returns a list of OrderTicket objects that correspond to the canceled orders.

// Cancel all open orders
var allCancelledOrders = Transactions.CancelOpenOrders();

// Cancel orders related to IBM and apply a tag
var ibmCancelledOrders = Transactions.CancelOpenOrders("IBM", "Hit stop price");
 # Cancel all open orders
all_cancelled_orders = self.transactions.cancel_open_orders()

# Cancel orders related to IBM and apply a tag
ibm_cancelled_orders = self.transactions.cancel_open_orders("IBM", "Hit stop price")

Live Trading Considerations

LEAN doesn't save orders and order tickets across live deployments.

When you deploy your algorithm, LEAN fetches the open orders from your brokerage and populate the algorithm transactions manager (SecurityTransactionManager).

Examples

The following examples demonstrate some common practices for using the transaction manager.

Example 1: 2-Level Take Profit And Stop Loss

The following algorithm trades volatility using the CBOE VIX dataset. It makes use of the transaction manager to handle a 2-layer take profit and stop loss risk management.

public class TransactionManagerAlgorithm : QCAlgorithm
{
    private Symbol _spy, _vix;
    private ExponentialMovingAverage _ema;

    public override void Initialize()
    {
        SetStartDate(2023, 1, 1);
        SetEndDate(2023, 11, 1);
        
        // Request SPY data for trading and trade on daily signals so we use daily resolution.
        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
        // Subscribe to VIX data to generate trade signals.
        _vix = AddData<CBOE>("VIX", Resolution.Daily).Symbol;

        // Use the EMA indicator to generate trade signals from VIX.
        _ema = EMA(_vix, 20, Resolution.Daily);
        // Warm up the EMA indicator to enable immediate use.
        WarmUpIndicator(_vix, _ema, Resolution.Daily);
    }

    public override void OnData(Slice slice)
    {
        if (slice.Bars.TryGetValue(_spy, out var spyBar))
        {
            var vix = Securities[_vix].Price;

            // Trade based on volatility. Sell SPY if VIX is in an uptrend, indicating low market volatility.
            // Buy SPY if VIX is in a downtrend, indicating a volatile market.
            if (vix > _ema && !Portfolio[_spy].IsLong)
            {
                // Cancel all open orders to remove redundant take profit and stop loss orders.
                Transactions.CancelOpenOrders();

                var quantity = 10 - Portfolio[_spy].Quantity;
                MarketOrder(_spy, quantity);
                // Stop Loss at 2%.
                StopMarketOrder(_spy, -10, spyBar.Close * 0.98m, tag: "stop loss");
                // Take profit at 4%.
                LimitOrder(_spy, -5, spyBar.Close * 1.04m, tag: "take profit");
            }
            else if (vix < _ema && !Portfolio[_spy].IsShort)
            {
                // Cancel all open orders to remove redundant take profit and stop loss orders.
                Transactions.CancelOpenOrders();

                var quantity = -10 - Portfolio[_spy].Quantity;
                MarketOrder(_spy, quantity);
                // Stop Loss at 2%.
                StopMarketOrder(_spy, 10, spyBar.Close * 1.02m, tag: "stop loss");
                // Take profit at 4%.
                LimitOrder(_spy, 5, spyBar.Close * 0.96m, tag: "take profit");
            }
        }
    }

    public override void OnOrderEvent(OrderEvent orderEvent)
    {
        // If the take profit order was filled out, we place the 2nd layer of take profit.
        var takeProfitOrders = Transactions.GetOrderTickets(x => x.OrderType == OrderType.Limit).ToList();
        if (takeProfitOrders.Count == 0)
        {
            return;
        }
        var lastTakeProfitOrderId = takeProfitOrders.MaxBy(x => x.Time).OrderId;

        if (orderEvent.Status == OrderStatus.Filled && orderEvent.Id == lastTakeProfitOrderId)
        {
            // Cancel all open orders to remove redundant stop loss orders.
            Transactions.CancelOpenOrders();
            // 2nd layer Take profit at extra 4%.
            var takeProfitPrice = orderEvent.FillQuantity > 0 ? orderEvent.FillPrice * 0.96m : orderEvent.FillPrice * 1.04m;
            LimitOrder(_spy, 5, takeProfitPrice, tag: "second-layer take profit");
        }
        else if (orderEvent.Status == OrderStatus.Filled && orderEvent.Ticket.OrderType == OrderType.StopMarket)
        {
            // Cancel all open orders to remove redundant take profit orders if stop loss.
            Transactions.CancelOpenOrders();
        }
    }
}
class TransactionManagerAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2023, 11, 1)
        
        # Request SPY data for trading and trade on daily signals so we use daily resolution.
        self.spy = self.add_equity("SPY", Resolution.DAILY).symbol
        #Subscribe to VIX data to generate trade signals.
        self.vix = self.add_data(CBOE, "VIX", Resolution.DAILY).symbol

        # Use the EMA indicator to generate trade signals from VIX.
        self._ema = self.ema(self.vix, 20, Resolution.DAILY)
        # Warm up the EMA indicator to enable immediate use.
        self.warm_up_indicator(self.vix, self._ema, Resolution.DAILY)

    def on_data(self, slice: Slice) -> None:
        spy_bar = slice.bars.get(self.spy)
        if spy_bar:
            vix = self.securities[self.vix].price
            ema = self._ema.current.value

            # Trade based on volatility. Sell SPY if VIX is in an uptrend, indicating low market volatility.
            # Buy SPY if VIX is in a downtrend, indicating a volatile market.
            if vix > ema and not self.portfolio[self.spy].is_long:
                # Cancel all open orders to remove redundant take profit and stop loss orders.
                self.transactions.cancel_open_orders()

                quantity = 10 - self.portfolio[self.spy].quantity
                self.market_order(self.spy, quantity)
                # Stop Loss at 2%.
                self.stop_market_order(self.spy, -10, spy_bar.close * 0.98, tag="stop loss")
                # Take profit at 4%.
                self.limit_order(self.spy, -5, spy_bar.close * 1.04, tag="take profit")
            elif vix < ema and not self.portfolio[self.spy].is_short:
                # Cancel all open orders to remove redundant take profit and stop loss orders.
                self.transactions.cancel_open_orders()

                quantity = -10 - self.portfolio[self.spy].quantity
                self.market_order(self.spy, quantity)
                # Stop Loss at 2%.
                self.stop_market_order(self.spy, 10, spy_bar.close * 1.02, tag="stop loss")
                # Take profit at 4%.
                self.limit_order(self.spy, 5, spy_bar.close * 0.96, tag="take profit")

    def on_order_event(self, order_event: OrderEvent) -> None:
        # If the take profit order was filled out, we place the 2nd layer of take profit.
        take_profit_orders = list(self.transactions.get_order_tickets(lambda x: x.order_type == OrderType.LIMIT))
        if len(take_profit_orders) == 0:
            return
        last_take_profit_order_id = sorted(take_profit_orders, key=lambda x: x.time)[-1].order_id

        if order_event.status == OrderStatus.FILLED and order_event.id == last_take_profit_order_id:
            # Cancel all open orders to remove redundant stop loss orders.
            self.transactions.cancel_open_orders()
            # 2nd layer Take profit at extra 4%.
            take_profit_price = order_event.fill_price * (0.96 if order_event.fill_quantity > 0 else 1.04)
            self.limit_order(self.spy, 5, take_profit_price, tag="second-layer take profit")
        elif order_event.status == OrderStatus.FILLED and order_event.ticket.order_type == OrderType.STOP_MARKET:
            # Cancel all open orders to remove redundant take profit orders if stop loss.
            self.transactions.cancel_open_orders()

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: