orekton
orekton личный блог
20 октября 2014, 12:55

Qlua для чайников. Часть 5. Работа с таблица Quik. Поиск заявок. Искусство отладки

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

Предыдущие уроки:

Qlua для чайников. Часть 1
Qlua для чайников. Часть 2
Qlua для чайников. Часть 3. Делаем робота-спредера
Qlua для чайников. Часть 4. Анализ информации из стакана и работа с заявками


На прошлом уроке мы с вами написали заготовку, которая рассчитывает цены выставления наших заявок, на основе крайних цен в стакане (программа считает заданный отступ от этих цен). Если вы не читали прошлый урок, все равно зайдите на него и скачайте приложение – заготовку робота, в этом уроке вам она понадобится.
Как я уже говорил, у нашей программы есть недочеты. Во-первых, из-за того, что события изменения стакана приходит раньше, чем событие выставления заявок, у нас иногда проскакивают неверные цены. Подробнее опять см. прошлый урок. Во-вторых, после запуска у нас робот начинает работать только после того, как произойдут первые изменения в стакане. Как исправить эти недочеты? Давайте подумаем.

Итак, начнем с первой проблемы – рассинхронизация событий. Как вариант решения – все таки сделать поиск заявок, а не читать параметры введенной заявки в событии OnOrder. А сейчас  внимание!!! – важная информация. Поиск заявок – это одна из задач, которые можно решить при помощи функции SearchItems. Эта функция предназначена для поиска информации в различных таблицах Quik. Таблица заявок – это одна из таких таблиц.  Полный список таких таблиц можно посмотреть здесь http://help.qlua.org/ch4_5_3.htm, нас же интересует пока только таблица orders – заявки.
Для поиска заявок пишем функцию find_orders:

--Процедура поиска ордеров
function find_orders()
      local NO=getNumberOf(«orders»)
      t_orders = SearchItems(«orders», 0, NO-1, fn, «flags, sec_code, class_code»)
      if t_orders ~= nil then
            for i=1,#t_orders,1 do
                  t_orders_item=getItem(«orders», t_orders[i])
                  remember_order(t_orders_item)
            end
      end
end
Что делает эта функция? Во-первых, она получает количество элементов в таблице orders (количество заявок):

local NO=getNumberOf(«orders»)
Для чего это нам надо? Для того, что бы вызвать функцию SearchItems, которой необходимо указать диапазон поиска. Диапазон поиска начинается с нуля. Поэтому мы указываем так:

t_orders = SearchItems(«orders», 0, NO-1, fn, «flags, sec_code, class_code»)
Первый параметр SearchItems – это имя таблицы, в которой мы ищем, в данном случае orders. Второй параметр – начало диапазона поиска, третий конец диапазона поиска, четвертый –поисковая функция, о ней сейчас скажу отдельно. Пятый параметр  — это список полей таблицы ордеров, которые будет анализировать поисковая функция. У каждой таблицы свой набор полей, что касается таблицы orders, то полный список полей можно посмотреть тут http://help.qlua.org/ch4_6_4.htm. В нашем же случае используются следующие поля:
  • Flags – набор битовых флагов, про них я рассказывал на уроке 4. (ссылка)
  • sec_code – код инструмента.
  • class_code – код класса.
Теперь сама поисковая функция:

--Поисковая функция
function fn(flags, sec_code, class_code)
      if sec_code==p_seccode and class_code==p_classcode and bit.band(flags,1)>0 then          
            return true
      else
            return false
      end
end
Эта функция производит анализ входных параметров. Значения входных параметров – это значения полей, перечисленных в пятом параметре функции SearchItems. В частности, мы проверяем поля sec_code и class_code – наш ли это инструмент и проверяем первый флаг битовых флагов, который сигнализирует о том, выставленная ли заявка. Работа с флагами так же была описана в уроке 4 (ссылка).
Если заявка найдена, а это значит, что она удовлетворяет заданным условиям – активна и по нашем инструменту – то  эта заявка запоминается. Для этого используется функция remember_order:

function remember_order(order)
      p_file:write(os.date().."  заявка "..order[«order_num»].."\n")
      --если заявка активна, то запоминаем ее
      if bit.band(order[«flags»],1)>0 then
            --message(«флаг:»..bit.band(order[«flags»],4),1)
            if bit.band(order[«flags»],4)>0 then
                  sell_order=order[«order_num»]
                  sell_price=tonumber(order[«price»])
                  sell_count=tonumber(order[«balance»])
            else
                  buy_order=order[«order_num»]
                  buy_price=tonumber(order[«price»])
                  buy_count=tonumber(order[«balance»])
            end
      else
            --если заявка не активна то сбрасываем информацию о заявке
            if bit.band(order[«flags»],1)>0 then
                  if bit.band(order[«flags»],4)>0 then
                        sell_order=""
                        sell_price=0
                        sell_count=0
                  else
                        buy_order=""
                        buy_price=0
                        buy_count=0
                  end
            end
      end
end
По сути, эта функция – кусок кода, выдранный из OnOrder и повторяющий ее. Поэтому мы можем просто вызвать remember_order из функции OnOrder, сократив ее:

function OnOrder(order)
      p_file:write(os.date().." OnOrder\n");
      --сначала проверим, по нашему ли инструменту эта заявка
      if order[«sec_code»]==p_seccode and order[«class_code»]==p_classcode then
            remember_order(order)
      end
end
А в функции анализа стакана OnQuote добавим вызов find_orders() :

