Продолжаем тему прошлого урока. Мы начали писать робота.
Предыдущие статьи:
Qlua для чайников. Часть 1
Qlua для чайников. Часть 2. Циклы
Qlua для чайников. Часть 3. Работа со стаканом
Так что, теперь, если вы принимаетесь за написание программы, у вас уже не должно возникать вопроса: «С чего начать?», ибо на прошлом уроке мы этот вопрос прекрасно разобрали. Но может возникнуть следующий вопрос: «А как продолжить?». Вот научились мы работать со стаканом, написали запись стакана в файл (чисто ради тренировки), а дальше-то что? Как реального робота создать?
Вообще, чтобы подобные вопросы не возникали («Как начать?», «Как продолжить?», «Как закончить?») полезно иметь определенный план действий. Вот сейчас мы с вами и составим такой план. Для начала разобьем процесс написания робота по шагам (начиная с текущего состояния):
- Разработать механизм определения границ лучших цен, с учетом уже выставленных заявок. Для этой цели нам придется написать механизм поиска своих заявок.
- Разработать механизм выставления заявок, с учетом того факта, что заявки могут быть уже выставлены и могут быть исполнены.
- Разработать механизм перевыставления заявок при изменении цен.
- Разработать механизм удаления выставленных заявок и закрытия всех открытых позиций по рынку в заданное время.
Теперь приступим к реализации этого плана. Согласно первому пункту, нам нужна какая-то подпрограмма, которая бы искала выставленные заявки. Давайте для начала определимся, а так ли уж нам нужен поиск заявок. Дело в том, что в qlua есть предопределенная функция OnOrder, которая вызывается каждый раз, когда вводиться новая заявка или когда меняются параметры существующей заявки. Давайте испытаем эту функцию. Добавим в нашего робота вот такой код:
function OnOrder(order)
message(tostring(order[«order_num»]),1)
end
|
Код внутри функции OnQuote можете временно убрать, чтобы не создавать на диске кучу файлов. Кстати, сразу расскажу, как код можно убрать временно. Один из способов – поставить перед каждой строкой кода значок “--”, который обозначает комментарий:
function OnQuote(class_code, sec_code)
if class_code==p_classcode and sec_code==p_seccode then
--l_file=io.open(«D:\\1\\1\\»..tostring(count)..".txt", «w»)
--tb=getQuoteLevel2(class_code, sec_code)
--l_file:write(«BID:\n»)
--for i=1,tb.bid_count,1 do
-- l_file:write(tostring(tb.bid[i].price).."; "..
-- tostring(tb.bid[i].quantity).."\n")
--end
--l_file:write(«OFFER:\n»)
--for i=1,tb.offer_count,1 do
-- l_file:write(tostring(tb.offer[i].price).."; "..
-- tostring(tb.offer[i].quantity).."\n")
--end
--count=count+1
--l_file:close()
end
end
|
А можно просто поставить return 0:
function OnQuote(class_code, sec_code)
if class_code==p_classcode and sec_code==p_seccode then
return 0
…
|
Тогда программа дойдет до этой команды и вернется из функции, не выполняя код, лежащий после return 0.
Итак, опробуем программу. Если сейчас мы запустим этот скрипт, а потом введем заявку, то у нас будет выдано сообщение, содержащее номер заявки:
Если мы эту заявку удалим, то получим сообщение еще раз. Все верно, OnOrder вызывается каждый раз, когда изменяется статус заявки. Удаление заявки меняет ее статус.
Но как узнать, что же мы делаем с заявкой? Вводим новую, удаляем или, может быть, событие OnOrder вызвано исполнением заявки?
На этот вопрос нам даст ответ свойство flags таблицы параметра, передаваемого в OnOrder. Измените функцию OnOrder следующим образом:
function OnOrder(order)
message(tostring(order[«order_num»].." flags="..order[«flags»]),1)
end
|
Теперь вы будете видеть, что когда мы вводим заявку, у нас flags=25, а когда удаляем 26. Если у нас заявка исполняется, то flags=24.
Но на самом деле не нужно сравнивать flags с числом. Дело в том, что это битовые флаги. В компьютере все числа представлены в двоичном виде. В отличие от десятичной системы счисления, в двоичной предусмотрено только две цифры 0 или 1. Соответственно, каждый разряд отличается от соседнего не в 10 раз, а в 2 раза. Таким образом, число 10 в двоичной системе это 2 в десятичной, а 100 это 4. 101 в двоичной это будет 5 в десятичной. Что бы перевести число из десятичной системы в двоичную, надо делить его на 2 и записывать остаток от деления на каждой итерации. Начинаем с младшего разряда. Например, переведем число 25 в двоичную систему. Делим на 2, получаем 12, остаток 1. Делим еще на 2, получаем 6, остаток 0. Затем 3 и остаток 0. Потом 1 и остаток 1 и плюс еще остаток 1. Получим 11001. Каждый бит этого числа имеет определенное свое значение. Естественно, если 1 значит данный признак включен, 0 – выключен. Рассмотрим подробнее эти флаги:
- бит 0 (0x1) Заявка активна, иначе – не активна
- бит 1 (0x2) Заявка снята. Если флаг не установлен и значение бита «0» равно «0», то заявка исполнена
- бит 2 (0x4) Заявка на продажу, иначе – на покупку. Данный флаг для сделок и сделок для исполнения определяет направление сделки (BUY/SELL)
- бит 3 (0x8) Заявка лимитированная, иначе – рыночная
- бит 4 (0x10) Возможно исполнение заявки несколькими сделками
- бит 5 (0x20) Исполнить заявку немедленно или снять (FILL OR KILL)
- бит 6 (0x40) Заявка маркет-мейкера. Для адресных заявок – заявка отправлена контрагенту
- бит 7 (0x80) Для адресных заявок – заявка получена от контрагента
- бит 8 (0x100) Снять остаток
- бит 9 (0x200) Айсберг-заявка
Число 11001 означает, что включены следующие флаги:
- Заявка активна.
- Заявка лимитированная.
- Возможно исполнение заявки несколькими сделками.
При снятии заявки у нас flags=26, что соответствует двоичному числу 11010. В этом случае у нас флаг «Заявка активна» равен нулю, а вот флаг «заявка снята» на этот раз равен единице.
Но, согласитесь, так работать с битовыми флагами неудобно. Поэтому в qlua предусмотрены специальные средства для работы с битовыми флагами. А именно, логические функции. С помощью таких функций, например, можно проверить, установлен или брошен отдельный конкретный флаг. Например, проверить флаг «заявка снята» можно следующим образом:
if bit.band(order[«flags»],2)>0 then
message(«Заявка удалена»,1)
end
|
Этот код может показаться странным и непонятным, поэтому, придется изучить эти самые логические функции. Всего их четыре:
- Логическое сложение – функция ИЛИ (OR).
- Логическое умножение – функция И (AND).
- Логическое отрицание – функция НЕ (NOT)
- Исключающее ИЛИ (XOR).
Разберем эти функции подробнее. Функция «ИЛИ» дает 1, если хотя бы один из ее входных битов равен 1, и дает 0, если все нули. Функция «И» дает единицу, если на всех ее входах единица, если хотя бы на одном 0 — то дает 0. Функция «НЕ» превращает 0 в 1 и 1 в 0. Исключающее или дает 0 если все входы одинаковы – только одни единицы или только одни нули. Если есть различия, то на выходе единица.
Таким образом, если мы применим операцию «И» к двоичным числам 1010 и 1100 то на выходе получим 1000, так как у нас только в старшем разряде на обоих входах единицы.
Теперь поговорим о том, как же проверить конкретный бит при помощи этих логических функций. Очень просто. Надо совершить операцию «И» между набором битовых флагов и числом, которое содержит единицу только в проверяемом разряде. В нашем примере bit.band – это как раз функция «И», первый ее аргумент – битовые флаги, второй число 2, которое соответствует в двоичном коде числу 10, содержащему единицу только во втором разряде, который соответствует биту флага «Заявка снята». Понятно, что если этот бит включен – то результат функции будет какое то число, больше чем 0, в противном случае результат будет равен нулю, ибо тогда обнулятся все разряды.
Теперь вроде бы мы пришли к выводу, что поиск заявок нам и не нужен. Но как быть в том случае, если мы робота остановили, потом снова запустили? Например, у нас завис компьютер, мы были вынуждены его перезагрузить и заново запустить квик. Даже если получив номера заявок через OnOrder мы сохраним их в памяти, эти данные при закрытии и повторном запуске робота потеряются. Как быть? Можно сохранять их в текстовом файле. Даже если файлик потеряется при аварийном завершении работы робота, мы всегда можем набрать его «ручками». Есть и другое решение. Например, можно прочитать таблицу заявок. Но тут возникают еще и другие вопросы. А что, если мы еще и вручную торгуем? Или у нас торгует несколько роботов, и каждый создает заявки. Как найти свои заявки?
Сейчас мы не будет разбирать такие тонкости, дабы не нагружать ваши головы, на которые уже и так свалилось много информации. Пока мы пишем учебный пример, и я постараюсь сделать его как можно проще. Но в будущем, когда будете разрабатывать своих роботов, обязательно задавайтесь этими вопросами, перед тем как начать программировать.
Итак, продолжаем. Сначала сделаем как проще, будем запоминать номера введенных заявок через OnOrder. Сохранением этих данных в файл пока заморачиваться не будем.
В начале программы объявим переменные, в которых у нас будут запоминаться сведения о введенных заявках. Разумеется, не в самом начале, не там где у нас параметры (переменные на букву p), а ниже, под ними:
buy_order="";
buy_price=0;
buy_count=0;
sell_order=""
sell_price=0;
sell_count=0;
|
Тут мы будем запоминать номер заявки, цену, и остаток заявки (баланс). Под остатком понимается количество инструмента, по которому еще не совершена сделка. То есть, если вы выставляете лимитированную заявку, которая исполняется не сразу, то начальное значение остатка – это количество из заявки. По мере исполнения (когда заявка исполняется частично), остаток будет уменьшатся, когда исполниться полностью станет 0. Для того чтобы получить такой остаток, нужно обратиться к атрибуту заявки balance. Не путать с атрибутом qty!
Вот каким образом мы будем получать эти данные:
function OnOrder(order)
p_file:write(os.date().." OnOrder\n");
--сначала проверим, по нашему ли инструменту эта заявка
if order[«sec_code»]==p_seccode and order[«class_code»]==p_classcode then
p_file:write(os.date().." заявка "..order[«order_num»].."\n")
--если заявка активна, то запоминаем ее
if bit.band(order[«flags»],1)>0 then
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»],8)>0 then
sell_order=""
sell_price=0
sell_count=0
else
buy_order=""
buy_price=0
buy_count=0
end
end
end
end
end
|
В этой процедуре мы, используя средства работы с битовыми флагами, проверяем, активна ли заявка. Если активна, то запоминаем ее данные, проверяя, что это за заявка, на покупку или на продажу. Если заявка не активна, то мы эти данные сбрасываем. Еще мы информацию о заявке пишем в лог, что бы потом мы могли проверить, правильно ли работает наша подпрограмма.
У вас может возникнуть вопрос: а для чего в программе используется tonumber? Дело в том, что в переменной order, куда функция OnOrder передает структуру с параметрами заявки, поля имеют строковый тип. А строка – это не число. Но нам нужно число, потому что далее мы с этими данными будем проводить различные числовые операции, в частности, сравнение. Почему строка? Так решили разработчики, нам остается с этим смириться и просто преобразовывать к нужному нам типу.
Идем дальше.
Полная версия статьи на
robostroy.ru
На вопросы автор отвечает там же.