Секреты TSLab | Торговые роботы | События
16 Апр

Торговый робот “Бегемот”

Данной статьей откроем целый цикл статей на тему торговых роботов сделанных в TSLab. За время работы с TSLab уже был написан вагон разных торговых роботов на заказ и для себя. Самые интересные технические решения и понравившиеся конструктивные особенности буду описывать в каждой статье. Раскрывать торговую логику я не буду, поэтому искателям халявных граалей тут делать нечего. Для остальных, тут будет много интересного, как мне кажется. Первым пробным шаром будет торговый робот “Бегемот”. Естественно, что все названия роботов условны :).

Торговый робот “Бегемот”. Техническое задание.

Данный робот был написан на заказ.

Итоговая цена: 16000 рублей.

Исходное техническое задание включало в себя достаточно обычную пробойную торговую логику. Главным условием, была необходимость работать с большим объемом позиции на мало ликвидных инструментах. Естественно, хотелось иметь хороший вход, поэтому не шло речи про маркет заявки. Обязательно нужно было применять только лимитные заявки. Заявка должна была двигаться постепенно в сторону дорогих для нас цен до тех пор, пока не будет завершен набор позиции. Так же и сброс позиции происходит лимитками.

Торговый робот Бегемот

Рисунок 1. Пример набора позиции.

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

Все остальные моменты торговой логики были тривиальны и не заслуживают особого внимания. А имя “Бегемот” появилось потому что скрипт реагирует только на большие движения и сразу большими объемам входов.

Выбор модели скрипта.

В результате анализа исходных условий и возможностей TSLab, было решено делать торговый робот “Бегемот” через прямое управление ордерами (ISecurityRt) с пересчетом “сделка” и квитированием (что это, чуть далее). У данного решения есть свои плюсы и минусы.

Плюсы:

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

Минусы:

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

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

Реализация

В самом начале реализации, необходимо было решить следующие вопросы:

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

Логгирование

Для быстрого и гибкого логгирования был написан типовой шаблон скрипта на прямом управлении ордерами. В нем был задействован написанный ранее фреймворк для многоуровнего логгирования. Данный шаблон скрипта решал следующие задачи:

  • обеспечивал обвязку торговой логики в плане логов и перехвата ошибок
  • перехватывал любые ошибки возникающие в логике скрипта и выводил их в лог
  • обеспечивал ротацию логов и на каждый день создавал новый лог
  • обеспечивал уникальное именование лог файлов вида “PriceOneChanRtTrade_SBER1M_2015.04.08_RT.log”
  • в случае возникновения ошибок останавливал логику скрипта до рестарта агента
  • при каждом старте агента автоматически логгировал стартовые параметры
  • обеспечивал логику перехода скрипта из состояния None в Started, а далее в Stopped, то есть всю логику смены состояний, что позволяло предотвращать выполнение торговой логики до наступления необходимых стартовых условий.
[2015.04.06 14:02:15.218] Debug: 
[2015.04.06 14:02:15.219] Direct: *****    START    *****
[2015.04.06 14:02:15.219] Direct: Debug: 1
[2015.04.06 14:02:15.220] Direct: MoveOrderInterval: 0,2
[2015.04.06 14:02:15.220] Direct: WaitTime: 750
[2015.04.06 14:02:15.220] Direct: SlipStep: 0,1
[2015.04.06 14:02:15.221] Direct: OffTime: 190000
[2015.04.06 14:02:15.221] Direct: ForbidShortStart: 18
[2015.04.06 14:02:15.221] Direct: ForbidShortEnd: 24
[2015.04.06 14:02:15.222] Direct: ForbidLongStart: 18
[2015.04.06 14:02:15.222] Direct: ForbidLongEnd: 24
[2015.04.06 14:02:15.222] Direct: DepoSize: 20000
[2015.04.06 14:02:15.223] Direct: DepoRiskPct: 1,2
[2015.04.06 14:02:15.223] Direct: ShowRisk: 1
[2015.04.06 14:02:15.223] Debug: ScriptState: None -> Starting
[2015.04.06 14:02:15.224] Debug: ScriptState: Starting
[2015.04.06 14:02:15.261] Debug: ScriptState: Starting -> Started
[2015.04.06 14:02:15.262] Debug: fullRecalc: false -> true, LastBarDate: 02.04.2015 18:39:40
[2015.04.06 14:02:15.271] Debug: ------------ конец пересчета --------------

Вот так выглядит старт любого скрипта использующего написанный шаблон.

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

По мере дальнейшей реализации торговой логики, все ключевые точки скрипта были подробно прологгированы. Пример логов одного из пересчетов представлен ниже:

[2015.04.08 18:56:50.390] Debug: ------------ начало пересчета --------------
[2015.04.08 18:56:50.391] Debug: ScriptState: Started
[2015.04.08 18:56:50.391] Debug: ordersRecalc: false -> true, LastOrdersRecalc: 08.04.2015 18:56:50
[2015.04.08 18:56:50.392] Debug: LeSize: 159. Начинаем отработку сигнала LE.
[2015.04.08 18:56:50.392] Debug: LeDate: 08.04.2015 14:56:49, LePrice: 69,3799987792969, LeSize: 159.
[2015.04.08 18:56:50.392] Debug: Сигнал уже имеет активную заяку, проверяем передвижение.
[2015.04.08 18:56:50.393] Debug: Закончили отработку сигнала LE.
[2015.04.08 18:56:50.395] Debug: ------------ конец пересчета --------------

