XROAD
Разработка робота на Python

Для того чтобы упростить разработку алгоритмов на Python предлагается вместо Python SDK использовать py_algo приложение. py_algo представляет собой Python runtime со встроеными методами и классами, используемыми для разработки торговых алгоритмов. Весь функционал написан на С и при этом доступен из Python кода алгоритма в виде Python классов и методов. При этом, задержка обработки котировки и выставления заявки (tick-to-trade) которую вносит py_algo приложение составляет в среднем 60 микросекунд. Приложение представляет собой консольную утилиту которая инициализирует Python runtime и запускает код алгоритма:

Для того, чтобы запустить алгоритм (например реализованный в файле algo.py) достаточно выполнить команду:

  bin/py_algo -s algo1 -p algo.py

при этом algo1 это название сконфигурированной ноды (см. Добавление ноды). Помимо ручного запуска алгоритма, есть возможность автоматического запуска алгоритма как сервис.

Справочное руководство Python Algo

Перечисления

class xroad.TimeInForce(IntEnum) - время жизни заявки

  • day = 48 - заявка валидна в течении дня
  • GTC = 49 - заявка валидна по ее не отмнят
  • open = 50 - заявка валидна только в течении сессии открытия
  • IOC = 51 - заявка исполняется немедленно (частично или полностью) или отклоняется.
  • FOK = 52 - заявка исполняется немедленно полностью или снимается
  • GTD = 54 - заявка валидна до определенной даты
  • close = 55 - заявка валидна в течении сессии закрытия

class OrderFlag(IntEnum) - конфигруционные настройки заявки

  • as_mmaker = 1 - заявка подается от лица официального маркер мейкера
  • otc = 8192 - OTC заявка

class OrderEvent(IntEnum) - события времени жизни заявки

  • activated = 1 - заявка активирована на бирже
  • canceled = 2 - заявка снята
  • trade = 3 - заявка исполнилась полностью или частично
  • rejected = 4 - заявка была отклонена биржей
  • cancel_rejected = 5 - операция отмены заявки была отклонена биржей
  • replaced = 6 - операция изменения параметров завяки была успешно обработана биржей
  • replace_rejected = 7 - операция изменения параметров заявки была отклонена биржей

class AssetEvent(IntEnum) - события возникающие у объекта типа Asset

  • pos_changed = 1 - событие об изменении позиции (в случае купли или продажи)
  • error = 2 - возникло ошибочние событие (например заявка была отклонена биржей)
  • info = 3 - информационное событие (например, заявка исполнилась)

class EnvEvent(IntEnum) - события уровня приложения

  • reconfig = 1 - конфигурация изменилась
  • start = 2 - приложение получило start событие
  • stop = 3 - приложение получило stop событие
  • msg = 4 - приложение получило сообщение от другого приложения
  • prop_changed = 5 - приложение получило сообщение об изменении свойства
  • prop_row_added = 6 - приложение получило сообщение об добавлении новой строки в таблице
  • prop_row_deleted = 7 - приложение получило сообщение об удалении строки из таблицы

class TradingStatus(IntEnum) - торговый интервал инструмента

  • regular_trading = 1 - торгуется в основную торговую сессию
  • opening_period = 2 - торгуется в пред-торгорвый период
  • closing_period = 3 - торгуется в период закрытия
  • opening_auction = 4 - торгуется в аукцион открытия
  • closing_auction = 5 - торгуется в аукцион закрытия
  • auction = 6 - торгуется в аукцион
  • auction_price_trading = 7 - период определения цены аукциона
  • halt = 8 - инструмент не торгуется
  • stopped = 9 - торги остановлены
  • auction_closing = 10 - торгуется в аукцион зактрытия
  • unknown = 11 - неопределенный торговый интервал

class MdataFeedStatus(IntEnum) - статус фида данных

  • offline = 0 - фид данных не работает
  • online = 1 - фид данных работает
  • unknown = 2 - неизвестный статус

class OrderState(IntEnum) - статус заявки

  • initial = 0 - начальный статус заявки
  • active = 1 - заявка активна
  • destroyed = 2 - заявка удалена
  • canceled = 3 - заявка отменена
  • rejected = 4 - заявка отклонена
  • filled = 5 - заявка полнстью исполнена
  • expired = 6 - срок действия заявки истек
  • awaiting_active = 7 - заявка ожидает актитвации
  • awaiting_destroy = 9 - заявка ожидает удаления
  • awaiting_cancel = 9 - заявка ожидает отменв
  • awaiting_replace = 10 - заявка ожидает изменения

