Блог им. morefinances

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

Продолжаем изучение qlua, cегодня:
Интегрируем таблицы в структуру скрипта qlua.
Удаляем таблицы через DestroyTable.
Останавливаем скрипт через IsWindowClosed.
Обработка события закрытия таблицы через коллбэк.
Работа с цветом SetColor, Highlight, SetSelectedRow.
Пишем простого советника.

В прошлый раз мы рассмотрели как с помощью qlua создать таблицу в торговом терминале и заполнить её информацией из таблицы текущих торгов. Но это была статичная табличка, чтобы её «оживить» нужно разместить операции с нашей таблицей внутри структуры скрипта в функции main.

Саму таблицу мы можем создать до цикла while и внести неизменяемые данные (в нашем случае тикер и наименование бумаги), а уже заполнить цифрами и обновлять внутри цикла. Пока будет работать скрипт таблица будет обновляться.

function OnInit()
  tikers = {"GAZP", "SBER", "VKCO"}
  progname = "mytable :"
  timeout = 5000
end

function OnStop()
  do_it = false
  message(progname.." Финиш.")
end

function main() 
  message(progname. <a name="cut"></a> ." Старт.")
  do_it = true
  if m_t==nil then     -- если таблица не создана ранее, то 
    m_t=AllocTable() -- создать таблицу
    AddColumn(m_t, 1, "Тикер", true, QTABLE_STRING_TYPE, 10) 
    -- добавить 1 столбец шириной в 10 символов
    AddColumn(m_t, 2, "Бумага", true, QTABLE_STRING_TYPE, 20)
    -- добавить 2 столбец шириной в 20 символов
    AddColumn(m_t, 3, "Цена", true, QTABLE_STRING_TYPE, 10)
    -- 3й столбец с шириной в 10
    AddColumn(m_t, 4, "Суммарное предложение", true, QTABLE_STRING_TYPE, 25)
    -- 4й и 5й столбцы с шириной в 25  
    AddColumn(m_t, 5, "Суммарный спрос", true, QTABLE_STRING_TYPE, 25)
    CreateWindow(m_t) -- создание окна таблицы
    SetWindowPos(m_t, 0, 430, 700, 110) -- позиционирвание (x,y от левого верхнего угла) 
							-- и размеры (ширина, высота)
    SetWindowCaption(m_t, "Вывод данных через таблицу") -- показать таблицу, пишем заголовок

    for u = 1, #tikers do
      InsertRow(m_t,-1)	-- добавить строку
    end
  end

  -- вносим в таблицу неизменяемые данные
  for i = 1, #tikers do
    local tName = getParamEx("TQBR", tikers[i], "SHORTNAME").param_image
    SetCell(m_t, i, 1, tikers[i])
    SetCell(m_t, i, 2, tName)
  end

  while do_it do
  -- заполнение и дальнейшее обновление таблицы
  for i = 1, #tikers do
 
  local tLast = getParamEx("TQBR", tikers[i], "LAST").param_image 
  local tOffer = getParamEx("TQBR", tikers[i], "OFFERDEPTHT").param_image 
  local tBid = getParamEx("TQBR", tikers[i], "BIDDEPTHT").param_image 
  <br />  SetCell(m_t, i, 3, tLast)
  SetCell(m_t, i, 4, tOffer)
  SetCell(m_t, i, 5, tBid)
  end
  
  sleep(timeout) 
  <br />end
end

Теперь наша таблица будет обновляться каждые 5 секунд, пока мы принудительно не остановим скрипт соответствующей кнопкой на панели управления доступных скриптов.


Функция DestroyTable.
Удалить таблицу можно с помощью DestroyTable.
Если добавить, например, в функцию OnStop строку DestroyTable(m_t), то при нажатии на кнопку остановки одновременно с завершением работы скрипта автоматически закроется и таблица.

function OnStop()
  do_it = false
  DestroyTable(m_t)
  message(progname.." Финиш.")
end

Скрипт можно также остановить не только кнопкой и вызовом OnStop, но и через закрытие таблицы. Есть 2 варианта, как это можно выполнить. Первый: использовать функцию IsWindowClosed.


Функция
IsWindowClosed

Вернёт true если окно таблицы закрыто пользователем вручную, false если окно таблицы открыто в терминале и nil если таблица c запрашиваемым id не существует (указан несуществующий/некорректный id или таблица удалена функцией DestroyTable).

Так если добавить в цикл while строку:

if IsWindowClosed(m_t) then OnStop() end

