Заметка

Qlua для чайников. Часть 8. Отвечаю на ваши вопросы. Часть 2

  5  

Продолжаю отвечать на ваши вопросы.

Вопрос: Есть ли какой-нибудь простенький примерчик с анализом волатильности и торговлей на пробой?

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

Для определения средней волатильности мы применим индикатор ATR (Average true rage). Стоит заметить, что в Quik-e, что бы программа на qpile или qlua могла брать с графика данный, график должен быть открыт (в том числе и индикатор). Поэтому давайте прямо сейчас откроем какой нибудь график, например, ЛУКОЙЛ:

 

И добавим к нему индикатор:

 

При добавлении выберите, что бы график добавился в новое окно:

 

Там же можно настроить цвет линий:

 

Параметры, например, количество периодов:

 

И ввести идентификатор:

 

Идентификатор нужен, что бы мы могли обратиться к нему в программе. В дальнейшем можно редактировать параметры индикатора щелкнув по графику правой кнопкой мыши и во всплывшем меню выбрать «Параметры графика»:

 

Аналогично можно отредактировать параметры свечек (Price), так же задав для них идентификатор:

 

А теперь будем учиться получать данные с графиков. Для начала со свечек:

local N=getNumCandles("MyPrice")

t,n,i=getCandlesByIndex("MyPrice", 0, N-2, 2)

message("Предыдущая свеча close="..t[0].close,1)

message("Текущая свеча close="..t[1].close,1)

     

Данный скрипт выдаст два числа: сначала закрытие предыдущей свечи, а потом закрытие текущей:

 

А затем для текущей свечи:

 

 

Стоит заметить, что значение close текущей свечи постоянно меняется, так как она еще не сформирована. Так же могут меняться higt и low  у этой свечи. А вот предыдущая и все те, что левее их, остаются прежними – они уже сформированы.

Теперь разберем конструкции:

local N=getNumCandles("MyPrice")

t,n,i=getCandlesByIndex("MyPrice", 0, N-2, 2)

Сначала мы получаем количество свечей. Дело в том, что функция getCandlesByIndex требует указывать, с какой по счету свечи мы получаем данные, а счет начинается с самой левой свечки. Она имеет номер 0, а самая права, текущая, соответственно N-1 – на единицу меньше количества свечек.

Теперь о синтаксисе функции getCandlesByIndex. У нее четыре параметра. Первый – идентификатор графика, который мы ставили выше. Если у  нас несколько графиков, то мы как раз сможем их различать по этому идентификатору. Второй параметр – номер линии, для свечей тут должен стоять нуль. Третий – с какой свечи мы начинаем получать свечи, ее номер. В нашем случае это N-2 – мы хотим получить предпоследнюю и последнюю свечки. И, наконец, четвертый параметр – количество свечек, которые мы хотим получить.

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

local N=getNumCandles("MyPrice")

t,n,i=getCandlesByIndex("MyPrice", 0, N-1, 1)

message("Текущая свеча close="..t[0].close,1)

А если нам нужно только одна предыдущая свеча, то мы можем сделать так:

local N=getNumCandles("MyPrice")

t,n,i=getCandlesByIndex("MyPrice", 0, N-2, 1)

message("Текущая свеча close="..t[0].close,1)

Обратите внимание, что функция getCandlesByIndex возвращает целых три значения. Самое первое, как вы уже поняли, это сама таблица свечей. Второе значение – это количество свечей в полученной таблице. Казалось, бы зачем оно нужно? Вроде как количество должно быть равно четвертому параметру, не? Не всегда. Как вы думаете, сколько свечей вернет вот такая конструкция:

local N=getNumCandles("MyPrice")

t,n,i=getCandlesByIndex("MyPrice", 0, N-3, 10)

message("Количество свечек в возвращенной таблице="..n,1)

Вовсе не 10, так как начиная со свечи N-3 мы имеем только три свечки, поэтому программа выдаст:

 

А вот если мы напишем:

local N=getNumCandles("MyPrice")

t,n,i=getCandlesByIndex("MyPrice", 0, N-3, 2)

