Блог им. vtvladim
Создание на Lua своего индикатора в графике Quik: Часть 2. Пример работы нестандартных индикаторов: спред между инструментами, прогноз Highи Lowследующего интервала; ценовых уровней по объемам
В первой части (https://smart-lab.ru/blog/930907.php) были изложены основы принципа создания своего индикатора и некоторые нюансы работы с кодом индикатора графика в Qiuk (подразумевается использование языка программирования Lua).
В данной статье немного продолжу тему нюансов кодирования индикатора и для иллюстрации приведу простой код индикатора спреда. В конце текста прикреплю видео с демонстрацией работы индикатора спреда и моих собственных индикаторов.
Небольшое лирическое отступление. Суть данных статей — показать, что делать подобные индикаторы вполне реально и не столь сложно, как может показаться на первый взгляд. Но, безусловно, требует определенных знаний в программировании. Создавать индикаторы из стандартного набора торговой системы Qiuk смысла нет – ведь они уже реализованы. Создавать свой индикатор целесообразно тогда, когда у вас имеется своя идея (метод, способ анализа и т.п.). Приведенный в 1 части код индикатора SMA, написанный разработчиками Quik, давал возможность увидеть текст и структуру кода индикатора, «своими руками» попробовать запустить индикатор и испытать его в работе. Оптимизация кода SMA мне абсолютно не интересна, он был приведен в варианте «как было в инструкции» – эта ремарка для особо нервных программистов. Реально полезную вам расчетную часть кода каждый должен написать самостоятельно в соответствии со своими целями.
Теперь по теме.
В предыдущем обсуждении, благодаря коллеге (https://smart-lab.ru/profile/Vkt/, за что ему огромное спасибо) открылся еще один нюанс при кодировании индикаторов. Связан он, как я понимаю, с некоторой недоработкой в самом Quik. А именно: в случае, когда в качестве основного источника данных используется более одного инструмента, верно работает только коллбек источника данных (ИД) первого инструмента. OnCalculate, как известно, по сути является коллбеком ИД в индикаторах. Следовательно, он по идее должен выполнять эту функцию по всем ИД, которые выведены на график. Однако, это происходит «половинчато» — цена у всех инструментов на графике меняется верно, но вот с обращением к значениям ИД в коде возникают проблемы. Проявляется это в следующем: при смене интервала данные второго и последующих инструментов недоступны (хотя графически их цена на графике отображается верно), аналогичная проблема — при смене инструмента и после запуска Quik. При первичном же добавлении индикатора на график все работает нормально. Т.е. коллбек (OnCalculate) начинает правильно работать при добавлении или обновлении индикатора (редактировании – причем в режиме редактирования можно ничего не менять, просто нажать «ОК» — см. видео). Никакими хитростями в коде ситуацию исправить не смог. Ну а какая платформа без греха? В принципе не смертельно – обновить график, но вот когда таких графиков несколько… Можно подписываться на инструменты самостоятельно через CreateDataSource — но в рамках задачи визуализации смысла так делать не вижу (если кто-то сможет «победить» этот нюанс – напишите, интересно будет узнать способ). Как вариант, эту проблему косвенно можно обойти следующим образом: создать несколько графиков вместо одного (для каждого инструмента, по которым считается спред) и присвоить идентификаторы инструментам на каждом графике. Тогда мы избегаем ситуации с несколькими ИД на одном графике и коллбек работает верно. Я этот способ не использую, потому что лишние графики в Quik – это лишние потоки данных со всеми вытекающими последствиями.
Ниже привожу скрипт расчета спреда (за основу взят код, публично выложенный в обсуждении первой части статьи). Для использования у себя в терминале следует сделать следующее:
1. Сохраняем текст индикатора в файле с расширением, например Spread.lua, в папке LuaIndicators (если отсутствует – создать), которая находится внутри папки Quik.
2. Создаем новую вкладку (например, «Спреды», чтобы разделить информацию разного рода)
3. На вкладке «Спреды» создаем таблицу текущих торгов (с нужными вам инструментами) и график (окно с объемом в графике спреда я считаю лишним и удаляю), «якорим» график с таблицей текущих торгов
4. Строим на графике цену двух инструментов (например, GOLD-9.23 и GOLD-12.23). Поскольку график заякорен, в таблице выбираем GOLD-9.23, а GOLD-12.23 добавляем. В дальнейшем, при смене инструмента в таблице, на графике сменятся оба инструмента и второй надо будет заменить вручную.
5. Задаем идентификаторы инструментов: для цены Gold-9 id1, для Gold-12 id2 (правой кнопкой мыши щелкаем на линии цены (или подписи внизу графика) — дополнительно — там есть поле «идентификатор» вписываем id1 (id2) и сохраняем).
6. Добавляем индикатор (ищем название «2AV_Spread») в новую область. Смотрим на результат. В режиме редактирования пользовательских настроек – если необходимо – меняем начальную дату расчета, цвет и тип линий. Названия идентификаторов НЕ ТРОГАЕМ!
Величина спреда определяется как (Цена1*K1 — Цена2*K2), а в случае отсутствия одного из значений цены равна предыдущему значению спреда.
Коэффициенты К1 и К2 – балансировочные. Если инструменты различаются только датой контракта, они должны быть равны 1. В случае разных инструментов (например, Si и его спот), К2 должен быть равен 1000. В общем случае может потребоваться изменение значений обоих коэффициентов (сложная балансировка в пределах вашей фантазии), поэтому коэффициентов два.
При вставке кода обратите внимание: начальные текстовые значения данных в Settings должны быть заключены в вот такие двойные кавычки "", а не в такие «». Иначе Quik выдаст ошибку и индикатор не запустится (в зависимости от начальных языковых параметров винды кавычки могут вставляться при копировании неверно).
— индикатор спреда между двумя инструментами
— «id1» и «id2» это идентификаторы графика (не забудьте задать на графике) первого и второго инструмента, K1 и K2 балансировочные коэффициенты
— величина спреда определяется как (Цена1*K1 — Цена2*K2), ее расчет начинается с даты «From_Date»
Settings =
{
Name = «2AV_Spread»,
Symbol1 = «id1»,
K1 = 1,
Symbol2 = «id2»,
K2 = 1,
From_Date = «01.08.2023»,
line=
{
{Name = «Spread»,
Color = RGB(128, 0, 2),
Type = 1,Width = 2}
}
}
Cur_Date = {}
result_prev = 0
function Init()
return #Settings.line
end
function OnCalculate(index)
local indx1, indx2, Start_Date, result
Cur_Date.day = tonumber(string.sub(Settings.From_Date, 1, 2 ))
Cur_Date.month = tonumber(string.sub(Settings.From_Date, 4, 5 ))
Cur_Date.year = tonumber(string.sub(Settings.From_Date, 7, 10))
Start_Date = os.time(Cur_Date)
if index >= 1 then
if os.time(T(index)) >= Start_Date then
indx1, indx2 = Find_Spread(index)
if indx2 == 0 then
result = result_prev --- нет данных: берем предыдущее значение
else
result = indx2 — indx1 — значение спреда на интервале
result_prev = result — запоминаем значение спреда
end
end
return result
end
end
function Find_Spread(index)
local indx1, indx2
local Data1, Num_candl, Name_Line = getCandlesByIndex(Settings.Symbol1, 0, index-1, 1)
if Num_candl ~= 0 and Num_candl ~= nil then
indx1 = tonumber(Data1[0].close) * Settings.K1
else
indx1 = 0
end
if indx1 ~= 0 then
Data1, Num_candl, Name_Line = getCandlesByIndex(Settings.Symbol2, 0, index-1, 1)
if Num_candl ~= 0 and Num_candl ~= nil then
indx2 = tonumber(Data1[0].close) * Settings.K2
else
indx2 = 0
end
else — ветка по идее невозможна, но лучше «затыкать» вариант потери данных
indx2 = 0
end
return indx1, indx2
end
Индикаторы прогнозных значений Highи Lowследующего интервала, уровней цены с максимальными объемами
Индикатор High и Low следующего интервала сделан мною для визуализации результатов расчетов алго: моя ТС использует прогнозные экстремумы цены (High/Low) как точки входа /выхода. Желание визуализировать эти расчеты и подтолкнуло меня к освоению темы индикаторов в Quik. А это, в свою очередь, послужило причиной написания статей с целью немного облегчить путь интересующемуся этим вопросом народу.
Вообще-то я не использую никаких стандартных индикаторов. Совсем. И данный индикатор называю индикатором только по причине того, что в Quik добавление «не родных» линий на график называется индикатором. Данный индикатор (назвал его High_Low) показывает значения прогнозных H и L следующего интервала. Расчет основан на данных OHLCV предыдущих интервалов. Сразу хочу успокоить нервно реагирующих на слова «прогноз» и «следующий интервал» — я в курсе, что такое предсказание невозможно, читал про это, осуждаю и проч. И, да — это не регрессия и не экстраполяция.
Краткое пояснение по индикатору High_Low:
Расчет полностью формализован: используется формула, в которой нет привязки к конкретному инструменту или интервалу, нет оптимизируемых параметров и т.д. Но есть несколько методов, скажем так, численного решения, в моей классификации это: линейный, нелинейный, матричный и средний. Зачем так много? Самому не нравится, но это вынужденная мера для достижения более корректной обработки «тяжелых хвостов». В общем случае, характер движения цены на интервале вверх (значение High) и вниз (значение Low) различен. Поэтому методы расчета High и Low на интервале раздельные (разные). Точность расчета существенно связана с ликвидностью инструмента – чем выше ликвидность, тем лучше точность. В коде (реализован на Lua, так исторически получилось, теперь переписывать нет желания) можно задавать конкретный метод расчета прогноза, либо задать авто выбор оптимального (по критерию минимального СКО между фактическими и прогнозными значениями для заданной глубины ретроспективы). Пользовательские настройки содержат: начальную дату расчета, метод расчета и глубину авто поиска оптимального метода. Как это работает продемонстрировано в видео.
Индикатор уровней цены по объемам (уровни цены с наибольшими объемами) сделал внепланово в качестве бонуса и закрепления полученных знаний по теме индикаторов в Quik. Хотя, по объемам и уровням сам не торгую. Просто из любопытства сделал, интересно было глянуть, да и «запас карман не тянет». Очевидно, что чем меньше задан интервал, тем точнее результат расчета. Для периода от начальной даты по текущий момент определяются по два уровня (дополнительный и основной -с наибольшими объемами.). Пользовательские настройки содержат: начальную дату расчета, способ отбора уровней (по два уровня выше/ниже: либо средней цены за анализируемый период, либо ближайшие к текущей цене), точность определения уровней, флаг отображать (рассчитывать) уровни или нет.
Оба индикатора реализовал в одном скрипте. В видео показана работа такого индикатора.
Краткий путеводитель по видео (мин: сек):
00-03:32 => про индикатор спреда;
03:33-15:25 => индикатор High_Low (демонстрируются: расчет прогнозных High и Low, масштабируемость расчетов по интервалам, инструментам и классам активов, управление выводом индикатора. Немного про определение трендов.
15:26-18:15 => индикатор уровней объемов (демонстрируются: расчет уровней, изменение уровней в зависимости от периода расчета (начальной даты), масштабируемость по интервалам, инструментам и классам активов, управление выводом индикатора.
Вопрос немного сложнее и шире. Вы правильно заметили, что подобная ситуация возникает после свечек с одинаковыми HL. Но это только часть ответа. На графике кажущееся в силу масштаба равенство экстремумов не всегда в цифрах действительно верно. В OnCalculate индекс — это порядковый номер (по времени) свечки, а в реальном ИД, на который вы подписываетесь — в нем пустых свечек нет. Я «засунул» в индикатор только расчетную часть кода от алго, где работа с реальными свечками (без пустот), а в ИД от графика как сказано выше — есть особенность. Пустые свечки в индикаторе я «затыкаю» предыдущими значениями (в алго это не требуется), а бороться с «практически пустыми» свечками… Не факт что вообще надо, разве что для красоты или на всякий случай. Почему? — В плане алго — эта ситуация не страшна, поскольку эти места бот 100 % пропускает (фильтр по слишком малой величине торгового диапазона, или резкому изменению торгового диапазона).
Я по ЕД нашел место с вашего первого сообщения. Это 22-20 вчерашнего дня, когда было две 5-минутки с одной ценой (O=H=L=C). А на видео использовался авто выбор метода расчета, видимо для этих трех интервалов выбрался нелинейный, чтобы сгладить резкое сужение ценового коридора. Вот он и сгладил )), а поскольку расчет по H и L раздельный, то по L сгладилось сильнее. Но это аномалии, которые, я уверен, надо не решать, а обходить. И самое простое и правильное — не ломать и не портить «затычками» расчет, а отсекать активную работу самого алго для этих моментов. Мое мнение — универсальность расчета это подтверждение его работоспособности. Из этих соображений я не использую оптимизационную подгонку под конкретный инструмент (интервал)..
Напишите в личку, пожалуйста