Заметка

Робот RSI с трейлинг-стопом на QLua

  11  

Продолжаем осваивать встроенный в Quik язык для создания торговых роботов. Разберем элементы кода торговой системы, построенной на основе индикатора RSI, со скользящим стоп-лоссом. Приложением к заметке готовый робот для Quik, параметры которого можно менять по своему усмотрению.

В своей прошлой статье (http://robostroy.ru/community/article.aspx?id=653) я рассказал о новом языке программирования qlua, который появился в Quik. В статье я привел простейший пример робота на qlua, который торгует по пересечению мувингов. Сегодня расскажу о более сложном роботе, который не просто будет торговать по сигналам, но еще и сдвигать стоп-лосс по ходу цены, постепенно перемещая его в зону безубыточности.

Итак, описание  стратеги.

Сигналы. Сигналами в данной ТС будет служить выход индикатора RSI из зоны перекупленности и перепроданности. Именно выход, а не вход. То есть, допустим, мы решили, что зона перекупленности – это ниже 30. Значит, сигнал приходит тогда, когда индикатор опустился ниже 30, а потом поднялся обратно выше 30.

Когда покупаем. Покупаем, когда RSI выходит вверх из нижней границы, конкретное значение можно подставить в робота в виде константы.

Когда продаем. Продаем, когда RSI выходит вниз из верхней границы, конкретное значение можно подставить в робота в виде константы.

Когда закрываемся. Закрываемся, когда сработает стоп-лосс. Никакого иного способа закрытия не предусмотрено. Прибыль мы получаем в том случае, если ко времени закрытия стоп-лосс переместится в зону безубыточности.

Когда сдвигаем стоп-лосс. Стоп-лосс сдвигаем, когда разница между текущей котировкой и стоп ценой выше уровня трейлинг стопа. Этот уровень так же в роботе можно задать константой.

Полный текст робота в приложении, в статье я разберу только наиболее важные моменты. Итак, самое начало робота, тут идут переменные настройки:

--Параметры:

p_classcode="TQBR" --Код класса

p_seccode="LKOH" --Код инструмента

p_account="L01-00000F00" --Код счета

p_clientcode="51914" --Клиентский код

p_count=2 --Размер позиции

p_spread=0.7 --Проскальзывание

p_sell_level_RSI=60 --уровень RSI, при котором продаем

p_buy_level_RSI=40 --уровень RSI, при котором покупаем

p_TRANS_ID="2" --идентификатор транзакций робота, нужен для того, что бы робот отличал свои транзакции от транзакций других роботов и ручных транзакций

p_TRANS_ID_STOP="3" --по работе со стоп ордерами

p_stop_loss_level=3 --Уровень стоп-лосса

p_traling_stop_level=3.1 --Уровень стоп-лосса, при котором мы "подтягиваем" стоп-лосс

p_file = io.open("D:\\1\\userlog.txt", "w") -- тут надо указать путь к файлу лога

Разберем, что значат эти настройки.

p_classcode – код класса бумаги, его можно посмотреть в текущей таблице параметров.

p_seccode – код самой бумаги, его можно посмотреть в текущей таблице параметров.

p_account – код счета, его вы можете посмотреть, когда делаете заявку, в ней выбирается код счета и код клиента.

p_clientcode– код клиента, его тоже можно посмотреть в заявке.

p_count – это размер позиции в лотках, которым робот будет торговать

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

p_sell_level_RSI – верхняя граница RSI, при которой продаем (когда робот выходит вниз из этой области).

p_buy_level_RSI — нижняя граница RSI, при которой покупаем (когда робот выходит вверх из этой области).

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

p_TRANS_ID_STOP — идентификатор транзакции стоп заявок. Этот параметр нужен для того, что бы робот мог узнавать свои стоп заявки. Если вы одновременно запускаете несколько роботов, то в каждом из этих роботов нужно поставить разные уникальные значения этих параметров, при чем, они не должны совпадать с параметром p_TRANS_ID ни одного из роботов. Тоесть, если у вас три робота, то вы можете в пером поставить p_TRANS_ID равный двум, а p_TRANS_ID_STOP – трем. Во втором это будет уже 4 и 5, а в третьем 6 и 7.

p_stop_loss_level – уровень стоп-лосса в единице цены инструмента. Это разница между ценой сделки и ценной исполнения стоп заявки. Например, если мы установили это параметр равным трем рублям, а робот купил акции Лукойла по 1837 рублей, то робот выставит стоп-лосс по 1834.

p_traling_stop_level – уровень разницы между текущей котировкой и ценой исполнения стоп-лосса. Например, пусть p_traling_stop_level=5. Мы выставили стоп-лосс по 1834 и находимся в длинной позиции. В этом случае стоп-лосс будет передвинут, когда котировки достигнут уровня 1839. И встанет стоп-лосс на  цену с учетом p_stop_loss_level. Тоесть, если p_stop_loss_level=3, тогда цена исполнения стоп-лосса будет 1836. И так каждый раз, когда разница между текущей котировкой и новым стоп-лоссом достигает уровня p_traling_stop_level.

p_file – переменная (дескриптор) файла лога. Имя задается в кавычках как параметр функции io.open, тоесть, дескриптор сразу же создается.

Теперь поговорим о том, как работает программа.

Как и в моей прошлой статье, в роботе организован цикл, в котором через определенный промежуток времени вызывается функция “robot”. Этот промежуток времени определятся параметров функции sleep. У меня он поставлен 2 сек.  – 2000 мс. Если хотите поставить свое время, измените этот параметр. Сам цикл находиться в функции main.

Функция “robot” построена примерно так же, как и в роботе моей предыдущей статьи – проверяются сигналы (только на этот раз используется индикатор RSI),  а потом вызывается функция trade, которая выставляет заявку. Единственная разница в том, что тут используется проверка in_trade – если мы уже в позиции, то не торгуем.  Для мониторинга цен и своевременно изменения стоп-лосса используется функция stop_loss_control. Она напротив, вызывается только тогда, когда мы вставил в позицию (функция, как вы заметили, когда смотрели код, расположена в ветви else).

Но первый стоп-лосс выставляется не в функции stop_loss_control, а в обработчике событий OnTrade:

--Обработчик события сделки

function OnTrade(trade)

      nord=trade["order_num"] --номер заявки

      price=trade["price"] --цена сделки

      to_log("Совершена сделка: номер заявки "..tostring(nord).."; цена "..tostring(price)..": количество "..tostring(trade["qty"]))

      if nord==order_num then

            qty=trade["qty"] --Количество бумаг в последней сделке в лотах

            if direction=="B" then

                  count=count+qty

            else

                  count=count-qty

            end

            send_stop_loss(direction,count,price)

            last_price=price

      end

end

Что такое обработчик событий? Это предопределенное имя функции, которую будет вызывать сам Quik, если вы, конечно, разместите эту функцию у себя в скрипте. Функция OnTrade вызывается каждый раз, когда происходит сделка (на вашем счете). У этой функции есть параметра – структура данных, описывающих данную сделку. В ней есть такие данные, как количество лот инструмента, цена, номер заявки и так далее. Вот по этим-то и данным мы формируем стоп-лосс и выставляем его, вызвав функцию send_stop_loss. Эта функция похода на функцию trade и этого и прошлого робота и я ее здесь приводить не буду, если интересно, см. полный исходник в приложении.

У вас может возникнуть вопрос: а что это за такое сравнение и что за переменная order_num?:

      if nord==order_num then

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

      --если это наша транзакция, обработаем ее

      if id==p_TRANS_ID then

            nord=trans_reply["order_num"] --Номер заявки

           

            to_log("Обработка транзакции номер заявки "..nord)

           

            --если заявка выставилась — запоминаем ее номер, иначе считаем, что мы не в сделке

            if nord==nil or nord==0 or nord=="0" then

                  message("Заявка не выставилась ",1)

                  in_trade=false

            else

                  order_num=nord

            end

      end

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

Теперь разберемся, как работает stop_loss_control. Сначала мы ищем нашу стоп заявку:

function stop_loss_control()

      local N=getNumCandles("Price")

      if N==nil or N==0 then return end

      p_t,p_n,p_i=getCandlesByIndex("Price", 0, N-1, 1)

     

      local NO=getNumberOf("stop_orders")

      local is_stop_order=true

      if NO==nil or NO==0 then

            is_stop_order=false

      else

            t_so = SearchItems("stop_orders", 0, NO-1, fn, "order_num")

      end

Для поиска используем функцию SearchItems, она у нас вызывает CALLBACK-функцию fn:

function fn(par1)

      if stop_loss_num=="" then

            return false

      end

      if tonumber(par1) - tonumber(stop_loss_num)==0 then

            return true

      else

            return false

      end

end

В ней производиться поиск стоп заявки по номеру. Номер стоп заявки присваивается в  так же в обработчике OnTransReply:

      if id==p_TRANS_ID_STOP then

            message("Сообщение транзакции стоп ордера "..trans_reply["result_msg"],1)

            nord=trans_reply["order_num"] --Номер заявки

           

            --если заявка выставилась — запоминаем ее номер, иначе считаем, что мы закончили выставлять стоп заявку

            if nord==nil then

                  message("Стоп заявка не выставилась ",1)

                  in_set_stop_loss=false

                  stop_loss_num=""

            else

                  stop_loss_num=nord

            end

      end

Разбираем stop_loss_control дальше. Далее у нас идет код, который выставляет стоп заявку, если мы в позиции, а ее еще нет по какой либо причине:

      --если стоп-лосса нет, то возможно, его надо выставить

      if t_so==nil and is_stop_order then

            is_stop_order = false

      end

      if is_stop_order then

            if t_so[1]==nil then

                  is_stop_order = false

            end

      end

     

      if not(is_stop_order) then        

            if in_trade and count~=0 then

                  if count>0 then

                        send_stop_loss("B",count,last_price)

                  else

                        send_stop_loss("S",count,last_price)

                  end

            end

            return

      end

А дальше мы вычисляем разницу в ценах, проверяем условие стоп-лосса и, при необходимости перевыставляем его. До этого кода дойдет только в том случае, если у нас уже есть выставленный стоп-лосс:

      to_log("stop_loss_control найден стоп ордер count="..count)

      t_so_item=getItem("stop_orders", t_so[1])

      delta_price=0;

     

      if count>0 then

            to_log("count>0")

            delta_price=p_t[0].close-t_so_item.condition_price;

            l_direction="B"

      else

            to_log("count<=0")

            delta_price=t_so_item.condition_price-p_t[0].close  

            l_direction="S"

      end

      to_log("delta_price="..delta_price.."    t_so[0].qty="..t_so_item.qty.."      p_t[0].close="..p_t[0].close)

     

      if delta_price>=p_traling_stop_level or count~=t_so_item.qty then

            to_log("Послылаем новую стоп заявку price="..p_t[0].close.." количество "..count)

            send_stop_loss(l_direction,count,p_t[0].close)

      end

Ну, и последнее. Немаловажный момент. Перевыставления стоп заявок происходит следующим образом: предыдущая заявка удаляется, новая выставляется:

--Послать стоп заявку

function send_stop_loss(a_direction,a_count,a_price)

      to_log("send_stop_loss in_set_stop_loss="..tostring(in_set_stop_loss))

      if not(in_set_stop_loss) then

     

            if stop_loss_num~="" then

                  delete_stop_loss(stop_loss_num)

            end

Функция delete_stop_loss работает аналогично send_stop_loss и Trade – заполняются поля данных транзакции и вызывается функция sendTransaction. Отличается только тем, что поле ACTION равно KILL_STOP_ORDER, а номер стоп заявки мы помещаем в поле STOP_ORDER_KEY.

На этом все, удачной торговли!.

Прикрепленные файлы

·   Робот RSI.rar

Комментарии

Максим Милованов — 22 мая 2014 г.

У меня возник вопрос по поводу переменных - p_TRANS_ID, точнее их использования для нескольких роботов. В данном контексте имеется ввиду что роботы запущены на едином счету ?
Может быть проще запускать роботов с использованием субсчетов? Как по вашему проще?

0 +

megabax — 22 мая 2014 г.

Максим Милованов, В принципе, да, идея состоит в том, что эти роботы будут работать на одном счете. Насчет субсчетов. Вот это я не проверял, но, скорее всего, в программу придется внести изменения, что бы она проверяла по номеру счетов (если p_TRANS_ID одинаковые). Если p_TRANS_ID поставить разные, то субсчета уже будут не важны, программа распознает по p_TRANS_ID.

0 +

drghestykmb — 23 июня 2014 г.

на фьючерсы какие данные вводить?

1 +

megabax — 2 июля 2014 г.

drghestykmb, Точно так же смотрим текущую таблицу параметров, только добавим туда интересующие фьючерсы, что бы видеть код бумаги и код класса.

0 +

ndrjjzbv — 1 июля 2014 г.

При запуске скрипта появляется "Синтаксическая ошибка во время компиляции"

0 +

megabax — 2 июля 2014 г.

ndrjjzbv, Должно быть, у вас скрип не в той кодировке. Вы его блокнотом открывали? Если октрыть в блокноте и сохранить, то возможно, кодировка и слетела. Попробуйте скачать заново и открыть в нотепад++

0 +

ndrjjzbv — 2 июля 2014 г.

megabax, повторная загрузка файла и открытие его в notepad++, не решило проблему, выдает ту же ошибку. И еще, при попытке скачать другие файлы из ваших тем, не приходит смс подтверждение, в результате чего скачать их не получается.

0 +

megabax — 7 июля 2014 г.

ndrjjzbv, Пардон, файл маленько покоцанный, в ближайшее время обновлю.

0 +

orekton — 7 июля 2014 г.

megabax, файлик обновили

0 +

orekton — 3 июля 2014 г.

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

0 +

ndrjjzbv — 3 июля 2014 г.

Все получилось, смс на Теле2 не приходит, на мегафон пришло без проблем.

0 +

drghestykmb — 4 июля 2014 г.

загрузил файл.пишет.nil.график рси с роботом настроить нужно.вместо ммвб фортс ввел и данные .порядок настройки нужен.

0 +

megabax — 7 июля 2014 г.

drghestykmb, Не понял немного вопрос. Порядок настройки на ФОРТС тот же, что и на мамбу, указывается данные бумаги и данные клиентского счета.

0 +

drghestykmb — 8 июля 2014 г.

85 строка ошибка

0 +

megabax — 8 июля 2014 г.

drghestykmb, Какая именно ошибка, можно ее текст? При каких обстоятельствах возникает?

0 +

drghestykmb — 8 июля 2014 г.

Syntax error while compiling D:\защитн... ...robot_work-SiZ4.lua:85: ')' expected near ','
при загрузке после распаковки.записан через блокнот в расширение.переписаны на фьючерсы данные. с виду не видно ничего непонятного.

0 +

Leha80 — 9 июля 2014 г.

Orekton, возможно ли робот подстроить под себя. Те он сам покупает, а продает через определенный интервал в автоматическом режиме?

0 +

orekton — 9 июля 2014 г.

Leha80, возможно, код открыт, вы можете его как угодно дорабатывать. К тому же это скорее демонстрационная система и, если вы собираетесь ее использовать для реальной работы, дорабатывать ее нужно.

0 +

drghestykmb — 9 июля 2014 г.

пробовал этого робота.открывает он сам .потом иногда встает.вопрос напрашивается.что медленнее надежнее?купайл или луа.луа для сс+ и контролировать.может код робота такой?на минутке работает на 5ти молчит.получасами.времени не было.дорабатывать конечно.универсальные блоки надо искать под стратегии. да луа быстрее .

0 +

drghestykmb — 10 июля 2014 г.

пробовал на SRU4 и на RIU4 вместе.малыми лотами.цену с сбера на риу написала и сбер продала. через стопы.система написала превышен диапазон.где то просачилось.RIU= 8680 .в стакане 135800.код не надежен?

0 +

megabax — 10 июля 2014 г.

drghestykmb, Это учебный пример, проверок на корректность настроек там нет.

0 +

just_pablo — 14 июля 2014 г.

Добрый день! Скачал на пробу, заполнил параметры под РИУ4, пишет при запуске
Robot.lua:47: attempt to index field '?' (a nil value)

надо еще какие-то идентификаторы на графиках прописывать?

0 +

just_pablo — 14 июля 2014 г.

just_pablo, открыл график с идентификатором RIU4, добавил цену PRICE и индекатор RSI. Ошибка таже

0 +

just_pablo — 14 июля 2014 г.

just_pablo, всем спасибо, разобрался

0 +

le9i0nx — 4 сентября 2015 г.

just_pablo,
для не телепатов
http://robostroy.ru/community/blog/image.ashx?id=2380
http://robostroy.ru/community/article.aspx?id=796
Вобщем надо отметить нужные индикаторы чтоб их скрипт мог найти

0 +

drghestykmb — 18 августа 2014 г.

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

0 +

drghestykmb — 19 августа 2014 г.

[MACD] пересекает [MACD]-Signal покупка снизу вверх.

0 +

megabax — 19 августа 2014 г.

drghestykmb, вам лучше всего переписать под себя блок сигналов. По аналогии, только индикаторы ваши поставить.

0 +

drghestykmb — 21 августа 2014 г.

понятно нужно выискивать позиции по лимитам.потом стопировать на количество позиций.программу не получится использовать.теперь блок сигналов (нужна таблица сигналов(стохастики,вильямсы...).правильно написать.или просто [MACD]-Signal заменить?почему то программа на уровне 40 не работает точно.если развернётся рси и тогда покупает.продаёт через стоп хорошо.на откатах.продажа наборот.в принцыпе в позицию не вошло бы пока ниже 40 не было бы.и в принцыпе ждать не надо!

0 +

drghestykmb — 25 августа 2014 г.

LAST_RSI<RSI сигнал bay.ПРИ ПЕРЕМЕНЕ НАПРАВЛЕНИЯ ДВИЖЕНИЯ РСИ СООТВЕТСТВЕННО ЛОНГ И ШОРТ.А ТРЕЛИНГ СТОП ПО ЦЕНЕ ВЫХОД.ПРЕДЫДУЩЕЕ ЗНАЧЕНИЕ РСИ ЗАПОМИНАТЬ КОЛЛЕКЦИЕЙ .1 ВАРИАНТ.ТЕКСТОВЫЙ ФАЙЛ 2Й ВАРИАНТ .ЧТО БЫСТРЕЕ И НАДЁЖНЕЕ.

0 +

drghestykmb — 25 августа 2014 г.

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

0 +

drghestykmb — 25 августа 2014 г.

трелинг на рси

0 +

drghestykmb — 25 августа 2014 г.

RSI>LAST_RSI bay

0 +

drghestykmb — 25 августа 2014 г.

текстовый файл,переменная... .что надежнее.

0 +

drghestykmb — 25 августа 2014 г.

запомнить предыдущее рси.

0 +

drghestykmb — 25 августа 2014 г.

извините не пишет много комментарий.обрезает

0 +

orekton — 25 августа 2014 г.

drghestykmb, добрый день! комментарии поправили. автор заметки собирался в отпуск, он может быть недоступен

0 +

drghestykmb — 25 августа 2014 г.

жду .треллинг на рси.вход рси 30-70.уровни долго ждать

0 +

drghestykmb — 26 августа 2014 г.

function robot()
local MACD =getNumCandles("MACD-RIU4")
local N=getNumCandles("Price-RIU4")
t,n,i=getCandlesByIndex("Price-RIU4", 0, N-1, 1)
MACD_t, MACD_n, MACD_i=getCandlesByIndex("MACD-RIU4", 0, MACD-3, 2)
MACD-Signal_t, MACD-Signal_n, MACD-Signal_i=getCandlesByIndex("MACD-RIU4", 0, MACD-3, 2)

--сигнла обрабатываем только если мы не в позиции
if not(in_trade) then
--сигнал на продажу (MACD Signal меньше пересекает уровень продажи сверху вниз)
if MACD_t[0].close> MACD-Signal then
Trade("S",count+p_count,t[0].close-p_spread)
end

--сигнал на покупку (RSI пересекает уровень покупки снизу вверх)
if MACD_t[0].close> MACD-Signal then
Trade("B",p_count-count,t[0].close+p_spread)
end

else
--проверить состояние стопа, не надо ли его сдвигать или изменить количество
stop_loss_control()
end

что не верно?

0 +

omg — 28 сентября 2014 г.

не ищите халявы, при любом раскладе Робот RSI.rar, будет сливать)

0 +

orekton — 28 сентября 2014 г.

omg, тут и кривой капитала нет. это пример, который может помочь освоить язык

0 +

robotqlua — 28 октября 2014 г.

Добрый день!

Прошу открыть доступ к прикрепленным файлам с телефоном без +7.
Спасибо.

0 +

orekton — 28 октября 2014 г.

robotqlua, качайте

0 +

robotqlua — 18 ноября 2014 г.

Добрый день!

Прошу подсказать как определить номер основной и сигнальной линии для RSI, Stochastic, MACD.
Спасибо.

0 +

frogofrock — 5 марта 2015 г.

всем привет! Интересная статья. Я новичок в роботах. Прикрутил этого робота к терминалу, но при пересечении уровней на покупку или продажу выдает ошибку в 48 строке a nil value.

0 +

frogofrock — 5 марта 2015 г.

ps: вот такая ошибка "robot_work.lua:53: attempt to index field '?' (a nil value)"

0 +

drghestykmb — 7 апреля 2015 г.

вы не искали причину плохой работы сразу 2х инструментов RIM иSRM.цену одного вставляет в цену другого.а если они были бы в одном диапазоне цен.тогда бы цена была принята биржей.это луа язык?.помехозащищенность инструментов должна быть.значит что написано на луа всё смешивается.

0 +

drghestykmb — 7 апреля 2015 г.

придумал.нужно фильтровать инструмент.работает хорошо на одном.допустим если пропускает номер стопзаявки проверять на какой инструмент.найти причину..просачивание.значит рси другого инструмента.

0 +

asvag — 11 октября 2017 г.

Не совсем понял зачем нужен лог файл?
p_file = io.open("D:\\1\\userlog.txt", "w") -- тут надо указать путь к файлу лога

0 +

Написать комментарий

Чтобы написать комментарий, необходимо авторизоваться.

Написать администратору