Таким образом мы имеем всю необходимую информацию для анализа любых штатных и нештатных ситуаций.

Запоминание внутреннего состояния скрипта

В связи с тем, что набор и сброс позиции производится в несколько шагов, а  пересчет по “Сделка”, необходимо запоминать состояние скрипта каждый раз. Для этого использовался кэш скриптов. Всю необходимую информацию для работы скрипта на каждом пересчете сохраняем в кэш скриптов. Для этого был создан специальный класс State, экземпляр которого использовался для хранения информации. Использование объектов гораздо удобнее сохранения каждого элемента в кэш напрямую, так как достаточно получить ссылку на объект из кэша в начале скрипта, и далее работать только с ней, не производя сохранения в кэш измененных данных.

    internal class State : StateBase
    {
        // последний бар для которого был расчет графики
        public DateTime LastBarDate { get; set; }
        public IList<double> PctRisk { get; set; }
        public IList<double> Profit { get; set; }

        // когда послед раз считали сигналы и считали отработку ордеров
        public DateTime LastSignalRecalc { get; set; }
        public DateTime LastOrdersRecalc { get; set; }

        public State()
        {
            LastBarDate = DateTime.MinValue;
        }
    }

Выше представлена часть класса State, показывающая общий принцип его построения.

Обеспечение защиты от сбоев и дисконнектов

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

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

Фреймворк обеспечивал следующие возможности:

  • удобный способ маркировки данных, которые должны быть сброшены на диск
  • сохранение только численных данных, дат и булевских данных. Списки и прочее невозможно.
  • автоматическое чтение и сохранение данных на диск
  • автоматическое уникальное именование файла кэша вида “PriceOneChanRtTrade_SBER1M_RT.cache”
  • защиту “от дурака”, то есть запрет записи в один файл кэша для разных агентов в автоматическом режиме на базе анализа имен инструментов, агентов и хеша параметров.

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

var cacheMan = new CM(this, ctx, sec, _cacheKey, true, _filesDir);
var state = CM.LoadOrBuild<State>();

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

Обеспечение высокой скорости работы

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

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

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

   // если началась новая свеча
   if (state.LastBarDate < sec.Bars.Last().Date)
   {
       state.LastBarDate = sec.Bars.Last().Date;
       newBarRecalc = true;
       ctx.LogDebug("newBarRecalc: false -> true, LastBarDate: {0}", state.LastBarDate);
   }

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

Логика передвижки ордеров нуждается в более быстрой реакции на рыночную ситуацию, поэтому она запускается не чаще раза в 1 секунду. Этого вполне достаточно для “Бегемота”. Пример кода выдачи квитанции:

  // если прошла секунда с прошлой отработки ордеров
  timePasssed = now - state.LastOrdersRecalc;
  if (timePasssed > ordersRecalcInterval)
  {
      ordersRecalc = true;
      state.LastOrdersRecalc = now;
      ctx.LogDebug("ordersRecalc: false -> true, LastOrdersRecalc: {0}", state.LastOrdersRecalc);
  }

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

Итоговая структура всей торговой логики вышла очень прозрачной и удобной для доработки и переработки.

Торговый робот Бегемот

Рисунок 2. Структура кода

Выбранная схема с квитированием обеспечила время расчета скрипта в районе 3 мс, при условии ограничения числа бар подаваемых в агента. Большое число бар, вызывает значительные задержки на отрисовку графики и скрипт может выполняться 150 мс и более при высоком уровне загрузки процессора.

Итоги

В результате работы ТЗ было полностью реализовано. Получены новые инструменты для использования в будущих скриптах:

  • менеджер кэша с кэшированием на диск
  • шаблон скрипта для алгоритмов на прямом управлении ордерами

Результаты тестирования данного алгоритма на истории, без учета специфичной логики входа порциями:

Торговый робот Бегемот

Рисунок 3. Результаты тестирования на истории

Результатов торговли еще нет, так как алгоритм медленный. Потому и бегемот :).


comments powered by HyperComments

ra81
2015-04-17 08:29:45
несколько сделок в месяц. Но больших :)
IgorZh
2015-04-17 10:08:14
МОЛОДЦЫ!
Igor_t
2015-04-17 02:20:50
На сколько медленный:) Сколько же он "разгоняется"?:) Приблизительно...
ra81
2015-06-05 18:51:40
Спасибо :). Интересные задачи интересно делать и интересно про них рассказывать
Иван
2017-01-01 03:40:23
Казино Вулкан раздают деньги сегодня http://cenforce100.ru/casino-vulkan.php
14
Июл
2017

Доверительное управление. Результаты в июне 2017 года.

Доверительное управление. Результаты в июне 2017 года. Июнь индекс РТС вновь провел преимущественно в боковых движениях, а… »

11
Июн
2017

Доверительное управление. Результаты в мае 2017 года.

Доверительное управление. Результаты в мае 2017 года. В мае “болтанка” индекса РТС продолжилась, на паре… »

7
Май
2017

Доверительное управление. Результаты в апреле 2017 года.

Доверительное управление. Результаты в апреле 2017 года. В апреле мы наблюдали очередной месяц “боковика” по… »

2
Апр
2017

Доверительное управление. Результаты в марте 2017 года.

Доверительное управление. Результаты в марте 2017 года. В марте волатильность на рынке несущественно выросла. Все… »

7
Мар
2017

Доверительное управление. Результаты в феврале 2017 года.

Доверительное управление. Результаты в феврале 2017 года. Февраль был самым коротким торговым месяцем, к тому же… »