Рубрики
Без рубрики

Как запросить и хранить данные, используя API Interactive Brokers

Введение Если вы начали работать с API Interactive Brokers (через IBAPI PYT … Tagged с Python, TURAND.

Если вы начали работать с API Interactive Brokers (через пакет Python ibapi , но вы не смущаете, как взаимодействовать с TWS, вы в нужном месте!

В этом посте я представлю одну методологию надежного запроса и получения данных из графического интерфейса TWS, используя ibapi упаковка в ваших программах.

Методология состоит из двух основных разделов в этом посте:

  1. Связывая вместе Eclient и EWrapper классы
  2. Реализация стратегии создания, чтения и уничтожения очередь объекты

Прежде чем мы погрузимся, хотя, Я собираюсь предположить, что у вас уже есть рабочее соединение с TWS из вашей программы Python.

Если вы этого не сделаете, то уже есть много отличных постов в блоге и примеров, которые проводят вас, как настройка и подключение, в том числе Официальные документы и этот пост от @Wrighter , так что продолжайте и подключитесь к подключению, а затем прыгайте сюда для низкого уровня.

Давайте сначала рассмотрим поток высокого уровня:

GUI TWS является нашим источником истины. Это то, с чем говорит наша программа Python- именно поэтому GUI TWS должен работать на местном уровне, чтобы наша программа работала.

Следовательно, единственный API, который мы используем, является API для этой локальной программы GUI. Любое общение, внешнее по отношению к нашей машине, обрабатывается самим графическим интерфейсом TWS, что не является нашей заботой (если у вас есть интернет).

Также API это Коллекция определений класса, приведенная нам в ibapi Пакет- это не URL, как вы, возможно, предполагали (это смутило меня некоторое время).

И вот скриншот из TWS, чтобы мы все на одной странице о том, что есть:

Теперь, когда у нас есть это отсортировано, давайте введите прямо в это.

Как вы, возможно, уже узнали из Официальная документация , ibapi Пакет инструктирует нас работать с двумя классами, EWrapper и Eclient Анкет

Eclient Класс дает нам одностороннее общение для отправки сообщений в TWS. Сообщения Eclient отправки Запросы для данных.

EWrapper Класс дает нам одностороннее общение для получения сообщений от TWS. Сообщения EWrapper получает являются данные.

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

Итак, как мы можем координировать запрос и получение данных в этих двух разных классах?

Если вы из мира веб -разработки, как я, и вы привыкли запрашивать и получать данные в том же вызове метода, используя что -то вроде: fetch ('https://google.com') .then (res => res.json ()) ... Это представляет серьезный сдвиг парадигмы (по крайней мере, до тех пор, пока мы не поработаем магией 😁).

Часть ответа – связать Eclient и EWrapper классы вместе в одном создании объекта. Этот процесс дается нам официальными документами в этом (слегка измененном) фрагменте кода:

from ibapi.wrapper import EWrapper
from ibapi.client import EClient

class TestWrapper(EWrapper):
    def __init__(self):
        pass

class TestClient(EClient):
    def __init__(self, wrapper):
        EClient.__init__(self, wrapper)

class TestApp(TestWrapper, TestClient):
    def __init__(self):
        TestWrapper.__init__(self)
        TestClient.__init__(self, wrapper=self)

Сначала вы увидите это TestWrapper это просто прямая копия EWrapper (Позже мы сделаем что -то полезное и другое).

Далее мы видим, что наш новый клиент -класс TestClient просто старый добрый Eclient что мы знаем и любим, за исключением того, что после экземпляра он должен быть обеспечен доступом к обертка объект.

И поэтому, когда мы создаем экземпляры Testapp через app () :

  • Сначала мы создаем экземпляр TestWrapper к приложение переменная
  • Затем мы передаем это приложение переменная к TestClient как обертка , а затем повторно увлекся приложение переменная как TestClient пример вместо

Если вы внимательно посмотрите на Eclient .__ init __ () Определение вы увидите, что обертка Аргумент прикреплен к новому объекту как Self.Wrapper :

class EClient(object):
    def __init__(self, wrapper):
        self.wrapper = wrapper
        ...

Таким образом, конечным результатом является то, что теперь у нас есть приложение объект:

  • Это имеет доступ к TestClient Свойства и методы через app.some_client_method () вызовы
  • Это имеет доступ к TestWrapper Свойства и методы через app.wrapper.some_wrapper_method () вызовы

Это в основном умный способ иметь два экземпляра объекта в одном. приложение полномасштабный TestClient экземпляр с полномасштабным TestWrapper экземпляр, прикрепленный к app.wrapper имущество.

Хорошо, отлично, теперь мы связали наши Eclient и EWrapper Занятия через это Testapp учебный класс. Но … и что?

Как сделать запрос TWS на сводку моей учетной записи? А после того, как я это сделаю, как мне собрать эти данные? Что ж, теперь, когда мы подключили нашу обертку и клиента через нашу приложение Объект, мы можем спроектировать поток, чтобы ответить на обоих этих вопросов.

Давайте будем очень явными и продолжим с примера сводного счета.

Во -первых, мы можем запросить сводку нашей учетной записи от TWS через Eclient.reqaccountsummary метод Как я это узнал? Ну, цель и описание этого и всех методов, предоставленных API, можно найти в ibapi Сам источник или в (еще раз) Официальные документы Анкет

Глядя на Docstring для Reqaccountsummary в Eclient , мы видим:

Вызовите этот метод, чтобы запросить и поддерживать в курсе данных, которые появляются на вкладке «Сводка с учетной записью». Данные возвращаются Accountsummary ().

Теперь здесь мы должны обсудить очень важный момент. accountsummary это EWrapper метод На самом деле, это не просто метод, а событие! Это означает, что accountsummary будет автоматически называется Когда TWS будет готов доставить нашу запрошенную резюме учетной записи. Вот как работают все эти методы/события обертки ( openorder , tickprice и т. Д.)

Быть на 100% ясно:

  • Мы запрашиваем сводку с учетной записью от TWS, работая reqaccountsummary () (метод eclient
  • TWS потребуется так много времени, чтобы собрать эти данные, и когда они будут готовы отправить их нам …
  • accountsummary () Метод (из eWrapper ) будет автоматически вызван, содержащий данные в своих аргументах метода

Опять же, мы не называем вручную accountsummary , это называется автоматически!

Хорошо, что происходит с данными, доставленными в краткий отчет ? Давайте посмотрим на его определение в EWrapper :

def accountSummary(self, reqId:int, account:str, tag:str, value:str, currency:str):
    self.logAnswer(current_fn_name(), vars())

Сначала отметьте аргументы функции. Мы возвращаем следующие данные: Reqid , учетная запись , тег , ценность и валюта ( : int и : str просто типовые аннотации).

Глядя на корпус функций, на самом деле ничего не происходит. И это намеренно. Интерактивные брокеры ожидают, что мы перезаписываем это accountsummary Метод в TestWrapper определить, что мы хотим делать с возвращенными данными. Вот почему TestWrapper Определение класса было пустым раньше (но теперь мы собираемся его заполнить!)

Здесь мы реализуем нашу стратегию потока данных. Суть методологии вращается вокруг двух принципов:

  1. Мы контролируем методы клиента, но мы не контролируем методы обертки
    • Да, мы контролируем, какие методы обертки делают (мы переопределяем eWrapper Методы с нашими собственными определениями в testwrapper ), но мы не контролируем при запуске. TWS будет автоматически запускать конкретные методы (такие как ewrapper.accountsummary ).
    • Тем не менее, мы контролируем, когда будут работать методы клиента.
  2. Клиентская часть нашего приложение Object знает о обертке, но обертка не знает о клиенте

    • Когда мы создаваем экземпляры Testapp , мы дали наши TestClient Класс создание TestWrapper объект.
    • Как мы уже обсуждали, это означает, что клиентские методы и свойства на приложение иметь доступ к методам/реквизитам обертки на app.wrapper , но обратное неправда. Методы обертки/реквизиты на app.wrapper не знайте о методах/реквизитах клиента на базе приложение объект.

Что это значит для нашего дизайна? Наши клиентские методы должны быть нашим командным центром и содержать наши условные проверки и управление потоком. Мы хотим это, потому что клиентские методы/реквизиты имеют высшую видимость в объекте и потому, что мы контролируем, когда методы работают. С другой стороны, наши методы обертки должны быть мертвыми, потому что они имеют ограниченную видимость по всем объектам и потому что мы не контролируем, когда некоторые из них работают.

Геймплан для запроса данных из TWS (с помощью метода TestClient)

  1. Инициализировать хранилище данных на объекте обертки (подумайте app.wrapper )
  2. Запросить данные от TWS (подумайте eclient.reqaccountsummary )

    • (Метод автоматической обертки помещает данные в хранилище -> Think Testwrapper.accountsummary )
  3. Получить целевые данные из хранилища на обертке
  4. Удалить хранилище данных на объекте обертки
  5. Проверьте на наличие ошибок, которые могли произойти во время этого процесса
  6. Вернуть полученные данные

Для всех визуальных людей:

В нашем случае мы хотим сводку нашей учетной записи. Итак, давайте займем эти шаг за шагом, а затем соединим все это в конце в одном TestClient Метод определение.

Шаг 1: Инициализация хранилища данных на объекте обертки

  • Во -первых, давайте определим метод на TestWrapper Это построит нам пустой очередь :
import queue

class TestWrapper(EWrapper):

    def init_accountSummary_queue(self):                                                                              
        self.accountSummary_queue = queue.Queue()
  • Что такое очередь ? Это просто первый список (FIFO). Зачем использовать это вместо Список или что-то другое? Следите за обновлениями!
  • Ок, отлично. Теперь у нас есть место на обертке, чтобы поместить краткие данные нашей учетной записи. (В качестве проверки здравомыслия, если вы создаете экземпляр app () прямо сейчас, у вас будет доступ к этому методу из app.wrapper.init_accountsummary_queue )

Шаг 2: Запрос данных от TWS

  • Это достигается путем запуска правильного Eclient метод Все, что нам нужно сделать, это позвонить Reqaccountsummary от Eclient (с соответствующими аргументами, конечно)

Шаг 2B: метод автоматической обертки помещает данные в хранилище

  • Вот где мы определяем наш обычай TestWrapper Метод переопределяет по умолчанию EWrapper.accountsummary
class TestWrapper(EWrapper):

    def accountSummary(
        self, reqId: int, account: str, tag: str, value: str, currency: str
    ):
        """
        Triggered by EClient.reqAccountSummary()
        """
        if hasattr(self, "accountSummary_queue"):
            self.accountSummary_queue.put(
                {
                    "reqId": reqId,
                    "account": account,
                    "tag": tag,
                    "value": value,
                    "currency": currency,
                }
            )
  • Обратите внимание, что (а) мы делаем это простым и просто передаем аргументы в нашем очередь В словаре и (б) мы делаем это только в том случае, если очередь Уже существует (мы не хотим накапливать данные, которые мы не просили, в случае, если TWS самостоятельно стреляет в события)

Шаг 3: Получить целевые данные из хранилища на обертке

  • Вот где очередь Объекты сияют, потому что мы можем неоднократно попытаться получить что -то из (изначально) пустой очереди, пока это либо: (а) что -то дает нам или (б) раз

  • Быстрое доказательство концепции

# Init an empty queue
myQ = queue.Queue()

try:
    # Try to remove an item from the queue    
    myItem = myQ.get(timeout=5)
except queue.Empty:
    print("myQ was empty and max time has been reached")
  • Это, конечно, поднимет очередь. Пусто Исключение, но если некоторые данные должны были волшебным образом быть положить в очередь во время пяти второго окна тайм -аута … мы получили бы данные и избегали исключения!

Шаг 4: Удалить хранилище данных на объекте обертки

  • Поскольку методы обертки работают автоматически, мы не хотим нашего очередь Объекты для накопления данных, отправленных нам из TWS, не снятых. Мы хотим данных только тогда, когда мы просим об этом.

  • Доказательство концепции

class A():
    def __init__(self):
        self.myQ = queue.Queue()

a = A()
del a.myQ

Шаг 5: Проверьте на наличие ошибок, которые могли произойти во время этого процесса

  • Мы будем отслеживать ошибки на стороне обертки, потому что, если вы думаете об этом, так же, как мы получаем данные, которые мы хотим на TestWrapper , вот как мы тоже получаем сообщения об ошибках от TWS …
class TestWrapper(EWrapper):

    def init_error(self):
        self.my_errors_queue = queue.Queue()

    def is_error(self):
        error_exist = not self.my_errors_queue.empty()
        return error_exist

    def get_error(self, timeout=5):
        if self.is_error():
            try:
                return self.my_errors_queue.get(timeout=timeout)
            except queue.Empty:
                return None
        return None

    def error(self, id, errorCode, errorString):
        errormessage = (
            "IB returns an error with %d errorcode %d that says %s"
            % (
                id,
                errorCode,
                errorString,
            )
        )

Шаг 6: вернуть полученные данные

  • Ну, это говорит сама за себя

Объединить все это в один метод TestClient

class TestClient(EClient):

    def __init__(self, wrapper):
        EClient.__init__(self, wrapper)
        # Maximum timeout we're comfortable with, in seconds
        self.max_wait_time = 5

    def getAccountSummary(self):
        """
        Runs EClient.reqAccountSummary()
        Returns value from EWrapper.accountSummary()
        """
        # [1] Init a queue on the wrapper
        self.wrapper.init_accountSummary_queue()

        # [2] Request data from TWS
        self.reqAccountSummary(
            9001, "All", "TotalCashValue, BuyingPower, AvailableFunds"
        )

        try:
            # [3] Get data from queue (if it shows up) or eventually timeout
            accountSummary = self.wrapper.accountSummary_queue.get(
                timeout=self.max_wait_time
            )
        except queue.Empty:
            print("accountSummary queue was empty or max time reached")
            accountSummary = None

        # [4] Delete queue from wrapper
        del self.wrapper.accountSummary_queue

        # [5] Check for errors
        while self.wrapper.is_error():
            print("Error:")
            print(self.get_error(timeout=self.max_wait_time)

        # [6] Return data
        return accountSummary

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

Чтобы достичь этого, поместите это в свой Testapp определение:

from threading import Thread

class TestApp(TestWrapper, TestClient):

    def __init__(self, ...):
        ...
        thread = Thread(target=self.run)
        thread.start()
        setattr(self, "_thread", thread)

И это должно это сделать! Это лучший способ реализовать IB API? Вероятно, нет, но это кажется довольно надежным способом сделать это (по крайней мере, в моем до сих пор ограниченный опыт).

Дайте мне знать, что вы думаете об этом дизайне в комментариях, и, наконец, спасибо за чтение!

Оригинал: “https://dev.to/spongechameleon/how-to-request-and-store-data-using-the-interactive-brokers-api-n38”