Tuesday, March 4, 2014

F#: Capture market depth data from InteractiveBrokers

In the last blog post, we discussed how to capture real-time tick data from InteractiveBrokers. Now let's see how we can capture real-time market depth data from InteractiveBrokers. The example code in this post is very similar to the one in the previous post. As far as the code is concerned, the main difference is just that now we subscribe to and process market depth events instead of tick price events. However, to interpret market depth events is slightly more complicated than to interpret tick data events, since an order book is basically a table with multiple entries.

Before we talk about how to process market depth events to maintain an order book, let's first take a look at an example below, which is a snapshot of the order book for the stock "Infosys" listed in Mumbai. The snapshot was taken at some time on 24th Feb 2014. You can see that the depth of this order book is 5, i.e., it shows the best 5 entries for both bid side and ask side, so there are 10 entries in total. Moreover, all entries are sorted by price, so that the entries for bid are in descending order and those for ask are in ascending order. By observing the market depth data, it's not difficult to see that the first bid/ask entry of an order book should be always the same as the tick price data. As such, subscribing to tick price events is equivalent to subscribing to market depth events with depth 1.

bid ask
position price size price size
1
3748.70 14 3749.00 71
2
3748.50 290 3749.40 10
3
3748.45 20 3749.45 1
4
3748.15 25 3749.85 50
5
3748.10 125 3749.90 15

So, how do we subscribe to market depth events? It's almost as easy as to subscribe to tick price events. The following code snippet creates an contract object specifying the stock we are interested in. In this case, it's Infosys listed in Mumbai.

        let contract = tws1.createContract()
        contract.secType <- "STK"
        contract.symbol <- "INFY"
        contract.exchange <- "NSE"
        contract.currency <- "INR"

Then, we use the reqMktDepthEx method, which takes 3 parameters: ticker ID, contract, and market depth. The following line instructs the broker that we wish to listen for market depth events up to depth 5.

        tws1.reqMktDepthEx(1, contract, 5)

Please note that though one can specify an arbitrarily large number for market depth, the effective market depth will still be subject to the exchange and your data subscription with InteractiveBrokers. More often than not, market depth data are not free, but there are some exceptions, like NSE in Mumbai. Once we have invoked the reqMktDepthEx method, we immediately receive the initial batch of market depth events, which are meant to initialize a complete snapshot of the order book. After the initial batch, we will receive subsequent market depth events on an ad hoc basis. These subsequent events represent increment changes to the order book. Basically, a market depth event consists of the following properties:
  • id: ticker ID, which is specified by us via the reqMktDepthEx method;
  • side: 0 = ask, 1 = bid;
  • operation: 0 = insert, 1 = update, 2 = delete;
  • position: indicate which position in the order book to update;
  • price: bid/ask price;
  • size: bid/ask  size.

For example, the snapshot example above was constructed by the following initial batch of events:

event sequence
id
side
operation
position
price
size
1
1
1
0
0
3748.7
14
2
1
1
0
1
3748.5
290
3
1
1
0
2
3748.45
20
4
1
1
0
3
3748.15
25
5
1
1
0
4
3748.1
125
6
1
0
0
0
3749
71
7
1
0
0
1
3749.4
10
8
1
0
0
2
3749.45
1
9
1
0
0
3
3749.85
50
10
1
0
0
4
3749.9
15

From the 11th event onward, we receive increment changes. For example, the 11th event was as follows:

event sequence
id
side
operation
position
price
size
11
1
1
1
0
3748.7
17
   
Based on this event, we should update the first bid entry of the order book with price 3748.7 and size 17. So I hope this is clear enough on how to interpret market depth events.

As for to unsubscribe, we just need invoke the cancelMktDepth method with the ticker ID for which we want to cancel the subscription.

        tws1.cancelMktDepth(1)

Finally, below is a simple demo program, which captures market depth events for 10 minutes and simply prints them out. This program does not present an order book visually.

----------------------------------------------------------------------------------------------
open AxTWSLib
open TWSLib
open System
open System.Drawing
open System.Windows.Forms

type MktDepthEvent =
    AxTWSLib._DTwsEvents_updateMktDepthEvent

type MktDepthEventHandler =
    AxTWSLib._DTwsEvents_updateMktDepthEventHandler

let now() = DateTime.Now

let processMktDepthEvent _ (e:MktDepthEvent) =
    // event timestamp 
    printf "%A," (now())
    // event ID, which should match the subscription ID
    printf "id=%A," e.id
    // side, 0=ask, 1=buy
    printf "side=%A," e.side
    // operation, 0=insert, 1=update, 2=delete
    printf "op=%A," e.operation
    // position
    printf "pos=%A," e.position
    // price
    printf "price=%A," e.price
    // size
    printfn "size=%A" e.size

[<EntryPoint; STAThread>]
let main _ = 
    printfn "start time: %A" (now())
    let form1 = new Form(Text="Dummy Form")
    let tws1 = new AxTws()
    tws1.BeginInit()
    form1.Controls.Add(tws1)
    tws1.EndInit()

    tws1.connect("", 7496, 1)
    printfn "server version = %d" tws1.serverVersion

    if tws1.serverVersion > 0 then
        tws1.updateMktDepth.AddHandler(
            new MktDepthEventHandler(processMktDepthEvent))

        let contract = tws1.createContract()
        contract.secType <- "STK"
        contract.symbol <- "INFY"
        contract.exchange <- "NSE"
        contract.currency <- "INR"
        tws1.reqMktDepthEx(1, contract, 5)

        let timerWorkflow = async { 
            do! Async.Sleep 10000
            Application.Exit() 
            }
        Async.Start timerWorkflow

        Application.Run()
        tws1.cancelMktDepth(1)
        tws1.disconnect()
        printfn "Diconnected!"

    printfn "end time: %A" (now())
    0





   

No comments:

Post a Comment