Торговый алгоритм на Python для IB.API: EClient и EWrapper

В прошлой статье мы разобрали, как подключаться к TWS. В данной статье мы поговорим про IB API, его архитектуру и ключевые аспекты, без понимания которых, следующие статьи не будут иметь никакой прикладной пользы.

Я настоятельно рекомендую дочитать эту статью до конца, ибо то, что описано здесь, вы не найдете ни в мануале IB, ни в других статьях, ни в репозиториях.

Введение

В прошлых статьях я обещал, что обязательно опишу два ключевых класса: EClient и EWrapper. Я помню про свои обещания. Настало время поговорить о самом главном в IB API.

Взгляд изнутри…

Работа с API сводится к работе с двумя классами EClient и EWrapper. У них разные назначения, разные применения и тем более разный функционал. Но применять их надо одновременно, в противном случае, вы даже не подключитесь к терминалу TWS.

Обратите внимание, я не пытаюсь сказать: «Выучите эти два класса и можете творить с терминалом всё, что угодно». Это далеко не так. В зависимости от задачи надо будет задействовать другие классы API. Но без этих двух всё остальное бесполезно.

EClient

Этот класс отправляет запросы в TWS и является инициатором любых действий, требующих участия терминала.

Существуют 4 «дежурных» метода, необходимых в любом приложении Python:

EClient.connect()

Открывает подключение к терминалу. Мы уже встречались с ним в прошлых статьях и всегда будем видеть в следующих.

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

Подробнее о работе с методом можете прочесть здесь.

EClient.disconnect()

Отвечает за отключение от терминала. Описание по ссылке выше.

EClient.isConnected()

Возвращает True, если в данный момент наше приложение подключено к терминалу. В противном случае возвращает False.

EClient.run()

Отвечает за процесс обмена сообщениями между TWS и Вашим приложением. Это самый главный метод в работе с этим классом и API в целом.

Важно помнить, что метод работает в бесконечном цикле, поэтому его надо запускать в отдельном потоке. Иначе приложение никогда не завершит свою работу.

Сам бесконечный цикл работает до тех пор, пока очередь сообщений от терминала не пуста или пока есть подключение к терминалу. И самое главное: цикл работает, пока:

Это атрибут объекта класса EClient, создающийся в методе __init__(). Смысл его очень прост. Он работает как «флаг», при помощи которого вы можете прервать работу данного цикла.

Так же важно знать, что цикл размещен в блоке try…except, в конце которого идет обязательное отключение от терминала.

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

Остальные методы этого класса мы рассмотрим в следующих статьях, решая ту или иную задачу.

EWrapper

Этот класс принимает ответы от TWS. Точнее, ответ от TWS получает метод EClient.run(). Обработав ответ, он запускает соответствующий (пришедшему ответу) метод в классе EWrapper. Для этого объект класса EWrapper необходимо передавать в инициализатор объекта (в качестве аргумента функции) класса EClient.

В итоге, обработка сообщений от TWS производится «руками» этого класса. Чтобы организовать это, нам необходимо создать класс-наследник от EWrapper. И уже в нем переопределить необходимые методы родительского класса EWrapper.

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

К дежурным методам данного класса относятся:

EWrapper.connectAck()

Срабатывает в момент успешного подключения к терминалу. Он запускается внутри метода EClient.connect(), после того как API установило сокетное подключение к TWS, организовало очередь для обработки будущих сообщений и обменялось с терминалом служебной информацией.

EWrapper.error()

Принимает ошибки от терминала. Если планируете создавать надежное и отказоустойчивое приложение, обратите на этот метод пристальное внимание. Сюда приходят абсолютно все ошибки, которые способен отправить TWS (начиная с ошибок подключения, заканчивая ошибками размещения ордеров).

EWrapper.connectionClosed()

Срабатывает в момент отключения от терминала. Запускается внутри метода EClient.disconnect(), после того, как API отключит сокетное соединение с терминалом.

EWrapper.nextValidId()

Получает от TWS Next Valid Identifier. Данный идентификатор нужен для запроса любой информации от TWS и отправки ему приказов на размещение ордеров. Метод срабатывает каждый раз, как меняется этот идентификатор.

Перечисленные методы далеко не все, остальные мы будем рассматривать в дальнейших статьях.

Все надоел! Код давай!

Пожалуйста, пожалуйста:

Согласитесь, не плохо! По сравнению с кодом прошлой статьи этот выглядит не так… «жиденько».

Заглянем за кулисы

Если вы запустите этот код, то не увидите ни одной ошибки, как было с кодом из прошлой статьи. Я обещал показать Вам код, где нет ошибок — вот он 🙂

Почему больше нет ошибок? Ни одной!… Все очень просто!

Ранее код проводил двойное отключение. Это из-за того, что мы вызывали tws.disconnect() в конце кода. Внутри объекта EClient существует объект класса Connection, достучаться до него можно вот так: EClient.conn. При подключении к терминалу (метод EClient.connect()) IB API берет атрибут EClient.conn и инициализирует его классом Connection. Теперь это объект, в котором хранится сокетное подключение к терминалу.

В методе EClient.disconnect() сначала вызывается отключение от терминала, руками EClient.conn, а затем этому атрибуту присваивается значение None. Казалось бы, все хорошо — сработало всё правильно. Но есть один момент: пока происходило отключение от терминала, у нас «висел» параллельный поток, в котором работал метод EClient.run() в бесконечном цикле. При очередной итерации цикла стало понятно, что подключения к терминалу уже не существует, цикл завершается и интерпретатор Python выходит из блока try…except и тут же выполняет блок finally, в котором повторно вызывается EClient.disconnect(). Угадайте, что произойдет, если к этому моменту EClient.conn=None (больше не объект с сокетным подключением)? Правильно — исключение, которое демонстрировал прошлый код.

Стоп! Раньше были еще ошибки. Которые не были ошибками (помните?). С ними что? Да ничего особенного. Загляните в наш класс-наследник, в метод MyWrapper.error(). Теперь в нем установлена проверка аргумента reqId. Ошибки, которые терминал присылает при подключении, имеют reqId=-1. Теперь эти ошибки игнорируются в нашем методе. Терминал их продолжает присылать через API, однако наше приложение не дает им выводиться на печать.

Следующий метод — MyWrapper.nextValidId(). Тут мы записываем Next Valid Identifier. Сейчас этот параметр нам не нужен, но со следующей статьи без него будет не обойтись. Поэтому я решил вас познакомить с ним заранее.

Следующий метод — MyWrapper.connectionClosed(). Здесь присваиваем внутреннему атрибуту end_work_with_TWS значение True. Это очень важно для отключения от терминала.

Сразу после десятисекундного «забвения», мы присваиваем атрибуту EClient.done значение True. После чего, уходим в бесконечный цикл. В данном цикле мы ожидаем, пока EClient.done снова станет False, а созданный в нашем классе атрибут MyWrapper.end_work_with_TWS станет True. Пока оба флага не переключаться в нужное значение, Python будет выводить «Жду отключение от терминала» и спать полсекунды.

Зачем так сделано?

Для того, чтобы точно убедиться в отключении от TWS. Мои эксперименты показали, что запуск данного кода подряд, показывает разное время на отключения от терминала. В лучшем случае надпись «Жду отключение от терминала» появлялась два раза (1 секунда), в худшем — 8 раз (4 секунды). Дабы убедится в отключении вы должны организовывать подобное ожидание в вашем приложении.

Конечно, финальное решение за вами. Однако, если вы планируете создавать приложение, которое будет использовать подключение к терминалу не один раз, лучше организовать подобное ожидание.

Зачем MyWrapper.end_work_with_TWS?

Исключительно ради демонстрации работы метода MyWrapper.connectionClosed(). Этот атрибут можно вообще удалить из кода, достаточно задействовать EClient.done для ожидания отключения.

Ну, а остальное в коде вам должно быть знакомо из прошлой статьи. Единственное, на что хочу обратить ваше внимание, так это на инициализацию объекта tws. Прежде всего он один, раньше был еще объект класса EWrapper. Все правильно, он надоел мне и я решил удалить эту строку. Вместо нее инициализировать объект класса EWrapper,  точнее объект класса MyWrapper, будем на лету, при передаче его в инициализатор, интерпретатор Python позволяет так делать.

Заключение

В данной статье мы рассмотрели два основных класса IB API: EClient и EWrapper. Познакомились с дежурными методами обоих классов. Определили подход к ним обоим и разобрались, как работать с ними из нашего приложения.

Теперь мы полностью готовы к созданию полезных алгоритмов.

Автор: Олег Минаев