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:
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):
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
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.