0

I am trying my hand at making my first algo-bot and decided to go with IB and use their api. I have followed Jacob Aramral's youtube series about making a bot and pretty much followed along and added tweaks along the way to make the logic performed how I wanted. After trying to run the code, I kept getting errors about not being able to connect. So, I just copied his code and tried to run that (https://github.com/Jake0303/InteractiveBrokersPythonBot/blob/main/InteractiveBrokersPythonBot.py). It connects to TWS (and I assume IB servers) and I can enter a ticker symbol and get real time data, but it wouldn't make trades. I then tweaked Jacob's code to basically make trades at every candle (just because it was reading my paper account and I wanted to see any trades be made), but when the criteria is met (last close higher than the close of the candle before) no trade is made. I'm starting to get a little discouraged, so hopefully someone can help me out. I also tried copying the intro guide here to try and get TWS to make a trade, but still no luck. If anyone can see what I'm doing wrong and can help me get it fixed, I would be grateful.

I have my IBpro account funded with play money and have subscribed to 'US Equity and Options Add-On Streaming Bundle' and the 'US Securities Snapshot and Futures Value Bundle' subscriptions. For the paper account, I use 7497 as the socket port, checked the Active X and socket clients, and disabled read-only API.I believe that's all I needed to enable to allow the API to make trades for me (aside from having a functional code, lol). Here is the code that should work. I would be curious if it work for other and places trades. Also, I have included a snip of the monitor once the code is ran. Any help is appreciated!

#Imports
import ibapi
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order import *
import ta
import numpy as np
import pandas as pd
import pytz
import math
from datetime import datetime, timedelta
import threading
import time
#Vars
orderId = 1
#Class for Interactive Brokers Connection
class IBApi(EWrapper,EClient):
    def __init__(self):
        EClient.__init__(self, self)
    # Historical Backtest Data
    def historicalData(self, reqId, bar):
        try:
            bot.on_bar_update(reqId,bar,False)
        except Exception as e:
            print(e)
    # On Realtime Bar after historical data finishes
    def historicalDataUpdate(self, reqId, bar):
        try:
            bot.on_bar_update(reqId,bar,True)
        except Exception as e:
            print(e)
    # On Historical Data End
    def historicalDataEnd(self, reqId, start, end):
        print(reqId)
    # Get next order id we can use
    def nextValidId(self, nextorderId):
        global orderId
        orderId = nextorderId
    # Listen for realtime bars
    def realtimeBar(self, reqId, time, open_, high, low, close,volume, wap, count):
        super().realtimeBar(reqId, time, open_, high, low, close, volume, wap, count)
        try:
            bot.on_bar_update(reqId, time, open_, high, low, close, volume, wap, count)
        except Exception as e:
            print(e)
    def error(self, id, errorCode, errorMsg):
        print(errorCode)
        print(errorMsg)
#Bar Object
class Bar:
    open = 0
    low = 0
    high = 0
    close = 0
    volume = 0
    date = datetime.now()
    def __init__(self):
        self.open = 0
        self.low = 0
        self.high = 0
        self.close = 0
        self.volume = 0
        self.date = datetime.now()
#Bot Logic
class Bot:
    ib = None
    barsize = 1
    currentBar = Bar()
    bars = []
    reqId = 1
    global orderId
    smaPeriod = 50
    symbol = ""
    initialbartime = datetime.now().astimezone(pytz.timezone("America/New_York"))
    def __init__(self):
        #Connect to IB on init
        self.ib = IBApi()
        self.ib.connect("127.0.0.1", 7497,221)
        ib_thread = threading.Thread(target=self.run_loop, daemon=True)
        ib_thread.start()
        time.sleep(1)
        currentBar = Bar()
        #Get symbol info
        self.symbol = input("Enter the symbol you want to trade : ")
        #Get bar size
        self.barsize = int(input("Enter the barsize you want to trade in minutes : "))
        mintext = " min"
        if (int(self.barsize) > 1):
            mintext = " mins"
        queryTime = (datetime.now().astimezone(pytz.timezone("America/New_York"))-timedelta(days=1)).replace(hour=16,minute=0,second=0,microsecond=0).strftime("%Y%m%d %H:%M:%S")
        #Create our IB Contract Object
        contract = Contract()
        contract.symbol = self.symbol.upper()
        contract.secType = "STK"
        contract.exchange = "SMART"
        contract.currency = "USD"
        self.ib.reqIds(-1)
        # Request Market Data
        #self.ib.reqRealTimeBars(0, contract, 5, "TRADES", 1, [])
        self.ib.reqHistoricalData(self.reqId,contract,"","2 D",str(self.barsize)+mintext,"TRADES",1,1,True,[])
    #Listen to socket in seperate thread
    def run_loop(self):
        self.ib.run()
    #Bracet Order Setup
    def bracketOrder(self, parentOrderId, action, quantity, profitTarget, stopLoss):
        #Initial Entry
        #Create our IB Contract Object
        contract = Contract()
        contract.symbol = self.symbol.upper()
        contract.secType = "STK"
        contract.exchange = "SMART"
        contract.currency = "USD"
        # Create Parent Order / Initial Entry
        parent = Order()
        parent.orderId = parentOrderId
        parent.orderType = "MKT"
        parent.action = action
        parent.totalQuantity = quantity
        parent.transmit = False
        # Profit Target
        profitTargetOrder = Order()
        profitTargetOrder.orderId = parent.orderId+1
        profitTargetOrder.orderType = "LMT"
        profitTargetOrder.action = "SELL"
        profitTargetOrder.totalQuantity = quantity
        profitTargetOrder.lmtPrice = round(profitTarget,2)
        profitTargetOrder.parentId = parentOrderId
        profitTargetOrder.transmit = False
        # Stop Loss
        stopLossOrder = Order()
        stopLossOrder.orderId = parent.orderId+2
        stopLossOrder.orderType = "STP"
        stopLossOrder.action = "SELL"
        stopLossOrder.totalQuantity = quantity
        stopLossOrder.parentId = parentOrderId
        stopLossOrder.auxPrice = round(stopLoss,2)
        stopLossOrder.transmit = True

        bracketOrders = [parent, profitTargetOrder, stopLossOrder]
        return bracketOrders
    #Pass realtime bar data back to our bot object
    def on_bar_update(self, reqId, bar,realtime):
        global orderId
        #Historical Data to catch up
        if (realtime == False):
            self.bars.append(bar)
        else:
            bartime = datetime.strptime(bar.date,"%Y%m%d %H:%M:%S").astimezone(pytz.timezone("America/New_York"))
            minutes_diff = (bartime-self.initialbartime).total_seconds() / 60.0
            self.currentBar.date = bartime
            lastBar = self.bars[len(self.bars)-1]
            #On Bar Close
            if (minutes_diff > 0 and math.floor(minutes_diff) % self.barsize == 0):
                self.initialbartime = bartime
                #Entry - If we have a higher high, a higher low and we cross the 50 SMA Buy
                #1.) SMA
                closes = []
                for bar in self.bars:
                    closes.append(bar.close)
                self.close_array = pd.Series(np.asarray(closes))
                self.sma = ta.trend.sma(self.close_array,self.smaPeriod,True)
                print("SMA : " + str(self.sma[len(self.sma)-1]))
                #2.) Calculate Higher Highs and Lows
                lastLow = self.bars[len(self.bars)-1].low
                lastHigh = self.bars[len(self.bars)-1].high
                lastClose = self.bars[len(self.bars)-1].close

                # Check Criteria
                if (bar.close > lastHigh
                    and self.currentBar.low > lastLow
                    and bar.close > str(self.sma[len(self.sma)-1])
                    and lastClose < str(self.sma[len(self.sma)-2])):
                    #Bracket Order 2% Profit Target 1% Stop Loss
                    profitTarget = bar.close*1.02
                    stopLoss = bar.close*0.99
                    quantity = 1
                    bracket = self.bracketOrder(orderId,"BUY",quantity, profitTarget, stopLoss)
                    contract = Contract()
                    contract.symbol = self.symbol.upper()
                    contract.secType = "STK"
                    contract.exchange = "SMART"
                    contract.currency = "USD"
                    #Place Bracket Order
                    for o in bracket:
                        o.ocaGroup = "OCA_"+str(orderId)
                        self.ib.placeOrder(o.orderId,contract,o)
                    orderId += 3
                #Bar closed append
                self.currentBar.close = bar.close
                print("New bar!")
                self.bars.append(self.currentBar)
                self.currentBar = Bar()
                self.currentBar.open = bar.open
        #Build  realtime bar
        if (self.currentBar.open == 0):
            self.currentBar.open = bar.open
        if (self.currentBar.high == 0 or bar.high > self.currentBar.high):
            self.currentBar.high = bar.high
        if (self.currentBar.low == 0 or bar.low < self.currentBar.low):
            self.currentBar.low = bar.low

#Start Bot
bot = Bot()
LifeEdit
  • 3
  • 1

1 Answers1

0

Made a few adjustments, including a message when criteria is not met. Should get you started

#Imports
import ibapi
from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
from ibapi.order import *

import pandas as pd
import pytz
import math
from datetime import datetime, timedelta
import threading
import time
#Vars
orderId = 1

#Class for Interactive Brokers Connection
class IBApi(EWrapper,EClient):
    def __init__(self):
        EClient.__init__(self, self)
    # Historical Backtest Data
    def historicalData(self, reqId, bar):
        try:
            myBar = Bar()
            myBar.open = bar.open
            myBar.low = bar.low
            myBar.high = bar.high
            myBar.close = bar.close
            myBar.volume = bar.volume
            myBar.date = pytz.timezone("America/New_York").localize(datetime.strptime(bar.date[:17], "%Y%m%d %H:%M:%S"))
            bot.on_bar_update(reqId,myBar,False)
        except Exception as e:
            print("historicalData Ex: ",e)

    # On Historical Data End
    def historicalDataEnd(self, reqId, start, end):
        try:
            print("\nHistorical data recieved: ",start," to ",end)
            print("Waiting for next bar close....")
            contract = Contract()
            contract.symbol = bot.symbol.upper()
            contract.secType = "STK"
            contract.exchange = "SMART"
            contract.currency = "USD"
            bot.ib.reqRealTimeBars(2, contract, 5, "TRADES", False, []) 
        except Exception as e:
            print("historicalDataEnd Ex: ",e)
    # Get next order id we can use
    def nextValidId(self, nextorderId):
        global orderId
        orderId = nextorderId
    # Listen for realtime bars
    def realtimeBar(self, reqId, time, open_, high, low, close,volume, wap, count):
        super().realtimeBar(reqId, time, open_, high, low, close, volume, wap, count)
        
        bar = Bar()
        bar.open = open_
        bar.low = low
        bar.high = high
        bar.close = close
        bar.volume = volume
        bar.date = datetime.fromtimestamp(time, pytz.timezone("America/New_York"))
        bot.on_bar_update(reqId, bar, True)

    def error(self, id, errorCode, errorMsg, advOrdRej):  # Depending on API version advOrdRej may not be required
        print(errorCode,": ",errorMsg)


#Bar Object
class Bar:
    open = 0
    low = 0
    high = 0
    close = 0
    volume = 0
    date = datetime.now()
    def __init__(self):
        self.open = 0
        self.low = 0
        self.high = 0
        self.close = 0
        self.volume = 0
        self.date = datetime.now()
