Если вы начали работать с API Interactive Brokers (через пакет Python ibapi
, но вы не смущаете, как взаимодействовать с TWS, вы в нужном месте!
В этом посте я представлю одну методологию надежного запроса и получения данных из графического интерфейса TWS, используя ibapi
упаковка в ваших программах.
Методология состоит из двух основных разделов в этом посте:
- Связывая вместе
Eclient
иEWrapper
классы - Реализация стратегии создания, чтения и уничтожения
очередь
объекты
Прежде чем мы погрузимся, хотя, Я собираюсь предположить, что у вас уже есть рабочее соединение с 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
Определение класса было пустым раньше (но теперь мы собираемся его заполнить!)
Здесь мы реализуем нашу стратегию потока данных. Суть методологии вращается вокруг двух принципов:
- Мы контролируем методы клиента, но мы не контролируем методы обертки
- Да, мы контролируем, какие методы обертки делают (мы переопределяем
eWrapper
Методы с нашими собственными определениями вtestwrapper
), но мы не контролируем при запуске. TWS будет автоматически запускать конкретные методы (такие какewrapper.accountsummary
). - Тем не менее, мы контролируем, когда будут работать методы клиента.
- Да, мы контролируем, какие методы обертки делают (мы переопределяем
Клиентская часть нашего
приложение
Object знает о обертке, но обертка не знает о клиенте- Когда мы создаваем экземпляры
Testapp
, мы дали нашиTestClient
Класс созданиеTestWrapper
объект. - Как мы уже обсуждали, это означает, что клиентские методы и свойства на
приложение
иметь доступ к методам/реквизитам обертки наapp.wrapper
, но обратное неправда. Методы обертки/реквизиты наapp.wrapper
не знайте о методах/реквизитах клиента на базеприложение
объект.
- Когда мы создаваем экземпляры
Что это значит для нашего дизайна? Наши клиентские методы должны быть нашим командным центром и содержать наши условные проверки и управление потоком. Мы хотим это, потому что клиентские методы/реквизиты имеют высшую видимость в объекте и потому, что мы контролируем, когда методы работают. С другой стороны, наши методы обертки должны быть мертвыми, потому что они имеют ограниченную видимость по всем объектам и потому что мы не контролируем, когда некоторые из них работают.
Геймплан для запроса данных из TWS (с помощью метода TestClient)
- Инициализировать хранилище данных на объекте обертки (подумайте
app.wrapper
) Запросить данные от TWS (подумайте
eclient.reqaccountsummary
)- (Метод автоматической обертки помещает данные в хранилище -> Think
Testwrapper.accountsummary
)
- (Метод автоматической обертки помещает данные в хранилище -> Think
- Получить целевые данные из хранилища на обертке
- Удалить хранилище данных на объекте обертки
- Проверьте на наличие ошибок, которые могли произойти во время этого процесса
- Вернуть полученные данные
Для всех визуальных людей:
В нашем случае мы хотим сводку нашей учетной записи. Итак, давайте займем эти шаг за шагом, а затем соединим все это в конце в одном 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”