Владимиров Владимир
Владимиров Владимир личный блог
12 августа 2023, 17:18

Создание на Lua своего индикатора в графике Quik: основы, нюансы, пример. Индикаторы: прогнозных High и Low следующего интервала; ценовых уровней объема.

   Кратко расскажу принципы и некоторые нюансы работы с графиком в Qiuk в плане создания своего индикатора (здесь и далее – подразумевается использование языка программирования Lua). В конце текста изначально хотел прикрепить видео с демонстрацией и краткими пояснениями работы моих индикаторов, но решил сделать это во второй части статьи, чтобы совместить просто иллюстрацию с небольшим анализом фьючерсов и акций.
   На полноту изложения вопроса по работе с индикаторами на графике Quik не претендую. Информация будет полезна интересующимся данной темой, не рассчитана на профессионалов (которые и так все знают, умеют и реализовали – свято в это верю), но все же предполагает наличие определенного уровня знаний Lua.
   Зачем мучиться со своими индикаторами? Конечно, в этом нет смысла, если вас устраивают стандартные индикаторы или отсутствуют самостоятельные подходы (методы) торговли, либо визуализация вам в принципе не требуется (не интересна).
   В моем случае мне банально захотелось сделать визуализацию своего метода прогнозирования экстремумов цены следующего интервала. Алго этого метода уже давно было реализовано. Предполагал, что сварганю все красиво максимум за день, ведь работающий код уже есть, оставалась только связка с графиком в Quik и наведение красоты. Но Quik внес свои коррективы в этот план: чтобы окончательно все сделать, разобраться в незадокументированных нюансах работы с графиком в Quik потребовалось больше времени. Про некоторые «грабли» расскажу, а также изложу основные азы для тех, кто заинтересуется вопросом в прикладном плане.
   Основы создания индикаторов в Quik изложены в соответствующих руководствах разработчиков (полнота и доступность изложения в них — на совести разработчиков), переписывать все нет смысла, но основные моменты изложу для полноты содержания и восприятия.

   Структура кода любого индикатора должна содержать три составляющих: глобальную таблицу с именем Settings, функции Init() и OnCalculate(), а файл с кодом размещается в папке LuaIndicators (создается вами если отсутствует), которая в свою очередь размещается внутри папки, где установлен сам Quik. Если отсутствует одна из вышеуказанных частей, либо в коде допущена ошибка, то в списке добавляемых на график индикаторов (в режиме редактирования графика – редактировать – добавить индикатор) вы свой индикатор не увидите. Кратко про каждую составляющую:

1). Settings =

