Мы продолжаем создавать нашего биржевого робота спредера. В этом уроке будем учиться искать заявки и разбираться с процессом отладки.
Предыдущие уроки:
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.
В данном случае логично предположить, что не работает функция поиска. Так что вставляем отладочное сообщение в нее:
--Процедура поиска ордеров
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
|
Запускаем. Видим ряд сообщений. Вводим заявку. Сначала видим сообщение о факте ввода заявки:
Затем видим наше сообщение:
Оно говорит о том, что функция SearchItems ничего не нашла. Но потом, с очередным изменением стакана приходит другое сообщение:
Оно уже говорит нам о том, что функция все-таки что-то нашла.
Мы выяснили, что функция 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
На вопросы автор отвечает там же.