11

EDITED: I found a solution regarding the error messages - it was a bug on IB's API. The code I show as an answer below should be useful for those looking for a clean solution to read positions, and also NAVs, from their accounts at IB.

The original question [SEE SOLUTION BELOW; LEAVING ORIGINAL QUESTION HERE FOR CONTEXT]: I'm trying to get all my accounts positions at Interactive Brokers [IB] using the firm's API. Their documentation, although extensive, is extremely confusing. Sample codes are full of unnecessary commands - I want something very streamlined.

I need help with:

  1. How to get the information "per account" [SOLVED]
  2. How to bring the variables to a DataFrame [SOLVED]
  3. How to avoid IB's API from printing a series of error messages [SOLVED]

The code so far:

from ibapi.client import EClient 
from ibapi.wrapper import EWrapper
from ibapi.common import * #for TickerId type
import pandas as pd

class ib_class(EWrapper, EClient): 
    def __init__(self): 
        EClient.__init__(self, self)
        self.all_positions = pd.DataFrame([], columns = ['Account','Symbol', 'Quantity', 'Average Cost'])

    def position(self, account, contract, pos, avgCost):
        index = str(account)+str(contract.symbol)
        self.all_positions.loc[index]=account,contract.symbol,pos,avgCost

    def error(self, reqId:TickerId, errorCode:int, errorString:str):
        if reqId > -1:
            print("Error. Id: " , reqId, " Code: " , errorCode , " Msg: " , errorString)

    def positionEnd(self):
        self.disconnect()

ib_api = ib_class() 
ib_api.connect("127.0.0.1", 7496, 0) 
ib_api.reqPositions()
current_positions = ib_api.all_positions
ib_api.run()

When I run the code above I get a series of (i) account numbers, (ii) the contract symbol, (iii) position and (iv) average cost. This, therefore, answers question #1. You might want to "print" these values to see how IB's API send you the information.

I was also able to define a DataFrame variable all_positions that receives the information I was looking for. See the result below. Note that I had to create an "index" variable that is a unique identifier for each row of the DataFrame (as a combination of the account number and symbol). I didn't find a way to 'append' information to the DataFrame without this 'index' (any idea on how to do it would be welcome):

enter image description here

As for the last issue (the error messages):

Brian's suggestion of the "error" function (see below) got rid of the "-1" errors. But I still get the following:

unhandled exception in EReader thread Traceback (most recent call last): File "C:\Users\danil\Anaconda3\lib\site-packages\ibapi-9.76.1-py3.7.egg\ibapi\reader.py", line 34, in run data = self.conn.recvMsg() File "C:\Users\danil\Anaconda3\lib\site-packages\ibapi-9.76.1-py3.7.egg\ibapi\connection.py", line 99, in recvMsg buf = self._recvAllMsg() File "C:\Users\danil\Anaconda3\lib\site-packages\ibapi-9.76.1-py3.7.egg\ibapi\connection.py", line 119, in _recvAllMsg buf = self.socket.recv(4096) OSError: [WinError 10038] An operation was attempted on something that is not a socket

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Rational-IM
  • 2,353
  • 1
  • 9
  • 25
  • This is one of the better questions for ib-api. I don't know why it has a downvote and close flag. It's a legit bug plus confusing language in the error messages. Maybe leave a comment to clarify. – brian Aug 23 '19 at 20:15
  • Thanks for this specific comment Brian. I don't understand either why someone had a downvote on the question! I will be working a little more on this code over the weekend and, hopefully, will be able to share here the smallest code possible to get a list of positions at IB. – Rational-IM Aug 23 '19 at 21:48
  • Brian: try running the code with an active TWS instance in the background. That's how IB knows who you are, and what account you're interfacing with. – Gerry Jan 16 '21 at 18:07

4 Answers4

18

After a lot of experimentation, here is the final code I wrote, which I placed on a ".py" file named AB_API (so I can "import" it as a library):

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.common import TickerId
from threading import Thread

import pandas as pd
import time

class ib_class(EWrapper, EClient):

    def __init__(self):
        EClient.__init__(self, self)

        self.all_positions = pd.DataFrame([], columns = ['Account','Symbol', 'Quantity', 'Average Cost', 'Sec Type'])
        self.all_accounts = pd.DataFrame([], columns = ['reqId','Account', 'Tag', 'Value' , 'Currency'])

    def error(self, reqId:TickerId, errorCode:int, errorString:str, advancedOrderRejectJson = ""):
        if reqId > -1:
            print("Error. Id: " , reqId, " Code: " , errorCode , " Msg: " , errorString)

    def position(self, account, contract, pos, avgCost):
        index = str(account)+str(contract.symbol)
        self.all_positions.loc[index]= account, contract.symbol, pos, avgCost, contract.secType
        
    def accountSummary(self, reqId, account, tag, value, currency):
        index = str(account)
        self.all_accounts.loc[index]=reqId, account, tag, value, currency

def connect_to_server(app):

    def run_loop():
        app.run()
    app.connect('127.0.0.1', 7496, 0)
    #Start the socket in a thread
    api_thread = Thread(target=run_loop, daemon=True)
    api_thread.start()
    time.sleep(0.5) #Sleep interval to allow time for connection to server