{
                        Name = " Имя индикатора ",
                        line = {
                                   {Name = «Название линии»,
                                   Type = (Тип линии),
                                   Width = (Толщина линии),
                                   Color = (Цвет линии)
                                    }
            }

Таблица Settings задает:
— имя индикатора, под которым вы найдете его в списке всех индикаторов (избегайте уже испольованных названий). В этой же области (между элементами Name и line) вы можете определить дополнительно свои параметры (не просто используемые в расчете, а которые вы предполагаете изменять)
— тип линии можно задавать следующим:
Type = TYPE_LINE                      --- линии
Type = TYPE_HISTOGRAM           --- гистограммы
Type = TYPE_POINT                    --- точки
Type = TYPE_DASHDOT               --- точка-тире
Type = TYPE_DASH                     --- тире
Type = TYPE_TRIANGLE_UP         --- треугольники вверх
Type = TYPE_TRIANGLE_DOWN   --- треугольники вниз
— толщина линии (число)
— цвет линии – в формате модели RGB
   Толщину, тип и цвет линий удобно выбирать в режиме редактирования настроек индикатора (после его добавления) и потом внести выбранные значения в код.
   Поле line может содержать несколько элементов (по числу линий, используемых в индикаторе), структура которых аналогична приведенной выше.  

2). function Init()
       interval_1 = getDataSourceInfo().interval     — не обязательная строка (см. текст ниже)
      return #Settings.line
   end

   Эта функция инициализирует скрипт индикатора и срабатывает в момент его добавления. Возвращается число линий индикатора, перечисленных в Settings.Line и управление передается функции function OnCalculate(index).
   Здесь есть первый нюанс. Когда вы меняете на графике инструмент (график «заякорен») или интервал — вновь срабатывает Init() и график автоматически подписывается на новые данные. Но если ваша расчетная часть кода использует параметры, существенно зависимые от верности указания кода актива (к примеру, шаг и точность цены, стоимость шага цены) вам надо считать класс и код инструмента, интервал. Это позволяет сделать стандартная функция getDataSourceInfo(), возвращающая эти параметры: interval; class_code и sec_code. Но считать изменение этих параметров в скрипте индикатора сразу же невозможно, предположу – что из-за паузы подгрузки данных. По факту, getDataSourceInfo в Init() возвращает только интервал (причем не стабильно верно).
   Чем это чревато – когда расчетная часть кода использует параметры, зависимые от верности указания кода актива, можно много чего интересного насчитать с неверными interval, class_code и sec_code. Для проверки такого «нежданчика» советую ставить вторую строчку (с interval_1, и объявить ее глобальной), которая будет вторично проверяться на верность в OnCalculate(). Считывать в Init() кода и класса актива бессмысленно, поскольку они в Init() вообще не определяются, это надо делать в OnCalculate().
   3). function OnCalculate(index)
   В функции происходит основная магия рисования индикатора. В ней перебираются данные TOHLCV (начиная с наиболее поздних) и по умолчанию отображается график цены. Чтобы на графике отобразился еще и ваш индикатор, необходимо чтобы OnCalculate() вернула значения индикатора, причем в порядке их перечисления в Settings.Line (если выводимых на график значений несколько). Естественно, до этого значения индикатора должны быть рассчитаны.
   Городить существенные расчеты внутри функции разработчики не рекомендуют. Впрочем, все равно более-менее сложные расчеты реализовываются в разрезе функций (или методов).
   Из нюансов.
   Считывание из getDataSourceInfo информации о интервале (interval), коде класса (class_code) и коде инструмента (sec_code) в OnCalculate при index = 1 нестабильно, я считываю их при index = 2 и проверяю верность считанного в Init интервала. Зачем так заморачиваться? Потому что в расчетной части используются такие параметры инструмента, как точность и шаг цены, которые определяются только при известных классах кода и инструмента. Если вам не требуются специфические параметры инструмента, то нет смысла делать эту проверку.
   В руководстве сказано, что функции со сложными вычислениями следует размещать в отдельном файле, который размещен в ином каталоге, чем папка индикатора. Разработчикам виднее, в моем случае более 600 строк кода индикатора с более 30 функциями и итерационными расчетами прекрасно работают, будучи размещенными в одном файле с вышеперечисленными функциями.
   Работа с данными TOHLCV имеет особенности: вне OnCalculate у Т() нет даты, только время, хотя Т(index) непосредственно в OnCalculate() является нормальной полной таблицей. Еще один нюанс — вне OnCalculate() получение значений OHLCV нестабильно при смене инструмента или интервала. Причину нестабильности не понял, пришлось с данными работать через таблицу данных (getCandlesByIndex), а не напрямую с источником данных (OHLCV). Этот вариант немного более извращенный в плане кода, но основное его неудобство не в этом – надо помнить о необходимости присвоения графику идентификатора, который служит названием таблицы, содержащей данные TOHLCV (график – редактировать — дополнительно). Связанное с этим неудобство заключается еще в том, что разные графики не могут иметь один идентификатор, а значит для добавления на другой график индикатора, работающего с таблицей источника данных, необходимо менять название идентификатора как в коде, так в самом графике. Поясню на примере: в Quik созданы два графика, работающих со срочным и фондовым рынками (каждый график «заякорен» к одному классу активов). Чтобы добавить один и тот же индикатор на оба графика, приходится сделать два файла с кодом индикатора, отличающиеся только названиями индикатора и идентификатора, и соответственно добавлять на графики разные по названию индикаторы. Этого геморроя не было бы, если бы можно было работать стабильно напрямую с TOHLCV.
   Небольшие советы:
   1) задавать в Settings параметр, который будет разрешать/запрещать вывод вашего индикатора на график. Например, для этого у меня задается параметр Settings.Show_Ind, который управляет выводом значений индикатора (=1 – есть вывод, =0 индикатор не выводится). Управление выводом в этом случае простое: OnCalculate() при Settings.Show_Ind=1 возвращает значения индикатора, а при Settings.Show_Ind=0 – нет; Такое управление позволяет простым изменением параметра Settings.Show_Ind при редактировании его значения (график – редактирование) убрать индикатор с графика не удаляя сам индикатор, когда вывод его на график не требуется (см. пример кода ниже);
   2) задавать в Settings параметр, который будет задавать дату, с которой рассчитывать и (или) показывать на графике индикатор (под датой здесь понимается дата+время, т.е. таблица). Для этого у меня задается параметр Settings.From_Date (например, он равен «20.07.2023» (в Lua нет типа данных «дата»). А в коде вы переводите данное значение в формат POSIX (Unix-время, т.е. число секунд, прошедших с полуночи 1 января 1970 г.). Такое представление времени упрощает сравнение времени разных дат до банального сравнения двух чисел (к примеру, сравнение разных дат в формате, который я видел в советах на сайте – приведение даты 10.07.2023 к виду 10072023 гораздо сложнее, как минимум год здесь надо будет обрабатывать отдельно). Делается перевод даты и времени несложно, в приведенном выше примере это будет так:
Cur_Date = {}       — не забываем объявить массив
Cur_Date.day = tonumber(string.sub(Settings.From_Date, 1, 2 ))        вырезали день из From_Date
Cur_Date.month = tonumber(string.sub(Settings.From_Date, 4, 5 ))   вырезали месяц из From_Date
Cur_Date.year  = tonumber(string.sub(Settings.From_Date, 7, 10)) )) вырезали год из From_Date
Start_Date = os.time(Cur_Date)         число (количество секунд) = дата и время в формате POSIX

   (Если есть необходимость уточнять дату до часов и минут, то аналогично формируются элементы массива с часом и минутой).
   Небольшое уточнение: данные OHLCV с МБ – естественно по Московскому времени, а перевод в POSIX делается по локальному (местному времени, т.е. времени вашего компьютера). Чтобы все было совсем верно, следует сдвинуть значение Start_Date на разницу часовых поясов вашего региона с Москвой. У меня это +4 часа или 4*60*60 секунд.
   Управление выводом (и расчетами) в этом случае аналогичное: если os.time(T(index)) > Start_Date (т.е. дата индекса позже заданной) то рассчитываем значения индикатора и возвращаем их значения, иначе – не возвращаем (не считаем). В моем случае расчет всех свечек, имеющихся в источнике данных (как известно, это минимум 3000 свечек (на коротких интервалах больше) + сохраненные данные предыдущих торговых сессий, доходило до более 40 тыс. свечек) при итерационных расчетах приводил к заметной (несколько секунд) задержке при выводе индикатора на график (смена интервала, инструмента). Поэтому при сложных расчетах такое управление с начальной датой существенно снизит «залипание» графика при смене инструмента или интервала, и собственно — самого Quik.
   3) задавайте в Settings все те параметры, которые вы собираетесь «качать». Это позволит простым их редактированием в свойствах графика увидеть результат изменений в индикаторе. Поскольку изменение таких параметров в самом коде приведет к изменению на графике только после удаления индикатора и добавления его вновь (сам код считывается только один раз – при запуске Quik если индикатор уже добавлен на график, или при добавлении индикатора). 

   Для примера: Стандартный индикатор скользящей SMA с возможностью изменения: периода, источника данных (O, H, L, C, V и их сочетания) для средней и управлением вывода значений индикатора (авторство – разработчиков Quik, мною исправлена пара «очепяток» и добавлено управление выводом). Желающие могут скопировать код и вставить его в размещенный в соответствующей папке (см. выше) файл с расширением «lua», в списке индикаторов вы увидите этот индикатор под именем «Test_SMA».

