Scenario:
After I place a market order using the TWS API, sometimes I do not receive the orderStatus callback for a very unusually long time. How then can I find out the status of the placed order ?
Important notes concerning IBApi.EWrapper.orderStatus :
- Typically there are duplicate orderStatus messages with the same information that will be received by a client. This corresponds to messages sent back from TWS, the IB server, or the exchange.
- There is no guarantee that an orderStatus callbacks will be triggered for every change in order status. For example with market orders when the order is accepted and executes immediately, there commonly will not be any corresponding orderStatus callbacks. For that reason it is recommended to monitor the IBApi.EWrapper.execDetails function in addition to IBApi.EWrapper.orderStatus.
You need to monitor the execDetails callback along with orderStatus callback. It needs to be noted that many times the orderStatus callback may not even get triggered.
Methodology to implement the Solution:
Before we go any further, take a look at the blogpost Thread Synchronization using Event Object . We will be using the concepts here to implement the solution.
Normally when we place an order by calling the IBApi.EClient.placeOrder function, the openOrder callback method and the orderStatus callback methods are invoked. And if this order is executed, then the execDetails callback method is also invoked.
NOTE: If we make an explicit call to IBApi.EClient.reqExecutions , then the execDetails callback is triggered and upon completion of sending all messages, the execDetailsEnd callback is also triggered. But a call to IBApi.EClient.placeOrder will trigger ONLY execDetails callback method, subject to the condition that the order is executed. Likewise if we make an explicit call to IBApi.Eclient.reqAllOpenOrders only then will both the the callback's openOrder and openOrderEnd be triggered. When we call IBApi.EClient.placeOrder function, the openOrderEnd callback is Not triggered.
We can use this understanding to check if the execDetails callback has been triggered. And then we can check to see if the orderStatus callback has been triggered. So this involves checking the mesages from two callback's.
But first let us take a look at an easier example to check the message from callback reqSummary. This will help to put things in context.
Handling messages from reqAccountSummary callback's:
When the client makes a call to reqAccountSummary, the accountSummary callback is triggered. For each message, the accountSummary callback is invoked once and after all the messages are delivered, the accountSummaryEnd callback is triggered.
For clarity, let us take a look at what exactly happens :
- We request our account summary from TWS by calling the Eclient method reqAccountSummary
- TWS will take however much time it needs to gather that data, and when ready it will be sent back to us.
- The Ewrapper accountSummary callback method will be automatically called, containing the data in its method arguments.
To store the messages that we receive in accountSummary callback, let us do the following:
- Let us first initialize an empty queue. A queue is a linear data structure that stores items in First In First Out (FIFO) manner. With a queue the least recently added item is removed first. It is very similar to a list. We are using a queue instead of a list so as to facilitate communication between two threads originating from the same process. We do this by using the task_done() function and join(). Another reason whey we prefer is the timeout when we use get() method of a queue.
import queue self.accountSummary_queue = queue.Queue() # initialize empty queue for accountSummary callbacks
- Request data from TWS by making a call to reqAccountSummary
client.reqAccountSummary( 9001, "All", "TotalCashValue, BuyingPower, AvailableFunds")
- We override the wrapper method and store the data we want. We are passing the arguments through to our queue in a dictionary and we only do this if the queue already exists (we don't want to accumulate data that we didn't ask for, in case TWS fires events on its own).
@iswrapper def accountSummary(self, reqId, account, tag, value, currency): ''' Callback to reqAccountSummary. Read Information about the Account ''' if hasattr(self, "accountSummary_queue"): self.accountSummary_queue.put( { "reqId": reqId, "account": account, "tag": tag, "value": value, "currency": currency, } )
- Retrieve the target data from the wrapper. This is where a queue shines vis-a-vis a list. We can repeatedly try to get something from an (initially) empty queue until it either: (a) gives us something or (b) times out . This code will wait till 5 seconds and if we get some data, it will execute. If till 5 seconds there is still no data sent by TWS to the wrapper, then it will raise the queue.empty exception
try: # Get data from queue (if it shows up) or eventually timeout acct_summary = client.accountSummary_queue.get(timeout=5) # print ('printing Q contents {}'.format(acct_summary)) print('Account {}: {} '.format(acct_summary['tag'], acct_summary['value'])) except queue.Empty: print("accountSummary_queue was empty until timeout period") acct_summary = None
- Print out the remaining messages in the queue until the queue is empty. For this we shall use the else statement following the Try-Except
else: # if try condition is successful with no execption, this 'else' follows # https://stackoverflow.com/questions/6517953/clear-all-items-from-the-queue while not client.accountSummary_queue.empty(): # If the queue is not empty try: acct_summary = client.accountSummary_queue.get(False) print('Account {}: {} '.format(acct_summary['tag'], acct_summary['value'])) except queue.Empty: continue client.accountSummary_queue.task_done() # task_done() is used to inform task completion
- Delete data storage on the wrapper object. Since the wrapper methods run automatically, we don't want our queue objects to accumulate data sent to us from TWS unprompted. We only want data when we ask for it.
del client.accountSummary_queue #Delete the queue
Handling messages from placeOrder callback's:
If you have perused the blogpost Thread Synchronization using Event Object and had a look at how we dealt with the handling of messages from the callback to reqAccountSummary , then we are done. Basically we are using a mix of both to deal with the messages.
- Firstly let us initialize the event object
from threading import Thread, Event self.execDetails_available = Event() # instead of of execDetailsEnd_available for market orders
- Let us also initialize two flags. If the execDetails callback is triggered, then self.order_executed is set to True. If orderStatus callback is triggered and for the orderId corresponding to the call placeOrder , the status is 'Submitted' or 'Filled' , then the self.order_submitted is set to True. This way we can keep track of both the callback's execDetails and orderStatus after we call a placeOrder.
self.order_executed = False # taken from from execDetails callback self.order_submitted = False # taken from from orderStatus callback
- If the execDetails callback is invoked, then we set the self.order_executed flag to True and the self.execDetails_available.set() to True
@iswrapper def execDetails(self, reqId, contract, execution): print("\nIn callback ExecDetails. ReqId: {}, Symbol: {}, SecType: {}, Currency: {}, Execution: {}".format(reqId, contract.localSymbol, contract.secType, contract.currency, execution)) self.order_executed = True self.execDetails_available.set() #internal flag is set to True
- The core logic is explained here. First we do a check to see if the callback execDetails is triggered in 5 seconds time. If that is successful, we check the orderStatus callback and set the flag client.order_submitted. The finer details have been explained earlier.
client.execDetails_available.clear() # internal flag is set to False client.placeOrder(client.orderId, contract, order) try: client.execDetails_available.wait(timeout=5) except: print (' *** An Exception Occured in client.execDetails_available.wait(timeout=5) *** ') else: # if try condition is successful with no execption, this 'else' follows if client.order_executed == True: print (" *** \nOrder is Successfully Executed ***\n") else: print (' *** \nOrder NOT Executed ***\n') # Handle the q from orderStatus callback # https://stackoverflow.com/questions/6517953/clear-all-items-from-the-queue while not client.orderStatus_queue.empty(): # If the queue is not empty try: order_status = client.orderStatus_queue.get(False) print('Status of Order: {}, Filled Positions: {}, Remaining Positions: {}, OrderId: {}, permId: {}'.format(order_status['status'], order_status['filled'], order_status['remaining'], order_status['orderId'], order_status['permId'])) if client.orderId == order_status['orderId']: # for the latest orderId if (order_status['status'] == 'Submitted') or (order_status['status'] == 'Filled'): # if for even 1 message, the status is filled or submitted client.order_submitted = True except queue.Empty: continue client.orderStatus_queue.task_done() # task_done() is used to inform task completion del client.orderStatus_queue # Delete the queue
At the end , we have two print statements to figure out if we have any feedback from the execDetails callback and orderStatus callback.
print('Feedback from orderStatus callback - Is order submitted?: {}'.format(client.order_submitted)) print('Feedback form execDetails callback - Is order executed? : {}'.format(client.order_executed))
No comments:
Post a Comment