function OnQuote(class_code, sec_code)
      if class_code==p_classcode and sec_code==p_seccode then
     
            find_orders()



Теперь, теоретически, у нас должен исправиться недочет. Но не тут-то было. Если посмотреть лог, то можно увидеть, что перед выставлением заявки крайние цены все равно считаются неправильно. Ну что ж, зато есть повод поучиться отлаживать программу. Сразу скажу, что в Квике с отладчиком очень тяжко. Точнее, его нет совсем. Так что придется довольствоваться либо логированием, либо сообщениями.
Начнем отладку. Процесс поиска ошибок можно проводить по следующему алгоритму:
  1. Выдвигаем гипотезу, где может быть ошибка.
  2. Проверяем эту гипотезу.
  3. Если ошибка найдена, то исправляем. Иначе см. п. 1.
В данном случае логично предположить, что не работает функция поиска. Так что вставляем отладочное сообщение в нее:

--Процедура поиска ордеров
function find_orders()
      local NO=getNumberOf(«orders»)
      t_orders = SearchItems(«orders», 0, NO-1, fn, «flags, sec_code, class_code»)
      message(«find_orders: „..tostring(t_orders),1)
      if t_orders ~= nil then
            for i=1,#t_orders,1 do
                  t_orders_item=getItem(“orders», t_orders[i])
                  remember_order(t_orders_item)
            end
      end
end
Запускаем. Видим ряд сообщений. Вводим заявку. Сначала видим сообщение о факте ввода заявки:
 Qlua для чайников. Часть 5. Работа с таблица Quik. Поиск заявок. Искусство отладки
Затем видим наше сообщение:
 Qlua для чайников. Часть 5. Работа с таблица Quik. Поиск заявок. Искусство отладки 
Оно говорит о том, что функция SearchItems ничего не нашла. Но потом, с очередным изменением стакана приходит другое сообщение:
 Qlua для чайников. Часть 5. Работа с таблица Quik. Поиск заявок. Искусство отладки 
Оно уже говорит нам о том, что функция все-таки что-то нашла.
Мы выяснили, что функция SearchItems находит выставленные заявки не сразу, а лишь спустя некоторое время после их выставления.
Итак, нам опять придется искать выход. Еще можно попробовать событие OnTransReply. Оно вызывается, когда происходит транзакция. То есть, если мы выставили заявку, то у нас должно прийти OnTransReply. Теоретически, OnTransReply должно прийти раньше, чем OnOrder. Но лучше все-таки это проверить.  Для этого добавляем в нашего робота вот такую функцию:

--обработка события транзакции
function OnTransReply(trans_reply)
      nord=trans_reply[«order_num»] --Номер заявки
      if nord==nil or nord==0 or nord==«0» then
            message(«Заявка не выставилась»,1)
            return
      end
      if trans_reply[«sec_code»]==p_seccode and trans_reply[«class_code»]==p_classcode then
            remember_order(trans_reply)
      end
end
Но, увы, и это ничего не дало. По-прежнему один такт выдается неверная цена:

09/25/14 14:31:31    300.68,302.2
09/25/14 14:31:31    300.68,302.2
09/25/14 14:31:32    301.01(!!!!),302.2
09/25/14 14:31:32 OnOrder
09/25/14 14:31:32  заявка 3217747
09/25/14 14:31:32 OnOrder
09/25/14 14:31:32  заявка 3217747
09/25/14 14:31:33  заявка 3217747
09/25/14 14:31:33    300.68,302.2
09/25/14 14:31:34  заявка 3217747
              09/25/14 14:31:34    300.68,302.19
Как быть? Продолжать отглючивать (отлаживать).  Воспользуемся тем же методом, что и ранее.  Возможно,  OnTransReply так же приходит после обновления стакана. А может, ошибка и в самом обработчике OnTransReply. Что бы это выяснить, добавляем туда логирование:

--обработка события транзакции
function OnTransReply(trans_reply)
      p_file:write(os.date().." OnTransReply\n")
      nord=trans_reply[«order_num»] --Номер заявки
      p_file:write(os.date()..«nord=»..nord.."\n")
      if nord==nil or nord==0 or nord==«0» then
            message(«Заявка не выставилась»,1)
            return
      end
      p_file:write(os.date()..«сейчас будет проверка условия»..trans_reply[«sec_code»].."   "..trans_reply[«class_code»].."\n")
      if trans_reply[«sec_code»]==p_seccode and trans_reply[«class_code»]==p_classcode then
            p_file:write(«Сейчас запустим remember_order\n»)
            remember_order(trans_reply)
      end
end
Логирование показало, что OnTransReply не запускается вообще. Обычно в таком случае имеет смыл задать вопрос на форуме по qlua (http://www.quik.ru/forum/lua/), хотя ответа иногда приходиться ждать несколько часов или дней. Я задал вопрос и получил ответ, что для заявок, введенных вручную, OnTransReply не работает.

Полная версия статьи и текущий код робота на robostroy.ru 
На вопросы автор отвечает там же.
5 Комментариев
  • Тихая Гавань
    20 октября 2014, 12:58
    спасибо за вашу работу! ловите плюсы
  • vito333
    20 октября 2014, 14:05
    полезно и интересно, спасибо
  • Дмитрий
    20 октября 2014, 19:39
    Спасибо! Очень интересно.
  • Фыва
    04 ноября 2014, 16:32
    спасибо и + в проф.
  • Дмитрий
    14 ноября 2014, 01:23
    Спасибо, очень полезно! А сделайте, пожалуйста, пост как написать скрипт для «плавающего стопа»))

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн