Блог им. orekton

Робот 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
Данная публикация является личным мнением автора. Мнение владельца сайта может не совпадать с мнением автора.
    1.9К | ★21
    9 комментариев
    Тут еще б пригодилась кривая доходности :)
    avatar
    SL, f(t) = f(t-1) + 0,4-rnd(1)
    avatar
    SL, это скорее пример. Будет полезен тем, кто начинает писать на QLua
    avatar
    Отличная системка на QLUA, спасибо!
    Кот Матроскин, в умелых руках может и заработать
    avatar
    скажите, а где еще можно скачать этого робота?
    avatar
    Здравствуйте! Ссылка на скачивание не работает, может у кого-нибудь есть рабочая, или архив?
    avatar

    Читайте на SMART-LAB:
    Фото
    Средние доходности облигаций в зависимости от кредитного рейтинга. От B- до AA+
    Средние доходности облигаций в зависимости от кредитного рейтинга (бледные столбцы — доходности без сглаживания). И как они изменились за...
    Фото
    Роснефть: скромная прибыль за 2-й квартал, но зато наполняют трубу Восток Ойл. Почему так?
    Фото
    Хэдхантер проведёт Headconf 2026
    Фото
    Рубль снова упал - на календаре третий квартал. Анализ исторической сезонности курса рубля
    Здравствуйте! Мне стало интересно проверить, существует ли статистически значимая сезонность курса рубля относительно равновзвешенной корзины...

    теги блога orekton

    ....все тэги



    UPDONW
    Новый дизайн