def read_positions(): #read all accounts positions and return DataFrame with information

    app = ib_class()    
    connect_to_server(app)

    app.reqPositions() # associated callback: position
    print("Waiting for IB's API response for accounts positions requests...\n")
    time.sleep(3)
    current_positions = app.all_positions # associated callback: position
    current_positions.set_index('Account',inplace=True,drop=True) #set all_positions DataFrame index to "Account"
    
    app.disconnect()

    return(current_positions)


def read_navs(): #read all accounts NAVs
    
    app = ib_class()  
    connect_to_server(app)

    app.reqAccountSummary(0,"All","NetLiquidation")  # associated callback: accountSummary / Can use "All" up to 50 accounts; after that might need to use specific group name(s) created on TWS workstation
    print("Waiting for IB's API response for NAVs requests...\n")
    time.sleep(3)
    current_nav = app.all_accounts
    
    app.disconnect()

    return(current_nav)

I can now read all accounts positions and NAVs with two single-line functions:

import IB_API

print("Testing IB's API as an imported library:")

all_positions = IB_API.read_positions()
all_navs = IB_API.read_navs()

If you want to run a very quick test (without importing/calling a function), just below the read_positions() and read_navs(), do the following:

print("Testing IB's API as an imported library:")

all_positions = read_positions()
print(all_positions, '\n')
all_navs = read_navs()
print(all_navs)

I realized some users were getting confused with the concept of importing functions, hence the quick suggestion above.

I wish Interactive Brokers had some simple examples available for clients that would make it easier for someone to test a few ideas and decide if they want to use their API or not.

Rational-IM
  • 2,353
  • 1
  • 9
  • 25
  • 1
    down-voted because when I run your implementation all that happens is that the API gets called, but the functions only return the column headers that they were assigned in the __init__: I can't see that you're doing anything to override the initial values of `all_accounts` or `all_positions` – aaron Jan 04 '21 at 23:39
  • Me, too. I'm guessing that the code has to be called (somehow?) from inside a gateway session? There's no immediately obvious way to do the oauth authentication in the code that I see. In any event, I think this is a great head start for me. Could you supplement this with some more info on who calls this code, and from where? I'm totally new to IB. THANKS! – Gerry Jan 16 '21 at 16:52
  • I use the code above daily. Make sure your initial set-up is correct: http://interactivebrokers.github.io/tws-api/initial_setup.html – Rational-IM Jan 17 '21 at 17:55
  • 1
    Very helpful, thanks! I was looking exactly for this. If you're interested in the cash balance, just replace "NetLiquidation" with "TotalCashValue" in the read_navs() function. I too find it quite difficult to work with the IB API. Happy trading! – RazzleDazzle Feb 11 '21 at 15:35
  • I used this code weekly, but it stopped working in Feb 2023. I am still able to connect retrieve market and company data using ibapi. – Sebapi Apr 09 '23 at 05:00
  • I fixed the code to account for a new item on the "error" function from Interactive Brokers. Note the - advancedOrderRejectJson = "" - item at the end. If this solution worked for you, don't forget to vote que question and answer up, so it can help other people too. – Rational-IM Apr 30 '23 at 15:42
2

The error I was getting was generated by a bug on IB's API (you can see another question I asked about the specific error). I'm adding this answer to provide more clear steps on how to downgrade to the older version:

1) Download the older version from here: http://interactivebrokers.github.io/downloads/TWS%20API%20Install%20974.01.msi

IB doesn't show the links for older versions but they keep the file on the Github (note the version name at the end of the link address)

2) Uninstall the current version. From IB's own website, do the following:

  • Uninstall the API from the Add/Remove Tool in the Windows Control Panel as usual
  • Delete the C:\TWS API\ folder if any files are still remaining to prevent a version mismatch.
  • Locate the file C:\Windows\SysWOW64\TwsSocketClient.dll. Delete this file. [didn't find this file]
  • Restart the computer before installing a different API version.

3) Install the API using the .msi file

4) Run the Anaconda prompt and navigate to the dir C:\TWS API\source\pythonclient

5) Run: python setup.py install

