Для того чтобы упростить разработку алгоритмов на 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:
print(get_config())
elif event == EnvEvent.start:
elif event == EnvEvent.stop:
elif event == EnvEvent.msg:
print(data)
elif event == EnvEvent.prop:
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 - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
- Возвращаемое значение:
Пример:
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 - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
- Возвращаемое значение:
Пример:
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 - необязательный параметр, который определяет нужно ли получить снепшот данных в начале
- Возвращаемое значение:
- Свойства:
- 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() - снять завяку
- 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 - необязательное время периодического срабатывания таймера в микросекундах
- Возвращаемое значение:
- 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)
t.stop()
t.start(start=datetime.time(hour=13, minute=20, second=0)
Примеры алгоритмов
Этот алгоритм использует котирует один инструмент (order_assset) при изменении цены на другой (quite_asset):
Конфигурация:
7 <
server address=
"shm://${NODE_NAME}_md:15000000" />
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" />
Реализация в процедурном стиле:
1 from xroad
import Props, Asset, OrderEvent, EnvEvent, set_env_event, stop
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
13 def on_env_event(event, data):
14 if event == EnvEvent.prop:
15 if data[
"name"] ==
"spread":
17 spread = data[
"value"]
18 print(
"spread changed to {}".format(spread))
20 set_env_event(event=on_env_event)
22 fut = Asset(fut_instr)
23 spot = xroad.Asset(spot_instr)
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))
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:
37 print(
"{} total_buy = {}, total_sell = {}".format(order.asset.name, order.asset.total_buy, order.asset.total_sell))
39 buy_order = fut.buy(**config, event = on_order_event)
40 sell_order = fut.sell(**config, event = on_order_event)
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)
55 spot.md_quote(event = on_quote)
тот же самый алгоритм, только написаный с использованием классов:
1 from xroad
import Props, Asset, OrderEvent, EnvEvent, set_env_event, stop
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))
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))
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:
38 print(
"{} total_buy = {}, total_sell = {}".format(order.asset.name, order.asset.total_buy, order.asset.total_sell))
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)