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
The Interactive Brokers Python native API is a functionality that allows you to trade automatically via Python code. In more technical terms, it is a communication protocol that allows for an interchange of information with Interactive Broker's (IB) servers and custom software applications.
Use our modern REST API to trade, monitor and manage your IBKR account. The Client Portal API enables simple integration and lets you access account and sub-account data plus FYI messages and more.
Our APIs are available for DDE for Excel, Java, C++, and ActiveX.
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):
def read_positions(): #read all accounts positions and return DataFrame with information
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'])
def error(self, reqId:TickerId, errorCode:int, errorString:str):
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 run_loop():
app.run()
app = ib_class()
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(1) #Sleep interval to allow time for connection to server
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
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
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_accounts = pd.DataFrame([], columns = ['reqId','Account', 'Tag', 'Value' , 'Currency'])
def error(self, reqId:TickerId, errorCode:int, errorString:str):
if reqId > -1:
print("Error. Id: " , reqId, " Code: " , errorCode , " Msg: " , errorString)
def accountSummary(self, reqId, account, tag, value, currency):
index = str(account)
self.all_accounts.loc[index]=reqId, account, tag, value, currency
def run_loop():
app.run()
app = ib_class()
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(1) #Sleep interval to allow time for connection to server
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)
print()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With