README.md

    О проекте

    qupy-trader - комплекс программ выполняемый в среде Linux и прелназначенный для разработки и тестирования торговых алгоритмов взаимодействующих с терминалом QUIK. Взаимодействие осуществляется через библиотеку https://github.com/cia76/QuikPy. Для хранения информации используется СУБД Postgresql.

    ВНИМАНИЕ: Программа не предназначена для неподготовленного пользователя. Нужны базовые знания по linux и питону, в противном случае всё закончится если не на этапе установки, так при написании конфига для ТС. ВНМАНИЕ: программа находится на ранем этапе разработки и может выкидывать фортеля. По мере наработки моторесурса баги будут уходить, фичи появляться, но как известно, разработку нельзя закончить, ее можно только пректатить.

    Состав

    Комплекс состоит из следующих компонентов:

    • README.md этот файл
    • INSTALL.md инструкция по установке
    • hub.py - шлюз выполняющий функции концентратора данных от QUIK (свечи, результаты обработки заявок) и отправляющий на QUIK заявки сгенерированные торговыми алгоритмами. Хранение данных и взаимодействие с другими модулями осуществляется через СУБД.
    • ctl.py - утилита для прямой (минуя шлюз) работы с quiksharp для отладки, импорта исторических данных, создания и просмотра заявок. Во многом надёргана из Examples/ проекта QuikPY но сведеных в одной точке и выдающая информацию в более понятном (на взгляд автора) виде.
    • agent.ctl - утилита взаимодействующая с QUIK через хаб для отладки взаимодействия.
    • agent.py - программный модуль торгового агента реализующий функции тестирования алгоритма на исторических данных и его посильного выполнения в реальном времени (устарел)
    • watchdog.py - утилита контроля работы хаба и агентов
    • config.yml.example - пример основного конфига используемого всеми утилитами
    • [hub|watchdog|agent|agent2].actions.yml - конфиг действий для хаба (сделана отдельным файлов что бы не захламлять основной конфиг)
    • examples/*.yml - примеры торговых стратегий (не про заработок, а про как писать) которая передается agent.py и содержит описание стратегии.
    • samples/scheme.sql - схема sql
    • samples/scheme_dumped.sql - схема полученная дампом
    • samples/sample.csv - заготовка для csv-кеша (по-моему уже нигде не используется)
    • local.d/ - каталог оверрайдов на экшены watchdo/hub/agent
    • iss.py - выгрузка календаря с iss (подробне docs/ISS.md)
    • iss-feeds.py - выгрузка баров с iss (подробне docs/ISS.md)
    • docs/
      • WINEQUIK - эксплуатация квика под вайном
      • ISS - детали по интеграции MOEX ISS API
      • FUNDING - детали интеграции учета фандинга
      • ALLTRADES - работа с обезличенными сделками

    Терминология проекта

    Может отличаться от общепринятой, ну как назвалось.

    • хаб, шлюз (hup.py) - прокладка между агентами и квиком (скриптом quiksharp если быть точным)
    • quiksharp - плагин для квика дающий доступ к его апи для внешних программ.
    • агент - экземпляр agent.py обрабатывающих заданную стратегию
    • конфиг агента - базовые настройк и алгоритм торговли записанный в yaml файл обрабатываемый агентом.
    • бар - запись об итогах торгов с заданной дискретностью (интервалом)
    • CLASS_CODE - класс инструмента, например SPBFUT. Доступные классы можно увидеть через ctl.py
    • SECURITY_CODE - код конкретного рычного инструмента. Например GAZR
    • INTERVAL - временной интервал данные по торгам внутри которого суммируются
    • code - кодировка инструмента в формате CLASS_CODE.SECURITY_CODE.INTERVAL
    • ticker - тоже что и код но без указания интервала
    • resample - передискретизация данных с одного интервала на другой (например с минуток на пятнашки)
    • гард (англ. guard) - контрольная функция проверяющая кастомизируемое условие и выполняющее действие. В основном применяется для выполнения задач из *.actions.yml.

    Установка

    Производится в соответствии с инструкцией INSTALL.md

    запуск шлюза и заполнение истории

    Так как оригинальный плагин quiksharp не умеет работать с несколькими соединениями, связь с ним осуществляется через прокладку hub.py. Модуль осуществляет заполнение таблиц с историей инструментов, транслирует заявки в квик и обрабатывает калбеки (приход новых баров, срабатывание заявок итп). В общем случае он не требует отдельно настройки и взводится запуском с ключом --run:

    root@tra2:~/qupy-trader# ./hub.py --run
    ...[notag]: Loadingi yaml: config.yml
    ...[connect_db]: База данных 'qupydb' подключена
    ...[prepare_debug_log]: Debug log: logs//hub.py.debug.20240831-151412
    ...[lib_open_qp_connection]: quik provider created
    ...[main_run]: Started up in 0:00:00.017015
    [quik:True][ltime:10:12:55][stime:10:12:54][data_gap:  48s] {'new': 6, 'duplicated': 0, 'updated': 0}
    

    С заданной в конфиге периодичностью он будет выводить:

    • qs: True/False - квик соединен с сервером или нет
    • lt: локальное время (local time)
    • st: время квик-сервера (server time)
    • dg: сколько секунд прошло с последнего пришедшего по подписке бара (для остлеживания что подписка не сломалась)
    • qd: глубина очереди. Если растет, значит хаб не справляется с потоком данных. В норме, должны быть значения меньше десятка (зависит от продолжительности паузы между циклами выполнения)
    • статистика по данным
      • new новые бары
      • дубликаты
      • обновленных
      • число стаканов
      • число обзличенных сделок

    Подписка на инструменты осуществляется путем добавления записей в таблицу subscribes напрямую, через конфиг (секция hub/subscriptions) или через утилиту:

    root@tra2:~/qupy-trader# ./agent.ctl --subscribe SPBFUT.CNYRUBF.1
    ...[notag]: Loadingi yaml: config.yml
    ...[connect_db]: База данных 'qupydb' подключена
    ...[prepare_debug_log]: Debug log: logs//agent.ctl.debug.20240831-152612
    ...[notag]: Subscribtion to SPBFUT.CNYRUBF.1 success
    ...
    
    

    Если подписка прошла успешно, хаб выдаст что-то вроде:

    ...[main_run]: New subscription found id: 1, code: SPBFUT.CNYRUBF.1
    +++[fastsync_dbcache]: No cached data for SPBFUT.CNYRUBF.1
    ...[fastsync_dbcache]: Loading QUIK history for SPBFUT.CNYRUBF.1
    ...[fastsync_dbcache]: Quik history data for SPBFUT.CNYRUBF.1 started from 2025-01-21 11:41:00 until 2025-01-24 23:48:00 have 2998 bars
    ...[fastsync_dbcache]: Cache for SPBFUT.CNYRUBF.1 will be updated by 2998 new bars
    ...[fastsync_dbcache]: Cache for SPBFUT.CNYRUBF.1 updated
    ...[qp_subscribe_by_code]: Subscribed to [SPBFUT.CNYRUBF.1]
    ...[set_subscription_status]: Updating status of subscription with id:[1]  to [active]
    +++[main_run]: Stream queue have 1249 record. Response may be slow
    ...[main_run]: Подписка на [SPBFUT.CNYRUBF.1] подтверждена
    ...[main_run]: Queue cleared, normal processing now
    +++[main_run]: Stream queue have 1607 record. Response may be slow
    ...[main_run]: Queue cleared, normal processing now
    
    

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

    После загрузки данных в бд (далее - кэш), их можно увидеть в том виде, как они дсоступны для агентов:

    root@tra2:~/qupy-trader# ./agent.ctl --print-cached SPBFUT.CNYRUBF.1|head
    ...[notag]: Loadingi yaml: config.yml
    ...[connect_db]: База данных 'qupydb' подключена
    ...[prepare_debug_log]: Debug log: logs//agent.ctl.debug.20240831-153005
    ...[main]: Data started from: 2024-08-29 09:59:00
                           open    high     low   close  volume
    datetime                                                   
    2024-08-29 09:59:00  11.869  11.869  11.869  11.869     114
    2024-08-29 10:00:00  11.869  11.887  11.869  11.878    1745
    2024-08-29 10:01:00  11.879  11.889  11.879  11.886    1808
    2024-08-29 10:02:00  11.888  11.917  11.887  11.912    5641
    ...
    

    Необоходимо учитывать, что процесс дозагрузки истории может занимать время. Если очередь баров на обработку превышает 3000, то будет выдано предупреждение и статистика обработки после окончания:

    +++[quik_connected_callback]: 31.08.2024 15:28:58 - {'data': '', 't': 1725107324796.2, 'cmd': 'OnConnected'}
    +++[main_run]: Stream queue have 229066 record. Response may be slow
    ...[main_run]: Queue cleared, normal processing now
    [server time: 15:28:39] {'new': 86417, 'duplicated': 1486143, 'updated': 0}
    

    При первом старте может возникать сообщение вида:

    +++[main_run]: Возможно разрыв в исторических данных для TQBR.GAZP.60: '2024-09-04 20:00:00' - '2024-09-04 21:00:00'
    False (datetime.datetime(2024, 9, 4, 21, 0), 'TQBR.GAZP.60', 122.55, 122.6, 122.06, 122.11, 66983.0)
    

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

    Зарузка исторических данных

    Квик отдает достаточно ограниченную глубину истории по инструментам (в зависимости от интервала), и ее может быть не достаточно дя проверк стратегий. Шлюз выдает бесшовную историю (сливает свежие данные из квика с кэшом), поэтому при необходимости исторические данные на произвольную глубину можно предзагрузить из csv. Штатно поддерживается тот формат который выдает ресурс https://daytradingschool.ru/trejderu/skachat-kotirovki-istoricheskie-dannye-po-fyuchersam/ (там доступны минуты).

    Для импорта данных нужно указать путь до файла, код инструмента и интервал:

    root@tra2:~/qupy-trader# ./ctl.py --import-bars ../GOLD.txt.short:SPBFUT.GOLD.1
    ...[notag]: Loadingi yaml: config.yml
    ...[main]: Обработка файла '../GOLD.txt.short', код 'SPBFUT.GOLD.1'
    ...[connect_db]: База данных 'qupydb' подключена
    ...[csv_file_to_pd]: Первая запись файла: 0
    ...[csv_file_to_pd]: Последняя запись файла: 8
    ...[csv_file_to_pd]: Кол-во записей в файле: 9
                 datetime       open       high        low      close  volume           code
    0 2009-01-11 10:33:00  859.90002  859.90002  854.00000  854.00000      13  SPBFUT.GOLD.1
    1 2009-01-11 10:37:00  854.40002  854.40002  854.40002  854.40002       2  SPBFUT.GOLD.1
    2 2009-01-11 10:39:00  855.29999  855.29999  855.29999  855.29999       8  SPBFUT.GOLD.1
    3 2009-01-11 10:47:00  856.29999  856.29999  856.29999  856.29999       1  SPBFUT.GOLD.1
    4 2009-01-11 10:51:00  856.20001  856.20001  856.20001  856.20001       8  SPBFUT.GOLD.1
    5 2009-01-11 10:54:00  856.50000  856.50000  856.50000  856.50000      10  SPBFUT.GOLD.1
    6 2009-01-11 10:58:00  857.00000  857.00000  857.00000  857.00000       1  SPBFUT.GOLD.1
    7 2009-01-11 11:00:00  857.00000  857.00000  857.00000  857.00000       1  SPBFUT.GOLD.1
    8 2009-01-11 11:03:00  857.00000  857.00000  856.20001  856.20001       8  SPBFUT.GOLD.1
    

    После этого кешированные данные должны стать доступными агенту:

    oot@tra2:~/qupy-trader# ./agent.ctl --print-cached SPBFUT.GOLD.1
    ...[notag]: Loadingi yaml: config.yml
    ...[connect_db]: База данных 'qupydb' подключена
    ...[prepare_debug_log]: Debug log: logs//agent.ctl.debug.20240831-163424
    ...[main]: Data started from: 2009-01-11 10:33:00
                              open       high        low      close  volume
    datetime                                                               
    2009-01-11 10:33:00  859.90002  859.90002  854.00000  854.00000      13
    2009-01-11 10:37:00  854.40002  854.40002  854.40002  854.40002       2
    2009-01-11 10:39:00  855.29999  855.29999  855.29999  855.29999       8
    2009-01-11 10:47:00  856.29999  856.29999  856.29999  856.29999       1
    2009-01-11 10:51:00  856.20001  856.20001  856.20001  856.20001       8
    2009-01-11 10:54:00  856.50000  856.50000  856.50000  856.50000      10
    2009-01-11 10:58:00  857.00000  857.00000  857.00000  857.00000       1
    2009-01-11 11:00:00  857.00000  857.00000  857.00000  857.00000       1
    

    Примечание: на настоящий момент импорт возможен только при условии, что данных в базе по данному инструменту и интервалу еще нет. Если данные требуется догрузить, то необходимо либо удалить старую историю, либо осуществить импорт с ограничением по стартовой дате добавив опцию --start-dt '2024-06-29 23:49:00' что бы избежать коллизий.

    В качестве альтернативы можно выгружать данные с сервера мосбиржи через ISS. Подробнее docs/ISS.md

    Разработка торговых стратегий и бэктест

    Терминология

    • runner - экземпляр агента выполняющий торговую стратегию
    • источники (sources) - символическое имя инструмента для адресации внутри стратегии
    • streams - потоковый индикатор. Принимает на вход фрейм пандас и выдает результат в тот-же датафрейм в отдельную колонку.
    • variabless - переменные типа true/false вычисляемые на основе потоковых индикаторов. Для упрощения доступа, перед прогоном булей данные клонируются в словарь списков с доступом по индексу
    • actions - действия, - код выполняемый при выполнении условий. Например изменение размера позиции на основании комбинации переменных. Фактически действия ограничены возможностями питона.

    тестирование на истории

    Перенесено в docs/BACKTEST.md

    калибровка глубины пересчета

    Многие потоковые индикаторы врут если число расчитываемых баров не достаточно. Как правило это решается тем, что пересчет по полной истории идет на каждой итерации (на каждый новый бар), но это не всегда подходит (пересчет может занимать много времени). Так как в системе пересчет происходит только по числу баров заданных параметром recalc_depth, необходимо убедиться, что значение достаточно для корректного расчета потоковых индикаторов. Для этого необходимо вызвать агента с параметром калибровки. Агент начнет увеличивать объем пересчитываемых данных до тех пор, пока значение индикаторов не перестает изменятся и полученную цифру выведет как результат

    Для агента версии 2 отдельного параметра нет, т.к. если recalc_depth не указан, то он будет подбираться автоматически, а если указан - проверен на достаточность при старте.

    Торговля

    Сделана на живую нитку. По сравнению с первой версией агента работа с заявками более акуратна, но тем не менее поддерживаются только лимитные или рыночные заявки. От пропусков сигналов из-за проскальзывания можно более-менее застраховаться только заявками по-рынку, со всеми вытекающими. В итоге, в таком режиме всё это можно запускать только под пристальным контролем и с пониманием, что программы не боятся и не думают, а программные ошибки могут принимать самые причудливые формы.

    При выставлении параметра trade = False в секции раннера, создаваемые позиции будут виртуальными (иметь статус ‘virtual’ в бд) как следствие не будут выставляться на рынок. При этом их учет будет производится по цене закрытия предыдущего бара.

    Первый запуск производится передачей ключей --init path/to/agent.yml --clean-state --run, например:

    ./agent.py --init local/agent-v2-bars.yml --run --clean-state
    

    После первого запуска с –init, последующие можно осуществлять просто указав имя агента, при этом конфиг будет выгружен из БД.

    ./agent.py --name agent-v2-bars -r
    

    Параметр –clean-state переинициализирует состояние агента (сделки, состояние депозита, позиции). Вызов с –init переписывает только конфиг.

    Состояние запущенных агентов можно увидеть командой ./agent.ctl --list-agents

    Заявки

    Работа с заявками коцептуально отличается от привычной схемы ‘сигнал-открыли-заявку-сигнал-закрыли заявку’. Реализованная схема про соответствие сигнал-заявка вообще ничего не знает, она отслеживает исключительно соответствие целевой позиции агента (которая может изменяться произвольно на основании сигналов) и текущей (вычисляемой на основании успешно выставленных заявок). Это тащит за собой ряд проблем, но взамен даёт возможность выстраивать позиции произвольного уровня вложенности. Имеет-ли такой подход ссмысл, покажет только время.

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

    При проблемах на квик-сервере (такое бывает например при сильной волатильности) заявки могут проходить с задержкой. Например отправка заявки квиком отвалилась по таймауту, хаб проставил ей статус инвалид и забил. На следующем баре агент попытается снова привести в соответствие целевые и текущую позиции, опять будет выставлена заявка, а квик-сервер через некоторое время напрмер продышался и сообщил что заявка отработала и старая и новая. Шлюз поменяет статус, агент по идее сменит размер текущей позиции - но всё это чисто теоретически. Поэтому если шлюз рапортует о проблемах с выставлением заявок, лучше вообще притормозить автоматику. У автора в связи с этими нюансами поведения был крайне познавательный опыт при работе на одной из коммерческих систем, когда она не получая от квика ответа всаживала по несколько одинаковых заявок, а они потом отрабатывали пачкой и вместо позиции 10 лотов в лонг, ты получал их 50.

    действия по сигналу

    Действия предпринимаемые при срабатывании сигнала определаются в поле function/functions (см. пример examples/execs-example.yml). Так как по сути ожидается выражение передаваемое exec, это позволяет производить не только действия по изменению позиции, но и выполнять произвольные команды. В примере в частности на один из сигналов вызывается ls -l.

    время реакции

    Следует учитывать, что от момента прихода на хаб нового бара, до момента когда его увидит агент может пройти до двух секунд (при условии, что хаб не загружен прожёвыванием очереди). Она складывается из периодичности опроса очереди баров заполняемой калбэками квика (раз в секунду) и периодичности опроса бд агентом (тоже секунда). Если задержка больше, следует посмотреть отладочный лог хаба (logs/hub.py.debug) на предмет времен прихода пакета (параметр pqt_enqueued) и времени извлечения его из очереди (pqt_dequeued).

    Частоту опроса очередей хабом можно увеличить снизив время задержки между циклами hub/query_interval, но ставить его в 0 или False не желательно, т.к. процессорное время он и субд жрать будут, а выигрыш по времени на фоне общей логики будет мизерный. Во избежании каких нибудь странностей, периодичность опроса сервера quik на предмет времени (фактически пинг) будет производится не чаще раза в секунду при любом значении задержки.

    Частоту опроса субд агентом так же можно увеличить параметром runner/refresh по аналогии с хабом.

    прочая фигня

    переменные

    • TSDEBUG=1 - отладочная информация

    статус агента

    Хранится в бд в таблице agents. Строчки расписаны ниже, а словарь внутреннего состояния выглядит следующим образом:

    ./agent.py --name NAME --show-state
    

    При необходимости состояние агента можно отредактировать передав команду –edit-state, но следует делать это крайне акуратно что бы не сломать стабильное состояние.

    БД

    описание таблиц

    • bars: таблица исторических данных
      • datetime дата бара
      • code код инструмента
      • open/high/low/close/volume
    • subscriptions подписки на бары
      • id сквозной идентификатор
      • code
      • status - если invalid то подписка не удалась
    • orders таблица заявок. записи создаются агентами и обрабатываются хабом.
      • datetime дата создания
      • agent имя агента создавшего заявки
      • code
      • operation_type покупка или продажа
      • price цена заявки. если 0 то тип приказа по рынку. иначе лимитная.
      • quantity обьем
      • client_code код генеренный агентом (фактически таймстамп )
      • status статус заявки
        • active заявка выставлена но не исполнена. проставляется хабом
        • canceled заявка отменена
        • filled заявка выполнена
        • rejected заявка отклонена квиком
        • invalid заявка отклонена хабом
        • processed промежуточное состояние перед active
        • req-new - выставляется агентом для новой заявки
        • req-cancel - выставляется агентом для отмена заявки
      • order_num - идентификатор заявки который возвращает квик после ее принятия
      • data - список словарей полученных от квика на обработку заявки
    • trades сделки. заполняется хабом по мере прихода колбэков на выставленные заявки
      • datetime
      • code
      • operation_type
      • price цена по которой операция была выполнена
      • quantity
      • trade_num номер сделки в понимании квика
      • order_num соответствует аналогичному полю заявки по которой сделка состоялась
      • brokerref соответствует полю client_code связанной заявки
      • data список словарей пришедших от квика
    • info таблица с общей информацией, в частности ISS
    • quotes таблица с котировками (стакан)

    работа на windows

    Теоретически оно должно работать, но как минимум есть проблема с отслеживанием таймаута на опрации взаимодействия с квиком, так как оно сделано через signal.SIGALRM который насколько я поняд в винде не але. Никто не мешает сделать альтернативную реализацию, но мне оно не надо. Желающие приглашаются к станку.

    watchdog

    Применяется для доп.контроля хаба и агентов (не повисли, не вылетели по исключению итп). Возможен запуск на реплике СУБД т.к. не осуществляется запись в базу.

    Отдельно идет контроль потока баров подписок. По-умолчанию гард срабатывает если за время 5хИнтервал не пришло ни одного бара. Параметр может настраиваться заданием переменно max_gap_td через ./hub.py --edit-subscription SPBFUT.CNYRUBF.1что нибудь типа:

    {'max_gap_td': 1}
    

    Примечание: если вотчдог работает на слейве, на нем регулярно будут возникать коллизии при выборке (сессия замирает и пока не пересоединишся обновлений приходить не будет). Как побороть не нашёл (рецепту про hot_standby и прочие чудеса не помогл), поэтому периодически скрипт будет ругаться на то что выявлен сбой (отслеживается отставание реплики) и соединение будет переустанавливться. Помогает не 100%, иногда бывают ложные срабатывания на повисших агентов, однако после переустановки соединения все нормализуется.

    +++[2024-12-27 09:04:05.238190][main_run]: Обнаружена остановка репликации. Переустановка соединения.
    +++[2024-12-27 09:53:04.582433][main_run]: Обнаружена остановка репликации. Переустановка соединения.
    

    архивация стакана

    Так как рабочих стратегий торговли в стакане у меня нет, пока сделано сохранение котировочной таблицы на каждом баре. В статистике хаба в поле ‘quotes’ выводится общее число изменений котировок за период. Для включения подписки на изменения стакана нужно добавить в опции подписки переменную ‘quotes’ с любым (пока) значением. Редактировать опции можно через ./hub.pu --edit-subs SPBFUT.CNYRUBF.1 и там что нибудь типа:

    {'max_gap_td': 10, 'quotes': 'onbar'}
    

    На каждом приехавшем баре в таблицу quotes будет сохраняться словарь состояния.

    Описание

    платформа для разработки и тестирования торговых систем интегрированная с QUIK

    Конвейеры
    0 успешных
    0 с ошибкой