Добрый день, Хочу поделиться некоторыми приемами, которые я использую при построении своих роботов. См. тему -мой робот. ----------------- В структуре спинного мозга робота , реализуемого в QUIK, условно можно выделить колбеки, в которые приходят сообщения о событиях и функцию main, работающую в отдельном потоке, которая обрабатывает эти события. ------------------- Как правило, в main делается бесконечный цикл, в котором и осуществляется обработка. ------------------------ Если обрабатывать нечего, то рекомендуют ставить sleep(time) , где time - число миллисекундах, на которое Вы заморозите работу main. =============== Но проблема в том, что пока Main не работает, в колбеки поступают события , которые надо обрабатывать либо как-то сохранить иначе они потеряются. ------------------------------ Если обрабатывать события в колбеках, то на время обработки будет остановлен терминал QUIK, так как колбеки работают в основном потоке термина. ------------------ Проблема решается путем сохранения сообщений из колбеков в массиве, из которого потом main сможет обработать все сохраненные сообщения. Такой список (массив,таблица и т д) сообщений называется очередью. =================== Вопрос лишь в том, как организовать эту очередь , чтобы было просто и быстро. --------------- Есть несколько вариантов, ------------ Вариант 1: классический - это сделать универсальные списки, очереди. Алгоритмов и их реализаций много можно найти в интернете. Сложно, малопонятно не спецам . Большая нагрузка на сборщик мусора, что замедляет исполнение скрипта. -------------------- Вариант 2: -рекомендуемый в документации QLUA - использовать потоко безопасные функции записи и удаления из массива. Просто, но относительно медленно. ------------------- Вариант 3: это мой, тот который я расскажу далее. просто и быстро. Объяснять буду на рабочем примере. ------------------- Продолжение следует...
Тем, кто хотел бы практически разобраться в данном вопросе, рекомендую установить демо КВИК и запускать скрипты, которые я буду выкладывать и пояснять. Итак начнем. Вот исходный скрипт, в нем закомментированы некоторые операторы, которые демонстрируют однотипность записи колбеков и их обработку в функции main. В первых трех строчках скрипта создаемся лог файл, куда скрипт выводит информацию о своей работе. Вам надо исправить 2-ю строку, записав в ней путь, где Вы хотите создать этот файл
Код
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then
DT=getInfoParam("TRADEDATE"); DT=tonumber(string.sub (DT,7,10)..string.sub (DT,4,5)..string.sub (DT,1,2));
fconnect=2;
Log:write("DT="..tostring(DT)..",fconnect="..fconnect.."\n");Log:flush();
end
------------------------------
local clas,sec,ncb,t,_time;
-----------------------------
while fconnect==2 do
while _N>0 do
t=_tN[_N]; _N=_N-1 ncb=t[1];
----------------------
if ncb==99 then
clas,sec=t[2],t[3];
else
t=t[2];
if ncb==1 then
clas=t.class_code; sec=t.sec_code; --может не быть
elseif ncb==2 then
clas=t.class_code; sec=t.sec_code;
-- elseif ncb==3 then
-- elseif ncb==4 then
-- elseif ncb==5 then
-- elseif ncb==6 then
-- elseif ncb==7 then
-- elseif ncb==8 then
-- elseif ncb==9 then
-- elseif ncb==10 then
-- elseif ncb==11 then
-- elseif ncb==12 then
end
end
Log:write(ncb..",clas="..tostring(clas)..",sec="..tostring(sec)..",_N=".._N.."\n");Log:flush();
_time=0;
end
sleep(1);
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s) _N=_N+1; _tN[_N]={99,c,s,tim};end
---------------------
function OnTransReply(t) _N=_N+1; _tN[_N]={1,t}; end
function OnOrder(t) _N=_N+1; _tN[_N]={2,t}; end
--function OnStopOrder(t) _N=_N+1; _tN[_N]={3,t}; end
--function OnTrade(t) _N=_N+1; _tN[_N]={4,t}; end
--function OnQuote(c,s) _N=_N+1; _tN[_N]={5,t}; end
--function OnAllTrade(t) _N=_N+1; _tN[_N]={6,t}; end
--function OnDepoLimit(t) _N=_N+1; _tN[_N]={7,t}; end
--function OnAccountBalance(t) _N=_N+1; _tN[_N]={8,t}; end-- изменение позиции по счету
--function OnAccountPosition(t) _N=_N+1; _tN[_N]={9,t}; end-- изменение позиции по счету
--function OnDepoLimit(t) _N=_N+1; _tN[_N]={10,t}; end -- изменение позиции по инструментам
--function OnFuturesClientHolding(t) _N=_N+1; _tN[_N]={11,t}; end -- изменение позиции по срочному рынку
--function OnMoneyLimit(t) _N=_N+1; _tN[_N]={12,t}; end -- изменение денежной позиции
--function OnClose() fconnect=nil end
--function OnConnected(flag) fconnect=1; end
--function OnDisconnected() fconnect=1 end
--function OnStop(flag) fconnect=nil; return 2000 end
--function OnCleanUp() end -- смена торговой сессии
--function OnDepoLimitDelete(t) end -- удаление позиции по инструментам
--function OnFirm(t) end -- получение описания новой фирмы
--function OnFuturesLimitChange(t) end -- изменение ограничений по срочному рынку
--function OnFuturesLimitDelete(t) end -- удаление лимита по срочному рынку
--function OnMoneyLimitDelete(t) end -- удаление денежной позиции
--function OnNegDeal end-- новая заявка на внебиржевую сделку или изменение параметров существующей заявки на внебиржевую сделку
--function OnNegTrade end-- новая сделка для исполнения или изменение существующей сделки для исполнения
для наглядности уберем эти комментарии:
Код
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then
DT=getInfoParam("TRADEDATE"); DT=tonumber(string.sub (DT,7,10)..string.sub (DT,4,5)..string.sub (DT,1,2));
fconnect=2;
Log:write("DT="..tostring(DT)..",fconnect="..fconnect.."\n");Log:flush();
end
------------------------------
local clas,sec,ncb,t,_time;
-----------------------------
while fconnect==2 do
while _N>0 do
t=_tN[_N]; _N=_N-1 ncb=t[1];
----------------------
if ncb==99 then
clas,sec=t[2],t[3];
else
t=t[2];
if ncb==1 then
clas=t.class_code; sec=t.sec_code; --может не быть
elseif ncb==2 then
clas=t.class_code; sec=t.sec_code;
end
end
Log:write(ncb..",clas="..tostring(clas)..",sec="..tostring(sec)..",_N=".._N.."\n");Log:flush();
_time=0;
end
sleep(1);
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s) _N=_N+1; _tN[_N]={99,c,s,tim};end
---------------------
function OnTransReply(t) _N=_N+1; _tN[_N]={1,t}; end
function OnOrder(t) _N=_N+1; _tN[_N]={2,t}; end
Запустим этот скрипт на исполнение и получим следующий результат в лог файле
В данном варианте скрипта у нас нет информации о времени исполнения каких-либо фрагментов. изменим скрипт и добавим измерения времени исполнения. кроме того, начнем процесс изучения с классической рекомендации разработчиков - поставить sleep на 1 секунду вот такой скрипт:
Код
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then
DT=getInfoParam("TRADEDATE"); DT=tonumber(string.sub (DT,7,10)..string.sub (DT,4,5)..string.sub (DT,1,2));
fconnect=2;
Log:write("DT="..tostring(DT)..",fconnect="..fconnect.."\n");Log:flush();
end
------------------------------
local clas,sec,ncb,t,_time,tim1,tim2,T;
local tim0,tim1,tim2,T;
-----------------------------
while fconnect==2 do
while _N>0 do
t=_tN[_N]; _N=_N-1 ncb=t[1];
----------------------
if ncb==99 then
clas,sec=t[2],t[3]; tim1=t[4];
else
t=t[2];
if ncb==1 then
clas=t.class_code; sec=t.sec_code; --может не быть
elseif ncb==2 then
clas=t.class_code; sec=t.sec_code;
end
end
T=os.sysdate(); tim=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs);
tim=(1000000.*(tim-tim1))//1;
Log:write(ncb..",tim="..tim..",clas="..tostring(clas)..",sec="..tostring(sec)..",_N=".._N.."\n");Log:flush();
end
sleep(1000);
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s)
local T=os.sysdate(); local tim=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs)
_N=_N+1; _tN[_N]={99,c,s,tim};
end
function OnTransReply(t) _N=_N+1; _tN[_N]={1,t}; end
function OnOrder(t) _N=_N+1; _tN[_N]={2,t}; end
В лог файле есть два значения времени tim, - задержка исполнения колбека onParam в main Как видно из лог файла , Как видим из лог файла очередь на обработку никогда не бывает пустой. Задержка составляет от 0.3 до 0.8 сек. В лог файле она в мкс. -------------------------- функция sleep установлена чтобы уменьшить загрузку ядра процессора холостыми циклами. Поэтому посмотрим в диспетчере задач эту загрузку. Она составляет:
Примерно 8.5% теперь давайте уменьшим эту величину до 1 ms в результате получим:
Примечательно то, что наличие очереди дает нам возможность получить и обработать все события, которые принимают колбеки вне зависимости от величины времени бездействия функции sleep. ---------------- Если бы я не использовал очередь, то в случае sleep(1000) - 1 секунда, в каждом цикле терялись несколько десятков событий. Если посмотрите тему "мой робот", то там видно что за 5 секунд приходит до 3000 событий, которые без организации очереди просто потеряются. ===================== А теперь я объясню как в моем скрипте организована очередь и почему она такая простая. --------------- Очередь реализована так. строка:
Код
_N=0; _tN={};
в ней задается счетчик элементов очереди _N и таблица для этих элементов ------------------- запись элемента в очередь производится в колбеках всегда одинаково просто:
Код
_N=_N+1; _tN[_N]={1,t};
а для колбека onParam так:
Код
_N=_N+1; _tN[_N]={99,c,s,tim};
т е увеличиваем счетчик элементов и записываем в таблицу по значению счетчика ключ и передаваемые параметры. ----------------------- При записи не требуется никакой синхронизации, так как запись лишь в колбеках, а они в одном потоке. ---------------------- Чтение и удаление элемента из очереди реализована в функции main так:
Код
t=_tN[_N]; _N=_N-1
Элемент не удаляется, а уменьшается лишь счетчик. Удаление элемента произойдет лишь, если на его место будет записан новый элемент в колбеке. ===================== Вот и вся очередь. ----------------------- Все просто , но весьма эффективно. --------------------- Далее я расскажу как сделать обработку еще быстрее, но не сложнее. ----------------- Продолжение возможно следует..