class OrderType(IntEnum) - тип заявки

  • limit = 49 - лимитная
  • market = 50 - рыночная

class PropType(IntEnum) - тип свойсва

  • string = 1 - свойство имеет строковый тип
  • boolean = 2 - свойство имеет булевый тип
  • integer = 3 - свойство имеет integer тип
  • double = 4 - свойство имеет double тип
  • time = 5 - свойство имеет time тип
  • table = 6 - свойство имеет table тип
  • date = 7 - свойство имеет date тип

Глобальные функции

set_env_event(callback:fn) - устанавливает функцию, которая будет вызываться в случае возникновения события уровня приложения (см. EnvEvent)

  • Параметры

    • callback - функция типа function(EnvEvent, dict)

    Пример:

    def env_events(event, data):
    if event == EnvEvent.reconfig:
    # reload config
    print(get_config())
    elif event == EnvEvent.start:
    # e.g. start algo
    elif event == EnvEvent.stop:
    # e.g. stop algo
    elif event == EnvEvent.msg:
    print(data)
    elif event == EnvEvent.prop:
    # property changed. data is dict {"name": "prop_name", "value": "prop_value"}
    set_env_event(env_events)

send_msg(name: str|int, data::dict) - посылает сообщение другой ноде системы

  • Параметры:

    • name - имя ноды или ее id
    • data - отправляемые данные

    Пример:

    send_msg(node="algo2", data={"parameter1": 10, "parameter2": "some usefull info"})

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

md_hbt_stop() - наоборот выключает хардбит

start() - переводит ноду в состояние active

stop() - переводит ноду в состояние inactive и снимает все активные заявки. Новые заявки посылаться не будут

Классы

