Блог им. morefinances
Продолжаем изучать основы qlua. Улучшаем советника, которого писали ранее и уже дополняли в разных вариантах работой со временем.
Сегодня рассмотрим:
Дополним сигналами на закрытие позиции.
Создадим дополнительную таблицу для вывода данных.
Научим скрипт делать расчет финансового результата.
Сигналы на закрытие позиции.
Логика выходов не менее важная часть любой торговой стратегии и должна тестироваться также скрупулезно как и логика сигналов на вход и разные варианты фильтров для лонга и шорта. Также может быть отдельная логика управлением позицией, например часть позы может частично докупаться если движение идет в сторону прибыли, частично резаться если в сторону убытков, могут по-разному управляться стопы: вся позиция или часть закрываться по уровням, вся или часть двигаться трейлингом в разной логике, например, какая-то часть или вся позиция закрываться по времени (перед закрытием основной сессии или через определенное количество часов после входа, если нет сильного движения и цена ни стоп, ни тейк не достигла).
Это всё тестируется и чаще берется какой-то один вариант в работу. Реже более сложная модель, совмещающая в разные сочетания несколько видов выходов.
Но здесь в качестве учебного варианта мы рассмотрим, что выход из сигнала происходит просто когда перестают работать условия самих сигналов. Напомню логику, которую взяли изначально для работы советника:
Сигнал LONG: в случае, если количество лотов на покупку (суммарный спрос) в 2 раза больше аналогичного объема на продажу (суммарное предложение), при этом текущая цена выше цены закрытия вчерашнего дня (основной торговой сессии).
Сигнал SHORT: если суммарные продажи в 2 раза больше суммарного спроса и текущая цена ниже цены закрытия вчерашнего дня (основной сессии).
В нашем коде достаточно разместить в условиях отработки сигналов следующий код после else:
--отработка выхода из сигналов if signal[i] ~= 0 then if signal[i] == 1 then message(progname.." закрытие позиции LONG по "..tikers[i].." по цене "..tLast.param_image) end if signal[i] == -1 then message(progname.." закрытие позиции SHORT по "..tikers[i].." по цене "..tLast.param_image) end signal[i] = 0 end
И если ранее соблюдались условия сигнала (signal[i] не равен 0), а теперь нет, то будет выдано необходимое сообщение и сигналу по инструменту присвоен ноль.
При этом, также как скользящие средние в боковике будут давать множество сигналов на открытие и закрытие позиции, так же и в нашем случае если нет сильного движения можно получить в коротком интервале множество входов и выходов по одному и тому же инструменту. Например только за первые 17 минут открытия торгов (скрипт начинает генерировать сигналы с 10:01) можно получить таким образом аж 22 сигнала на вход:
В случае нашего советника обратный сигнал чаще будет возникать, когда соотношение продаж и покупок будет меняться, и реже, когда будет «прыгать» цена вокруг цены вчерашнего закрытия, но на открытии торгов цены обычно оказываются близко к закрытию вчерашнего дня, поэтому и такая череда сигналов на старте. После первых 1-2 часов торгов советник будет давать гораздо меньше сигналов.
Отдельная таблица для вывода сигналов, логов и результатов работы скрипта.
Бывает удобным, чтобы не смотреть постоянно в таблицу системных сообщений вывести дополнительно необходимые сообщения в отдельную таблицу. Это может пригодится, когда, например, одновременно работают несколько скриптов и по одному нужно более пристально контролировать работу.
Такую таблицу можно сделать в фиксированном формате, например в 10 строк и когда они будут заполнятся, то счетчик индекса строки будет обновляться до единицы. При желании, для наглядности строку с последними данными можно подсвечивать цветом.
Либо сделать «резиновой», когда она будет просто увеличиваться на одну строку, если добавляется новая запись. В этом случае можно последнюю строку не подсвечивать, т.к. последняя запись всегда будет в самом низу страницы:
Добавим сперва саму таблицу в main:
size_table = 10 -- количество строк таблицы table_result_ind = 1 -- индекс строки if table_result==nil then table_result = AllocTable() AddColumn(table_result, 1, "Сигналы и результаты:", true, QTABLE_STRING_TYPE, 70) CreateWindow(table_result) SetWindowPos(table_result,0,430,500,300) SetWindowCaption(table_result, progname.." сообщения скрипта") for u = 1, size_table do InsertRow(table_result,-1) end end
Для вывода в таблицу удобно будет сделать функцию, которую будем вызывать из условий сигнала.
В первом случае функция будет выглядеть так:
function table_print(table_text, table_result_ind) SetCell(table_result, table_result_ind, 1, table_text) if table_result_ind == 1 then SetColor(table_result, 10, QTABLE_NO_INDEX, mWhite, mBlack, mWhite, mBlack) SetColor(table_result, table_result_ind, QTABLE_NO_INDEX, mBlue, mBlack, mBlue, mBlack) else SetColor(table_result, table_result_ind - 1, QTABLE_NO_INDEX, mWhite, mBlack, mWhite, mBlack) SetColor(table_result, table_result_ind, QTABLE_NO_INDEX, mBlue, mBlack, mBlue, mBlack) end end
Во втором случае:
function table_print(table_text, table_result_ind) if table_result_ind <=10 then SetCell(table_result, table_result_ind, 1, table_text) else InsertRow(table_result,-1) SetCell(table_result, table_result_ind, 1, table_text) end end
Добавим в условиях сигнала соответствующий вывод в таблицу и обновление счетчика строки:
И мы получим необходимый результат.
Ссылка на github первого варианта.
Ссылка на github второго варианта.
Чтобы в нашей таблице отражалось еще и время сигнала достаточно, например, добавить в строку для отображения time3, которое мы отслеживаем в каждом случае if (помним, что отражение будет числом).
В эту же таблицу далее мы сможем размещать информацию, например, про разрывы связи или о приостановке получения сигналов на вход. Также эту таблицу, в отличие от основной, мы можем не закрывать при остановке скрипта, чтобы можно было лишний раз посмотреть как отработал алгоритм, какие были сигналы, их результат и пр.
Вывод информации по финансовому результату позиции.
Удобно, чтобы скрипт мог считать финансовый результат. На боевых счетах это может быть точное вычисление с учетом всех особенностей тарифа брокера, плечей, FIFO или даже с учетом налогов. Но сейчас, в первичном исполнении советника, мы упростим и будем находить в качестве финансового результата простой валовый доход по трейду: разницу между покупкой или продажей для лонгов и продажей и дальнейшим откупом для шортов. Когда мы подойдем к работе с реальными счетами расчеты можно будет сделаем уже с учетом всех затрат. Пока простая арифметика для понимания насколько в целом успешна или убыточна взятая в качестве стратегии для советника гипотеза по инструментам.
Предположим, что в каждую сделку мы входим минимальной суммой. Т.е. нам нужно знать лотность по каждому инструменту советника. Плюс нам нужны будут цены входа и выхода по каждой сделки.
Добавим 3 массива:
lots = {} -- лотность по инструменту buypos = {} -- будем фиксировать цену покупки по каждой бумаги sellpos = {} -- аналогично фиксируем цену продажи
Лоты можно запросить, например, в цикле, где получали цену закрытия предыдущего дня. Там же поставить нулевые значения массивам цен:
for x = 1, #tikers do closeprice[x] = getParamEx("TQBR", tikers[x], "PREVLEGALCLOSEPR") -- цена закрытия предыдущего дня signal[x] = 0 -- статус сигнала по инструменту lots[x] = getParamEx("TQBR", tikers[x], "LOTSIZE") buypos[x] = 0 -- добавляем нулевые значения в массив цен покупок sellpos[x] = 0 -- и в массив цен продаж end
В условиях сигналов добавляем фиксацию цены входа (для шорта это цена продажи sellpos, для лонга это цена покупки buypos):
Нужно сказать, что данный пример входа в позицию – это очень большая условность, т.к. цена при срабатывании сигнала будет отличатся (в отдельные моменты значительно) от цены реального входа (более правильно – в случае советника брать ближайший BID/OFFER со стакана, работу с которым мы будем рассматривать в скором времени, а в случае роботов цену фиксируем по конкретной сделке, т.к. даже ориентируясь на стакан иногда приходится предусматривать повторное выставление заявки, если рынок в моменте резко сдвинулся в одну или другую сторону).
Теперь добавим в условиях сигналов if в части отработки выхода цену закрытия (обратная цене входа, т.е. для лонговых позиций это sellpos, для шортовых buypos), необходимые расчеты результата и вывод в нашу дополнительную таблицу:
Округление цифр.
Как видим, результаты по трейду скрипт считает, но в силу особенности арифметики с плавающей запятой получаем всякие небольшие неточности.
Кто впервые с этим столкнулся удивится, что привычные 0.1 для компьютера на самом деле 0.1000000000000000055511151231257827021181583404541015625, и данную погрешность порой
можно встретить даже в Excel просто чаще её нам Excel не показывает.
Например простой скрипт message(tostring(0.1+0.2 == 0.3)) вернет нам false, а не true, что было бы логичным. Подробнее об этом можно почитать в сети загуглив «погрешности при операциях с плавающей запятой», например.
К сожалению, просто отсечь эту погрешность до второго знака нельзя, как мы это делали ранее с нулями, т.к. иначе 0.9999999999998 превратится в 0,99, а не в 1. Нам нужна функция, которая сделает округления по принятым математическим правилам.
И проще всего это реализовать через строковое форматирование данного числа. Сделаем функцию:
function mprint(numb) return string.format("%.2f", numb) end
Через которую будем выводить итоговый результат. Она округлит по всем правилам число до второго знака.
Кстати, если применить вывод с помощью форматирования к сумме 0,1+0,2 и к числу 0,3 с точностью до 30 знака
message(string.format("%.30f",0.1+0.2)) message(string.format("%.30f",0.3))
то увидим, почему эти числа для компьютера не равны:
0.300000000000000044408920985006
0.299999999999999988897769753748
Теперь в скрипте выдачу информации finres будем проводить через данную функцию mprint с точностью до 2 знака, в итоге получим уже нормальное отражение цифр:
Код варианта советника с учетом расчетов результата:
https://github.com/morefinances/qlua/blob/main/simple%20advisor%20v1_2_finres.lua
В качестве самостоятельных упражнений:
1. Сделайте расчет финансового результата с учетом комиссий брокера (например 0,05% от суммы за сделку).
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: дополняем скрипт советника таймингом
Устанавливаем время старта работы скрипта,
Ставим тайминг на получение сигналов на вход,
Устанавливаем таймер на приостановку скрипта.
я больше по облигациям — ISIN и уровни покупки/продажи хранятся в Excel файле
Даже если не хочется работать с форматом csv, а нужен именно xls файл с формулами и форматированием, то можно сделать небольшой макрос, который нужные данные сохранит по кнопке (или при активации нужной ячейки, например) в csv, а скрипт на qlua уже все данные возьмет с csv. Зато работать это всё будет а) быстрее и б) более устойчиво, т.к. делается полностью штатными средствами.
1/ luacom у меня не взлетел, excel = luacom.GetObject(«Excel.Application») не видит работающий экземпляр Excel2019/Win10
2/ перехожу к плану «Файл обмена», подскажете нет ли рабочих примеров чтения/записи в файл. Пока нашел вот такой пример записи (https://quikluacsharp.ru/qlua-osnovy/iz-qlua-lua-v-excel-csv/)
Не подскажите пример чтения?
Работу с файлами, в т.ч. чтение рассматривал здесь:
smart-lab.ru/blog/922044.php
Про запись в файл рассматривал еще в сегодняшней статье:
https://smart-lab.ru/blog/927748.php
wdho.ru/f4704dc, разместите в директории терминала.
Вышеприведенный пример по первой ссылке работает (выдает данные с ячеек A1 и B1). Только его нужно поместить в main.