ibpy Получение информации о портфеле: Interactive Broker, Python

Я успешно написал код для извлечения информации из демо-версии TWS о моих позициях, используя код:

    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')

Однако он сбрасывает информацию как:

<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>

Мне было интересно, как вышеуказанная информация может быть сохранена вместо этого в массиве со столбцами для контракта или биржевого тикера, количества в портфеле и цены покупки в разных столбцах.


person Zanam    schedule 02.01.2016    source источник
comment
есть ли еще код, которым можно поделиться?   -  person Caffeinated    schedule 02.01.2016
comment
Что вы имеете в виду, я поделился фрагментом кода, который я использовал?   -  person Zanam    schedule 02.01.2016


Ответы (4)


Добавление еще одного ответа с учетом длины и серьезных изменений. Учитывая, что я работал над некоторым фрагментом кода, я ответил им на другой вопрос, и с небольшой адаптацией его можно использовать и для портфолио.

Код:

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

Результат

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

Последние 2 строки можно импортировать напрямую (например) в Excel. Или, учитывая, что это список словарей (то, что распечатывается), им можно дополнительно манипулировать в сценарии.

person mementum    schedule 12.01.2016
comment
Спасибо!! .. отличное упражнение для меня, так как я начал возиться с Python всего несколько недель назад. - person Zanam; 13.01.2016
comment
@mementum: вы, вероятно, хотите закрыть соединение с self.con.disconnect() - person Abramodj; 07.04.2020

Требование «столбцы» как-то расплывчато, учитывая, что список уже выполняет требование, если каждая запись в списке является самим списком (и каждая позиция индекса в подсписках всегда содержит одно и то же поле)

Полученное сообщение отправляется на обратный вызов, который вы зарегистрировали в tws. Доступ к каждому из «выгруженных» полей можно получить с помощью «точечной» нотации или с помощью методов, подобных диктовке, таких как «ключи», «значения» и «элементы».

Основной проблемой является контракт: IB дает доступ к большому количеству бирж и различным торговым активам. Объект «контракт» (и информация в бэкэнде IB), по-видимому, отражает стремление обеспечить единый/унифицированный доступ ко всему и в то же время показывает, что им приходилось опираться на существующую инфраструктуру.

Вы упомянули «биржевой тикер», поэтому я думаю, вам, вероятно, понравится что-то вроде: IBM-STK-SMART с обозначением отраслевого стандарта «ib» (2-е поле указывает, что это акции, а 3-е, что IB будет использовать SMART-маршрутизацию для заказов и цены обновлены)

Перейдем к списку списков:

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)

К сожалению, метод "keys" в сообщениях ibpy не является classmethod, но имена на самом деле __slots__. В классе, содержащем метод acct_update, вы можете сделать следующее:

class MyClass(object):
    portfields = ['ticker'] + ib.opt.message.updatePortfolio.__slots__

Если вместо доступа к полям в списке по индексу вы предпочитаете имена полей, уже предоставленные ibpy, вы также можете составить список 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())

Что позволит вам получить последнюю информацию о тикере по имени и получить доступ к полям по имени.

Если вам нужно вести историю каждого тикера

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())

Вы можете хранить «элементы», а не значения, а затем при необходимости обращаться к кортежам.

person mementum    schedule 11.01.2016
comment
Возможно ли, что вы можете поделиться своим основным файлом и файлом, в котором вы определяете вышеуказанное? - person Zanam; 12.01.2016
comment
Не существует такого понятия, как главный файл. Фрагменты кода были созданы на лету и основаны на моем опыте работы с ibpy, который я использую в основном для получения исторических данных. В будущем я намерен также размещать заказы (и, следовательно, получать портфолио) для моей платформы на основе Python github. com/mementum/backtrader - person mementum; 12.01.2016

Помимо использования ibpy, я также импортирую IBWrapper, который можно загрузить с 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)

Это ваша обновленная стоимость счета и сводка портфеля:

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

Это ваша обновленная сводка позиции:

# 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))
person Aqueous Carlos    schedule 10.11.2018

Я написал следующий код, чтобы позволить мне читать свои позиции и NAV непосредственно из 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)

Чтобы протестировать код, я сохранил его в файле .py с именем IB_API и запустил следующее:

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")

Я все еще пытаюсь избежать ошибки [WinError 10038], в которой говорится: «Попытка выполнить операцию над чем-то, что не является сокетом» — см. мой вопрос по теме

person Rational-IM    schedule 05.09.2019