#Bot Logic
class Bot:
    ib = None
    barsize = 1
    currentBar = Bar()
    bars = []
    reqId = 1
    global orderId
    smaPeriod = 50
    symbol = ""
    nextBarTime = 0
    
    def __init__(self):
        #Connect to IB on init
        self.ib = IBApi()
        self.ib.connect("127.0.0.1", 7497,221)
        ib_thread = threading.Thread(target=self.run_loop, daemon=True)
        ib_thread.start()
        time.sleep(1)
        currentBar = Bar()
        #Get symbol info
        self.symbol = input("Enter the symbol you want to trade : ")
        #Get bar size
        self.barsize = int(input("Enter the barsize you want to trade in minutes : "))
        mintext = " min"
        if (int(self.barsize) > 1):
            mintext = " mins"
            
        secs = 60 * self.barsize * 200 # barcount to retrieve
        queryTime = (datetime.now().astimezone(pytz.timezone("America/New_York"))-timedelta(seconds=secs)).strftime("%Y%m%d %H:%M:%S")
        
        #Create our IB Contract Object
        contract = Contract()
        contract.symbol = self.symbol.upper()
        contract.secType = "STK"
        contract.exchange = "SMART"
        contract.currency = "USD"
        self.ib.reqIds(-1)
        # Request History
        self.ib.reqHistoricalData(1,contract,"",str(secs)+" S",str(self.barsize)+mintext,"TRADES",0,1,False,[])
        
    #Listen to socket in seperate thread
    def run_loop(self):
        self.ib.run()
    #Bracet Order Setup
    def bracketOrder(self, parentOrderId, action, quantity, profitTarget, stopLoss):
        #Initial Entry
        #Create our IB Contract Object
        contract = Contract()
        contract.symbol = self.symbol.upper()
        contract.secType = "STK"
        contract.exchange = "SMART"
        contract.currency = "USD"
        # Create Parent Order / Initial Entry
        parent = Order()
        parent.orderId = parentOrderId
        parent.orderType = "MKT"
        parent.action = action
        parent.totalQuantity = quantity
        parent.transmit = False
        # Profit Target
        profitTargetOrder = Order()
        profitTargetOrder.orderId = parent.orderId+1
        profitTargetOrder.orderType = "LMT"
        profitTargetOrder.action = "SELL"
        profitTargetOrder.totalQuantity = quantity
        profitTargetOrder.lmtPrice = round(profitTarget,2)
        profitTargetOrder.parentId = parentOrderId
        profitTargetOrder.transmit = False
        # Stop Loss
        stopLossOrder = Order()
        stopLossOrder.orderId = parent.orderId+2
        stopLossOrder.orderType = "STP"
        stopLossOrder.action = "SELL"
        stopLossOrder.totalQuantity = quantity
        stopLossOrder.parentId = parentOrderId
        stopLossOrder.auxPrice = round(stopLoss,2)
        stopLossOrder.transmit = True

        bracketOrders = [parent, profitTargetOrder, stopLossOrder]
        return bracketOrders
    

    #Pass realtime bar data back to our bot object
    def on_bar_update(self, reqId, bar, realtime):
        global orderId,nextBarTime,currentBar
        
        try:
            #Historical Data to catch up
            if (realtime == False):
                self.bars.append(bar)
                self.nextBarTime = bar.date+timedelta(seconds=60 * self.barsize)
            else:
                if (bar.date >= self.nextBarTime):
                    #On Bar Close
                    self.bars.append(self.currentBar)
                    print("Bar Closed ",self.currentBar.date.strftime('%H:%M'),"   o:",self.currentBar.open," h:",self.currentBar.high," l:",self.currentBar.low," c:",self.currentBar.close)
                    
                    # Calc SMA
                    closes = []
                    for bar in self.bars:
                        closes.append(bar.close)
                        
                    numbers_series = pd.Series(closes)
                    windows = numbers_series.rolling(self.smaPeriod)
                    moving_averages = windows.mean()
                    sma = moving_averages.tolist()

                    # Calculate Higher Highs and Lows
                    lastLow = self.bars[len(self.bars)-1].low
                    lastHigh = self.bars[len(self.bars)-1].high
                    lastClose = self.bars[len(self.bars)-1].close
                    
                    # Check Criteria
                    if (self.currentBar.close > lastHigh
                        and self.currentBar.low > lastLow
                        and self.currentBar.close > sma[len(sma)-1]
                        and lastClose < sma[len(sma)-2]):
                        
                        print("Placing Order")
                        
                        #Bracket Order 2% Profit Target 1% Stop Loss
                        profitTarget = bar.close*1.02
                        stopLoss = bar.close*0.99
                        quantity = 1
                        bracket = self.bracketOrder(orderId,"BUY",quantity, profitTarget, stopLoss)
                        contract = Contract()
                        contract.symbol = self.symbol.upper()
                        contract.secType = "STK"
                        contract.exchange = "SMART"
                        contract.currency = "USD"
                        #Place Bracket Order
                        for o in bracket:
                            o.ocaGroup = "OCA_"+str(orderId)
                            self.ib.placeOrder(o.orderId,contract,o)
                        orderId += 3
                        
                    else:
                        if(self.currentBar.close <= lastHigh):
                            print("-- Criteria not met: 'self.currentBar.close > lastHigh'    Close:",self.currentBar.close,"   lastHigh:",lastHigh)
                        if(self.currentBar.low <= lastLow):
                            print("-- Criteria not met: 'self.currentBar.low > lastLow'    Low:",self.currentBar.close,"   lastLow:",lastLow)
                        if(self.currentBar.close <= sma[len(sma)-1]):
                            print("-- Criteria not met: 'self.currentBar.close > sma[len(sma)-1]'     Close:",self.currentBar.close,"   sma:",sma[len(sma)-1])
                        if(lastClose >= sma[len(sma)-2]):
                            print("-- Criteria not met: 'lastClose < sma[len(sma)-2]'    lastClose:",lastClose,"   sma:",sma[len(sma)-2])
                    
                    
                    # Create new bar 
                    self.currentBar = Bar()    
                    self.currentBar.open = bar.open
                    self.currentBar.high = bar.high
                    self.currentBar.low = bar.low
                    self.currentBar.close = bar.close              
                    self.currentBar.date = self.nextBarTime #bar.date
                    
                    self.nextBarTime += timedelta(seconds=60 * self.barsize)  
           
                else:
                    #Build realtime bar
                    if (self.currentBar.open == 0):
                        self.currentBar.open = bar.open
                    if (self.currentBar.high == 0 or bar.high > self.currentBar.high):
                        self.currentBar.high = bar.high
                    if (self.currentBar.low == 0 or bar.low < self.currentBar.low):
                        self.currentBar.low = bar.low
                    self.currentBar.close = bar.close
                    
        except Exception as e:
            print("on_bar_update Ex: ",e)

