Блог им. morefinances
Сегодня рассмотрим:
Вывод текста на график
Вывод графических сигналов
Удаление меток с графика
Торговый советник на индикаторах
Удаление данных вечерней/утренней сессии с графика.
В торговом терминале почти нет графических инструментов, которых можно было бы задействовать через скрипт. Фактически разработчики оставили возможность использовать только индикаторы (неточность или ошибка в написании которых может подвесить весь терминал) и специальные метки, которые можно наносить на график.
И хотя сам терминал имеет возможность отрисовки различных линий, фигур, каналов, дуг, уровней, но из lua скрипта ничего этого до сих пор штатными методами не доступно. Разработчики оставили единственную возможность — вывод рисунка (bmp или jpg), поэтому желающий нарисовать, например, прямоугольник должен сперва его отрисовать где-то (или взять из библиотек рисунков), сохранить в нужном формате, и далее уже через метку поместить в конкретном месте графика. Вот такой вот «кружок авиамоделизма») Посмотрим как это работает.
Для начала нам необходимо присвоить нужному графику уникальный идентификатор. Именно по этому id терминал будет понимать на какой конкретно график наносить метку.
Метка на график добавляется через AddLabel:
label_id = AddLabel(идентификатор, таблица параметров)
При корректном выполнении функция присвоит метке номер, с которым мы сможем далее работать через label_id.
Таблицу параметров с подробным описанием можно посмотреть в файле QLUA в директории терминала.
Раньше для работы с метками я всегда использовал функции mmhhss и YYYYMMDD, которые можно было видеть в примерах предыдущих статей. Однако в комментариях мне подсказали, что можно сократить код, просто форматируя методами lua результаты функции os.date. Поэтому задать координаты по дате и времени можно гораздо более компактно, не прибегая к самописным функциям:
DATE = tostring(os.date("%Y%m%d")) TIME = tostring(os.date("%H%M%S"))
Создадим две функции, с помощью первой будем выводить на график текст, с помощью другой графические метки сигналов.
Вывод текста на график
По задумке разработчиков терминала метка может содержать и текст, и графику, но мне всегда было проще разделять их, т.к. порой на график полезно выдать только текстовую информацию, а какие-то важные моменты (сигналы, например) уже выделить значками нужного цвета.
Сделаем функцию для вывода текста:
function labeldraw(price, textlabel, texthint) label_params = { TEXT = textlabel, ALIGNMENT = "LEFT", DATE = tostring(os.date("%Y%m%d")), TIME = tostring(os.date("%H%M%S")), R = 0, G = 0, B = 0, TRANSPARENCY = 90, FONT_HEIGHT = 10, TRANSPARENT_BACKGROUND = 1, YVALUE = price, HINT = texthint } label_id_text = AddLabel(tiker_id, label_params) end
в main включаем:
function main() tiker_id = "SBER_ID" -- подгрузка текущей свечи с графика number_of_candles = getNumCandles(tiker_id) price, _, _ = getCandlesByIndex(tiker_id, 0, number_of_candles - 1, 1) sleep(300) -- пауза для подгрузки данных text = "Вход в сделку: "..price[0].close labeldraw(price[0].close, text, "Вход в сделку") end
Получаем следующее:
Видно, что текст трудно читаем, если заходит на котировки графика цены. Поэтому логично функцию дополнить направлением сигнала. Изменить позиционирование метки можно с помощью ключа ALIGNMENT (TOP / BOTTOM), а можно поставить другую привязку по цене. Например, если сигнал в лонг сделаем метку на 0,3 р. выше от текущей цены, если сигнал в шорт, то на те же 0,3 р. ниже:
Вызовем функцию с указанием сигнала лонг:
labeldraw(price[0].close, 1, text, "Вход в сделку")
Текст стал выше относительно текущей цены. В каких-то случаях надпись всё равно будет накладываться на график, если чуть ранее котировки динамично менялись, но по крайней мере мы ушли от того, чтобы «лепить всё» вокруг цены входа в позицию.
Если далее планируете работать с id номером метки, то можно в функции заложить return label_id_text. Я, как правило, добавляю на график все метки в процессе работы скрипта, а в конце дня (при необходимости ранее), их можно удалить все разом через DelAllLabels(идентификатор).
Удалить метки также можно по конкретному id: DelLabel(идентификатор, id номер метки).
Также метки спокойно удаляются/перемещаются в ручную на графике.
Переместить метку можно и скриптом, задав новые параметры через:
SetLabelParams(идентификатор графика, id номер метки, таблица новых параметров).
Теперь напишем функцию вывода графического сигнала направления сделки (лонг/шорт):
function signaldraw(price, direction) arrow_params = { ALIGNMENT = "LEFT", IMAGE_PATH = getScriptPath() .. "\\arrow.bmp", YVALUE = price, DATE = tostring(os.date("%Y%m%d")), TIME = tostring(os.date("%H%M%S")), TRANSPARENT_BACKGROUND = 1 } if direction == 1 then arrow_params.TRANSPARENCY = 15 else arrow_params.TRANSPARENCY = 85 end label_id_arrow = AddLabel("SBER_ID", arrow_params) end
Для этого нам понадобится стрелка (cкачать в bmp формате):
Либо можете нарисовать/взять свою.
Разместим её в ту же папку, где работаем со скриптом, меняя параметры прозрачности можно будет менять и цвет на графике.
При TRANSPARENCY = 15 в лонг стрелка будет зеленой:
При TRANSPARENCY = 85 в шорт стрелка будет красной:
И для красоты можно добавить метку закрытия позиции (в bmp):
function drawexit(price) arrow_params = { ALIGNMENT = "LEFT", IMAGE_PATH = getScriptPath() .. "\\arrowexit.bmp", YVALUE = price, DATE = tostring(os.date("%Y%m%d")), TIME = tostring(os.date('%H%M%S')), TRANSPARENCY = 80, TRANSPARENT_BACKGROUND = 1 } label_id_arrow = AddLabel("SBER_ID", arrow_params) end
Торговый советник на индикаторах
Теперь можно написать вариант советника, работающего на индикаторах с использованием меток для отображения сигналов на вход и закрытие позиции.
Возьмем график Сбера (TF=1M, идентификатор SBER_ID) и добавим 2 индикатора:
«максимум/минимум за» — Price Channel (период 60, с идентификатором SBER_PRICECHANNEL_ID) и конверт по простой средней Envelopes (период 60, коэффициент 0,25, Simple, Close, id: SBER_ENVELOPES_ID).
В OnInit пропишем привязку к графикам:
tiker_id = "SBER_ID" tiker_price_channel_id = "SBER_PRICECHANNEL_ID" tiker_envelopes_id = "SBER_ENVELOPES_ID"
А в цикле while запросы на получение данных:
--котировки цены number_of_candles = getNumCandles(tiker_id) price, _, _ = getCandlesByIndex(tiker_id, 0, number_of_candles - 2, 2) sleep(300) -- Price Channel number_of_candles_price_channel = getNumCandles(tiker_price_channel_id) price_channel_upper, _, _ = getCandlesByIndex(tiker_price_channel_id, 0, number_of_candles_price_channel - 3, 3) price_channel_lower, _, _ = getCandlesByIndex(tiker_price_channel_id, 2, number_of_candles_price_channel - 3, 3) sleep(300) --конверты number_of_candles_envelopes = getNumCandles(tiker_envelopes_id) envelopes_upper, _, _ = getCandlesByIndex(tiker_envelopes_id, 1, number_of_candles_envelopes - 2, 2) sleep(300) envelopes_lower, _, _ = getCandlesByIndex(tiker_envelopes_id, 2, number_of_candles_envelopes - 2, 2) sleep(300)
Т.к. мы работаем не с текущей свечкой, а с последней закрытой, то с графика цены мы подтягиваем 2 свечи, при этом работать будем с первой (помним, про индекс 0 для первых свечей при работе с графика). Аналогично мы берем 2 значения для Envelopes. А вот для ценового канала берем уже 3 свечи, т.к. текущая свеча индикатора всегда отражает high/low в т.ч. текущей, незакрытой свечи графика. Предыдущая «максимум/минимум за» в т.ч. значение предыдущей, но нам для понимания пробили или нет этот индикатор нужно взять более ранее значение, поэтому в этом случае будет выгружено 3 свечи с графика, а сравнивать будем также по самому первому (индекс 0).
Оставляем время на догрузку данных (sleep(300)) после каждого обращения к графику, иначе рискуем периодически получать nil.
Торговая логика советника
В качестве учебной стратегии предположим, что мы будем открывать позицию в лонг по закрытию свечи при условии, что её high пробил максимум часа, при этом цена закрытия выше верхней линии Envelopes:
if position~=1 and price[0].high > price_channel_upper[0].close and price[0].close > envelopes_upper[0].close
В шорт зеркальные условия вниз:
if position~=-1 and price[0].low < price_channel_lower[0].close and price[0].close < envelopes_lower[0].close
Выход из позиции при условии, что цена закрытия вернулась в конверт Envelopes.
Для лонга:
if position==1 and price[0].close < envelopes_upper[0].close
Для шорта:
if position==-1 and price[0].close and price[0].close > envelopes_lower[0].close
При этом перед блоком с условиями необходимо разместить проверку получения данных с графиков:
if price[0].high and price[0].low and price_channel_upper[0].close and price_channel_lower[0].close and price[0].close and envelopes_upper[0].close and envelopes_lower[0].close then
Это на случай, если несмотря на нашу паузу для догрузки (sleep), скрипт не получил данных c индикаторов или по ценам.
Включаем в код функции отображения меток и у нас вполне работающий торговый советник:
Который показывает сигналы и отфильтровывает входы с помощью Envelopes, если цена не вышла из 0,5% коридора относительно средней (0,25% в каждую сторону):
И хотя стопы, как видно, получаются вполне короткими, а на сильных внутридневных трендах могут находиться неплохие трейды, но, как и большинство трендовых стратегий, она будет прилично минусовать в боковиках без должных дополнительных настроек и фильтров.
Конечно это не конечная торговая стратегия, а только учебный пример, но на нем хорошо видно, как можно быстро прописать торговую логику без глубокого погружения в формулы/расчеты самих индикаторов. При этом если, например, после оптимизации стратегии необходимо изменить параметры индикаторов, то это делается легко уже через настройки графика, а не через редактирование кода.
Файл советника на github: https://github.com/morefinances/qlua/blob/main/simpleadvisor_v2_0.lua
Чтобы скрипт корректно работал необходимо настроить все указанные графики и корректно прописать к каждому идентификатор.
К текущему алгоритму мы еще вернёмся, когда дойдем до написания собственных индикаторов и можно будет дополнить уже ими торговую логику советника.
Удаление данных вечерней/утренней сессии с графика.
С появлением вечерней сессии, а позже и утренней встал вопрос относительно того использовать эти данные для тестирования или нет, осуществлять ли на них входы или только выходы, или полностью игнорировать. Не останавливаясь на этих аспектах (каждый решает сам) просто хочу показать, что терминал позволяет редактировать график, тем самым удалив эти периоды из наблюдений/работы, если они вам не нужны.
Удалим из нашего примера минутки с вечерней сессии:
Для этого нажимаем на графике CTRL+E (либо правая клавиша и редактировать график), далее выбираем Диаграмма в левой части настроек и ставим фильтр по времени: с 10:00 до 18:45
Получаем данные без вечерней сессии:
С одной стороны, это удобно, если вы хотите наблюдать только основные торги и сам алгоритм работает у вас только в этом диапазоне.
С другой строны, как видно на графике: старт торгов в этом случае может быть со значительным гэпом относительно закрытия, да и сами индикаторы будут резко перерисовываться. По этой причине некоторые стратегии на минутках ждут когда отрисуются индикаторы по данным нового дня (в нашем случае для этого нужно было бы пропустить первый час торгов, т.к. оба индикатора строятся по данным последних 60 минут) и только после этого приступают к отработке сигналов.
Упражнения:
1) Переделайте код текущего советника, так, чтобы он пропускал первый час торгов.
2) Поменяйте настройки советника так, чтобы он работал не по данным последнего часа, а по последним 15 минутам.
3) Измените условие выхода: закрытие позиций при пересечении текущей цены простой средней SMA60.
4*) Постарайтесь последовательно усовершенствовать данный скрипт по аналогии с апгредом, который мы делали с советником 1.0, добавьте:
Теги: qlua для начинающих, кружок авиамоделизма.
Ранее:
Qlua: введение
Доля клиентов, использующих алгоритмическую торговлю
«Кружок авиамоделизма»
Разные торговые терминалы, почему Quik
Основной функционал qlua
Настраиваем торговый терминал и редактор кода
Установка торгового терминала
Подготовка терминала
Вкладки в терминале
Сохранение и загрузка настроек
Таблица системных сообщений
Отключение окна сообщений
Редактор Notepad++
Настройка русского языка в редакторе
Выбор синтаксиса языка и кодировки
Цветовые настройки
Дублирующий просмотр
Запуск первого скрипта
Основы qlua, часть 1:
message, конкатенация
фильтрация по сообщениям в терминале
PrintDbgStr, комментарии
типы данных, type
операции с числами
операции со строками
операции с таблицами
условные операторы
Основы qlua, часть 2:
Циклы for … do … end, while do … end, repeat … until.
sleep, break, goto.
как пройти весь массив циклом, как пройти таблицу по ключам
локальные и глобальные переменные, функции
получение даты и времени
получение данных через getInfoParam
Qlua: структура скрипта.
Структура типового скрипта qlua с примерами.
Обработка скриптом «обрыва связи» с сервером и возобновления работы.
Работа с файлами: запись, перезапись и чтение файла.
getScriptPath, getWorkingFolder
Qlua: получение данных из таблицы текущих торгов, создание таблиц в торговом терминале.
Получение биржевых данных через функцию getParamEx
Выгрузка списка параметров функции getParamEx через DDE из торгового терминала
Создание пользовательских таблиц в торговом терминале
Qlua: работа с таблицами (продолжение). Пишем своего советника (начало).
Интегрируем таблицы в структуру скрипта qlua.
Удаляем таблицы через DestroyTable.
Останавливаем скрипт через IsWindowClosed.
Обработка события закрытия таблицы через коллбэк.
Работа с цветом SetColor, Highlight, SetSelectedRow.
Пишем простого советника.
Qlua: дополняем скрипт советника таймингом:
Устанавливаем время старта работы скрипта,
Ставим тайминг на получение сигналов на вход,
Устанавливаем таймер на приостановку скрипта.
Qlua советник: дополняем сигналами на закрытие позиции, таблицей для вывода данных и расчетом финансового результата по позициям.
Дополняем сигналами на закрытие позиции.
Создаем дополнительную таблицу для вывода данных.
Делаем расчет финансового результата.
Qlua: завершаем апгрейд советника:
Пропуск «поздних» сигналов на старте.
Обработка советником обрыва связи.
Сохранение сигналов и логов в файл.
Qlua: пишем скринер акций Московской биржи
Qlua: получение данных биржевых свечей с сервера брокера, обработка данных, пишем скрипт выгрузки котировок
Функция CreateDataSource
Получение количества свечек данных
Пауза для подгрузки данных
Получение по инструменту OPEN, HIGH, LOW, CLOSE, VOLUME
Обработка времени и даты
Закрытие источника данных
Примеры: получение данных последних 10 свечей, выгрузка новой минутной свечки после её закрытия, текущее значение простой средней SMA10 по минуткам
Простой скрипт выгрузки котировок
Qlua: получение данных с графиков терминала.
Идентификатор инструмента
Получаем количество свечей через getNumCandles
Получаем свечные данных через getCandlesByIndex
Читаем данные с индикатора SMA
Данные с верхней и нижней линии Price Channel
Графики с таблицы текущих торгов.
Сравнение получение данных через CreateDataSource и через getCandlesByIndex
СПА-СИ-БО!
Давайте попробуем еще тем 20, захватим посложнее ...))
Например, на продвинутых темах, попробуем сделать coroutine www.lua.org/pil/9.1.html «сопрограммы» )))