问题
I have successfully written the code to extract the information from demo version of TWS regarding my positions using the code:
tws_conn = conn.Connection.create(port=7497, clientId=100)
tws_conn.register( acct_update, msg.updateAccountValue,
msg.updateAccountTime, msg.updatePortfolio)
tws_conn.connect()
tws_conn.reqPositions()
tws_conn.reqAccountUpdates(True,'DU15181')
However, it dumps the information as:
<updatePortfolio contract=<Packages.IbPy.ib.ext.Contract.Contract object at 0x06B0FE30>, position=-10, marketPrice=3.4000001, marketValue=-3400.0, averageCost=334.345, unrealizedPNL=-56.55, realizedPNL=0.0, accountName=DU15181>
I was wondering how the above information can be stored instead into an array with columns for contract or stock ticker, quantity in portfolio and purchase price in different columns
回答1:
Adding another answer given the length and major changes. Given I was working on some piece of code I answered another question with it and with a small adaptation can be used for the portfolio too.
Code:
from __future__ import (absolute_import, division, print_function,)
# unicode_literals)
import collections
import sys
if sys.version_info.major == 2:
import Queue as queue
import itertools
map = itertools.imap
else: # >= 3
import queue
import ib.opt
import ib.ext.Contract
class IbManager(object):
def __init__(self, timeout=20, **kwargs):
self.q = queue.Queue()
self.timeout = 20
self.con = ib.opt.ibConnection(**kwargs)
self.con.registerAll(self.watcher)
self.msgs = {
ib.opt.message.error: self.errors,
ib.opt.message.updatePortfolio: self.acct_update,
ib.opt.message.accountDownloadEnd: self.acct_update,
}
# Skip the registered ones plus noisy ones from acctUpdate
self.skipmsgs = tuple(self.msgs.keys()) + (
ib.opt.message.updateAccountValue,
ib.opt.message.updateAccountTime)
for msgtype, handler in self.msgs.items():
self.con.register(handler, msgtype)
self.con.connect()
def watcher(self, msg):
if isinstance(msg, ib.opt.message.error):
if msg.errorCode > 2000: # informative message
print('-' * 10, msg)
elif not isinstance(msg, self.skipmsgs):
print('-' * 10, msg)
def errors(self, msg):
if msg.id is None: # something is very wrong in the connection to tws
self.q.put((True, -1, 'Lost Connection to TWS'))
elif msg.errorCode < 1000:
self.q.put((True, msg.errorCode, msg.errorMsg))
def acct_update(self, msg):
self.q.put((False, -1, msg))
def get_account_update(self):
self.con.reqAccountUpdates(True, 'D999999')
portfolio = list()
while True:
try:
err, mid, msg = self.q.get(block=True, timeout=self.timeout)
except queue.Empty:
err, mid, msg = True, -1, "Timeout receiving information"
break
if isinstance(msg, ib.opt.message.accountDownloadEnd):
break
if isinstance(msg, ib.opt.message.updatePortfolio):
c = msg.contract
ticker = '%s-%s-%s' % (c.m_symbol, c.m_secType, c.m_exchange)
entry = collections.OrderedDict(msg.items())
# Don't do this if contract object needs to be referenced later
entry['contract'] = ticker # replace object with the ticker
portfolio.append(entry)
# return list of contract details, followed by:
# last return code (False means no error / True Error)
# last error code or None if no error
# last error message or None if no error
# last error message
return portfolio, err, mid, msg
ibm = IbManager(clientId=5001)
portfolio, err, errid, errmsg = ibm.get_account_update()
if portfolio:
print(','.join(portfolio[0].keys()))
for p in portfolio:
print(','.join(map(str, p.values())))
sys.exit(0) # Ensure ib thread is terminated
Result
Server Version: 76
TWS Time at connection:20160113 00:15:29 CET
---------- <managedAccounts accountsList=D999999>
---------- <nextValidId orderId=1>
---------- <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfuture>
---------- <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:eufarm>
---------- <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:cashfarm>
---------- <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfarm.us>
---------- <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:ushmds.us>
---------- <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:ilhmds>
---------- <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:cashhmds>
---------- <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:ethmds>
contract,position,marketPrice,marketValue,averageCost,unrealizedPNL,realizedPNL,accountName
IBM-STK-,10,25.0,250.0,210.0,40.0,0.0,D999999
The last 2 lines can be imported directly to (for example) Excel. Or given it's a list of dictionaries (what's getting printed out) it can be further manipulated in an script.
回答2:
The "columns" requirement is somehow vague, given a list already fulfills the requirement if each entry in the list is a list itself (and each index position in the sublists always contains the same field)
The message you receive is sent to the callback you have registered with tws. Each of the "dumped" fields can be accessed with "dot" notation or through dict-like methods like "keys", "values" and "items"
The major challenge is the contract: IB gives access to a large amount of exchanges and different trading assets. The "contract" object (and information in the IB Backend) seems to reflect an effort to provide a uniform/unified access to everything and at the same time shows they had to build upon existing infrastructure.
You mention "stock ticker" so I guess you'll probably be happy with something like: IBM-STK-SMART with the "ib" industry standard notation (the 2nd field indicates it is a stock and the 3rd that IB will use SMART routing for orders and price updated)
Let's go for a list of lists:
def acct_update(self, msg):
# Assume the function is a method in a class with access to a member
# 'portfolio' which is a list
if isinstance(msg, ib.opt.message.updatePortfolio):
c = msg.contract
ticker = '%s-%s-%s' % (contract.m_symbol, c.m_secType, c.m_exchange)
entry = [ticker]
entry.extend(msg.values)
self.portfolio.append(entry)
Unfortunately the "keys" method in the ibpy
messages is not a classmethod
, but the names are actually __slots__
. In the class holding the acct_update
method you coud do the following:
class MyClass(object):
portfields = ['ticker'] + ib.opt.message.updatePortfolio.__slots__
If rather than accessing the fields in a list by index you prefer the names of the fields already provided by ibpy, you can also make a dict of dicts
def __init__(self, ...)
self.portfolio = collections.OrderedDict()
def acct_update(self, msg):
# Assume the function is a method in a class with access to a member
# 'portfolio' which is a list
if isinstance(msg, ib.opt.message.updatePortfolio):
c = msg.contract
ticker = '%s-%s-%s' % (contract.m_symbol, c.m_secType, c.m_exchange)
self.portfolio[ticker] = collections.OrderedDict(msg.items())
Which would allow you to get the latest ticker information by name and access the fields by name.
If you need to keep a per ticker history
def __init__(self, ...)
self.portfolio = collections.defaultdict(list)
def acct_update(self, msg):
# Assume the function is a method in a class with access to a member
# 'portfolio' which is a list
if isinstance(msg, ib.opt.message.updatePortfolio):
c = msg.contract
ticker = '%s-%s-%s' % (contract.m_symbol, c.m_secType, c.m_exchange)
self.portfolio[ticker].extend(msg.values())
You could store the "items" rather than the values and later access the tuples if needed.
回答3:
Besides using ibpy, I will also import IBWrapper which can be downloaded from Github: https://github.com/anthonyng2/ib
import pandas as pd
import numpy as np
import time
from IBWrapper import IBWrapper, contract
from ib.ext.EClientSocket import EClientSocket
accountName = "Your Account ID"
callback = IBWrapper() # Instantiate IBWrapper. callback
tws = EClientSocket(callback) # Instantiate EClientSocket and return data to callback
host = ""
port = 4002 # It is for default port no. in demo account
clientId = 25
tws.eConnect(host, port, clientId) # connect to TWS
create = contract() # Instantiate contract class
callback.initiate_variables()
tws.reqAccountUpdates(1, accountName)
time.sleep(2)
They are your updated account value and portfolio summary:
accvalue = pd.DataFrame(callback.update_AccountValue, columns = ['key', 'value', 'currency', 'accountName']) #[:199]
portfolio = pd.DataFrame(callback.update_Portfolio, columns=['Contract ID','Currency', 'Expiry','Include Expired','Local Symbol','Multiplier','Primary Exchange','Right',
'Security Type','Strike','Symbol','Trading Class','Position','Market Price','Market Value',
'Average Cost', 'Unrealised PnL', 'Realised PnL', 'Account Name'])
callback.update_AccountTime
print("AccountValue: \n" + str(accvalue))
print("portfolio: \n" + str(portfolio))
It is your updated Position Summary:
# Position Summary
tws.reqPositions()
time.sleep(2)
dat = pd.DataFrame(callback.update_Position,
columns=['Account','Contract ID','Currency','Exchange','Expiry',
'Include Expired','Local Symbol','Multiplier','Right',
'Security Type','Strike','Symbol','Trading Class',
'Position','Average Cost'])
dat[dat["Account"] == accountName]
print("Position Summary: \n" + str(dat))
回答4:
I wrote the following code to allow me to read my positions and NAVs directly from Interactive Brokers API.
# Interactive Brokers functions to import data
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
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):
super().positionEnd()
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()
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
import pandas as pd
class ib_class(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.all_accounts = pd.DataFrame([], columns = ['reqId','Account', 'Tag', 'Value' , 'Currency'])
def accountSummary(self, reqId, account, tag, value, currency):
if tag == 'NetLiquidationByCurrency':
index = str(account)
self.all_accounts.loc[index]=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 accountSummaryEnd(self, reqId:int):
super().accountSummaryEnd(reqId)
self.disconnect()
ib_api = ib_class()
ib_api.connect("127.0.0.1", 7496, 0)
ib_api.reqAccountSummary(9001,"All","$LEDGER")
current_nav = ib_api.all_accounts
ib_api.run()
return(current_nav)
To test the code I saved it on a .py file named IB_API and run the following:
import IB_API
print("Testing IB's API as an imported library:")
all_positions = IB_API.read_positions()
all_navs = IB_API.read_navs()
print("Test ended")
I'm still trying to avoid an error [WinError 10038] that says "An operation was attempted on something that is not a socket" - please see my question on the topic
来源:https://stackoverflow.com/questions/34561626/ibpy-getting-portfolio-information-interactive-broker-python