orekton
orekton личный блог
17 июля 2014, 15:08

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

 
Продолжаем осваивать встроенный в 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.
На этом все, удачной торговли!.

Код робота тут http://robostroy.ru/community/article.aspx?id=765
9 Комментариев
  • SL
    17 июля 2014, 15:56
    Тут еще б пригодилась кривая доходности :)
    • Turboslon
      17 июля 2014, 16:05
      SL, f(t) = f(t-1) + 0,4-rnd(1)
  • Максим Милованов
    17 июля 2014, 16:10
    Отличная системка на QLUA, спасибо!
  • VK
    13 апреля 2020, 10:36
    скажите, а где еще можно скачать этого робота?
  • kiris
    26 января 2022, 23:56
    Здравствуйте! Ссылка на скачивание не работает, может у кого-нибудь есть рабочая, или архив?

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн