В прошлый раз был создан советник, который по минимальной торговой логике давал нам некие сигналы на лонг/шорт. Но было что улучшать и, может, далеко не все пункты кто-то захочет включить в алгоритм собственного советника, но в учебных целях мы поэтапно рассмотрим каждый, чтобы было понимание как это можно реализовать.
Сегодня дополним скрипт теми пунктами, которые были связанные с обработкой времени:
Прежде всего начала сделаем правильное отображение цифровых данных.
В прошлых примерах все данные в скрипте выводились в формате строки.
При внимательном рассмотрении можно было заметить, что число выравнивалось по левому (как текст), а не правому краю (как число) ячейки.
Типы всегда можно посмотреть по функции AddColumn в файле QLUA (говорили об этом ранее) по запросу «Функции для работы с таблицами Рабочего места QUIK»:
Тип данных в колонке:
QTABLE_INT_TYPE – целое число,
QTABLE_DOUBLE_TYPE – число с плавающей точкой,
QTABLE_INT64_TYPE – 64-битное целое число,
QTABLE_CACHED_STRING_TYPE – кэшируемая строка,
QTABLE_TIME_TYPE – время,
QTABLE_DATE_TYPE – дата,
QTABLE_STRING_TYPE – строка.
Подправим код при создании таблицы, чтобы цены и размер покупок и продаж отражались именно цифрами:
AddColumn(m_t, 3, "Тек.Цена", true, QTABLE_DOUBLE_TYPE, 10) AddColumn(m_t, 4, "Закрытие", true, QTABLE_DOUBLE_TYPE, 10) AddColumn(m_t, 5, "Продажи", true, QTABLE_INT64_TYPE, 10) AddColumn(m_t, 6, "Покупки", true, QTABLE_INT64_TYPE, 10)
Теперь числа в таблице выглядят приятно и более привычно:
Видим, что сегодня перестал торговаться Polymetal. Если его исключат из индекса, то придётся в дальнейшем скорректировать и список отслеживаемых бумаг в tikers.
Функция обработки времени
Удобно будет сделать функцию, которая позволит нам работать со временем как с числом:
-- принимает на вход таблицу date_time -- возвращает дату и время в формате ччммсс 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 tonumber(Hour..Min..Sec) end
В этом случае, например, если мы хотим задать время старта 10:01 достаточно, чтобы выполнялось условие hhmmss(os.sysdate())>=100100, а если хотим остановить скрипт после 18:30:59, то, соответственно hhmmss(os.sysdate())>=183059
Устанавливаем время старта работы скрипта.
Скрипт может продолжать работать если не остановить его программно (достаточно, чтобы в main отcуствовал цикл, например, либо в нужный момент вызывался OnStop) или пользователем кнопкой «Остановить» из панели терминала. Если запустить скрипт в терминале и далее закрыть квик, то при его перезапуске обнаружим, что скрипт продолжит свою работу (вопрос к коду насколько корректно, мы будем это учитывать и обрабатывать позднее).
Однако нужно уметь устанавливать время старта скрипта, чтобы, например как в нашем случае, скрипт не пытался работать в холостую до начала торговой сессии. Для этого дополним OnInit переменной starttime и приравняем к нужному времени в формате числа:
starttime = 100100 -- пропускаем первую минуту торгов, запуск скрипта после 10:01:00
После добавления функции hhmmss() нам теперь нужно будет только добавить обработку времени
Добавим в константы timeout2 = 25000 (25 секунды) по которым будет сверять время.
И до создания таблицы скрипта небольшой цикл по которому будем проверять наступило время старта или нет:
-- обработка starttime while hhmmss(os.sysdate())<starttime do local time1 = hhmmss(os.sysdate()) message('Ожидаем время старта скрипта : '..starttime..", текущее время: "..time1) sleep(timeout2) end
Цикл будет пропущен, если текущее время (при запуске скрипта) больше starttime, иначе через каждые timeout2 мс дожидаться нужного времени, с выводом в таблицу сообщений.
В отдельных стратегиях сам торговый алгоритм по результатам динамики дня может определять наилучшее время старта для следующего дня (например в период высокой волатильности не торговать первый час после открытия рынка, а в более спокойные моменты пропускать, исходя из своих вычислений, только первые 5М или 10М). В этом случае будет корректироваться только starttime. В тех стратегиях, где этого не требуется starttime будет константой.
Тайминг завершения сигналов на вход.
В некоторых случаях может понадобится, чтобы сигналы на вход мы уже не принимали, при этом скрипт должен отрабатывать сигналы на закрытие позиции. Такое может быть, если, например, стратегия внутридневная работает на открытие сигналов только в первой половине дня, хотя сами позиции могут держаться дольше и закрываться по стопу, тейку или по таймингу (перед аукционом закрытия, например).
Другой вариант внутридневных стратегий – сигналы на вход принимаются, например, до 18:00, те что поступают после игнорируются, закрываются аналогично либо по установленным уровням, либо по времени.
Реализовать это можно через добавления еще одной переменной nonewsignalstime и внутри кода немного перепишем условия лонговых сигналов:
if tonumber(tLast.param_value) > tonumber(closeprice[i].param_value) and signal[i] == 0 then if time3 < nonewsignalstime then SetCell(m_t, i, 7, "LONG") message(progname.." сигнал LONG по "..tikers[i].." по цене "..tLast.param_image) signal[i] = 1 else SetCell(m_t, i, 7, "no signal LONG") message(progname.." пропускаем сигнал LONG по таймеру "..nonewsignalstime.." по "..tikers[i].." по цене "..tLast.param_image) signal[i] = 2 end end
И шортовых:
if tonumber(tLast.param_value) < tonumber(closeprice[i].param_value) and signal[i] == 0 then if time3 < nonewsignalstime then SetCell(m_t, i, 7, "SHORT") message(progname.." сигнал SHORT по "..tikers[i].." по цене "..tLast.param_image) signal[i] = -1 else SetCell(m_t, i, 7, "no signal SHORT") message(progname.." пропускаем сигнал на SHORT по таймеру "..nonewsignalstime.." по "..tikers[i].." по цене "..tLast.param_image) signal[i] = -1 end end
Теперь если текущее время становится равным или больше nonewsignalstime, то скрипт в таблице сообщений будет выводить информацию о том, что сигнал пропускается. Аналогично будут отражаться сигналы в самой таблице скрипта:
Как и в случае со starttime отдельные стратегии могут сами определять (исходя из волатильности, объемов и пр.) временные рамки по nonewsignalstime, корректируя их под текущий рынок. В дни сильных трендов, например, сигналы на вход могут приниматься дольше, а в период боковиков по отслеживаемым инструментам (или после определенного количество стопов за день) не приниматься до следующей торговой сессии.
Остановка скрипта по таймеру.
Несмотря на то, что мы в какой-то момент приостановили сигналы на вход советник еще какое-то время может работать. Он может в целом находится в спящем режиме до следующих суток, о чем говорили в самом начале, но сейчас рассмотрим вариант, когда нам нужно принудительно остановить по таймеру работу алгоритма.
Ставим тайминг на получение сигналов на вход, например на 18:00 finishtime = 183000
Внутри цикла while после обновления таблицы (for … end) дополним:
--остановка по таймеру local time2 = hhmmss(os.sysdate()) if time2>= finishtime then message(progname.." скрипт остановлен по таймеру : "..time2) OnStop() end
Таблица скрипта при этом будет закрыта (мы это прописывали в OnStop, который вызовет данный if).
Для удобства (чтобы каждый раз не лезть в код) можно в начале main дополнить вывод всех 3 параметров таймера:
message(progname.." starttime = "..starttime) message(progname.." nonewsignalstime = "..nonewsignalstime) message(progname.." finishtime = "..finishtime)
Итого после этих дополнений работы с таймером получаем в финале следующий вариант скрипта:
function OnInit() tikers = { "AFKS" , "AFLT" , "AGRO" , "ALRS" , "CBOM" , "CHMF" , "ENPG" , "FEES" , "FIVE" , "FIXP" , "GAZP" , "GLTR" , "GMKN" , "HYDR" , "IRAO" , "LKOH" , "MAGN" , "MGNT" , "MOEX" , "MTSS" , "NLMK" , "NVTK" , "OZON" , "PHOR" , "PIKK" , "PLZL" , "POLY" , "ROSN" , "RTKM" , "RUAL" , "SBER" , "SBERP" , "SGZH" , "SNGS" , "SNGSP" , "TATN" , "TATNP" , "TCSG" , "TRNFP" , "VKCO" , "VTBR" , "YNDX" } progname = "simple advisor v.1.1 :" timeout = 10000 timeout2 = 25000 -- пауза 25 секунд для проверки старта скрипта startind = 0 -- индекс старта скрипта (первой итерации) -- цветовые константы mBlack = RGB(0,0,0) mWhite = RGB(255,255,255) mRed = RGB(255,204,250) mGreen = RGB(199,254,236) mGray = RGB(226,226,226) starttime = 100059 nonewsignalstime = 180000 finishtime = 183000 end function OnStop() DestroyTable(m_t) do_it = false message(progname.." Финиш.") 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 tonumber(Hour..Min..Sec) end function main() message(progname.." Старт.") message(progname.." starttime = "..starttime) message(progname.." nonewsignalstime = "..nonewsignalstime) message(progname.." finishtime = "..finishtime) -- обработка starttime while hhmmss(os.sysdate())<starttime do local time1 = hhmmss(os.sysdate()) message('Ожидаем время старта скрипта : '..starttime..", текущее время: "..time1) sleep(timeout2) end do_it = true if m_t==nil then -- если таблица не создана ранее, то m_t = AllocTable() -- создать таблицу AddColumn(m_t, 1, "Тикер", true, QTABLE_STRING_TYPE, 10) AddColumn(m_t, 2, "Бумага", true, QTABLE_STRING_TYPE, 20) AddColumn(m_t, 3, "Тек.Цена", true, QTABLE_DOUBLE_TYPE, 10) AddColumn(m_t, 4, "Закрытие", true, QTABLE_DOUBLE_TYPE, 10) AddColumn(m_t, 5, "Продажи", true, QTABLE_INT64_TYPE, 10) AddColumn(m_t, 6, "Покупки", true, QTABLE_INT64_TYPE, 10) AddColumn(m_t, 7, "Сигнал", true, QTABLE_STRING_TYPE, 23) CreateWindow(m_t) SetWindowPos(m_t,700,0,690,780) SetWindowCaption(m_t, progname.." создание советника") -- показать таблицу, пишем заголовок -- добавляем строки циклом for u = 1, #tikers do InsertRow(m_t,-1) end end closeprice = {} -- создаем массив цен закрытия signal = {} for x = 1, #tikers do closeprice[x] = getParamEx("TQBR", tikers[x], "PREVLEGALCLOSEPR") -- цена закрытия предыдущего дня signal[x] = 0 -- статус сигнала по инструменту end while do_it do -- заполнение таблицы for i = 1, #tikers do local tLast = getParamEx("TQBR", tikers[i], "LAST") local tOffer = getParamEx("TQBR", tikers[i], "OFFERDEPTHT") local tBid = getParamEx("TQBR", tikers[i], "BIDDEPTHT") if startind == 0 then -- вывод неизменямой части таблицы local tName = getParamEx("TQBR", tikers[i], "SHORTNAME") SetCell(m_t, i, 1, tikers[i]) SetCell(m_t, i, 2, tName.param_image) SetCell(m_t, i, 4, closeprice[i].param_image) end SetCell(m_t, i, 3, tLast.param_image) SetCell(m_t, i, 5, tOffer.param_image) SetCell(m_t, i, 6, tBid.param_image) time3 = hhmmss(os.sysdate()) if tonumber(tOffer.param_value) > 2 * tonumber(tBid.param_value) then SetColor(m_t, i, 5, mRed, mBlack, mRed, mBlack) SetColor(m_t, i, 6, mWhite, mBlack, mWhite, mBlack) if tonumber(tLast.param_value) < tonumber(closeprice[i].param_value) and signal[i] == 0 then if time3 < nonewsignalstime then SetCell(m_t, i, 7, "SHORT") message(progname.." сигнал SHORT по "..tikers[i].." по цене "..tLast.param_image) signal[i] = -1 else SetCell(m_t, i, 7, "no signal SHORT") message(progname.." пропускаем сигнал на SHORT по таймеру "..nonewsignalstime.." по "..tikers[i].." по цене "..tLast.param_image) signal[i] = -2 end end elseif 2 * tonumber(tOffer.param_value) < tonumber(tBid.param_value) then SetColor(m_t, i, 5, mWhite, mBlack, mWhite, mBlack) SetColor(m_t, i, 6, mGreen, mBlack, mGreen, mBlack) if tonumber(tLast.param_value) > tonumber(closeprice[i].param_value) and signal[i] == 0 then if time3 < nonewsignalstime and signal[i] == 0 then SetCell(m_t, i, 7, "LONG") message(progname.." сигнал LONG по "..tikers[i].." по цене "..tLast.param_image) signal[i] = 1 else SetCell(m_t, i, 7, "no signal LONG") message(progname.." пропускаем сигнал LONG по таймеру "..nonewsignalstime.." по "..tikers[i].." по цене "..tLast.param_image) signal[i] = 2 end end else SetColor(m_t, i, 5, mWhite, mBlack, mWhite, mBlack) SetColor(m_t, i, 6, mWhite, mBlack, mWhite, mBlack) --отработка выхода из сигналов SetCell(m_t, i, 7, " ") if signal[i] ~= 0 then signal[i] = 0 end end Highlight(m_t, i, QTABLE_NO_INDEX, mGray, mBlack, 500) -- выделение цветом вносимых изменений if startind == 0 and i == #tikers then startind = 1 end -- убираем индекс первой итерации sleep(100) end --остановка по таймеру local time2 = hhmmss(os.sysdate()) if time2>= finishtime then message(progname.." скрипт остановлен по таймеру : "..time2) OnStop() end if IsWindowClosed(m_t) then OnStop() end sleep(timeout) end end
Можно скачать с github:
https://github.com/morefinances/qlua/blob/main/simple%20advisor%20v1_1.lua
В следующей статье продолжим его апргрейд.
Теги: qlua для начинающих, кружок авиамоделизма.
Ранее:
Qlua: введение
https://smart-lab.ru/blog/917696.php
Настраиваем торговый терминал и редактор кода
https://smart-lab.ru/blog/918869.php
Основы qlua, часть 1:
message, конкатенация
фильтрация по сообщениям в терминале
PrintDbgStr, комментарии
типы данных, type
операции с числами
операции со строками
операции с таблицами
условные операторы
https://smart-lab.ru/blog/920031.php
Основы qlua, часть 2:
Циклы for … do … end, while do … end, repeat … until.
sleep, break, goto.
как пройти весь массив циклом, как пройти таблицу по ключам
локальные и глобальные переменные, функции
получение даты и времени
получение данных через getInfoParam
https://smart-lab.ru/blog/921366.php
Qlua: структура скрипта.
Структура типового скрипта qlua с примерами.
Обработка скриптом «обрыва связи» с сервером и возобновления работы.
Работа с файлами: запись, перезапись и чтение файла.
getScriptPath, getWorkingFolder
https://smart-lab.ru/blog/922044.php
Qlua: получение данных из таблицы текущих торгов, создание таблиц в торговом терминале.
Получение биржевых данных через функцию getParamEx
Выгрузка списка параметров функции getParamEx через DDE из торгового терминала
Создание пользовательских таблиц в торговом терминале
https://smart-lab.ru/blog/923365.php
Qlua: работа с таблицами (продолжение). Пишем своего советника (начало).
Интегрируем таблицы в структуру скрипта qlua.
Удаляем таблицы через DestroyTable.
Останавливаем скрипт через IsWindowClosed.
Обработка события закрытия таблицы через коллбэк.
Работа с цветом SetColor, Highlight, SetSelectedRow.
Пишем простого советника.
https://smart-lab.ru/blog/924710.php
но лично мне так вызывать каждый раз os.systime — это накладно
я беру из OnParam время и сравниваю с нужным мне временем.
при условии что торгуются самые ликвидные инструменты, котировки будут всяко каждую секудну скакать и время также будет обновляться/сравниваться..
хотя известно что биржа шлет сразу пакет данных, а не каждый чих стакана. на практике выянил что onParam даже на фьюч СИ исполняется всего 10-12 раз в секунду
Работа с временем организуется намного проще. Достаточно перейти на unixtime. Необходимо определить переменные необходимых временных меток и просто сравнивать числа. Впрочем, если уж так необходимо получить число ччммсс из таблицы времени, достаточно использовать:
tonumber(os.date('%H%M%S', date_table))Например, таблица из os.sysdate() гораздо больше, чем Вы рассказываете.
И желательно пояснять хотя бы кратенько откуда что берется, как связано с реальностью. Локальное время, время сервера, время последней сделки из параметров и время источника Stratum 0 — это, возможно, разные сущности.
Вопросик: на просторах интернета нашел вот рекомендацию
«При использовании универсального формата описания параметров транзакции рекомендуем получать список параметров и их значений из „Кармана транзакций“: создайте таблицу „Карман транзакций“, добавьте транзакцию с необходимыми параметрами, а затем сохраните транзакцию в tri-файл.» не подскажете как это можно сделать?
Про tri-файлы и карман транзакций можно почитать в документации по терминалу (есть на сайте разработчиков: arqatech.com/upload/iblock/e35/Doc1031.zip) Раздел 6. Совместная работа с другими приложениями, п.6.9. Импорт тразакций.
Плюс почитайте про sendTransaction (есть в файле QLUA в директории квика). Только я бы рекомендовал сперва с заявками потренироваться на демосчете, не переходить сразу на боевой терминал, есть много нюансов.
1. попробую почитать про карман
2. Вопросов по коду выше
for u = 1, #tikers do
InsertRow(m_t,-1)
end
почему в InsertRow параметр -1, вероятно должен быть u
3. Когда ожидать следующий урок?
Продолжение: smart-lab.ru/blog/926972.php
Вот здесь рассматривали подробнее где и как можно смотреть весь список параметров:
smart-lab.ru/blog/923365.php
Это коллбэки на обработку событий в таблице. Отдельные ячейки можно выделить цветом, например, и сделать их кнопками. Нажав на которые ловить соответствующий коллбэк. Это единственный штатный «костыль» который позволяет что-то менять по ходу работы из скрипта.
Саму таблицу при этом можете перерисовывать (удаляя или добавляя строки, либо совсем удалив таблицу, пересоздав и отрисовывая заново с новыми параметрами) как угодно.
1.Нет ли у Вас каких-нибудь примеров кодов по сортировке пользовательских таблиц?
2. Как-то можно мышью перетаскивать строки в пользовательской таблице, подобно, таблице текущих торгов?
3. Можно ли каким-либо образом вносить изменения в таблицу текущих торгов?
1. есть пример из числа своих эксприментов с таблицами, чуть позже размещу.
2. да, я указал выше в комментарии, что вы можете отрабатывать события по таблице, смотрите мануал разработчика.
3. да, можно менять значения ячеек, удалять значения, смотреть по координатам текущее значение ячейки, удалять и добавлять строки/столбцы, удалять и заново перерисовывать таблицу.
Скрипт исключительно в учебных целях делал. Он создает таблицу с крупнейшими эмитентами (тикеры задаются в самом скрипте), далее выгружает объемы (не обороты за день) и в финале сортирует таблицу по суммарным объемам.
По пункту 3, я имел ввиду, что изменять стандартную терминалов куб таблицу текущих торгов. Ведь, чтобы её менять кодом, должен быть изначальный её код. Может как-то замудренно изложил?
Таблицы терминала скрипты редактировать не могут, только обращаться к ним или получать коллбэки. Можете сделать свою таблицу, дублируя в неё всё что вам нужно из таблицы текущих торгов и с ней уже производить любые изменения из скрипта.
Да по логике:
а) получить коллбэк
б) перестроить таблицу
Кодировку ставьте в Notepad++ windows1251:
smart-lab.ru/blog/918869.php
Еще, данный код автоматически сортирует данные, или можно как-то вручную вносить изменения сортировки по столбцам?
Если вы в обычном блокноте открываете, то вопросов нет, будет криво.
Но я для того и отправил ссылку на то где лучше открывать.
Если по каким-то причинам у вас и в Notepad++ открывает криво, то нужно выбрать в меню: Кодировки / преобразовать в ANSI.