#Start Bot
bot = Bot()
dno
  • 971
  • 6
  • 13
  • 1
    Appreciate it! I did have to comment out the AvdOrdRej parameter, and it is definitely closer to being a functioning algo bot. I was also able to add a bot.volume to get that information to the bot as well. I commented out some of the criteria required to enter a trade. This was done to see if the bot would execute a trade. I'm still having trouble getting any trades executed. I'm going to try this on IB gateway rather than TWS to see if that helps. Thanks, dno! – LifeEdit Jan 16 '23 at 03:19
  • Let us know what your issues are for executing trades. Working on this end with TWS here but shouldn't be any difference. – dno Jan 26 '23 at 01:05
  • Hey @dno, my current is that when the criteria for placing a trade is met, I get a message 'The 'ETradeOnly' attribute is not supported'. I'm not sure if it's a problem with the bracket orders, but if it is, I would be fine if the bracket order was removed, and the code executed a market order when criteria was met. Any idea how to get rid of this error? – LifeEdit Mar 17 '23 at 22:05
  • Also, when I run the code as is (with AdvOrdRej commented out) with a one minute timeframe, the program is updated every second for about 1.5 minutes, before finally transitioning to updated the output every minute. During the initial 1.5 minutes, no ticker info is shown (high, open, close, etc. all show zero). When the output transitions to updating every minute, then the ticker info is accurate. I can't figure out why this is. – LifeEdit Mar 17 '23 at 22:08
  • Re: ETradeOnly error, assuming you haven't set it in code (like parent.ETradeOnly=) then I would start by checking your API version in \TWS API\source\pythonclient\ibapi.egg-info. I would recommend checking your TWS version (more options before login), the first option is stable->10.19 or latest->10.20. If your API version above does not match I would update it. I can't test today... market is closed, will get back to you – dno Mar 19 '23 at 01:05
  • That was definitely an issue. I check and I was using TWS vesion 10.21, but I was only using the PyPI ibapi which was 9.81. I installed TWS-API version 10.20, but now the market is closed. I had to add advOrdRej back in (which makes sense for the newer versions). We shall see tomorrow – LifeEdit Mar 21 '23 at 03:32
  • dno, thank you! I finally got a functional algo bot! Appreciate it! I do have a new issue, tho, if you're willingly to entertain me. My strat relies on accurate volume data, which I do not appear to be receiving. A quick goog shows other people have issues with the volume data from IB as well. Comparing volume on IB with ToS and YF show large error. I know IB divides volume by 100, but there seems to be a bigger issue. My volumes arent off by a factor, but rather appear to be random (rel. to other sources.) Any idea for why volume would be inaccurate? Thanks again! – LifeEdit Mar 22 '23 at 00:05
  • Glad to hear you had success. IBKR is quite open in saying they are not a data provider, and encourage anyone who needs extra/different data to use a specialized provider. So I would not expect any help there. With 'random' volumes I would check OHLC values to ensure no tz issues. Consider the prevalence of dark pools such as IBKRATS and many others do not reports trades until it suits them.... so even the most 'accurate' volume can never be 'accurate' if dark pools trade that security. But that is my opinion, and as such is open for debate :) – dno Mar 22 '23 at 23:13
  • @LifeEdit please consider, only if I have been of assistance, accepting and upvoting the answer. It goes a long way to keeping me motivated in spending time here. I do not use Python, but have been learning it in the course of answering StackOverflow questions. Should you choose to do so, I thank you. In any case I wish you the best of luck in trading :) – dno Mar 24 '23 at 22:05
  • Of course! I don't have the rep yet to vote, but this is definitely solved. Thanks dno! – LifeEdit Mar 26 '23 at 14:00