Блог им. morefinances
Сегодня дополним наш алгоритм советника следующими пунктами:
1. Пропуск «поздних» сигналов на старте.
2. Обработка советником обрыва связи.
3. Сохранение сигналов и логов в файл.
Еще один пункт, связанный со временем, который был выбран для апгрейда советника – это пропуск сигналов на старте, если запуск скрипта состоялся не в начале торговой сессии (например любой старт после 10:30). Это может быть полезным, если выбрана активная внутридневная стратегия и сигналы полученные на старте скрипта, например в середине дня, могут быть уже не актуальными (с низким потенциалом прибыли) и лучше дождаться новых. Т.е. необходимо разделить сигналы на те, которые сгенерировались на старте и остальные сигналы, которые будем далее брать в работу. Сигнал на старте может закрыться (по обратному/сигналу выхода) и если переоткроется снова, то его уже можно брать в работу как новый.
В нашем скрипте сигналы по каждому инструменту (массив signal) ранее могли принимать значение:
0 – вне позиции по инструменту
1 или -1 лонг или шорт
2 или -2 пропуск лонга или шорта по timeout (после заданного времени, когда пропускаем сигналы).
Добавим еще значение 3 и -3 для лонга и шорта соответственно в случаях, когда мы запустили скрипт, но текущее время уже более заданного (nomorningtime). В этом случае все сигналы, которые мы получили при самой первой итерации построения таблицы мы зафиксируем и будем дожидаться их завершения.
Разместим в OnInit соответствующую переменную:
nomorningtime = 103000
В условиях отработки сигнала необходимо предусмотреть, что если проходим первую итерацию и время > nomorningtime, то уведомляем о пропуске и signal по инструменту присваиваем соответствующее значение. Если данное условие не выполняется, то работает старый блок if с проверкой на сигнал.
if startind == 0 and time3 > nomorningtime then SetCell(m_t, i, 7, "noSHORT (start pass)") table_text = "Пропуск на старте SHORT сигнала "..tikers[i].." по цене "..tLast.param_image signal[i] = -3 else if time3 < nonewsignalstime then SetCell(m_t, i, 7, "SHORT") table_text = "сигнал SHORT по "..tikers[i].." по цене "..tLast.param_image signal[i] = -1 sellpos[i] = tonumber(tLast.param_value) else SetCell(m_t, i, 7, "noSHORT (time out pass)") table_text ="пропускаем сигнал на SHORT по таймеру "..nonewsignalstime.." по "..tikers[i].." по цене "..tLast.param_image signal[i] = -2 end end
Аналогично делаем для лонга:
if startind == 0 and time3 > nomorningtime then SetCell(m_t, i, 7, "noLONG (start pass)") table_text = "Пропуск на старте LONG сигнала по "..tikers[i].." по цене "..tLast.param_image signal[i] = 3 else if time3 < nonewsignalstime then SetCell(m_t, i, 7, "LONG") table_text = "сигнал LONG по "..tikers[i].." по цене "..tLast.param_image signal[i] = 1 buypos[i] = tonumber(tLast.param_value) else SetCell(m_t, i, 7, "noLONG (time out pass)") table_text = "пропускаем сигнал LONG по таймеру "..nonewsignalstime.." по "..tikers[i].." по цене "..tLast.param_image signal[i] = 2 end end
В отработке закрытия позиции предусматриваем, что signal может быть по модулю равен 3:
if math.abs(signal[i]) == 3 then if signal[i] > 0 then table_text = "Пропуск закрытия позиции LONG (nomorningtime) по "..tikers[i].." по цене "..tLast.param_image else table_text = "Пропуск закрытия позиции SHORT (nomorningtime) по "..tikers[i].." по цене "..tLast.param_image end textfinres = "Ближайший сигнал по "..tikers[i].." будет отработан на вход" end
Запускаем после 10:30, получаем на старте следующую картину:
При этом по прошествии закрытия стартовых позиций появляются новые, которые уже в привычном режиме штатно отслеживаются и закрываются, с выводом соответствующего результата:
Так, например, по Татнефть преф (TATNP) был сигнал при запуске скрипта, он через несколько минут был закрыт алгоритмом и позже уже предлагался на открытие позиции:
Теперь данный пункт нашего апгрейда полностью закрыт: сигналы отслеживаются согласно нашей логике – те, что мы получили на старте, если время старта более заданного дожидаются завершения, после чего срабатывают согласно первоначальной стратегии, по другим инструментам, которые на старте сигналов не подавали, сигналы работают в обычном режиме до времени nonewsignalstime, после которого новые сигналы не принимаются, но закрываются старые позиции.
Небольшое дополнение: в прошлой статье пропустил в выводе финансового результата: т.к. сигналы 2 и -2 мы пропускаем – не входим их по таймеру, то и вывод финансового результата не понадобится. Ставим дополнительное условие по выводу результата:
Итоговый вариант файла с правками по пропуску «поздних» сигналов на старте: https://github.com/morefinances/qlua/blob/main/simple%20advisor%20v1_3_nonewsignalstime.lua
Обработка обрыва связи.
Варианты того как можно обрабатывать разорванные соединения с сервером мы уже проходили, просто добавим переменную connect=isConnected() и в зависимости от того есть у нас связь или нет будем обновлять таблицу, либо выведем сообщение:
Переменная discreport (прописываем её в OnInit: discreport = 0) позволит нам вывести это сообщение только один раз (после вывода меняем значение с 0 на 1). А при возобновлении связи мы добавим перед условиями сигналов вывод:
При этом, если мы хотим, например, чтобы сигналы, которые получим при возобновлении связи не принимались в работу (по аналогии с рассмотренным запуском после nomorningtime), то нужно добавить в это условие всего 2 строки:
startind = 0 nomorningtime = time3 - 1
Старые позиции скрипт будет закрывать по тейку, а сигналы, которые нарисовались сразу после запуска пропускать до следующих новых по данным инструментам.
На старте имеем после разрыва связи 3 сигнала, которые скрипт пропускает. Спустя небольшое время по 1 происходит закрытие и скрипт далее готов по нему принимать новые сигналы:
Через 15 минут скрипт уже получил закрывающие сигналы и отслеживает только новые позиции:
Итоговый файл с правками по обработки разрыва связи: https://github.com/morefinances/qlua/blob/main/simple%20advisor%20v1_3_disconnect.lua
Сохранение сигналов и логов в файл.
И на последок самое простое. Работу с файлами мы также уже проходили ранее.
Наши сигналы и логи будем сохранять в файл в директории C:\files\ (для корректной работы нужно создать).
Наименование файла будет состоять из название самого советника плюс дата и время:
filename = string.sub(progname, 1, 14).." "..os.date('%d%m%Y_%H%M%S')
Есть 2 варианта реализации сохранения в файл.
Первый – самый простой, мы открываем файл на запись в main в начале исполнения скрипта:
--создаем/открываем файл для записи DirectionSaveFile=tostring("C:\\files\\"..filename..".csv") my_csv=io.open(DirectionSaveFile,"a+")
И закрываем при остановке скрипта в OnStop:
-- закрытие файла my_csv:flush() my_csv:close()
А во всех местах, где у нас есть вывод в файл мы размещаем:
my_csv:write(abc..";\n")
где abc – данные для сохранения, ";\n" – знак разделителя и переноса строки.
Файл1: https://github.com/morefinances/qlua/blob/main/simple%20advisor%20v1_3_save_file_easy.lua
Этот вариант вполне рабочий, но я бы назвал его «ленивым», т.к. делается быстро, но не совсем правильно: если по каким-то причинам у нас будет нештатно приостановлена работа скрипта (завис компьютер или вылетел квик, например), то данные не сохранятся в файл, т.к. всё это время работы скрипта данные только собирались в поток вывода, но не были сохранены (это делает flush, которую мы спрятали в OnStop).
Второй вариант. Более правильно сделать функции по каждому блоку: открытие файла, запись (в накопитель), закрытие файла (сохранение из накопителя в файл и его закрытие).
И использовать эти 3 функции последовательно в каждом блоке вывода информации. Да, мы эксплуатируем файл более активно (постоянно открывая и закрывая после вывода при сохранении сигналов, результатов или логов связи), но зато у нас сохраняются все данные и даже если по каким-то причинам скрипт не будет остановлен штатно, то мы сможем посмотреть все записи от старта и до последних изменений.
Функция открытия файла:
function open_file() --создаем/открываем файл для записи DirectionSaveFile=tostring("C:\\files\\"..filename..".csv") my_csv=io.open(DirectionSaveFile,"a+") end
Функция записи:
function print_file(text) my_csv:write(text..";\n") end
И функцию закрытия файла:
function close_file() -- закрытие файла my_csv:flush() my_csv:close() end
Файл2: https://github.com/morefinances/qlua/blob/main/simple%20advisor%20v1_3_save_file_normal.lua
Запускаем скрипт, получаем за пару часов работы отработку сигналов:
И аналогичные записи в наш csv файл:
На этом с апгрейдом советника завершим, но вы сами можете подумать какие еще полезные опции можно было бы внедрить и какие важные условия скрипт мог бы учитывать.
Зачем нужно было столько времени тратить на этот пример с различными усовершенствованиями скрипта? Ведь простого советника можно было в один заход расписать еще в самой первой статье по этой теме.
С большим упрощением можно сказать, что торговый робот – это советник, который дополнительно управляет заявками, позициями и счетами. Поэтому важно на этом этапе понимать, что нам нужно не только сигналы получать, но и учитывать различные узкие моменты работы терминала, самой стратегии, работы со временем и прочими техническими и биржевыми аспектами.
Будет большим шагом на пути построения своего робота если вы на этом этапе начнете расписывать своего робота (логику входа, выхода, управления капиталом) и на первых порах сделаете простого советника, выдающего сигналы, тестируя его и поэтапно докручивая. В ближайшее время мы будем проходить более практичные темы для торговли: работа с биржевыми стаканами, свечками, заявками, позициями и счетами и вашего советника легко можно будет поэтапно трансформировать в рабочего торгового робота. И всегда лучше писать своего робота, чем брать за основу уже написанный код (покупать или искать в сети), т.к. гораздо труднее найти ошибку в чужом коде, чем отыскать в своем. Да и любая перестройка своего алгоритма будет даваться в разы быстрее.
Упражнения:
1. Дополните скрипт закрытием оставшихся позиции и финальным расчетом результатов по ним при остановке скрипта.
2. В нашем скрипте есть повторяющийся блок кода, который можно вынести в отдельную функцию. Попробуйте найти его, заменить на функцию и оптимизировать, таким образом, алгоритм.
3*. В логике сигналов при написании алгоритма я использовал небольшое упрощение (чтобы не перегружать код), однако если подходить более строго и писать боевого робота, то код нужно дополнить в условиях сигнала. Попробуйте найти ошибку в логике сигналов данного скрипта и корректно переписать условия для изначально выбранной стратегии.
Теги: 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 советник: продолжение.
Дополняем сигналами на закрытие позиции.
Создаем дополнительную таблицу для вывода данных.
Делаем расчеты финансового результата.