class Asset(object) - определяет операции уровня инструмента

  • Конструктор:

    • Asset(name::String[, event::fn(Asset, AssetEvent, dict)]) - создает объект типа Asset
      • Параметры:
        • name - имя инструмента
        • event - необязательная вызываемая функция для обработки событий типа AssetEvent
      • Возвращаемое значение:
        • объект типа Asset или генерируется исключение, в случае если инструмент с таким именем не найден

    Пример:

    import logging
    from xroad import *
    logger = logging.getLogger()
    a1 = Asset(name="LKOH.TQBR")
    def asset_event(event, data):
    if event == AssetEvent.pos_changed:
    print(data)
    a2 = Asset(name="SBER.TQBR", event=asset_event)
  • Методы:

    • md_quote(event::fn(quote::Quote)[, snapshot::Boolean]) -> MdQuote|None - создает подписку на квоту
      • Параметры:
        • event - вызываемая функция типа fn(Quote)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
      • Возвращаемое значение:
        • объект типа Quote

    Пример:

    import logging
    from xroad import *
    logger = logging.getLogger()
    a = Asset("LKOH.TQBR")
    def on_quote(quote):
    logging.info("asset = {}, quote = {}".format(quote.asset.name, quote.data))
    a.md_quote(event=on_quote)
    • md_trade(event::fn(trade::Trade)[, snapshot::Boolean]) -> MdTrade|None - создает подписку на сделку
      • Параметры:
        • event - вызываемая функция типа fn(Trade)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
      • Возвращаемое значение:
        • объект типа Quote

    Пример:

    import logging
    from xroad import *
    logger = logging.getLogger()
    a = Asset("LKOH.TQBR")
    def on_trade(trade):
    logging.info("asset = {}, trade = {}".format(trade.asset.name, trade.data))
    t = a.md_trade(event=on_trade)
    • md_info(event::fn(Info)[, snapshot::Boolean]) -> MdInfo|None - подписывается на различную статистику по инструменту (например: OI, цена последней сделки, и т.д.)
      • Параметры:
        • event - вызываемая функция типа fn(Info)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
    • md_book(event::fn(Book), snapshot::Boolean) -> MdBook|None - subscribe to asset book
      • Параметры:
        • event - вызываемая функция типа fn(Info)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале

    Пример:

    import logging
    from xroad import *
    logger = logging.getLogger()
    a = Asset("LKOH.TQBR")
    def on_book(book):
    logging.info("asset = {}, book = {}".format(book.asset.name, book.data))
    b = a.md_book(event=on_book)
    • md_indicator(event::fn(Indicator), snapshot::Boolean) -> MdIndicator|None - подписывается на индикатор
      • Параметры:
        • event - вызываемая функция типа fn(Indicator)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
    • md_feed_status(event::fn(FeedStatus), snapshot::Boolean) -> MdFeedStatus|None - подписывается на статус фида
      • Параметры:
        • event - вызываемая функция типа fn(FeedStatus)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
    • md_stop() -> True - удалает все подписки
    • cancel() -> True - отменяет все активные заявки
    • buy([account:str, client_code:str, qty:float, price:float, tif::TimeInForce, trader_book::str, flags::int, event::fn(OrderEvent, dict), till_date:datetime, send::bool], till_date:datetime.date, send:bool, type:OrderType, comment:str, collect_trades:bool) -> Order - создает заявку на покупку
      • Параметры:
        • account - счет клиента
        • client_code - код клиента
        • qty - необязательная кол-во заявки в штуках или лотах
        • price - необязательная цена завяки
        • tif - см. TimeInForce
        • trader_book - книга трейдера
        • flags - необязательные настройки заявки
        • event - необязательная вызываемая функция для полного контроля поведения завяки
        • till_data - дата жизни заявки (используется в том случае, если tif = TimeInForce.GTD)
        • send - необязательный параметр. По умолчанию заявка посылается на биржу в момент создания. Этот параметр позволяет выключить данное поведение
        • comment - комментарий в заявке
        • collect_trades - позволяет сохранять сделки в объекте Order, чтобы впоследствии их просмативать итератором
      • Возвращаемое значение:
        • объект типа Order
        • исключение

    Пример:

    import logging
    from xroad import *
    cfg = {"account": "acc1", "client_code": "ccode1", "tif": TimeInForce.day}
    logger = logging.getLogger()
    a = Asset("LKOH.TQBR")
    b = a.buy(qty=1, price=256.12, **cfg);
    • sell([account:str, client_code:str, qty:float, price:float, tif::TimeInForce, trader_book::str, flags::int, event::fn(OrderEvent, dict), till_date:datetime, send::bool], till_date:datetime.date, send:bool, type:OrderType, comment:str, collect_trades:bool) -> Order - создает заявку на покупку
    • orders() ->OrderIter - создает итератор для получения списка ордеров
      • Параметры:
        • event - вызываемая функция типа fn(Trade)
        • snapshot - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
      • Возвращаемое значение:
        • объект типа Quote
  • Свойства:
    • name->String - имя инструмента
    • total_buy->Float - сколько всего куплено
    • avg_buy->Float - средневзвешенная цена покупки
    • total_sell->Float - сколько всего продано
    • avg_sell->Float - средневзвешенная цена продажи
    • md_stat->MdStat - возвращает статистику по инcтрументу(см. MdStat)
    • trading_status->TradingStatus - статус инструмента
    • ref->dict - возвращает спецификацию инструмента (lot_size, price precision, expiration date, etc)

class MdBook(object) - возвращаемое значение подписки на книги (см. Asset.md_book)

  • Свойства:
    • asset::Asset - ссылка на py_algo_Asset
    • data::dict - последние биржевые данные

class MdInfo(object) - возвращаемое значение подписки на информацию по инструменту(см. Asset.md_info)

  • Свойства:
    • asset::Asset - ссылка на py_algo_Asset
    • data::dict - последние биржевые данные

class MdQuote(object) - возвращаемое значение подписки на квоты (см. Asset.md_quote)

  • Свойства:
    • asset::Asset - ссылка на py_algo_Asset
    • data::dict - последние биржевые данные

class MdTrade(object) - возвращаемое значение подписки на сделки (см. Asset.md_trade)

  • Свойства:
    • asset::Asset - ссылка на py_algo_Asset
    • data::dict - последние биржевые данные

class MdStat - возвращаемое значение вызова Asset.md_stat

  • Свойства:
    • asset::Asset - ссылка на py_algo_Asset
    • settle_date->datetime:date - дата поставки
    • last->dict - последняя сделка
    • quote->dict - последняя квота
    • open_price->dict - цена открытия
    • close_price->dict - цена закрытия
    • oi->dict - open interest
    • volume->dict - торговый объем
    • vwap->dict - VWAP price
    • min_price->dict - минимально допустимая цена
    • max_price->dict - максимально допустимая цена
    • low->dict - минимальная цена сделки
    • high->dict - максимальная цена сделки
    • wa_price->dict - средняя цена