То после закрытия окна мы увидим, что остановится и скрипт в панели доступных скриптов (это будет не мгновенно, т.к. цикл while ожидает завершение паузы sleep(timeout), которая у нас в OnInit определена как 5 секунд (timeout=5000), цикл выжидает его завершения.


Обработка события закрытия таблицы через коллбэк.

Мы можем получать коллбэки на различные операции, которые пользователь проводит с таблицей. В дальнейшем это нам особенно пригодится для создания диалоговых окон и взаимодействия со скриптом (когда можно уже в процессе запуска алгоритма менять различные параметры и переменные, например размер стопа и тейка, размера позиции и пр.).

В текущей ситуации нам нужно только отслеживать коллбэк на закрытие таблицы. Для этого подпишемся после создания таблицы на коллбэки по ней:

SetTableNotificationCallback(m_t, table_callback)


А выше функции main создадим функцию table_callback, в которой укажем, что если таблицу закрыта пользователям, то вызвать OnStop:

function table_callback(m_t,msg,par1,par2)
  if msg==QTABLE_CLOSE then OnStop()end
end

Если подходить с позиции ресурсов, хоть IsWindowClosed и запрашивается нами постоянно в цикле, а функция table_callback вызывается всего один раз от коллбэка, но сам коллбэк несет в себе существенно большую информацию по событиям таблицы (как мы будем рассматривать позднее) и дает эту информацию не разово, а на постоянной основе, поэтому будет потреблять и несколько больше ресурсов:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

Поэтому пока оставим в нашем скрипте только функцию IsWindowClosed, хотя в дальнейшем еще задействуем в полном объеме и коллбэки.


Работа с цветом

Разработчики терминала предусмотрели возможность выделять цветом информацию в пользовательских таблицах. Для этого есть несколько функций:


SetColor
позволяет установить цвет ячейки и/или столбца таблицы в терминале:

SetColor(id таблицы, строка, столбец, цвет фона, цвет текста, цвет фона2, цвет текста2)

фон2 и текст2 – цвета при выделении ячейки (когда на соответствующую строку таблицы нажимаем мышкой).


Для определения цветов предусмотрена функция RGB, через которую можно задавать соответствующую палитру.

Так наш скрипт можно немного изменить, если сравнить покупки и продажи и подсветить большую из этих цифр. Для этого если ранее в таблице мы данные отражали как строки, то теперь нам нужно будет в цикле for в локальные переменные получить сразу всю таблицу данных от getParamEx по инструменту, в таблице разместим как строки, а в условиях сравнения уже цифры.

 

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")

  SetCell(m_t, i, 3, tLast.param_image)
  SetCell(m_t, i, 4, tOffer.param_image)
  SetCell(m_t, i, 5, tBid.param_image)

-- продажи больше покупок подсвечиваем продажи, покупки без цвета
if tOffer.param_value > tBid.param_value then 
    SetColor(m_t, i, 4, RGB(255,204,250), RGB(0,0,0), RGB(255,204,250), RGB(0,0,0))
    SetColor(m_t, i, 5, RGB(255,255,255), RGB(0,0,0), RGB(255,255,255), RGB(0,0,0))
  -- покупки больше продаж подсвечивам покупки, продажи без цвета
elseif tOffer.param_value < tBid.param_value then 
    SetColor(m_t, i, 4, RGB(255,255,255), RGB(0,0,0), RGB(255,255,255), RGB(0,0,0))
    SetColor(m_t, i, 5, RGB(199,254,236), RGB(0,0,0), RGB(199,254,236), RGB(0,0,0))
  -- продажи равны покупкам оставляем без подсветки оба значения
else
    SetColor(m_t, i, 4, RGB(255,255,255), RGB(0,0,0), RGB(255,255,255), RGB(0,0,0))
    SetColor(m_t, i, 5, RGB(255,255,255), RGB(0,0,0), RGB(255,255,255), RGB(0,0,0))
end

end

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

Если продажи будут равны покупкам (редкий случай), то цветом ничего не выделяется.


В функциях работы с цветом предусмотрено несколько констант:

QTABLE_DEFAULT_COLOR – цвет заданный по умолчанию в системе.

QTABLE_NO_INDEX – в параметре столбца будет распространять цвет сразу на весь столбец, а не на одну ячейку. В параметре строки аналогично сделает цвет на всю строку. Если поставить эту константу (равна -1) и там, и там, то окрасится вся таблица.


Есть еще несколько вариантов выделения цветом ячеек – это так называем плавное затухание:

Highlight  этот эффект работает по умолчанию в таблице текущих торгов, если не убирать цветовые настройки:

Highlight(id таблицы, строка, столбец, цвет фона, цвет текста, время подсветки в мс). Пример мы рассмотрим позднее при создании советника.


И выделение строки (аналог нажатия мышкой на строку таблицы в терминале) через
SetSelectedRow:

SetSelectedRow(id таблицы, номер строки).

Закрывая основные функции работы с таблицами можно еще указать функцию GetCell, которая позволяет получить данные из конкретной ячейки:

GetCell (id таблицы, строка, столбец).

Вернет таблицу с ключами: value – числовое значение, image – строковое представление в таблице.

Но это очень прикладная ситуация, когда нам нужно получить такие данные из таблицы, т.к. а) все ячейки таблицы, как правило, содержат переменные или константы, которые мы и так знаем и б) работа с переменными в разы быстрее, чем делать подобный запрос на получение значения ячейки. Даже если мы хотим посмотреть какое именно значение было ранее в таблице в конкретной ячейке до того как обновим в ней информацию проще ввести еще одну переменную, которая будет эти данные хранить и перед обновлением ячейки присваивать текущее значение.


