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):
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.
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:
Add/Remove
Tool in the Windows Control Panel as usualC:\\TWS API\\
folder if any files are still remaining to prevent a version mismatch.C:\\Windows\\SysWOW64\\TwsSocketClient.dll
. Delete this file. [didn't find this file] 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!
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.
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 technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.