Settings = {
                   Name = «Test_SMA»,
                   mode = «C»,
                   period = 5,
                   Show_Ind = 1,
                   line = {
                                      {Name = «Test_SMA»,
                                      Color = RGB(255, 0, 0),
                                      Type = TYPE_LINE,
                                      Width = 2 }
                                      }
                   }

function Init()
                   #Settings.line
end

 

function OnCalculate(idx)
            local per = Settings.period
            local mode = Settings.mode
            local lValue = iValue
            if idx >= per then
                        local ma_value=0
                        for j = (idx-per)+1, idx do
                                    ma_value = ma_value+dValue(j, mode)
                        end
                        if Settings.Show_Ind == 1 then
                                    return ma_value/per
                        end
            else
                        return nil
            end
end

 

function dValue(i,param)
            local v = param or «C»
            if v == «O» then
                        return O(i)
            elseif v == «H» then
                        return H(i)
            elseif v == «L» then
                        return L(i)
            elseif v == «C» then
                        return C(i)
            elseif v == «V» then
                        return V(i)
            elseif v == «M» then
                        return (H(i) + L(i))/2
            elseif v == «T» then
                        return (H(i) + L(i)+C(i))/3
            elseif v == «W» then
                        return (H(i) + L(i)+2*C(i))/4
            else
                        return C(i)
            end
end

 

 

55 Комментариев
  • bascomo
    12 августа 2023, 17:18
    Ничего себе кратко
  • Vkt
    12 августа 2023, 19:47
    Никогда не встречался такой косяк Квика, когда строишь график спреда между двумя инструментами с помощью getCandlesByIndex. Ну т.е. индикатор нормальный рабочий, график построился, а при новом включении Квика графика в окне нет — пусто. И нужно вручную его обновить чтобы он прорисовался. Жутко бесит, когда таких графиков много и все обновлять.
    Может есть какой способ чтобы это исправить?
  • Михаил К.
    12 августа 2023, 20:42
    А что у вас в индикаторе за значение iValue? Мне кажется, оно не определено.
  • Beach Bunny
    12 августа 2023, 21:56
    Код для индикатора SMA в статье плохой, так писать плохо, при более сложных расчетах будет тормозить.
    В примерах для Квика такой стиль был нуууу очень давно.
    Новую версию индикаторов на Qlua скачай у них с форума, там все нормально и более быстрый вариант расчета.
    А идентификатор графику зачем присваиваешь, ты его потом в роботе что ли используешь? — Если так то это плохой — тупой подход хоть и популярный у многих.

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

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