Полный список всех функций работы с таблицами можно посмотреть в файле помощи по qlua от разработчиков (это файл QLUA.chm, который лежит в папке торгового терминала с info.exe). Открываем файл QLUA, в поиске забиваем «Функции для работы с таблицами», в числе первых находим:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало). 

Создание нескольких таблиц.

При необходимости можно одновременно создавать и несколько таблиц (id в этом случае должны отличаться). Например при написании советника мы можем сделать еще одну таблицу, где будем фиксировать все сигналы, чтобы не отлавливать их в таблице системных сообщений.

По ситуации также можно в процессе работы скрипта добавлять таблицы и в необходимый момент удалять их. Но всё хорошо в меру. Порой достаточно просто предусмотреть дополнительные поля в одной таблице, чтобы всё было органично.


Таблицы и вкладки в терминале.

Скриптом не получится разместить таблицу в конкретную вкладку терминала. Если необходимо перенести таблицу в другую вкладку — это делается уже вручную: после того как мы запустили скрипт и нужная таблица создана в самом терминале выделяем её (нажимаем на заголовок или на любую строку), далее в меню Окна > Переместить окно на вкладку.

Чтобы закрепить тему работы с таблицами предлагаю самостоятельно поупражняться в создании таблиц.
Например, сделав шахматную доску:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

Или создав таблицу умножения с цветовой подсветкой. Выделив, например, те ячейки, по которым в результате перемножения получается такая двузначная величина цифра единиц которой целочисленно делится на цифру десятков:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

Пишем простого советника.

К текущему моменту мы изучили qlua уже достаточно, чтобы попробовать написать своего советника.

Но прежде чем перейти к этому шагу считаю необходимым разместить важный дисклеймер, т.к. наше законодательство ужесточается и нужно предусмотреть всё, в т.ч. чтобы данные статьи не «выпилили» в будущем в результате очередных ограничений и различных законотворческих инициатив со стороны ЦБ по робоэдвайзингу или инвестсоветникам:

Дисклеймер: все рассматриваемые примеры здесь и далее являются исключительно упражнениями для закрепления материала по программированию на qlua и не несут каких-либо инвестиционных идей или конкретных сигналов. Автор не планирует делиться в каком-либо виде своими торговыми стратегиями, либо инвестиционными рекомендациями, либо в иной форме (в т.ч. в виде кода) давать сигналы по конкретным инструментам. Любая торговая стратегия или гипотеза (в т.ч. высказанная в комментариях к статьям) требует многократного тестирования на истории на разных таймфреймах и инструментах, правильного управления капиталом и риск-менеджментом. Вся ответственность за торговлю по данным и иным материалам автора полностью лежит на инвесторе. Доходность прошлых периодов любой торговой стратегии или финансового инструмента не гарантирует аналогичной доходности в будущем.

Одним словом, ни прошлые, ни текущие, ни будущие материалы не являются инвестрекомендацией. Мы здесь «про программирование», а уже какие именно стратегии закладывать в код решает каждый самостоятельно.

По этой же причине (риски работы скриптов на реальных счетах и ответственность за их работу) прежде чем мы начнем работать в терминале с реальными заявками, сделками и торговыми счетами нужно будет установить демо терминал и изначально потренироваться на «кошечках» в деморежиме, всё настроить, получить и исправить первые ошибки в алгоритмах и только после всех проб и доработок можно будет переходить на работу с реальным счетом. Не предлагаю установить демо квик сейчас, т.к. чаще всего демодоступ дают только на небольшой интервал (3-4 недели). И чтобы не запрашивать продление или проходить перерегистрацию позже под другой почтой проще дождаться этих темы и только потом установить, чтобы успеть максимум в эти временные рамки работы демо терминала.

Обычно советников пишутся либо для ручной торговли, либо для «полуатоматической», когда трейдер дополнительно хочет поучаствовать в решении и в каких-то случаях менять, например, размер позиции, либо вообще пропустить сигнал и не входить в сделку. Иногда советника запускают перед тем как полностью автоматизировать скрипт, чтобы проверить, что всё работает правильно, в соответствии с логикой стратегии.

Торговая логика советника.

С одной стороны, на текущем этапе нам нужен алгоритм с достаточно динамичной генерацией сигналов, т.к., например, 1 торговый сигнал в день или неделю может быть даже очень не плохо для оптимизированной торговой стратегии, но не очень показательно в учебных целях.

С другой стороны, всегда проще достраивать логику на примере, который мы уже рассматривали, чем прописывать алгоритм с нуля. По этой причине возьмем за основу предыдущий пример и просто немного дополним его. Будем отслеживать не 3 акции, а например, список акций, входящих в индекс Московской Биржи IMOEX как наиболее ликвидных инструментов.

На текущий момент в индекс входит 42 компании (состав индекса всегда можно посмотреть на сайте биржи: https://www.moex.com/ru/index/IMOEX/constituents/)

Нам достаточно только расширить массив с тикерами необходимыми бумагами, чтобы автоматически таблица перестроилась:

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"
}

Если на момент прочтения статьи индекс поменялся в составе (что периодически происходит) просто добавьте новый или удалите неактуальный тикер из этого массива.


Стратегия

В качестве торговой стратегии (предположим, что мы выдвинули несколько гипотез и смогли после тестирования на истории определиться на одной, наиболее перспективной по показателям) возьмем следующую логику:

Сигнал LONG:  в случае, если количество лотов на покупку (суммарный спрос) в 2 раза больше аналогичного объема на продажу (суммарное предложение), при этом текущая цена выше цены закрытия вчерашнего дня (основной торговой сессии).

Сигнал SHORT:  если суммарные продажи в 2 раза больше суммарного спроса и текущая цена ниже цены закрытия вчерашнего дня (основной сессии).

Если условия не соблюдаются, то остаемся вне позиций.


Есть несколько вариантов как можно получать биржевую информацию с таблицы текущих торгов через getParamEx. Один из них – пример с которого мы начали: мы в функции main с определенной периодичностью (указанную через sleep) запрашиваем данные и обрабатываем их.

Второй вариант: мы можем получать информацию через функцию обратного вызова OnParam, которая при любых изменениях таблицы будет давать нам коллбэк на изменения, которые мы будем далее обрабатывать. Второй вариант мы рассмотрим позднее, он пригодится для более быстродейственных скриптов. В нашем текущем т.к. рассматриваемые параметры не меняются глобально каждую секунду достаточно выставить паузу в 10 секунд.

Делаем небольшие правки в нашем предыдущем примере:

1) вынесем цветовые константы в OnInit;
2) уменьшим размеры и название столбцов;
3) добавим цену закрытия предыдущего дня по основной сессии (используем для этого параметр PREVLEGALCLOSEPR);
4) добавляем условия сигналов LONG/SHORT;
5) создаем массив signal, который будет хранить текущий статус по каждому инструменту (1 – лонг, -1 – шорт, 0 – вне позиции).


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 :"
  timeout = 10000
  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)
end

function OnStop()
  DestroyTable(m_t)
  do_it = false
  message(progname.." Финиш.")
end

function main() 
  message(progname.." Старт.")
  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_STRING_TYPE, 10)
  AddColumn(m_t, 4, "Закрытие", true, QTABLE_STRING_TYPE, 10)
  AddColumn(m_t, 5, "Продажи", true, QTABLE_STRING_TYPE, 10)
  AddColumn(m_t, 6, "Покупки", true, QTABLE_STRING_TYPE, 10)
  AddColumn(m_t, 7, "Сигнал", true, QTABLE_STRING_TYPE, 10)
  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)


  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) then 
        SetCell(m_t, i, 7, "SHORT")
        if signal[i] == 0 then
           message(progname.." сигнал SHORT по "..tikers[i].." по цене "..tLast.param_image)
           signal[i] = -1 
        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) then 
        SetCell(m_t, i, 7, "LONG")
        if signal[i] == 0 then
           message(progname.." сигнал LONG по "..tikers[i].." по цене "..tLast.param_image)
           signal[i] = 1 
        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

