Блог им. morefinances
Функция CreateDataSource
Получение количества свечек данных
Пауза для подгрузки данных
Получение по инструменту OPEN, HIGH, LOW, CLOSE, VOLUME
Обработка времени и даты
Закрытие источника данных
Примеры: получение данных последних 10 свечей, выгрузка новой минутной свечки после её закрытия, текущее значение простой средней SMA10 по минуткам
Простой скрипт выгрузки котировок
Сегодня рассмотрим функцию, с помощью которой можно получать данные биржевых свечек. Это можно делать и с графиков (чуть позже рассмотрим), но в этом случае нужно, чтобы сам график как источник данных был открытым, что не очень удобно, особенно если скрипт использует несколько таймфреймов – необходимо аналогичным образом держать открытыми и соответствующее количество графиков.
Более практичным вариантом является получение данных через функцию CreateDataSource, запрос осуществляется следующим образом:
ds, err = CreateDataSource(код класса, тикер инструмента, интервал)
Код класса: для акций «TQBR», для срочного рынка «SPBFUT».
Код класса и тикер указываются в кавычках, интервал без – это одна из следующих констант:
INTERVAL_TICK тиковые данные
INTERVAL_M1 1 минута
INTERVAL_M2 2 минуты
INTERVAL_M3 3 минуты
INTERVAL_M4 4 минуты
INTERVAL_M5 5 минут
INTERVAL_M6 6 минут
INTERVAL_M10 10 минут
INTERVAL_M15 15 минут
INTERVAL_M20 20 минут
INTERVAL_M30 30 минут
INTERVAL_H1 1 час
INTERVAL_H2 2 часа
INTERVAL_H4 4 часа
INTERVAL_D1 1 день
INTERVAL_W1 1 неделя
INTERVAL_MN1 1 месяц
Если запрос обрабатывается нормально (все параметры указаны корректно), то ds – выдает таблицу данных, с которой и будем работать. Если нет, то err будет содержать описание ошибки (неверный код класса, код инструмента, интервал или параметр).
Особенность работы CreateDataSource: в отличие от прошлых примеров c небольшими кусками кода на lua, которые мы могли сразу запускать в терминале, данная функция будет работать только в полноценной структуре скрипта qlua. Поэтому во всех следующих примерах кода будем размещать в main(), иначе терминал не сможет запустить скрипт.
Количество свечек данных
Количество свечей можно получить через:
number_of_candles = ds:Size()
Нумерация идёт с самой ранней свечи.
Пауза для подгрузки данных
Если мы первый раз обращаемся к источнику данных по инструменту, то необходимо сделать паузу для подгрузки данных после вызова функции.
Это можно сделать, например, поставив паузу в 500-1000 мс:
function main() ds, err = CreateDataSource("TQBR", "SBER", INTERVAL_M1) sleep(500) -- пауза для подгрузки данных number_of_candles = ds:Size() message("Количество свечей в источнике данных: "..number_of_candles) end
Более профессиональный подход – сделать это через цикл:
function main() ds, err = CreateDataSource("TQBR", "SBER", INTERVAL_M1) indexload = 0 repeat sleep(100) indexload = indexload + 1 until(ds:Size()~=0 or indexload==10) number_of_candles = ds:Size() message("Количество свечей в источнике данных: "..number_of_candles) end
Здесь цикл может прерваться ранее 1000 мс, если получены ненулевые данные по количеству свечей по инструменту. Обычно хватает 100 мс, т.е. в реальности получаем гораздо более быстродейственный вариант.
Получениеданных OPEN, HIGH, LOW, CLOSE, VOLUME свечи
Получив количество свечей можно обращаться уже за основной информацией.
Следующий код выдаст данные по последней (текущей) свечке:
function main() ds, error_discr = CreateDataSource("TQBR", "SBER", INTERVAL_M1) sleep(500) openprice = ds:O(index) highprice = ds:H(index) lowprice = ds:L(index) closeprice = ds:C(index) volume = ds:V(index) message("Цена открытия="..openprice.." максимум="..highprice.." минимум="..lowprice.." последняя цена="..closeprice.." объем="..volume) end
Если нужна не текущая свечка, а предыдущая (последняя закрытая), то нужно здесь указать не index, а index-1. Аналогичным образом обращаемся к более ранним свечкам, вплоть до 1й.
Обработка времени
Дата и время, как это уже мы встречали ранее, даётся в табличном виде.
ds:T() при нормальной работе CreateDataSource выдаст таблицу вида: {year, month, day, week_day, hour, min, sec, ms, count}
c которой уже можно работать с помощью ключей.
Для удобства воспользуемся функциями, которые мы использовали ранее для перевода даты и времени в нужный формат:
function hhmmss(date_time) local Hour = date_time.hour if Hour<10 then Hour = "0"..Hour end local Min = date_time.min if Min<10 then Min = "0"..Min end local Sec=date_time.sec if Sec<10 then Sec="0"..Sec end return Hour..Min..Sec end
function DDMMYYYY(date_time) local DD = date_time.day if DD<10 then DD = "0"..DD end local MM = date_time.month if MM<10 then MM = "0"..MM end local YYYY = date_time.year return DD..MM..YYYY end
Закрытие источника данных
После того как мы получили и обработали данные и больше не планируем к ним обращаться следует закрыть источник данных.
Это делается через ds:Close()
Пример получения последних 10 свечей по Сбербанку:
function main() ds, error_discr = CreateDataSource("TQBR", "ABRD", INTERVAL_M1) repeat sleep(100) indexload = indexload + 1 until(ds:Size()~=0 or indexload>=10) number_of_candles = ds:Size() message("Количество свечей в источнике данных: "..number_of_candles) for index = number_of_candles-9, number_of_candles do openprice = ds:O(index) highprice = ds:H(index) lowprice = ds:L(index) closeprice = ds:C(index) volume = ds:V(index) localtime = hhmmss(ds:T(index)) localdata = DDMMYYYY(ds:T(index)) message("Свеча "..index.." data:"..localdata.." time:"..localtime.." open="..openprice.." high="..highprice.." low="..lowprice.." close="..closeprice.." volume="..volume) end ds:Close() -- закрыть источник данных end
Полный код: https://github.com/morefinances/qlua/blob/main/CreateDataSource_10candles.lua.
Получим следующий вывод:
Здесь 4619 – текущая свеча (еще не закрылась) и более ранние минутные данные.
Раньше можно было еще через функцию обратного вызова SetUpdateCallback подписаться на данные по свечке, чтобы получать коллбэк при её изменении, но у разработчиков при очередном обновлении терминала в конце 2021 года «что-то пошло не так» и функция стала недоступной в новых версиях квика.
На форуме арка еще в начале 2022 года писала, что «Проблема изучается, будет устранена в одной из очередных версий ПО», но, видимо, еще не время.
Поэтому хоть в мануале QLUA есть даже пример, но он на текущий момент не рабочий, пока не останавливаемся на этом, хотя было удобно получать таким образом изменения по свечке.
Пример выгрузки новой свечки при закрытии минуты.
В качестве альтернативы в части появления новой свечки можно в цикле while с необходимой периодичностью запрашивать данные по инструменту и сравнивать ds:Size, когда пришла новая свеча, тогда и обрабатываем её:
if ds:Size()~=number_of_candles then …
тогда получим скрипт, который будет выгружать новую минутную свечку после её закрытия:
function OnInit() indexload = 0 end function hhmmss(date_time) local Hour = date_time.hour if Hour<10 then Hour = "0"..Hour end local Min = date_time.min if Min<10 then Min = "0"..Min end local Sec=date_time.sec if Sec<10 then Sec="0"..Sec end return Hour..Min..Sec end function DDMMYYYY(date_time) local DD = date_time.day if DD<10 then DD = "0"..DD end local MM = date_time.month if MM<10 then MM = "0"..MM end local YYYY = date_time.year return DD..MM..YYYY end function OnStop() do_it = false ds:Close() end function main() do_it = true ds, error_discr = CreateDataSource("TQBR", "SBER", INTERVAL_M1) repeat sleep(100) indexload = indexload + 1 until(ds:Size()~=0 or indexload>=10) number_of_candles = ds:Size() message("Количество свечей в источнике данных: "..number_of_candles) while do_it do ds, error_discr = CreateDataSource("TQBR", "SBER", INTERVAL_M1) if ds:Size()~=number_of_candles and do_it then if ds:Size() == number_of_candles + 1 then number_of_candles = ds:Size() openprice= ds:O(number_of_candles) highprice = ds:H(number_of_candles) lowprice = ds:L(number_of_candles) closeprice = ds:C(number_of_candles) volume = ds:V(number_of_candles) localtime = hhmmss(ds:T(number_of_candles)) localdata = DDMMYYYY(ds:T(number_of_candles)) message(number_of_candles.." "..localdata.." "..localtime.." OPEN="..openprice.." HIGH="..highprice.." LOW="..lowprice.." CLOSE="..closeprice.." VOLUME="..volume) end end sleep(1000) end end
Файл: https://github.com/morefinances/qlua/blob/main/new_candle_example.lua
Видим, что данные по свечке иногда приходят не сразу. Обычно это 1-2 секунды задержки, но может доходить и до 3-5 секунд и больше (например в 15:08 данные получены лишь на 49й секунде). По этой причине скальперских роботов, ориентируясь на получение данных через свечки, лучше не писать. Для этого используют другие, более быстродейственные варианты получения данных по инструментам, один из них – это работа с таблицей текущих торгов, которую мы проходили ранее, если для алгоритма достаточно информации из неё, другой вариант – это работа с лентой всех сделок, которую мы рассмотрим позднее. Однако получение данных по большим таймфреймам для дополнительных расчетов, фильтров, построения средних вполне нормальный вариант.
Пример: код простая средняя SMA10 по минуткам.
function main() ds, error_discr = CreateDataSource("TQBR", "SBER", INTERVAL_M1) sleep(500) number_of_candles = ds:Size() message("Количество свечей в источнике данных: "..number_of_candles) N = 10 summ = 0 if number_of_candles < N then message("Недостаточно данных для расчета") message(number_of_candles.." < "..N) else for index = number_of_candles-(N-1), number_of_candles do summ = summ + ds:C(index) end message("SMA("..N..")="..summ/N) end -- закрыть источники данных ds:Close() end
Результат:
Файл: https://github.com/morefinances/qlua/blob/main/SMA10_from_CreateDataSource.lua
Можно настроить график средней (Moving Average) на минутках, поставить параметры построения по закрытию, простая скользящая (Simple), по 10 свечкам и убедиться, что цифры будут биться.
Скрипт выгрузки котировок.
За рубежом маркетдата платная. Московская Биржа тоже предлагает за достаточно большие деньги получать большой объем биржевой информации по рынку, но есть несколько ресурсов, где можно скачать котировки бесплатно. Если мы говорим про наш рынок, то сейчас это сайт брокера FINAM и сайт MFD. Раньше еще была возможность скачать котировки с сайта Алора, но с новым дизайном несколько лет назад они убрали эту опцию.
Сегодня мы разгрузим немного серверы Финама и MFD, т.к. напишем скрипт, который поможет нам самостоятельно выгружать нужную информацию с терминала в файл.
Возьмем скрипт в котором мы проходились по 10 свечкам и просто заменим с небольшими изменениями вывод через message на вывод в файл, а сам цикл будет проходить по всем данным, а не по 10 последним значениям.
Вначале main создаем файл для записи:
-- наименование файла: тикер _ дата и время текущий свечи _ дата и время первой свечи filename = tiker.."_"..YYYYDDMM(ds:T(number_of_candles))..hhmmss(number_of_candles).."_"..YYYYDDMM(ds:T(1))..hhmmss(ds:T(1)) --размещение файла на C:\files DirectionSaveFile=tostring("C:\\files\\"..filename..".csv") --создаем файл для записи my_csv=io.open(DirectionSaveFile,"a+")
До самого цикла for создадим шапку таблицы:
my_csv:write("<DATA>;<TIME>;<OPEN>;<HIGH>;<LOW>;<CLOSE>;<VOLUME>;\n")
my_csv:write(localdata..";"..localtime..";"..openprice..";"..highprice..";"..lowprice..";"..closeprice..";"..volume..";\n")
После прохождения всего цикла сохраняем и закрываем файл:
my_csv:flush() my_csv:close()
Добавим таблицу для контроля выгрузки, в которой отразим первые и последние 10 строк данных и наш скрипт готов:
if table_result==nil then table_result = AllocTable() AddColumn(table_result, 1, "<DATA>", true, QTABLE_DATE_TYPE, 12) AddColumn(table_result, 2, "<TIME>", true, QTABLE_TIME_TYPE, 10) -- QTABLE_STRING_TYPE AddColumn(table_result, 3, "<OPEN>", true, QTABLE_DOUBLE_TYPE, 8) AddColumn(table_result, 4, "<HIGH>", true, QTABLE_DOUBLE_TYPE, 8) AddColumn(table_result, 5, "<LOW>", true, QTABLE_DOUBLE_TYPE, 8) AddColumn(table_result, 6, "<CLOSE>", true, QTABLE_DOUBLE_TYPE, 9) AddColumn(table_result, 7, "<VOL>", true, QTABLE_INT_TYPE, 15) CreateWindow(table_result) SetWindowPos(table_result,0,440,700,420) SetWindowCaption(table_result, "Выгрузка котировок : "..tiker.." таймфрейм : "..timeframe) for u = 1, size_table do InsertRow(table_result,-1) end end
Заполним таблицу данными:
for index = 1, number_of_candles do openprice = ds:O(index) highprice = ds:H(index) lowprice = ds:L(index) closeprice = ds:C(index) volume = ds:V(index) localtime = hhmmss(ds:T(index)) localdata = DDMMYYYY(ds:T(index)) if index >= number_of_candles-9 and index <= (number_of_candles) then local linetable = 1 + number_of_candles - index SetCell(table_result, linetable, 1, tostring(localdata)) SetCell(table_result, linetable, 2, tostring(localtime)) SetCell(table_result, linetable, 3, tostring(openprice)) SetCell(table_result, linetable, 4, tostring(highprice)) SetCell(table_result, linetable, 5, tostring(lowprice)) SetCell(table_result, linetable, 6, tostring(closeprice)) SetCell(table_result, linetable, 7, tostring(math.floor(volume))) end for i = 1, 7 do SetCell(table_result, 11, i, "...") end if index >= 1 and index <= 10 then local linetable = 22 - index SetCell(table_result, linetable, 1, tostring(localdata)) SetCell(table_result, linetable, 2, tostring(localtime)) SetCell(table_result, linetable, 3, tostring(openprice)) SetCell(table_result, linetable, 4, tostring(highprice)) SetCell(table_result, linetable, 5, tostring(lowprice)) SetCell(table_result, linetable, 6, tostring(closeprice)) SetCell(table_result, linetable, 7, tostring(math.floor(volume))) end end
Подправим дату на более привычный для выгрузки формат и получим:
И видим аналогичные данные в хронологическом порядке в файле:
Итоговый файл скрипта: https://github.com/morefinances/qlua/blob/main/downloader.lua
Удобство этого алгоритма в том, что можно самому определять какие данные грузить и в какой форме. Например: если для стратегии не нужны вечерние/утренние сессии, то их здесь же в коде можно по условию не сохранять в файл.
С минимальными правками кода можно в одном файле сохранять котировки базового актива и фьючерса на него или одновременно грузить несколько инструментов, чтобы отслеживать корреляцию по ним.
В дальнейшем мы еще будем возвращаться к этому скрипту и делать его апгрейд. Например, когда пройдем тему реализации в терминале диалоговых окон, появится возможность выбирать инструмент и таймфрейм уже после запуска скрипта, а не править это постоянно в коде.
Упражнения
1. Поменяйте порядок выдачи строк в таблице скрипта так, чтобы они соответствовали прямому хронологическому порядку (как в файле).
2. Попробуйте написать советника, который будет давать сигналы по выбранным свечным формациям (например разворотным).
Теги: 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: пишем скринер акций Московской биржи
Версия лайт.
Я бы настоятельно не рекомендовал создавать блокирующий цикл ожидания получения данных с сервера. Вы не знаете сколько будет идти ответ 100 млс. или 10 секунд и больше.
Обычно такие вещи делаются через неблокирующие ожидания. В Lua, например, это можно сделать через корутины, очереди задач, последовательно опрашивая о результате. Ведь задача может быть более сложной — заказать одномоментно данные по 100-1000 инструментам. И если последовательно ждать на каждом, то это будет долго, очень долго. Поэтому сначала все заказывается. А потом проверяем каждый заказ. Не дошли данные, просто переходим к следующему. И так пока все не придет. При этом вполне могут быть ситуации, когда данные не придут т.к. их нет и надо сбросить ожидание с ошибкой по достижению некого большого периода ожидания.
или здесь но помните что Lua это не QLua
fingercomp.gitlab.io/lua-coroutines/
вопрос по теме: чем CreateDataSource лучше считывания данных через Идентификатор (задающийся в квик настройках графика)? не считая того, что нужно будет сделать нужное количество графиков в отдельную вкладку квика. минусы как раз описаны в первом комменте. если правильно понимаю, то CreateDataSource при заказе данных влияет на пинг (квиковские данные начинают опаздывать, важно для скоростной торговли) или ошибаюсь?
p.s. пару раз видел темы, где писали что CreateDataSource возвращает неправильные данные, отличные от данных в терминале. не знаю насколько это правда
По основному вопросу:
1) Мне лично не удобно через графики т.к. несколько квиков и нужно было бы на каждом настраивать перед запуском скрипта нужные инструменты в графике, сверять таймфреймы, прописывать id. Через CreateDataSource скрипт сразу готов к запуску на любом терминале.
2) В случае смены инструментов (есть алгоритмы, которые торгуют не одну бумагу, а отфильтровывают по критериям инструменты, например с высоким потенциалом движения, и далее ведут торговлю по ним) нужно было бы в ручную перестраивать сами графики.
3) Бывает, что графики у отдельных брокеров «зависают» (теряется блок данных от нескольких минут до часов). Знаю это не только по небольшим брокерам, но и у крупняков уровня Сбера. Это решается перезапуском терминала и/или перезаказом данных, но скрипт завязанный на графики точно встанет в ступор. Либо нужно закладывать в алгоритм такой форс-мажор, что свечки не подтягиваются несколько минут/часов и что именно должен в этом случае делать скрипт со своими позициями.
4) Работа с графиками, как мы посмотрим далее, это вполне лайтовый вариант написания советников, особенно завязанных на индикаторы. Т.к. почти всегда трейдер в этом случае у терминала и если с графиком что-то не так, то оперативно перезакажет данные. Ну и не нужно вникать в логику расчетов самих индикаторов, в коде которых новичок может вполне себе наделать ошибок (хотя код всех стандартных индикаторов представлены самими разработчиками, можно покапаться желающим).
5) Более профессионально вообще вести работу через ленту всех сделок. Обычно даже минутные свечки алгоритмы сами с них формируют и уже далее обрабатывают. Это а) более устойчиво, надежно и быстро (здесь в статье пример, когда свечка пришла задержкой в 49 секунд, с лентой такого бы не было) б) более информативно, т.к. лента содержит в т.ч. и направление сделки, а именно это, в конечном итоге (рыночные заявки), двигают цену. С ленты кто-то рисует горизонтальные профили/класстеры, кто-то делает более быстродейственные стратегии. Мы работу с таблицей обезличенных сделок тоже коснемся, через 1-2 темы примерно.
посмотрите плс вопросы ниже
Хотелось бы побыстрее дойти до заявок )))))) перейти от теории к практике ))
1. «стоит на всякий перезаказать данные в терминале» — поясните как это сделать
2. Таблица которую мы создаем m_t=AllocTable() немного отличается от стандартных «квиковских», нет возможности «скопировать ячейку» или «скопировать всю таблицу» нет ли других вариантов создания собственых таблиц?
3.лента всех сделок — это как?
1. в терминале Система / Заказ данных / Перезаказать данные. Ставим выгрузку данных текущей сессии и архивных данных. Кнопка «Перезакать».
2. какие есть. Собственно зачем еще копипаста, если мы все данные уже в файл скопировали?
3. таблица обезличенных сделок. Тиковые данные по инструменту с указанием направления сделки. Подключается через Создать окно. Если там не находите такого пункта, то в этом же подменю выбираете «Настроить меню» и добавляете. После этого сможете выбрать. У некоторых брокеров лента всех сделок работает только после подключения через поддержку (она бесплатная, но по умолчанию может не транслироваться всем).
Хотел бы задать условие, если файл пустой, то что-то выполнить, но почему-то никак не распознается, что пуст.
smart-lab.ru/blog/1011401.php