Search code examples
pythonib-api

IBAPI get historical bars continuously


I'm new in programming, sorry if somwhere make simplemistakes or do not get something.

I try to use IBApi for build script. Main point - request historical data continuously(one time in 15 sec for last week timerange, as example), convert it to dataframe and make further calculations.

But i can't do that as guide shows, it does not work that way.

I guess there is a way to do it simply, as with ib_insync.

To get code example, how i can request bars data from IB using IBApi and convert to dataframe

import time

from ibapi.client import *
from ibapi.wrapper import *
from ibapi.contract import Contract
import pandas as pd
from datetime import timedelta
import mplfinance as mpf


    class TestApp(EClient, EWrapper):
        # put number of candles for plot(check timerange in "self.reqHistoricalData" - must be bigger than this value
        cnadles_plot = 10000
    
        # Simply shift hours forward+ or back-
        hours_change = -5
    
    
        def __init__(self):
            EClient.__init__(self, self)
    
        def nextValidId(self, orderId: int):
            mycontract = Contract()
            mycontract.symbol = "TSLA"
            mycontract.secType = "STK"
            mycontract.exchange = "SMART"
            mycontract.currency = "USD"
            self.reqMarketDataType(4)
            self.reqMktData(orderId, mycontract, "", 0, 0, [])
            self.histbars = []
    
            self.reqHistoricalData(orderId, mycontract, "20221010-15:00:00", "5 D", "1 min", "TRADES", 0, 1, 0, [])
    
    
        def historicalData(self, reqId: int, bar: BarData):
            bardict = {"HistoricalData": reqId, "Date": bar.date, "Open": bar.open, "High": bar.high, "Low": bar.low,
                       "Close": bar.close, "Volume": bar.volume, "Count": bar.barCount}
            self.histbars.append(bardict)
    
    
    
        def historicalDataEnd(self, reqId: int, start: str, end: str):
            print(f"End of request")
            print(f"Start: {start}, End {end}")
    
            df = pd.DataFrame.from_records(self.histbars)
            df["Date"] = df["Date"].str.split().str[:2].str.join(' ')
            df["Date"] = pd.to_datetime(df["Date"])
    
            df["Date"] = df["Date"] + timedelta(hours=self.hours_change)
            df.set_index("Date", inplace=True)
            df["Volume"] = pd.to_numeric(df["Volume"])
    
            def vwap(df):
                high = df.High.values
                low = df.Low.values
                vol = df.Volume.values
                return df.assign(vwap=((high + low) / 2 * vol).cumsum() / vol.cumsum())
            df = df.groupby(df.index.date, group_keys=False).apply(vwap)
    
    
            print(df.tail(self.cnadles_plot))
            print(df.dtypes)
            apdict = mpf.make_addplot(df['vwap'])
            mpf.plot(df, type="candle", volume=True,tight_layout=True,show_nontrading=True, addplot=apdict)
    
    
    
    
    
    
    
    
    app = TestApp()
    app.connect("127.0.0.1", 7496, 1000)
    while True:
        app.run()
        time.sleep(20)

Solution

  • Firstly check out https://interactivebrokers.github.io/tws-api/historical_limitations.html and https://interactivebrokers.github.io/tws-api/historical_bars.html#hd_request for the limitations of data requests and available bar sizes. Depending on exactly what timeframes and quantity of data you require you may need to introduce delays into the code for rate limitations.

    I never use python, the code works but is most likely low quality, it should get you started with IBApi.

    Assuming you want 15sec bars...

    import time
    
    from ibapi.client import *
    from ibapi.wrapper import *
    from ibapi.contract import Contract
    from datetime import datetime, timedelta
    
    
    next_req_id = 1
    bars_recieved = 0
    req_date = datetime.now() + timedelta(days=-9)
    req_end_date = datetime.now() + timedelta(days=-8)
    
    mycontract = Contract()
    mycontract.symbol = "TSLA"
    mycontract.secType = "STK"
    mycontract.exchange = "SMART"
    mycontract.currency = "USD"
    
    
    class TestApp(EClient, EWrapper):
    
        def __init__(self):
            EClient.__init__(self, self)
    
        def nextValidId(self, orderId: int):
            # We get here after connected and ready, but do not need orderId because this is not an order.
            global next_req_id
    
            # 14400 S is 4hours, the maximum duration allowed for the 15 sec bar size.
            self.reqHistoricalData(next_req_id, mycontract, format(req_date, "%Y%m%d-%H:%M:%S"), "14400 S", "15 secs", "TRADES", 0, 1, 0, [])
            
            #each request has its own ID
            next_req_id += 1
    
        def historicalData(self, reqId: int, bar: BarData):
            global bars_recieved
            bars_recieved += 1
            #print(f"Recieved: {bar.date} {bar.open} {bar.high} {bar.low} {bar.close}")
    
    
        def historicalDataEnd(self, reqId: int, start: str, end: str):
            global next_req_id
            global req_date
            global bars_recieved
            
            print(f"Recieved: {bars_recieved} bars from {start}, End {end}")
            bars_recieved = 0
            next_req_id += 1
            # 4 hours - should match requested duration, in this case 14400 seconds
            req_date += timedelta(hours=4)
            
            # exit when all bars are recieved
            if req_date > req_end_date:
                app.disconnect()
                time.sleep(2)
                quit()
    
            # A timed event would be better, but longer durations will need to delay for either soft or hard rate limit
            time.sleep(2)                
            self.reqHistoricalData(next_req_id, mycontract, format(req_date, "%Y%m%d-%H:%M:%S"), "14400 S", "15 secs", "TRADES", 0, 1, 0, [])
    
    
    
    app = TestApp()
    app.connect("127.0.0.1", 7496, 1000)
    app.run()
    

    Pay particular attention to this (from 1st link)

    Pacing Violations for Small Bars (30 secs or less)... occurs whenever one or more of the following restrictions is not observed:

    Making identical historical data requests within 15 seconds.
    Making six or more historical data requests for the same Contract, Exchange and Tick Type within two seconds.
    Making more than 60 requests within any ten minute period.
    

    At this time Historical Data Limitations for barSize = "1 mins" and greater have been lifted. However, please use caution when requesting large amounts of historical data or sending historical data requests too frequently. Though IB has lifted the "hard" limit, we still implement a "soft" slow to load-balance client requests vs. server response. Requesting too much historical data can lead to throttling and eventual disconnect of the API client. If a request requires more than several minutes to return data, it would be best to cancel the request using the IBApi.EClient.cancelHistoricalData function.