6) After python setup.py install, restart Spyder (if it was opened; I closed mine before step #5 above)

After I installed this older version, the error disappeared! Now the output on my console is clean again!

Aqueous Carlos
  • 445
  • 7
  • 20
Rational-IM
  • 2,353
  • 1
  • 9
  • 25
1

Your code is fine. If you want to ignore the errors just override the error callback.

from ibapi.common import * #for TickerId type

def error(self, reqId:TickerId, errorCode:int, errorString:str):
    if reqId > -1:
        print("Error. Id: " , reqId, " Code: " , errorCode , " Msg: " , errorString)

The errors > -1 are for actions that need an ID.

I would still keep track of the connection state if you're getting data or placing orders.

You initialize the ddf twice. You also call super which just logs the data I think.

The socket errors on disconnect are a bug or just the way they do it at IB. It's supposed to have been fixed but it may be a while for it to be in a release. Maybe try updating, I see the pull request to fix it on Apr. 26th.

brian
  • 10,619
  • 4
  • 21
  • 79
  • Thanks Brian. All the messages that started with "ERROR -1" disappeared. A couple of questions: (i) how do I get the securities information "per account", as I have multiple accounts; (ii) is it possible to get rid of all the other messages? I.e. can I run IB's API in some sort of "mute mode"? Also, where am I initializing de ddf twice? I'm still puzzled by the way IB is using Classes... – Rational-IM Aug 23 '19 at 14:08
  • i You can use reqPositionsMulti http://interactivebrokers.github.io/tws-api/positions.html for accounts I think but I've never used it. ii The errors are from Python from the OS because of the way IB disconnects, not much you can do until it's updated (it may be already). You have the df init in ____init____ and in nextValidId. – brian Aug 23 '19 at 14:48
  • https://groups.io/g/twsapi/message/42580 has the supposed fix to the disconnect error. Read the whole thread if interested. – brian Aug 23 '19 at 14:55
  • I just implement the solution above. It didn't work. I still get the "OSError: [WinError 10038] An operation was attempted on something that is not a socket" error (plus a lot of extra error lines with it, messing up my code output). I asked another question focusing only on this issue - maybe there is a way to suppress it (i.e., maybe I could prevent the console from printing anything while I'm dealing with IB's API). – Rational-IM Sep 05 '19 at 12:19
  • You didn't implement a solution for the socket error. Also, your code is now much worse than your original. I don't use python but you connect and disconnect twice and you don't make sure the api is started before calling run. – brian Sep 05 '19 at 13:27
  • The socket error can be fixed by re-writing the source code in connection.py and re-installing ib-api . Check the previous link for the fix. Or you could just ignore it. I just downloaded 976.1 and the fix still isn't there yet. – brian Sep 05 '19 at 13:34
  • The -1 'errors' you're ignoring aren't errors but are important information from IB, use the information and just don't print them if you don't want to see it. – brian Sep 05 '19 at 13:35
  • Please see my answer below - it was indeed a bug on IB's API. The code I provided above was fine. Note that there are two functions, so no repetition of disconnect(), etc. Also, per IB's API instructions, there is no need to start the API anymore. – Rational-IM Sep 05 '19 at 21:45
  • How do you download tws version 976.1 for linux? Can find version older than 972 on the website. – phage Dec 17 '19 at 15:14
  • @phage http://interactivebrokers.github.io/# latest is 976, stable is 972. – brian Dec 17 '19 at 16:53
0

I guess I don't have enough reputation yet to add a comment to the above code, but the second highest rated code here is badly structured - your imports should be at the top of the file (outside of functions and classes), you also shouldn't define a class nested inside a function, I've actually never seen someone do that in 10 years of coding python. You'll run into scope access issues, and might never even define the variables, probably lose access to "self" etc. Classes should be a grouping of (hopefully) related functions. so the structure should look like:

imports 
class:
   func1()
   func2()
   func3()

Then if you need to call the function you'd do like: class.func1() for example...

To provide an actual example of how you could do this, something like:

from ibapi.client import EClient
from ibapi.wrapper import EWrapper
from ibapi.contract import Contract
import threading

class MyWrapper(EWrapper):
    def __init__(self):
        self.positions = {}

    def position(self, account, contract, position, avgCost):
        self.positions[contract.symbol] = {
            "symbol": contract.symbol,
            "position": float(position),
            "average_cost": avgCost
        }

class IBClient(EClient):
    def __init__(self, wrapper):
        EClient.__init__(self, wrapper)

def request_positions():
    wrapper = MyWrapper()
    client = IBClient(wrapper)
    client.connect("127.0.0.1", 7497, clientId=0)  # Connect to TWS/Gateway
    
    # Request positions
    client.reqPositions()

    # Wait for positions data to be received
    time.sleep(5)  # You might need to adjust the time as needed
    
    # Access the positions data
    positions_data = wrapper.positions
    
    # Filter or process positions as needed
    # e.g., filter by futures or stocks
    
    client.disconnect()

if __name__ == "__main__":
    request_positions()

Then if you wanted to get fancier you could look into threading - re: set and wait (events). Which would allow you to not do "time.sleep" and continue whenever the api responds.

  • I appreciate the feedback. The code you saw reflects my earlier efforts when I was learning the IBKR's API. I come from a C++ background, so I was still adapting to the use of classes over functions. I've now revised it to have a proper structure. My primary focus is on fundamental analysis for companies within my Circle of Competence. While my Python code is crucial for my company's operations, it's not my core competence (i.e., coding is my "side job"). I wasn't aware that it was the "second highest rated code here". As such, it certainly merits a well-structured approach! – Rational-IM Aug 24 '23 at 18:54
  • Also, because I revised the code, your response might be out of context. You might consider deleting it in the future to avoid clutter on the topic. In time: I kept the "time.sleep" functions there to avoid having to introduce new methods (e.g. positionEnd) and how to deal with it. I guess the classes are sufficiently challenging for a beginner! – Rational-IM Aug 24 '23 at 19:04