In case the code below is too mangled, I uploaded the txt file version of the following note here.

One of the main challenges for beginners with Interactive Brokers' API is that it's asynchronous- you say reqHistData and then nothing happens, and then some fractions of a second later a totally different method gets fired with the data you requested as an argument. For many systems, this slight speed optimization is unnecessary. It only helps when you're requesting many different things all at once. For the speed increase, the API is significantly less beginner-friendly.

I'll explain how to make the API synchronized so that when you call an API function, it returns what you requested directly, just like a normal function. After googling it, in the technical terminology, it looks like I'm converting the API from "non-blocking asynchronous" to "blocking synchronous". You'll need IbPy if you don't already have it.

This is inappropriate for systems that rapidly react to streams of intraday data, but is useful for requesting historical data, portfolio information, executions, the time, and the next valid id.

Let's review how the API works:
The two key parts are:
1) EClientSocket - sends requests
2) EWrapper - receives requested values

After requesting something through the EClientSocket, you need to wait for a bit until all the requested values have hit the EWrapper.
In general to syncronize things we'll follow this pattern:
def syncronized_request(x):
myEClientSocket.request_x
wait
return myEWrapper.x

'request_x' is a usual EClientSocket method which you can look up in the API doc.
'wait' is defined like this-
def wait():
inc_secs = .2 # a fifth of a second
while not myEWrapper.isDone:
sleep(inc_secs)
myEWrapper.isDone = False
'myEWrapper.x' pulls the requested data out of the local variable 'x' where it has been stored in your overwridden EWrapper class.

First import some libraries:
Boilerplate Code (1)
Then create your event listener subclass:
Boilerplate Code (2)

Then instantiate the requester and receiver:
myEWrapper = syncEWrapper()
myEClientSocket = EClientSocket(myEWrapper)

Now we can write the two simplest syncronized API functions:
def getTime():
myEClientSocket.reqCurrentTime()
wait()
return myEWrapper.time

def getData(contract):
myEClientSocket.reqRealTimeBars(tickerId=1,
contract=contract,
barSize=5,
whatToShow='MIDPOINT',
useRTH=1)
wait()
myEClientSocket.cancelRealTimeBars(tickerId=1)
return myEWrapper.bar

Notice that we already added the variables 'time' and 'bar' to myEWrapper

Let's add two more functions to hide the implementation:
def connect(clientId):
myEClientSocket.eConnect('localhost', 7496, clientId)

def disconnect():
myEClientSocket.eDisconnect()

Run Boilerplate Code (3) to define my simplifying etf contract factory

Now we can use the API easily, just call:
spy = contract_etf('SPY')
connect(1)
getTime()
getData(spy)
sleep(1)
getData(spy)
disconnect()
exit()

Now that you see it works, you can look back and see how it worked
Feel free to reuse or modify my code

------------------------
Boilerplate Code (1)
from ib.ext.EWrapper import EWrapper
from ib.ext.EClientSocket import EClientSocket
import datetime as dt
from time import sleep

Boilerplate Code (3)
import ib.ext
def contract_etf(sym):
c = ib.ext.Contract.Contract()
c.m_symbol = sym.upper()
c.m_secType = 'STK'
c.m_exchange = 'SMART'
c.m_currency = 'USD'
return c
Boilerplate Code (2)
Subclassing EWrapper is the most intimidating part at first. Here's my subclass of it, which you can just copy:
class syncEWrapper(EWrapper):
## variables to store requested data
isDone = False
time = None
bar = None
## overwridden listener functions
def author():
return 'Max F. Dama'
def tickPrice(self, tickerId, field, price, canAutoExecute):
raise NotImplementedError()
def tickSize(self, tickerId, field, size):
raise NotImplementedError()
def tickOptionComputation(self, tickerId,
field,
impliedVol,
delta,
modelPrice,
pvDividend):
raise NotImplementedError()
def tickGeneric(self, tickerId, tickType, value):
raise NotImplementedError()
def tickString(self, tickerId, tickType, value):
raise NotImplementedError()
def tickEFP(self, tickerId,
tickType,
basisPoints,
formattedBasisPoints,
impliedFuture,
holdDays,
futureExpiry,
dividendImpact,
dividendsToExpiry):
raise NotImplementedError()
def orderStatus(self, orderId,
status,
filled,
remaining,
avgFillPrice,
permId,
parentId,
lastFillPrice,
clientId,
whyHeld):
raise NotImplementedError()
def openOrder(self, orderId, contract, order, orderState):
raise NotImplementedError()
def updateAccountValue(self, key, value, currency, accountName):
raise NotImplementedError()
def updatePortfolio(self, contract,
position,
marketPrice,
marketValue,
averageCost,
unrealizedPNL,
realizedPNL,
accountName):
raise NotImplementedError()
def updateAccountTime(self, timeStamp):
raise NotImplementedError()
def nextValidId(self, orderId):
pass
def contractDetails(self, reqId, contractDetails):
raise NotImplementedError()
def bondContractDetails(self, reqId, contractDetails):
raise NotImplementedError()
def contractDetailsEnd(self, reqId):
raise NotImplementedError()
def execDetails(self, orderId, contract, execution):
raise NotImplementedError()
def updateMktDepth(self, tickerId,
position,
operation,
side,
price,
size):
raise NotImplementedError()
def updateMktDepthL2(self, tickerId,
position,
marketMaker,
operation,
side,
price,
size):
raise NotImplementedError()
def updateNewsBulletin(self, msgId, msgType, message, origExchange):
raise NotImplementedError()
def managedAccounts(self, accountsList):
raise NotImplementedError()
def receiveFA(self, faDataType, xml):
raise NotImplementedError()
def historicalData(self, reqId,
date,
open,
high,
low,
close,
volume,
count,
WAP,
hasGaps):
raise NotImplementedError()
def scannerParameters(self, xml):
raise NotImplementedError()
def scannerData(self, reqId,
rank,
contractDetails,
distance,
benchmark,
projection,
legsStr):
raise NotImplementedError()
def scannerDataEnd(self, reqId):
raise NotImplementedError()
def realtimeBar(self, reqId,
time,
open,
high,
low,
close,
volume,
wap,
count):
self.bar = (time, open, high, low, close, volume)
self.isDone = True
def currentTime(self, time):
self.time = dt.datetime.fromtimestamp(time)
self.isDone = True
def fundamentalData(self, reqId, data):
raise NotImplementedError()
def error(self,*err):
print err



Here is a video of me executing the code so you can see how if all the jumping around was confusing.

5 comments:

Thomas Ott said...

Max,

Thanks for this vid, this helps. I'm trying to learn PY and apply it to trading, as you are doing.

All the best,
Tom

chintan shah said...
This post has been removed by the author.
shabbychef said...

have you found any work-arounds for TWS' auto logoff 'feature'? fiddling with .xml files in the background does not appear to work...

Max Dama said...

Chef,

I'm happy restarting it every day, so I haven't been pursuing it. Have you tried twsstart?

Regards,
Max

Anonymous said...

Excellent post. Thank you.