То количество свечей будет равно 2.

А вот что это за третье возвращенное значение? А это так называемая легенда – подпись графика:

 

По умолчанию это название бумаги, но можно отредактировать и поставить свое название.  

Аналогично мы можем брать данные и с индикатора:

local N=getNumCandles("MyATR")

t,n,i=getCandlesByIndex("MyATR", 0, N-2, 2)

message("Предыдущее значение индикатора "..t[0].close,1)

message("Текущее значение индикатора "..t[1].close,1)

           

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

Далее вопрос: а как торговать на пробой уровней? Можно использовать стоп лоссы. Они как раз сработают при пробое: купят при пробое вверх и продадут при пробое вниз. Стоп лосс выставляется практически точно так же, как и обычная заявка (см.  урок 1 http://robostroy.ru/community/article.aspx?id=773), только в поле ACTION ставиться значение NEW_STOP_ORDER, а так же обязательно заполняются поля STOP_ORDER_KIND, STOPPRICE и EXPIRY_DATE.

После прихода очередной свечи может возникнуть необходимость переставить стоп лоссы (если они еще не сработали).  Для преставления стоп лоссов мы удаляем старые стоп лоссы и выставляем новые. Как удалять см. урок 6 (http://robostroy.ru/community/article.aspx?id=790). Стоп заявки удаляться точно так же, как и обычные, только ACTION ставиться  KILL_STOP_ORDER.  

Что бы не выставлять повторно стоп заявки, будем запоминать номер выставленной заявки, а если заявка удалена или исполнена – снимать этот номер. Для этого в процедуре OnStopOrder (это предопределенное имя) будем анализировать флаги. Как анализировать флаги см. урок 4 (http://robostroy.ru/community/article.aspx?id=783).

Может возникнуть вопрос: А как обеспечить, что бы проверка сигнала была только на каждой новой свече? Вообще, можно воспользоваться функциями CreateDataSource и SetUpdateCallback, но до них мы еще дойдем. А пока, в простейшем примере торговли на пробоях с анализом волатильности, который находиться в приложении 1, я сделал проще: старые уровни для пробоя запоминаются в момент выставления стоп ордеров. Когда новые уровни отличаются от старых (а это может быть только с приходом новой свечи, так как уровни рассчитываются по предпоследней свече, а она не меняется), робот пересматривает стопы, в случае необходимости удаляет и выставляет новые.

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

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

Ну что-ж, тему с написанием робота спредера, где была необходима работа со стаканом, я закончил уроком 6 (http://robostroy.ru/community/article.aspx?id=790).  Что касается работы со свечками и индикаторами – я ее рассмотрел выше, когда отвечал на предыдущий вопрос, а пример с роботом на индикаторе «скользящая средняя» рассмотрен тут: http://robostroy.ru/community/article.aspx?id=653.

По работе со свечами, могу дополнить обещанным чуть выше описанием функций CreateDataSource и SetUpdateCallback. Эти функции позволяют создать CALLBACL функцию и привязать ее к графику. Эта функция будет вызываться каждый раз, как только на графике произойдут какие либо изменения. Вот пример:

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

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

     

--Служебные переменные

is_run = true

function cb(index)

      local t = ds:T(index)

      local str = "свеча № "..tostring(index).." из "..tostring(ds:Size()).." open="..ds:O(index)

      str = str.."  high="..tostring(ds:H(index)).."  low="..tostring(ds:L(index))

      str = str.."  close="..tostring(ds:C(index)).." объем: "..tostring(ds:V(index))

      str = str.." дата и время="..tostring(t.day).."."..tostring(t.month).."."..tostring(t.year).."  "..tostring(t.hour)..":"..tostring(t.min)

      str = str..":"..tostring(t.sec).."  и "..tostring(t.ms).." мс"

      message(str,1)

end

function main()

      ds=CreateDataSource(p_classcode, p_seccode, INTERVAL_M1)

      ds:SetUpdateCallback(cb)

      while is_run do

            sleep(100)

      end

end

 

function OnStop(stop_flag)

      is_run=false

end

Запустите его и каждый тик на графике вы будете получать вот такое вот сообщение:

 

Каким образом при помощи данной функции мы можем отследить приход новой свечи? Очень просто. Если изменился номер текущей свечи, либо ее дата и время, то это новая свеча.

Вопрос. Интересно не само программирование, тут просто логика... а именно разбор наиболее часто применяемых, стандартных операторов языка и их синтаксис.

На самом деле мы уже разобрали достаточно много конструкций qlua. Но вот работу с окнами еще не разбирали. Помните, на qpile робот назывался «портфелем» и имел специальную таблицу-окно, в которую можно было выводить различную информацию, например, сообщать пользователю о ходе работы робота?

Вот так, например, выглядела эта таблица для робота, который мы разбирали на уроке Стохастик с фильтром на Qpile (часть 2) (http://robostroy.ru/community/article.aspx?id=246):

 

На qlua Тоже можно создавать такие окна, при чем не одно, как на qpile, а несколько. Различаются такие окна по дескрипторам. Дескриптор – это его идентификационный номер, который возвращает функция AllocTable(). После того, как при помощи функции AllocTable() был создан дескриптор, он может быть использован для создания окна. Сначала мы объявляем заголовки при помощи функции AddColumn, затем при помощи CreateWindow мы открываем это окно. После чего можем добавлять в него строки, например, при помощи функции InsertRow.

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

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

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

     

--Служебные переменные

is_run = true

id=0

function cb(index)

      local t = ds:T(index)

      row = InsertRow(id, -1)

      local str=tostring(t.day).."."..tostring(t.month).."."..tostring(t.year).."  "..tostring(t.hour)..":"..tostring(t.min)..":"..tostring(t.sec)

      SetCell(id, row, 1, tostring(index))

      SetCell(id, row, 2, tostring(ds:O(index)))

      SetCell(id, row, 3, tostring(ds:H(index)))

      SetCell(id, row, 4, tostring(ds:L(index)))

      SetCell(id, row, 5, tostring(ds:C(index)))

      SetCell(id, row, 6, tostring(str))

      SetCell(id, row, 7, tostring(ds:V(index)))

      message(str,1)

end

function main()

      id=AllocTable()

      AddColumn(id, 1, "№ свечи", true, QTABLE_INT_TYPE, 11)

      AddColumn(id, 2, "open", true, QTABLE_DOUBLE_TYPE, 10)

      AddColumn(id, 3, "high", true, QTABLE_DOUBLE_TYPE, 10)

      AddColumn(id, 4, "low", true, QTABLE_DOUBLE_TYPE, 10)

      AddColumn(id, 5, "close", true, QTABLE_DOUBLE_TYPE, 10)

      AddColumn(id, 6, "datetime", true, QTABLE_STRING_TYPE, 20)

      AddColumn(id, 7, "volume", true, QTABLE_DOUBLE_TYPE, 15)

      message(tostring(CreateWindow(id)),1)

      ds=CreateDataSource(p_classcode, p_seccode, INTERVAL_M1)

      ds:SetUpdateCallback(cb)

      while is_run do

            sleep(100)

      end

end

function OnStop(stop_flag)

      is_run=false

end

А вот результат работы этой программы:

 

На этом урок окончен, но мы с вами не прощаемся, до новых встреч.

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

·   Приложение.rar

Комментарии

Himik — 3 января 2015 г.

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

0 +

megabax — 12 января 2015 г.

Himik, Тут, скорее всего, придется какие то внешние библиотеки подцеплять или даже вообще часть программы писать на другом языке, например, C# или С++

0 +

Himik — 12 января 2015 г.

megabax, А как вставить код С# в луа? Он будет полноценно исполнятся? Если я скажем напишу торгового робота на С#, его что можно вклеить в оболочку луа?

0 +

megabax — 12 января 2015 г.

Himik, Можно подключить как внешнюю библиотеку. По C# пока не скажу как (сам еще не разбирался), а по C++ описано здесь: http://robostroy.ru/community/article.aspx?id=792

0 +

Himik — 16 января 2015 г.

megabax, а можно ли это реализовать используя vclua? Я так понял что им можно любой интерфейс нарисовать? Может что-то наподобие кьюскальпа, потому как пользоватся стаканом квика просто убийство, пока стопы поставишь их могут уже давно пролететь :(

0 +

Snowmax — 17 января 2015 г.

Himik, теперь на Lua можно собственный индикатор создавать. И он будет в квике на графике рисоваться. Подробнее есть на официальном сайте квика в разделе документация.

0 +

Himik — 18 января 2015 г.

Snowmax, я пока только учусь, потому, пару вопросов. 1. Насколько я понял в индикаторе нельзя просто рисовать на графике, данные берутся из масива свечей? 2. Собственоо получаеться что создать графический интерфейс также не получится?. 3 Даже если написать индикатор он не будет выполнять роль торговоого робота, нужно будет писать скрипт на основе того индикатора?

0 +

Snowmax — 17 января 2015 г.

megabax, с 1го января lua поддерживает рисование на графиках. (Описание можно скачать на официальном сайте quik)
А мой вопрос следующий- можно ли сохранить например все клоуз свечек в отдельный массив? А потом создать еще один массив в который будут попадать клоузы по определнному условию. Ну например максимальный из 10 последних свечек.

0 +

finstrateg — 24 января 2015 г.

Спасибо! А если размещать индикатор не в новом окне, а в том же - к нему и свечкам можно будет получить доступ? Не всегда удобно "плодить" окна!

0 +

finstrateg — 25 января 2015 г.

что-то сайт help.qlua.org - хелп негде посмотреть

0 +

finstrateg — 25 января 2015 г.

finstrateg, в смысле сайт не работает, может проект умер

0 +

finstrateg — 25 января 2015 г.

по поводу индикатора message("Предыдущее значение индикатора "..t[0].close,1) - обязательно писать .close, может можно ничего не писать? если запросить .open, то будет ли это значение меняться на текущей свече?

0 +

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

t[0].close,1) наверно пишется всегда.t[1] предыдущее .0 текущее.свеча последняя.может и наоборот.практика

0 +

Shpinat — 5 ноября 2015 г.

Спасибо за ваши уроки, очень познавательно )
Есть небольшой вопрос.
Как с помощью описанного выше метода по типу:

local N=getNumCandles("MACD")
t,n,i=getCandlesByIndex("MACD", 0, N-2, 2)
message("Текущее значение индикатора "..t[1].close,1)

получить значение сигнальной (сглаженной) линии MACD?

Трудность для меня в том, что у этого индикатора два выдаваемых параметра: собственно MACD, и она же, но сглаженная. Вышеприведенный код выдает значение самой MACD, а мне в данном случае нужно значение сигнальной (сглаженной) линии, которая задана параметрами соответствующего графика.
Как этого добиться?
Буду очень благодарен за подсказку, желательно в виде подобных строчек кода.

0 +

drghestykmb — 25 ноября 2015 г.

Shpinat, t,n,i=getCandlesByIndex("MACD", 0, N-2, 2)
0 сигнальная.1 обычная.
t2,n2,i2=getCandlesByIndex("мацд-ртс", 0, N2-1, 1).первая.
обьявить t3,n3,i3=getCandlesByIndex("мацд-ртс", 0, N2-2, 1)
или t2,n1,i1=getCandlesByIndex("мацд-ртс", 0, N2-7, 1).message- есть и посмотреть .

0 +

drghestykmb — 25 ноября 2015 г.

у меня вопрос к megabax — как расчитать сужение и расширение болинджера?. записывать в файл.и считать. или циклом считать сужение и расширение.за 5-10 свечек допустим.какая из них меньше и больше.какие легкие варианты?наклон болинджера как мацд любой свечи предыдущей найдется.

0 +

drghestykmb — 25 ноября 2015 г.

ShpinatgetCandlesByIndex("MACD", 0, N-2, 2).-сигнальная.getCandlesByIndex("MACD", 1, N-2, 2).-обычная

0 +

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

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

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