Очередь -элементарно и полезно

Страницы: 1
RSS
Очередь -элементарно и полезно, Как сделать очередь в роботе и зачем.
 
Добрый день,
Хочу поделиться некоторыми приемами, которые я использую при построении своих роботов.
См. тему -мой робот.
-----------------
В структуре спинного мозга робота , реализуемого в 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
Запустим этот скрипт на исполнение
и получим следующий результат в лог файле
Код
DT=20240119,fconnect=2
99,clas=SPBFUT,sec=SiH4,_N=0
99,clas=SPBFUT,sec=MMH4,_N=0
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=SFH4,_N=0
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=EuH4,_N=1
99,clas=SPBFUT,sec=USDRUBF,_N=0
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=USDRUBF,_N=0
99,clas=CETS,sec=USDCNY_TOM,_N=4
99,clas=CETS,sec=EURUSD000TOD,_N=3
99,clas=CETS,sec=CNYRUB_TOM,_N=2
99,clas=CETS,sec=EUR_RUB__TOM,_N=1
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=TNH4,_N=1
99,clas=SPBFUT,sec=EuH4,_N=0
99,clas=SPBFUT,sec=SGH4,_N=0
99,clas=SPBFUT,sec=CHH4,_N=0
99,clas=SPBFUT,sec=SGH4,_N=1
99,clas=SPBFUT,sec=CHH4,_N=0
99,clas=SPBFUT,sec=LKH4,_N=1
99,clas=SPBFUT,sec=SGH4,_N=0
99,clas=SPBFUT,sec=TNH4,_N=3
99,clas=SPBFUT,sec=TNH4,_N=2
99,clas=SPBFUT,sec=EDH4,_N=1
99,clas=SPBFUT,sec=EDH4,_N=0
99,clas=QJSIM,sec=WUSH,_N=12
99,clas=QJSIM,sec=UPRO,_N=11
99,clas=QJSIM,sec=UGLD,_N=10
99,clas=QJSIM,sec=SIBN,_N=9
99,clas=QJSIM,sec=NMTP,_N=8
99,clas=QJSIM,sec=MTLR,_N=7
99,clas=QJSIM,sec=MDMG,_N=6
99,clas=QJSIM,sec=GLTR,_N=5
99,clas=QJSIM,sec=CHMF,_N=4
99,clas=QJSIM,sec=ALRS,_N=3
99,clas=QJSIM,sec=ABIO,_N=2
99,clas=SPBFUT,sec=CRH4,_N=1
99,clas=SPBFUT,sec=TNH4,_N=0

Последним значением в строке переменная _N - длина очереди.
--------------
Пояснение следует...
 
В данном варианте скрипта у нас нет информации о времени исполнения каких-либо фрагментов.
изменим скрипт и добавим измерения времени исполнения.
кроме того, начнем процесс изучения с классической рекомендации разработчиков - поставить 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
это результат в лог файле:
Код
DT=20240119,fconnect=2
99,tim=218014.0,clas=SPBFUT,sec=EuH4,_N=25
99,tim=304359.0,clas=SPBFUT,sec=SVH4,_N=24
99,tim=541966.0,clas=SPBFUT,sec=NKH4,_N=23
99,tim=595608.0,clas=CETS,sec=EUR_RUB__TOD,_N=22
99,tim=1213820.0,clas=SPBFUT,sec=EuH4,_N=21
99,tim=1344144.0,clas=SPBFUT,sec=GZH4,_N=20
99,tim=1404051.0,clas=SPBFUT,sec=EuH4,_N=19
99,tim=587619.0,clas=CETS,sec=EUR_RUB__TOD,_N=18
99,tim=587619.0,clas=SPBFUT,sec=EDH4,_N=17
99,tim=772196.0,clas=QJSIM,sec=YNDX,_N=16
99,tim=772196.0,clas=QJSIM,sec=WUSH,_N=15
99,tim=772196.0,clas=QJSIM,sec=TGKA,_N=14
99,tim=772196.0,clas=QJSIM,sec=TATNP,_N=13
99,tim=772196.0,clas=QJSIM,sec=SNGS,_N=12
99,tim=772196.0,clas=QJSIM,sec=SFIN,_N=11
99,tim=772196.0,clas=QJSIM,sec=SBER,_N=10
99,tim=772196.0,clas=QJSIM,sec=RTKMP,_N=9
99,tim=772196.0,clas=QJSIM,sec=RTKM,_N=8
99,tim=772196.0,clas=QJSIM,sec=OZON,_N=7
99,tim=772196.0,clas=QJSIM,sec=MTLR,_N=6
99,tim=772196.0,clas=QJSIM,sec=KGKCP,_N=5
99,tim=772196.0,clas=QJSIM,sec=IRAO,_N=4
99,tim=772196.0,clas=QJSIM,sec=GLTR,_N=3
99,tim=772196.0,clas=QJSIM,sec=GAZP,_N=2
99,tim=772196.0,clas=QJSIM,sec=ALRS,_N=1
99,tim=772196.0,clas=QJSIM,sec=ABIO,_N=0
99,tim=61608.0,clas=SPBFUT,sec=EuH4,_N=26
99,tim=127575.0,clas=SPBFUT,sec=BRH4,_N=25
99,tim=559817.0,clas=CETS,sec=EURUSD000TOD,_N=24
99,tim=653453.0,clas=SPBFUT,sec=EuH4,_N=23
99,tim=899874.0,clas=SPBFUT,sec=CRH4,_N=22
99,tim=995760.0,clas=SPBFUT,sec=GZH4,_N=21
99,tim=1022059.0,clas=QJSIM,sec=WUSH,_N=20
99,tim=1022059.0,clas=QJSIM,sec=TTLK,_N=19
99,tim=1022059.0,clas=QJSIM,sec=TGKA,_N=18
99,tim=1022059.0,clas=QJSIM,sec=TCSG,_N=17
99,tim=1022059.0,clas=QJSIM,sec=SOFL,_N=16
99,tim=1022059.0,clas=QJSIM,sec=SFIN,_N=15
99,tim=1022059.0,clas=QJSIM,sec=SBERP,_N=14
99,tim=1022059.0,clas=QJSIM,sec=SBER,_N=13
99,tim=1022059.0,clas=QJSIM,sec=RTKM,_N=12
99,tim=1022059.0,clas=QJSIM,sec=MTSS,_N=11
99,tim=1022059.0,clas=QJSIM,sec=MTLR,_N=10
99,tim=1022059.0,clas=QJSIM,sec=KZOSP,_N=9
99,tim=1022059.0,clas=QJSIM,sec=KGKCP,_N=8
99,tim=1022059.0,clas=QJSIM,sec=IRAO,_N=7
99,tim=1022059.0,clas=QJSIM,sec=GAZP,_N=6
99,tim=1022059.0,clas=QJSIM,sec=ALRS,_N=5
99,tim=1430033.0,clas=SPBFUT,sec=SiH4,_N=4
99,tim=619443.0,clas=CETS,sec=SLVRUB_TOM,_N=3
99,tim=619443.0,clas=SPBFUT,sec=EuH4,_N=2
99,tim=681738.0,clas=SPBFUT,sec=EuH4,_N=1
99,tim=805853.0,clas=SPBFUT,sec=SGH4,_N=0
99,tim=311503.0,clas=QJSIM,sec=WUSH,_N=23
99,tim=311503.0,clas=QJSIM,sec=SNGS,_N=22
99,tim=311503.0,clas=QJSIM,sec=SFIN,_N=21
99,tim=311503.0,clas=QJSIM,sec=SBERP,_N=20
В лог файле есть два значения времени tim, - задержка исполнения колбека onParam в main
Как видно из лог файла , Как видим из лог файла очередь на обработку никогда не бывает пустой.
Задержка составляет от 0.3 до 0.8 сек. В лог файле она в мкс.
--------------------------
функция sleep установлена чтобы уменьшить загрузку ядра процессора холостыми циклами.
Поэтому посмотрим в диспетчере задач эту загрузку.
Она составляет:


Примерно 8.5%
теперь давайте уменьшим эту величину до 1 ms
в результате получим:
Код
DT=20240119,fconnect=2
99,tim=32006.0,clas=SPBFUT,sec=SiH4,_N=1
99,tim=32006.0,clas=SPBFUT,sec=SiH4,_N=0
99,tim=31589.0,clas=SPBFUT,sec=RNH4,_N=1
99,tim=31589.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=24404.0,clas=SPBFUT,sec=SiH4,_N=2
99,tim=24404.0,clas=SPBFUT,sec=SPH4,_N=1
99,tim=24404.0,clas=SPBFUT,sec=EuH4,_N=0
99,tim=32006.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=30568.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=30675.0,clas=SPBFUT,sec=RNH4,_N=2
99,tim=30675.0,clas=SPBFUT,sec=SPH4,_N=1
99,tim=30675.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=30164.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=20010.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=30185.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=28578.0,clas=SPBFUT,sec=RIH4,_N=1
99,tim=28578.0,clas=SPBFUT,sec=SiH4,_N=0
99,tim=4000.0,clas=SPBFUT,sec=EuH4,_N=0
99,tim=26515.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=2383.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=30316.0,clas=SPBFUT,sec=SiH4,_N=1
99,tim=30316.0,clas=SPBFUT,sec=SiH4,_N=0
99,tim=31911.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=11245.0,clas=SPBFUT,sec=VBH4,_N=2
99,tim=11245.0,clas=SPBFUT,sec=VBH4,_N=1
99,tim=11508.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=11998.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=28664.0,clas=SPBFUT,sec=SiH4,_N=0
99,tim=27900.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=831.0,clas=SPBFUT,sec=BRG4,_N=0
99,tim=11832.0,clas=SPBFUT,sec=GZH4,_N=1
99,tim=11832.0,clas=SPBFUT,sec=EuH4,_N=0
99,tim=11998.0,clas=CETS,sec=TRYRUB_TOM,_N=1
99,tim=11998.0,clas=CETS,sec=CNYRUB_TOM,_N=0
99,tim=31687.0,clas=SPBFUT,sec=GZH4,_N=0
99,tim=23877.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=11997.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=31793.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=30233.0,clas=SPBFUT,sec=SPH4,_N=0
99,tim=29818.0,clas=SPBFUT,sec=MMH4,_N=0
99,tim=30141.0,clas=SPBFUT,sec=RNH4,_N=0
99,tim=1998.0,clas=QJSIM,sec=YNDX,_N=19
99,tim=1998.0,clas=QJSIM,sec=WUSH,_N=18
99,tim=1998.0,clas=QJSIM,sec=VTBR,_N=17
99,tim=1998.0,clas=QJSIM,sec=VKCO,_N=16
99,tim=1998.0,clas=QJSIM,sec=TCSG,_N=15
99,tim=1998.0,clas=QJSIM,sec=SFIN,_N=14
99,tim=1998.0,clas=QJSIM,sec=SELG,_N=13
99,tim=1998.0,clas=QJSIM,sec=SBER,_N=12
99,tim=1998.0,clas=QJSIM,sec=ROSN,_N=11
99,tim=1998.0,clas=QJSIM,sec=OZON,_N=10
99,tim=1998.0,clas=QJSIM,sec=MOEX,_N=9
99,tim=1998.0,clas=QJSIM,sec=LKOH,_N=8
99,tim=1998.0,clas=QJSIM,sec=KAZTP,_N=7
99,tim=1998.0,clas=QJSIM,sec=IRAO,_N=6
99,tim=4000.0,clas=QJSIM,sec=GLTR,_N=5

Задержка составляет 1998 мкс примерно 2 ms.  Очередь бывает пустой.
Смотрим загрузку ядра:

Примерно 10%. Что вполне допустимо
------------------
Продолжение следует...
 
Примечательно то, что наличие очереди дает нам возможность получить и обработать все события, которые принимают колбеки вне зависимости от величины времени  бездействия  функции 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 

Элемент не удаляется, а уменьшается лишь счетчик.
Удаление элемента произойдет лишь, если на его место будет записан новый элемент в колбеке.
=====================
Вот и вся очередь.
-----------------------
Все просто , но весьма эффективно.
---------------------
Далее я расскажу как сделать обработку еще быстрее, но не сложнее.
-----------------
Продолжение возможно следует..  
Страницы: 1
Читают тему
Наверх