Простейшая торговая стратегия на языке Javascript
Как уже, наверное, многим известно, QuantPro Platform разработана на языке C++, что дает ряд неоспоримых преимуществ в быстродействии и объёме исполняемого кода. Соответственно, до настоящего времени стратегии для платформы также должны были быть написаны на С++, что требовало от разработчика соответствующих знаний и навыков. Это побудило нас сделать возможность использования других языков программирования, чтобы увеличить целевую аудиторию и дать возможность программистам и продвинутым пользователям, не знающим C++, успешно разрабатывать собственные торговые алгоритмы.
С некоторых пор в нашей платформе существует возможность создания торговых стратегий на языке javascript, и в этой статье я хочу рассмотреть пример создания простейшего торгового робота с использованием этой технологии.
Поскольку в данном случае наша задача — это разобраться, что нужно сделать, чтобы запустить стратегию в работу, а не пытаться реализовать стабильно работающую стратегию, возьмем простейший алгоритм, который входит в позицию при пересечении текущей ценой максимума предыдущей свечи и выходит при пересечении значения минимума свечи, которая была до предыдущей.
Прежде всего, определимся с окружением. Для инициализации стратегии вызывается функция init(), в которой можно произвести все необходимые приготовления к работе: инициализировать переменные, запросить у системы требуемые данные, при необходимости восстановить состояние из ранее сохраненных параметров. Во время работы на каждое изменение цены по торгуемому инструменту системой будет вызвана функция onTick(), где алгоритм будет принимать все необходимые торговые решения по покупкам и продажам этого инструмента.
Помимо этого существуют еще много других функций и объектов, однако в нашем случае нас интересует singleStockTrader, который специально разработан для написания простейших алгоритмов. У него есть ряд собственных функций и свойств, которые мы рассмотрим ниже.
Итак, сначала опишем конфигурацию трейдера, где укажем значения параметров, которые стратегия будет использовать во время торговли. Эти параметры могут изменяться, например, в случае, если нужно запустить несколько таких алгоритмов на разных инструментах или таймфреймах.
1 2 3 4 5 6 7 8 9 10 |
// Объявление параметров конфигурации трейдера. var config = { // Торгуемый инструмент - фьючерс на акции Сбербанка. security : "MCXF.SBRF", // Рабочий интервал - 1 час. timeframe : Timeframe.H1, // Количество торгуемых лотов - 1. quantity : 1 } |
Далее определим функцию инициализации, где мы передадим параметры, описанные выше, в объект singleStockTrader, чтобы настроить его для работы:
1 2 3 4 5 6 |
// Функция init() вызывается при старте трейдера. function init() { // Инициализируем объект singleStockTrader. singleStockTrader.init(config.security, config.quantity, config.timeframe, 3); } |
Первые три параметра очевидны и не должны вызывать вопросов — это торгуемый инструмент, количество лотов и таймфрейм. Четвертый параметр — это количество последних баров указанного таймфрейма, которое должен хранить настраиваемый объект. Дело в том, что в функции singleStockTrader.init() происходит инициализация переменной candle_series, которая как раз и хранит эти свечи и обновляется каждый раз перед вызовом функции onTick(), где мы будем реализовывать весь алгоритм нашей работы. Таким образом, каждый раз, когда мы будем обращаться к переменной singleStockTrader.candle_series из этой функции, все нужные нам данные в ней к этому моменту уже будут актуальны. Поскольку нам нужны только предыдущая свеча и ее предшедственница, то всего нам нужно три последних всечи, что и отражено в последнем параметре.
Также для работы нам понадобится дополнительная переменная candle_changed, которая будет хранить значение признака смены бара между вызовами функции onTick():
1 2 3 |
// Объявление набора локальных переменных для хранения состояния трейдера. // Признак того, что появилась новая свеча. var candle_changed = false; |
И наконец, опишем саму функцию onTick(), где и реализуем наш алгоритм торговли:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
function onTick(tick) { // Если данный тик привел к смене бара, запоминаем это событие. В случае, если будет открыт ордер, // эту переменную нужно будет сбросить, чтобы закрытие ордера не могло произойти, пока бар не сменится. if(singleStockTrader.candle_series.is_new_bar) candle_changed = true; var position = singleStockTrader.position(); // Если трейдер в кеше, проверяем условия для входа в позицию. if(position == TraderPosition.CASH) { // Получаем предыдущую свечу (индекс 1) var prev_candle = singleStockTrader.candle_series.getCandle(1); if(prev_candle.isNull()) { // Предыдущей свечи нет, значит candle_series еще не заполнен, выходим. return; } // Если текущая цена выше максимума предыдущей свечи, входим в лонг. if(tick.price > prev_candle.high) { if(singleStockTrader.openOrder(OrderOperation.BUY)) candle_changed = false; } // Иначе, если текущая цена ниже минимума предыдущей свечи, входим в шорт. else if(tick.price < prev_candle.low) { if(singleStockTrader.openOrder(OrderOperation.SELL)) candle_changed = false; } } // Иначе, если трейдер в позиции, проверяем условия для выхода из позиции. // Выходить из позиции мы можем только после смены бара. else if(candle_changed) { // Получаем свечу, которая была до предыдущей (индекс 2) var prev_candle = singleStockTrader.candle_series.getCandle(2); if(prev_candle.isNull()) { // Свечи еще нет, выходим. return; } // Если мы в лонге и текущая цена меньше минимума пред-предыдущей свечи, закрываем ордер. if(position == TraderPosition.LONG && tick.price < prev_candle.low) singleStockTrader.closeOrder(); // Если мы в шорте и текущая цена больше максимума пред-предыдущей свечи, закрываем ордер. else if(trader.position == TraderPosition.SHORT && tick.price > prev_candle.high) singleStockTrader.closeOrder(); } } |
Вкратце опишу, что тут происходит. Каждый раз, когда в систему попадает новое событие о прошедшей сделке на бирже по нужному нам инструменту, у нашего трейдера будет вызвана функция onTick(). В ней мы каждый раз с самого начала проверяем, не привело ли это событие к появлению новой свечи в singleStockTrader.candle_series, и если это так, запоминаем этот факт в переменной candle_changed, которую мы определили ранее.
Далее, с помощью вызова функции singleStockTrader.position() мы определяем, в какой позиции находится наш трейдер. Если он в кеше и не имеет отрытых ордеров (строка 9), то мы проверяем набор условий, которые могут привести к решению об осуществлении сделки. Для этого мы должны получить предыдущую свечу (строка 12), удостовериться, что она не пустая, и сравнить текущее значение цены с максимумом и минимумом этой свечи. Следует обратить внимание, что нумерация свечей идет в обратном порядке, то есть текущая свеча идет под номером 0, предыдущая под номером 1, и так далее.
Если одно из условий было удовлетворено, то есть цена стала выше максимума предыдущей свечи (строка 19) или меньше ее минимума (строка 24), трейдеру с помощью вызова singleStockTrader.openOrder() отправляется команда на открытие ордера соответственно в целью покупки или продажи целевого инструмента. Если эта команда была успешно зарегистрирована, то мы сбрасываем переменную candle_changed, тем самым обеспечивая условие, которое проверяется в строке 33, и по которому закрытие ордера сможет быть произведено не ранее появления следующей свечи. Следует иметь в виду, что успешная регистрация открытия или закрытитя ордера не гарантирует его успешного выполнения, поскольку сделки может не произойти по ряду причин, начиная от проблем с сетью и заканчивая недостатком денег на счете. Система позволяет самостоятельно контролировать действительное выполнение ордеров, однако этот аспект мы рассмотрим в одной из следующих статей.
Итак, когда трейдер перешел в позицию LONG или SHORT, условие в строке 9 выполняться не будет, и поэтому далее будет проверяться условие в строке 33. Как только произойдет смена бара и переменная candle_changed снова станет равной true, мы сможем получить возможность проверить условия закрытия ордера. Здесь все происходит аналогичным образом: сначала мы получаем свечу по индексу 2 (помним, что нам нужна пред-предыдущая свеча), проверяем, что она не пуста и далее сравниваем текущую цену с максимумом и минимумом этой свечи. Если одно из условий выполняется (строки 44 и 48), то происходит закрытие ордера и трейдер переходит в состояние CASH.
Теперь нам следует настроить стратегию для запуска в работу. Для этого нужно подготовить файл конфигурации со следующим содержимым:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
CandleBreak_MCXF_SBRF { script_files { conf/SimpleSingleStockStrategy.js conf/CandleBreak_MCXF_SBRF.js } order_securities { MCXF.SBRF } subscribe_securities { MCXF.SBRF } display_timeframe H1 } |
Секция script_files описывает список файлов со скриптами javascript, которые будут загружены платформой при запуске трейдера в работу. В данном случае у нас указаны два файла, находящихся в папке conf: CandleBreak_MCXF_SBRF.js, который содержит код, описанный в этой статье, и SimpleSingleStockStrategy.js, где реализован объект singleStockTrader. В секциях order_securities и sibscribe_securities указываются соответственно инструменты, которые будут торговаться и инструменты, по которым мы хотим получать данные. display_timeframe нужен для того, чтобы дать указание системе, какой таймфрейм должен показываться в пользовательском интерфейсе для данной стратегии.
Прогон теста этой простейшей стратегии на исторических данных в три месяца показал такой результат:
В данном случае нам было больше важно не получить какую-либо доходность, а удостовериться, что написанная нами стратегия работает без ошибок, поэтому полученный график критически оценивать не стоит.
Скрипты и конфигурационный файл, описанные в статье: CandleBreak_MCXF_SBRF.zip
А можно ответить на некоторые вопросы по данному материалу https://quantpro.ru/forums/topic/6437 ?