Friday, 25 April 2014

Streaming prices from IB API swigibpy

There is now a native python API. I've written an updated version of this post, here, which uses the native API.


In my last post (http://qoppac.blogspot.co.uk/2014/04/getting-prices-out-of-ib-api-with.html) we looked at getting snapshot historical prices. In this one we will look at streamed prices - 'market tick data'. Rather than wait until the historical price feed ends with streaming prices we need to tell the streamer when to stop.


No stream rises higher than its source


Get the source code from this git repository. The files you will want to examine are test3_IB.py and wrapper_v3.py.


No stream drives anything without being confined

The example code is similar to historical data; we make one of these weird client objects containing a server 'callback' connection, make one of these slightly less weird contract objects (here it is for December 2016 Eurodollar futures) and then shove one into a request function for the other.

from test3_IB.py, __main function:

    callback = IBWrapper()
    client=IBclient(callback)
   
    ibcontract = IBcontract()
    ibcontract.secType = "FUT"
    ibcontract.expiry="201612"
    ibcontract.symbol="GE"
    ibcontract.exchange="GLOBEX"

    ans=client.get_IB_market_data(ibcontract)
    print "Bid size, Ask size; Bid price; Ask price"
    print ans


This should produce something like the following (assuming the market is open and you have a IB gateway or TWS server open.:

Bid size, Ask size; Bid price; Ask price
[10836, 12362, 98.665, 98.67]

Of course in the dull familar story inside the client object functions that actually get the data we've actually got hidden event driven code again.


from wrapper_v3.py, IBclient.get_IB_market_data() method:

    def get_IB_market_data(self, ibcontract, seconds=30,  tickerid=MEANINGLESS_ID):         
        """
        Returns granular market data
       
        Returns a tuple (bid price, bid size, ask price, ask size)
       
        """
       
       
        ## initialise the tuple
        self.cb.init_tickdata(tickerid)
        self.cb.init_error()
           
        # Request a market data stream
        self.tws.reqMktData(
                tickerid,
                ibcontract,
                "",
                False)      
       
 

        <SNIP   ... more code to come>

Ah yes its the usual stuff of setting up space within the self.cb callback instance to get the data and then call the tws server request function (strictly speaking its one of those 'EClientSocket' whatdoyoucallits again).

Only dead fish swim with the stream...

 

We now look inside the server callback object which gets populated as an instance self.cb. As before there are a few EWrapper functions which get triggered whenever the market data arrives.

There are in fact several methods for 'tickString', 'tickGeneric', 'tickSize' and 'tickPrice'; it seems a bit stochastic (quant speak: english translation completely bloody random and arbitrary) which of these methods gets called when a tick arrives (a tick could be an update to a price or to a quoted size on the top level of the order book). Lets look at the most generic of these:
    def tickGeneric(self, TickerId, tickType, value):
        marketdata=self.data_tickdata[TickerId]

        ## update generic ticks

        if int(tickType)==0:
            ## bid size
            marketdata[0]=int(value)
        elif int(tickType)==3:
            ## ask size
            marketdata[1]=int(value)

        elif int(tickType)==1:
            ## bid
            marketdata[2]=float(value)
        elif int(tickType)==2:
            ## ask
            marketdata[3]=float(value)
       
       

All the code does is identify which type of tick it is and then populate the appropriate part of marketdata. Obviously we will end up overwriting the values already in marketdata tuple but you could store them for some kind of averaging if you want.


Once in the stream of history you can't get out 

 

If we just let that baby run we'd be receiving streams of prices until the cows came home. So what we do back in the client world is say STOP I've had enough after a preset amount of time (we could also STOP when the N'th tick has arrived, or when there all the slots in marketdata are filled, which would be easy enough to code up).

from wrapper_v3.py, IBclient.get_IB_market_data() method:

        <Continuing from where we were before. Repeating the last call for clarity>

        self.tws.reqMktData(
                tickerid,
                ibcontract,
                "",
                False)      
       
        start_time=time.time()

        finished=False
        iserror=False

       
        while not finished and not iserror:
            iserror=self.cb.flag_iserror
            if (time.time() - start_time) > seconds:
                finished=True
            pass
        self.tws.cancelMktData(tickerid)
       
        marketdata=self.cb.data_tickdata[tickerid]
        ## marketdata should now contain some interesting information
        ## Note in this implementation we overwrite the contents with each tick; we could keep them
       
       
        if iserror:
            print "Error: "+self.cb.error_msg
            print "Failed to get any prices with marketdata"
       
        return marketdata
   
   

So we return the last values we have populated marketdata with before we stopped (although again I could take the medians).

By the way it can be a bit dangerous to average prices too much; for example if you sample prices throughout the day and then take an average as your input into your trading algorithm you will underestimate the actual amount of volatility in the market. Similarly if you are trading high frequency stuff you will be using the active state of the order book and averaging average real time bars is probably not going to be a very wise thing to do. Over this short time period relative to my typical trading speed however its probably okay as mostly all we are going to be removing is a little illusory volatility caused by 'bid-ask' bounce.

 
Also even with this averaging its still worth running your prices through a 'jump detector' to make sure you don't trade off dirty prices showing spuriously large moves; I see these about once a month for each instrument I trade!

Much much more on this subject in this post


Islands in the stream...

That is it for prices. I use the historical data function whenever I start trading a particular contract but also every day as it gets close prices. This makes my system self recovering since even if it drops for a few days I will end up with daily prices at least being infilled. Also often non actively traded contracts still have close prices, useful if you are using intra contract spreads as a data input. Just be careful how you treat intraday and closing prices if you append them together.

Much much more on this subject in this post

I use market data to get intraday prices, and when I am just about to trade to check the market is liquid enough for what I want to do (or even just to check it is open since I don't bother keeping a holidays calendar for all my markets - I wouldn't want to spend more than 10 minutes of my time a day running this system now would I?). Plus it allows me to dis-aggregate my trading costs into what is coming from the inside spread, the cost of processing / execution delays and having to drop deeper into the order book.

Next on the menu will be placing an order! I will leave the trivial task of building a system which decides what the orders will be to the reader (hint: you might want to use the price in some way).

This is the third in a series of posts. The first two posts are:

http://qoppac.blogspot.co.uk/2014/04/getting-prices-out-of-ib-api-with.html

http://qoppac.blogspot.co.uk/2014/03/using-swigibpy-so-that-python-will-play.html

The next post is:
http://qoppac.blogspot.co.uk/2014/05/the-placing-of-orders-to-interactive.html 

No comments:

Post a Comment

Comments are moderated. So there will be a delay before they are published. Don't bother with spam, it wastes your time and mine.