if IsWindowClosed(m_t) then OnStop() end

sleep(timeout)

end
end



И запускаем:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

Видим, что на старте сегодня в районе полудня скрипт показал всего 1 сигнал в лонг (по VKCO). По остальным бумагам вне позиций, хотя есть тикеры, по которым суммарные продажи превышают более чем в 2 раза суммарные покупки.

За пару часов работы алгоритм бы добавил еще несколько сигналов:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало).

В первичном варианте советник работает, но есть несколько но:

1: если мы запускаем скрипт не в начале торговой сессии, то при внутридневной торговле правильнее не использовать сигналы, которые получаем на старте, а «отлавливать» только новые. Это как пересечение скользящих – оно могло состоятся уже приличное время назад, а мы будем работать по сильно отстающему сигналу, который, возможно, в ближайшее время уже полностью «отработает» свой потенциал. Для кого-то позиция покажется спорной, но мы возьмем в работу для корректировки алгоритма.


2: учитываем время старта. Многие не торгуют первую минуту открытия рынка (разновидности: не торгуют первые 5М, 30М или даже 1H).

3: аналогично нужно учесть время приостановки получения сигналов на вход. Например, торгуя внутри дня в основную сессию, кто-то не принимает новые сигналы, если они приходят после 18:00 (либо за 10-15 минут до аукциона закрытия).

Наглядно посмотреть в жизни 1 и 3 пункты мы могли бы, если бы запустили наш скрипт в пятницу ближе к вечеру:

Qlua: размещаем таблицу в скрипте, остановка скрипта при удалении таблицы, работа с цветом. Пишем своего советника (начало). 

Мы видим, что первые 3 сигнала сформировались сразу на старте (и возможно уже потеряли свою актуальность), а более 20 сигналов генерируются в аукцион закрытия и позднее, что при торговле в основную сессию нас бы уже не интересовало.

4: правильнее было бы, чтобы советник не только сигналил о входе в LONG/SHORT, но и как только условия на соответствующий сигнал перестали бы выполняться подавал сигнал на закрытие позиции.

5: по результатам торгового дня скрипт также может выводить информация о бумажном финансовом результате по каждой позиции (понятно, что при торговле руками может быть сильная задержка по времени и расхождение).

6: сейчас все сигналы выводятся через message, а было бы полезно сделать отдельную таблицу, где бы фиксировались все сигналы, чтобы не отлавливать сообщения в общем потоке таблицы.

7: скрипт можно также дополнить отработкой прерывания связи с сервером. Т.к. при возобновлении связи может быть сгенерировано большое число новых сигналов, часть из которых уже не будут актуальны (аналогия с первым пунктом).

8: все сигналы и информацию о разрыве соединения можно записать в файл. Первые нужны для анализа качества сигналов, вторые для понимания насколько всё устойчиво работает и не пропустили из-за технических проблем какие-то входы/выходы.

9: скрипт должен уметь останавливаться по таймеру. Так если торгуем в основную сессию внутри дня сигналы на вход, например, мы можем получать до 18:30. При этом в 18:35 должны просигналить о закрытии всех текущих открытых позиций и, например, в 18:40 завершить работу скрипта с выводом всей необходимой информации в таблицы и файл (есть трейдеры, торгующие только в первую половину дня, у них скрипт в обед по таймеру будет завершать работу).

Какие-то из этих пунктов кто-то посчитает избыточными, а кто-то, уверен, захочет еще дополнить парой десятковважных для него позиций. В любом случае есть что улучшать и мы займемся этим уже в следующий раз.

Кто хочет попробовать свои силы самостоятельно может выбрать несколько пунктов из этого списка для апгрейда скрипта и попробовать их реализовать в качестве «домашнего задания».

Сегодняшние примеры, как и ранее, размещены на github:
https://github.com/morefinances/qlua
(example_10, 11 и simple adviser)


Теги: 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

 