class Order - возвращаемое значение вызова Asset.buy или Asset.sell)

  • Методы:
    • cancel() - снять завяку
      • Возвращаемое значение:
        • True
    • replace(qty::float, price:float) - изменить заявку
      • Параметры:
        • qty - новое кол-во заяки
        • price - новая цена заявки
    • reset() - сбрасывает накопленную статистику
    • **trades()->TradeIter - возвращает итератор на сделки завяки
  • Свойства:
    • asset->Asset - ссылка на py_algo_Asset
    • name->str -** возвращает имя заявки, генерируемое при ее создании
    • active->bool - проверяет активна завяка или нет
    • side->str - сторона заявки: "B" - заявка на покупку, "S" - заявка на продажу
    • price->float - цена заявки, если она есть
    • avg_price->float - средневзвешенная цена исполнения
    • qty->float - кол-во заявки в штуках или лотах
    • filled_qty->float - исполненное кол-во заявки в штуках или лотах
    • state->OrderState - статус заявки py_algo_OrderState
    • type->OrderType - тип ордера py_algo_OrderType
    • exch_id->str - ID ордера на бирже
    • exch_ts->datetime.datetime -> время активации на биржк

class Timer - имплементация системного таймера

  • Конструктор:
    • Timer(name::str, event::fn()) - создает объект типа Timer
      • Параметры:
        • name - имя таймера
        • event - вызываемая по таймеру функция
  • Методы:

    • start(start::Integer|datetime.time, repeat=0) - запускает таймер
      • Параметры:
        • start - или абсолютное время запуска таймера (задается datetime.time) или относительное, задаваемое в микросекундах
        • repeat - необязательное время периодического срабатывания таймера в микросекундах
      • Возвращаемое значение:
        • None
    • stop() - останавливает таймер

    Пример:

    import logging
    import datetime
    from xroad import *
    logger = logging.getLogger()
    def call_me():
    print("timer fired")
    t = Timer(call_me)
    t.start(start=1000000, repeat=1000000) # таймер запускается через однy секунду и будет вызываться каждую
    # следующую секунду
    t.stop() # останавливаем таймер
    t.start(start=datetime.time(hour=13, minute=20, second=0) # таймер запустится в 13:20:00

Примеры алгоритмов

Этот алгоритм использует котирует один инструмент (order_assset) при изменении цены на другой (quite_asset):

Конфигурация:

1 <?xml version="1.0"?>
2 <config>
3  <node
4  log_level="debug"
5  />
6  <mdata_engine>
7  <server address="shm://${NODE_NAME}_md:15000000" />
8  </mdata_engine>
9  <ui>
10  <form>
11  <textbox name="fo_account" type="string" />
12  <textbox name="spread" type="integer" value="10" />
13  <textbox name="book_size" type="integer" value="100" />
14  <textbox name="max_fill_qty" type="integer" value="10000" />
15  <textbox name="fut_instr" type="string" value="Si-9.22" />
16  <textbox name="spot_instr" type="string" value="SBER.TQBR" />
17  <textbox name="offset" type="integer" value="0" />
18  <textbox name="bid_price" type="double" />
19  <textbox name="ask_price" type="double" />
20  </form>
21  </ui>
22 </config>

Реализация в процедурном стиле:

1 from xroad import Props, Asset, OrderEvent, EnvEvent, set_env_event, stop
2 
3 props = Props()
4 
5 spread = props["spread"].value
6 book_size = props["book_size"].value
7 max_fill_qty = props["max_fill_qty"].value
8 config = {"account": props["fo_account"].value, "collect_trades": True}
9 fut_instr = props["fut_instr"].value
10 spot_instr = props["spot_instr"].value
11 offset = props["offset"].value
12 
13 def on_env_event(event, data):
14  if event == EnvEvent.prop:
15  if data["name"] == "spread":
16  global spread
17  spread = data["value"]
18  print("spread changed to {}".format(spread))
19 
20 set_env_event(event=on_env_event)
21 
22 fut = Asset(fut_instr)
23 spot = xroad.Asset(spot_instr)
24 
25 def on_order_event(order, event, data):
26  print("order_name = {} state = {} event = {} data = {}".format(order.name, str(order.state), str(event), data))
27  if event == OrderEvent.replaced:
28  print("order {} replaced. qty = {} price = {}".format(order.name, order.qty, order.price))
29  elif event == OrderEvent.trade:
30  print("order {} trade. qty = {} price = {}".format(order.name, order.qty, order.price))
31  qty = book_size/2
32  order.replace(qty = qty)
33  for i, t in enumerate(order.trades(), start=1):
34  print("{} {}".format(i, t))
35  elif event == OrderEvent.rejected:
36  stop()
37  print("{} total_buy = {}, total_sell = {}".format(order.asset.name, order.asset.total_buy, order.asset.total_sell))
38 
39 buy_order = fut.buy(**config, event = on_order_event)
40 sell_order = fut.sell(**config, event = on_order_event)
41 
42 def on_quote(q):
43  mid_price = (q.data["bid"]["price"] + q.data["ask"]["price"]) / 2 + offset
44  print("---------------------------------------")
45  print("mid_price = {}".format(mid_price))
46  bid_price = int(mid_price * (1 - spread / 20000))
47  ask_price = int(mid_price * (1 + spread / 20000))
48  print("bid_price = {}".format(bid_price))
49  print("ask_price = {}".format(ask_price))
50  props["bid_price"].value = bid_price
51  props["ask_price"].value = ask_price
52  buy_order.replace(qty = book_size/2, price = bid_price)
53  sell_order.replace(qty = book_size/2, price = ask_price)
54 
55 spot.md_quote(event = on_quote)

тот же самый алгоритм, только написаный с использованием классов:

1 from xroad import Props, Asset, OrderEvent, EnvEvent, set_env_event, stop
2 
3 class Robot(object):
4 
5  def __init__(self):
6  self._props = Props()
7  self._spread = self._props["spread"].value
8  self._book_size = self._props["book_size"].value
9  self._max_fill_qty = self._props["max_fill_qty"].value
10  self._config = {"account": self._props["fo_account"].value, "collect_trades": True}
11  self._offset = self._props["offset"].value
12  set_env_event(event=lambda event, data: self.on_env_event(event, data))
13  self._fut = Asset(self._props["fut_instr"].value)
14  self._spot = Asset(self._props["spot_instr"].value)
15  self._buy_order = self._fut.buy(**self._config, event = lambda order, event, data: self.on_order_event(order, event, data))
16  self._sell_order = self._fut.sell(**self._config, event = lambda order, event, data: self.on_order_event(order, event, data))
17  self._spot.md_quote(event = lambda q: self.on_quote(q))
18 
19  def on_env_event(self, event, data):
20  if event == EnvEvent.prop:
21  if data["name"] == "spread":
22  self._spread = data["value"]
23  print("spread changed to {}".format(self._spread))
24 
25 
26  def on_order_event(self, order, event, data):
27  print("order_name = {} state = {} event = {} data = {}".format(order.name, str(order.state), str(event), data))
28  if event == OrderEvent.replaced:
29  print("order {} replaced. qty = {} price = {}".format(order.name, order.qty, order.price))
30  elif event == OrderEvent.trade:
31  print("order {} trade. qty = {} price = {}".format(order.name, order.qty, order.price))
32  qty = self._book_size/2
33  order.replace(qty = qty)
34  for i, t in enumerate(order.trades(), start=1):
35  print("{} {}".format(i, t))
36  elif event == OrderEvent.rejected:
37  stop()
38  print("{} total_buy = {}, total_sell = {}".format(order.asset.name, order.asset.total_buy, order.asset.total_sell))
39 
40  def on_quote(self, q):
41  mid_price = (q.data["bid"]["price"] + q.data["ask"]["price"]) / 2 + self._offset
42  print("---------------------------------------")
43  print("mid_price = {}".format(mid_price))
44  bid_price = int(mid_price * (1 - self._spread / 20000))
45  ask_price = int(mid_price * (1 + self._spread / 20000))
46  print("bid_price = {}".format(bid_price))
47  print("ask_price = {}".format(ask_price))
48  self._props["bid_price"].value = bid_price
49  self._props["ask_price"].value = ask_price
50  self._buy_order.replace(qty = self._book_size/2, price = bid_price)
51  self._sell_order.replace(qty = self._book_size/2, price = ask_price)
52 
53 
54 robot = Robot()