n this lesson we move beyond core API functionality and build on previous lessons to examine a case study in which advanced order types may be advantageous in implementing a specific trading strategy. The TWS API supports more than 60 of the order types and attributes available in TWS. Here we consider a scenario in which the Pegged-To-Primary/Relative order type is used with orders in a hedging pair trade to place related orders for two different instruments, such that placement of the second order is handled automatically by the IBKR server. We walk-through the code of a Python program to place the initial and the hedging order and demonstrate how they will appear in TWS.
In this lesson we present a case study to build on concepts discussed in previous lessons. Examples are shown of a few of the many different types of orders accessible from the API, namely the Relative aka pegged-to-primary order type, and hedging pair trades. Major takeaway points are:
- That many different order types can be used from the TWS API, for instance ‘Pegged-to-Primary’ or ‘Relative (REL)’ orders. In this order type an order is placed to the exchange at a limit price which is automatically modified by the IB server as the market moves up or down.
- Second, that orders can be submitted to the IB server such that they are ‘attached’ to one another, that is to say that only one order is placed to the exchange, and that the other order(s) are held back until the first order executes. In the case of pair trades, the two orders are for different instruments, whereas for bracket orders they would be two different orders for the same instrument.
- And finally, just to keep in mind that there are many different creative ways to place, and combine, orders from the TWS API.
Pair-trading is a popular strategy in algorithmic trading where an instrument is bought, and a related instrument is sold short. For a general discussion of pair trading there are many resources available on the internet. Often pair trading stocks is a mean-reversion strategy where the expectation is that the relative price difference in two related stocks will return to a historical mean. A possible advantage, in theory, of pair trading is that it should be a market-neutral strategy if both instruments have the same correlation, or Beta, to the general market. Additionally, different instrument pair trades might be expected to be uncorrelated with one another. In this lesson we won’t discuss techniques by which instruments in pair trades might be selected, which is a topic outside the scope of the API, but we will focus on how to create a place an API hedging pair trade.
It is important to keep in mind that all order types and combinations available to the API can also be created manually in TWS. The main advantage of using the API to place orders is that the process can be automated, potentially saving time if there are a large number of orders to be placed, or if there is a custom automated strategy.
In this example after sufficient analysis, a trader decides to place a pair trade for Dominos Pizza (DPZ), and Papa Johns (PZZA) stock. He places a large number of orders, and so prefers to automate order placement via the API.
These are the steps the trader follows to place a Dominos Pizza/Papa Johns stock pair trade.
- First an API Contract object must be defined for Dominos Pizza Stock (symbol DPZ). This is done by creating a Smart-routed Contract object with DPZ as the symbol. There are many example contract definitions for stocks and other instruments in the API reference guide: http://interactivebrokers.github.io/tws-api/basic_contracts.html#stk
- Next, an order type is chosen for the DPZ order. The trader decides here to use Relative orders because he thinks he may obtain a better price because of a bid/ask spread than he would with a plain limit order placed at the bid or ask price.
- The first order for DPZ stock is placed but with the transmit flag set to False, so it is not sent to the IB server before a second order is linked to it.
- Then a contract object for Papa Johns’ stock (symbol: PZZA) is created.
- Finally, hedging order is placed for PZZA stock by setting a value for the hedging type to (P), for Pair Trade, a ratio of (5) which is the ratio of the size of the second order to the size of the first, and the parent order ID chosen in step (3) which will link the orders together.
In general, when placing orders from the API there are two different types of settings which might prevent a valid order from being placed:
- There is the “Read-Only API” setting which is enabled by default in TWS and IB Gateway Global Configuration. This setting prevents any API order from being placed as a precautionary measure. It can be disabled in TWS or IB Gateway Configuration under API -> Settings.
- There are also Precautionary Settings which are enabled by default on an instrument-type basis to prevent orders which exceed certain criteria from being sent without confirmation. These precautionary settings apply to both TWS and API orders. They can be bypassed one-by-one in Global Configuration, or they can be bypassed globally for API orders by checking a settings box under Global Configuration at API -> Precautions -> “Bypass order precautions for API orders”.
Example of DPZ/PZZA pair trade:
from ibapi.client import EClient from ibapi.wrapper import EWrapper from ibapi.contract import Contract from ibapi.order import Order from threading import Timer import time class TestApp(EWrapper, EClient): def __init__(self): EClient.__init__(self, self) def error(self, reqId, errorCode, errorString): print("Error: ", reqId, " ", errorCode, " ", errorString) def nextValidId(self, orderId): # Store initial next order ID sent back on connection self.nextValidOrderId = orderId self.start() def nextOrderId(self): # There must be a larger ID for each new order oid = self.nextValidOrderId self.nextValidOrderId += 1 return oid def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld, mktCapPrice): print("OrderStatus. Id: ", orderId, ", Status: ", status, ", Filled: ", filled, ", Remaining: ", remaining, ", LastFillPrice: ", lastFillPrice) def openOrder(self, orderId, contract, order, orderState): print("OpenOrder. ID:", orderId, contract.symbol, contract.secType, "@", contract.exchange, ":", order.action, order.orderType, order.totalQuantity, orderState.status) def execDetails(self, reqId, contract, execution): print("ExecDetails. ", reqId, contract.symbol, contract.secType, contract.currency, execution.execId, execution.orderId, execution.shares, execution.lastLiquidity) def start(self): dpzStock = USStock("DPZ") dpzOrder = RelativePeggedToPrimary("BUY", 1, 0, 0) dpzOrder.transmit = False dpzOrderId = self.nextOrderId() self.placeOrder(dpzOrderId, dpzStock, dpzOrder) time.sleep(0.2) #planned to be no longer necessary in future # Pair trading documentation: http://interactivebrokers.github.io/tws-api/hedging.html pzzaStock = USStock("PZZA") # Size is 0 for hedge orders because it is calculated using the ratio pzzaOrder = RelativePeggedToPrimary("SELL", 0, 0, 0) pzzaOrder.parentId = dpzOrderId # parent ID links child to parent order pzzaOrder.hedgeType = "P" # "P" stands for Pair Trade pzzaOrder.hedgeParam = "5" # 5 is the hedging ratio self.placeOrder(self.nextOrderId(), pzzaStock, pzzaOrder) def stop(self): self.done = True self.disconnect() # The REL order type is adjusted by the system automatically with the bid (for Buy) or ask (for Sell ) orders def RelativePeggedToPrimary(action: str, quantity: float, priceCap: float, offsetAmount: float): order = Order() order.action = action order.orderType = "REL" order.totalQuantity = quantity order.lmtPrice = priceCap order.auxPrice = offsetAmount return order # API contract definition documentation: http://interactivebrokers.github.io/tws-api/basic_contracts.html#stk def USStock(ticker: str): contract = Contract() contract.symbol = ticker contract.secType = "STK" contract.exchange = "SMART" contract.currency = "USD" contract.primaryExchange = "NYSE" # Should be native exchange of stock return contract def main(): app = TestApp() app.connect("127.0.0.1", 4002, 1) Timer(5, app.stop).start() app.run() if __name__ == "__main__": main()
Most of the parts of this example will look familiar if you’ve watched the previous lessons. Here the order status callbacks which we have overridden are the same as in the previous lesson which discussed order placement, namely nextValidId, orderStatus, openOrder, and execDetails. These callback functions are only overridden to print returned data to the console in our example. We use a helper function called USStock() to provide default fields for US stock definitions. Here that is the:
- security type (STK),
- exchange (SMART),
- currency (USD),
- and primary exchange (NYSE).
so that only a symbol needs to be provided to return a uniquely-defined US stock contract object. The Primary exchange field here is defined as NYSE since we are placing orders for US stocks which are listed on NYSE. This helps to resolve an ambiguity if there is a contract in a different region with the same ticker and currency which can also be Smart-routed.
We also define a helper function for creating Relative orders, taken from the OrderSamples.py file, which helps to make the code more readable. We pass in the:
- action, which can be either Buy or Sell in most account types,
- the totalQuantity, which is the size of the order,
- the ‘priceCap’ which defines the furthest price the order will be set at by the IB server as the market moves, and
- an offset value which is the offset between the market price and where the order will be set.
For instance, if it is a Buy relative order with an offset of 0.05 and the current market bid price is 10, the system will place the initial Buy order at a limit price of 10.05. If the market moves up in this direction, the limit price would be modified by the system so that it remains at an offset of 0.05 above the current bid, but if the market moves in the opposite direction it will not be changed. It is important to note that Relative orders with offset of 0 can exhibit different behavior in moving in both directions, as described further on the website.
The main body of the code specific to this program begins in the Start function. Here we first create a DPZ stock contract using the USStock helper function, and create a Relative order using the RelativePeggedToPrimary helper function. We set the transmit flag in that Order object to false, so that this order will not be forwarded to the IB server by TWS before the second order is received. The API Order ID used is the next valid ID received automatically after the initial connection is established. We then invoke placeOrder to send this order to TWS.
Next, we create a second order in the pair trade for PZZA using similar steps. The difference for this order is that:
- the parentId field is set as the API Order ID of the previous order so that the two are linked together,
- the hedgeType field is “P” to indicate it’s a pair trade (there are other hedge order types available such as a Forex hedging trade), and
- that there is a ratio set, here 5, to indicate the ratio of shares of the second order to the first.
- Since the number of shares is calculated by the system using the ratio, the totalQuantity field in the order class is set as 0.
Now, placeOrder is invoked for the second order. At the current time, there should be a slight pause before an attached hedging order is placed to allow the first order to be processed by TWS, here it is 200 ms. This requirement for a pause is planned to be removed in the future. The ‘Transmit’ flag is set to True by default for orders in the Order class constructor, so this order is already configured to be transmitted to the IB server by TWS. Since it is linked to the parent order using the parentId field, the previous order will be transmitted as well.
We can then execute the program to see the results. After it is executed, both orders should appear in TWS. There will also be related callback messages to the API functions openOrder and orderStatus, as well as execDetails if there is an execution. We can tell by the orderStatus messages that the first, or parent, order should be working while the second order is waiting on the IB server for the initial order to execute.
Disclosure: Interactive Brokers
The analysis in this material is provided for information only and is not and should not be construed as an offer to sell or the solicitation of an offer to buy any security. To the extent that this material discusses general market activity, industry or sector trends or other broad-based economic or political conditions, it should not be construed as research or investment advice. To the extent that it includes references to specific securities, commodities, currencies, or other instruments, those references do not constitute a recommendation by IBKR to buy, sell or hold such investments. This material does not and is not intended to take into account the particular financial conditions, investment objectives or requirements of individual customers. Before acting on this material, you should consider whether it is suitable for your particular circumstances and, as necessary, seek professional advice.
Supporting documentation for any claims and statistical information will be provided upon request.
Any stock, options or futures symbols displayed are for illustrative purposes only and are not intended to portray recommendations.
Disclosure: API Examples Discussed
Throughout the lesson, please keep in mind that the examples discussed are purely for technical demonstration purposes, and do not constitute trading advice. Also, it is important to remember that placing trades in a paper account is recommended before any live trading.