★23
16 комментариев
А как составить таблицу подобную simple advisor v.1, в которой произвести сравнение по всем инструментам по закрытию свечей. Если дневная свеча закрылась сегодня выше, чем дневная свеча закрылась вчера, а вчерашняя свеча закрылась выше чем позавчерашняя. (скажем три-пять дней, на выбор закрываются выше предыдущей) тогда идёт сигнал на лонг, аналогично в шорт, тогда идёт сигнал на продажу… ???
Ramil Shahattudinov, мы работу со свечками будем позднее рассматривать, пока примеры на таблице текущих торгов, чтобы не отходить далеко от последней темы. Если хотите прямо сейчас вникнуть самостоятельно погуглите про функцию CreateDataSource.
avatar
Тоже писал для себя вещи на QLua. Но был разочарован. У меня была цель написать полноценную инфраструктуру, но отбило желание реализовывать это в Quick. Начать можно с того, что в плане создания гибкого UX / UI мы ограничены.

Если кто-то захочет реализовывать масштабные вещи, то вам дорога в Transaq Connector. Благо на Github куча проектов для изучения. Хоть на NodeJS всё делай. 
avatar
VLASSAL, Согласен, много ограничений. В какие-то моменты каждый со временем что-то подключает дополнительно (я немного совмещаю с python). Относительно выбора терминала писал в самых первых статьях, пока квик самый массовый и позволяет при необходимости менять брокера. При этом минусов много.
avatar
Спасибо, очень интересно. А не могли бы вы в следующий раз привести пример построения сканера акций и (или) стаканного робота?
avatar
Михаил К., скринер акций будем отдельным примером проходить, до роботов еще далеко, но работу с биржевым стаканом коснемся через несколько тем.

Если хотите самостоятельно посмотреть примеры — погуглите робот Бегемот в сети (не мой, просто там как раз стратегия на стакане работает). 
avatar
alfacentavra, «Бегемот в сети» — хорошее название
avatar
Михаил К., сегодня, как и просили, разместил пример скринера акций: 
smart-lab.ru/blog/928152.php

avatar
alfacentavra, спасибо за информацию!
avatar
Шахматная доска неправильно нарисована. Зеркально перевернута. 
avatar
Reader, наверное вы правы, я не большой шахматист) исходил из того, что a1 на доске это черное поле.
avatar
Для тех, кто только начинает свои опыты в алгоритмизации торговли — норм. Когда упретесь в ограничения QLua, но захотите остаться на QUIK — добро пожаловать в QUIKSharp.
avatar
Prophetic, знаю о существовании stocksharp, а quiksharp что за зверь? 
avatar
Михаил К., Это коннектор к квику, для написания роботов в C#. Коннектор позволяет выполнять функции Qlua в среде C#, со всеми вытекающими отсюда плюсами. Проект бесплатный и имеет открытый исходный код.
github.com/finsight/QUIKSharp/issues
avatar
Prophetic, можете в двух словах, чем он лучше стандартных средств Qlua? Что нибудь для примера. Просто, lua как язык и сам по себе не плох (типа Питона), функционал его вряд-ли должен как-то уступать сишарпу, но даже если и уступает, как может сишарп перепрыгнуть ограничения qlua, наложенные разработчиком quik? 
avatar
1. Самое главное в переходе на C# это вынос логики расчетов за пределы потока QUIK. Это снижает нагрузку на терминал, и предотвращает тормоза в нем. Когда попытаетесь запустить обработку пары-тройки десятков инструментов — поймете.
2. Возможность делать нормальный пользовательский интерфейс. В QLua из вменяемого был VLC, но его возможностей в какой-то момент стало не хватать.
3. Доступ к мощной среде разработки, и что еще важнее ОТЛАДКИ.
4. Доступ к внешним библиотекам, про которые я в QLua даже не слышал. В качестве примера: Попробуйте какую-нибудь нейронку к QLua прикрутить.
5. Возможность создать самостоятельное (не привязанное к терминалу) приложение для бектестирования стратегий, код которых потом можно легко превращать в «боевых» роботов, практически не переписывая его.

Что касается ограничений qlua, то чтобы ответить на вопрос надо понять, что конкретно Вы имеете в виду.

Ну и наконец, хочу чтобы Вы правильно меня поняли. Я не собираюсь никого убеждать в необходимости перехода на QUIKSharp. Если человека полностью устраивает QLua — пусть остается на нем. Мне, несколько лет назад, его возможностей стало не хватать, и тогда я начал поиски альтернативы. Для себя нашел. И бесплатной альтернативы этому решению, на сколько я знаю, до сих пор не появилось.
avatar

теги блога alfacentavra

....все тэги



UPDONW
Новый дизайн