Модуль реализации интерфейса обработки событий QUIK с использованием очередей событий

Автор записи: TGB
1 звезда2 звезды3 звезды4 звезды5 звезд (Голосов 2, среднее: 5,00 из 5)
Загрузка...

В существующих (на момент 15.10.21) версиях QUIK, его основной (и единственный) служебный поток обслуживания скриптов пользователя запускает их на выполнение (по команде из меню <Загруженные скрипты>). Этот же служебный поток, транслирует скриптам пользователя события, возникающие в QUIK, запуская все функции обратного вызова (колбеки), всех запущенных на исполнение его скриптов. Таким образом, в каждом работающем скрипте пользователя существует его поток main, а все его колбеки запускаются в отдельном, (единственном) служебном потоке QUIK.
Существуют два вида колбеков:
1) колбеки обслуживающие события фондового рынка;
2) колбеки обслуживающие события, возникающие в созданных пользователем таблицах QIUK.
То, что служебный поток QUIK один обслуживает колбеки всех, запущенных на исполнение скриптов пользователя, является проблемой: если функции колбеков некоторого скрипта выполняются долго, или в них возникают длительные блокировки, то перестают обслуживаться колбеки остальных, работающих скриптов пользователя. Кроме того, в скриптах пользователя необходимо учитывать факт параллелизма выполнения потока main и служебного потока QUIK, выполняющего колбеки. То есть, заниматься, сложным для непрофессионала, параллельным программированием.
Из, всего выше сказанного, можно сделать вывод о том, что: в том виде, как он представлен, в текущий момент времени, интерфейс обработки событий в QLua «сырой» для использования его «широким кругом» пользователей.
Для устранения выше озвученных проблем разработчиком QUIK рекомендуется использовать потокобезопасные очереди для передачи параметров, запус-каемых колбеков, потокам main, в которых должны выполняться функции реакции на такие колбеки (документ «Использование Lua в Рабочем месте QUIK»).
Как это можно реализовать, в удобном и эффективном для пользователя виде, представлено ниже в виде:
1) кода модуля even_handling_module.lua ;
2) шаблона использования этого модуля.
Особенности реализации модуля even_handling_module.lua:
1) код модуля написан на «чистом» QLua;
2) очереди передачи параметров колбеков в скриптах пользователя в его потоки main (в разных скриптах это разные потоки) потокобезопасные и при этом они реализованы без использования синхронизации;
3) при реализации записи/чтения очередей, количество операций (на языке Lua) < 6 и не зависит от текущего размера очередей.
Выше перечисленные особенности реализации модуля even_handling_module.lua обеспечивают минимальное влияние на служебный поток QUIK того, что выполняется в скриптах пользователя, а также удобство использования модуля в скриптах пользователя.
Детальное описание использования модуля even_handling_module.lua приведено в его коде и коде шаблона, описывающего схему обработки событий QUIK.
Для подключения модуля к шаблону его надо сохранить в файле под именем even_handling_module.lua в папку хранения кода запуска QUIK (info.exe).
Коды модуля и скрипта-примера его использования:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
--[[     TGB      ----   Модуль реализации обработки событий QUIK с использованием очередей событий  фондового рынка и событий таблиц QUIK ---
 
                                                                               Краткая спецификация модуля.
1. Реализуется схема взаимодействия main с колбеками через две потокобезопасные эффективные очереди, одна из которых обслуживает колбеки 
фондового рынка, а вторая, колбеки событий таблиц QUIK, созданных скриптером;
2. При работе обработке колбеков есть две функции: <функция подписки на события фондового рынка>
 и <функция подписки на события пользовательских таблиц >; в этих функциях скриптер может, в виде списка (в таблице) подписаться на события 
 в соответствии с существующими именами колбеков; реализация подписки сведена к автоматическому созданию (скрытых) от пользователя  
 функций колбеков, записывающих свои параметры с добавлением «от кого параметры» в соответствующие очереди;
3. Для работы с очередями по новой схеме есть две функции чтения соответственно очередям;
 при чтении очередей в считываемой таблице передаются: <имя события>, <время записи в очередь>, <параметры в том виде, как они описаны, в существующих колбеках>.
                  При работе по этой схеме, QLua для скриптера становится однопоточным и снимаются многие сложные проблемы. 
----
                              !!  Колбеки OnInit и OnStop обрабатываются как обычно.
-------------------------
--]]
 
----   Функции работы с очередями------
--- При использовании между потоком колбеков и потоком main  потокобезопасные ---<a id="post-preview" class="preview button" href="https://quikluacsharp.ru/?p=3858&amp;preview=true" target="wp-preview-3858">Просмотреть</a>
    local new  =   function  ()
        return  {first  =   1 , last  =   0 }
     end 
	 ---
    local push  =   function  (self, v)
        local  last  =  self.last  +  1 
        self[last]  =  v
        self.last  =  last
        return  last
     end 
	 ---
    local pop  =   function  (self)
       local  first  =  self.first
       if  first  >  self.last  then   return   nil   end 
       local  v  =  self [first]
       self.first  =  first + 1 
       self[first]  =   nil 
       return  v, first
     end 
	 ---
    local size  =   function  (self)
        return  self.last  -  self.first  + 1 
     end 
-----
--------- Создание очередей-----------
local  Queue_QUIK = new()
local  Queue_QUIK_tbl = new()
----- Функции запроса текущих размеров  очередей -------
local function sizeQueue_evn_QUIK()  return  size (Queue_QUIK)  end 
local function sizeQueue_evn_QUIK_tbl()  return size (Queue_QUIK_tbl)  end 
--  Конец Функции работы с очередями  ---------------------------------------
 
              local function f_nil ()  end 
            ---     Переменные  событий фондового рынка -------
		       OnAccountBalance = f_nil        --- изменение позиции по счету
		 	   OnAccountPosition = f_nil          --- изменение позиции по счету
		 	   OnAllTrade = f_nil                       --- новая обезличенная сделка
		 	   OnCleanUp = f_nil                       --- смена торговой сессии
		 	   OnClose = f_nil                            --- закрытие терминала QUIK или выгрузка файла qlua.dll
		 	   OnConnected = f_nil                    --- установление связи с сервером QUIK
		 	   OnDepoLimit = f_nil                    --- изменение бумажного лимита
		 	   OnDepoLimitDelete = f_nil         --- удаление бумажного лимита
		 	   OnDisconnected = f_nil               --- отключение от сервера QUIK
		 	   OnFirm = f_nil                               --- получение описания новой фирмы
		 	   OnFuturesClientHolding = f_nil        --- изменение позиции по срочному рынку
		 	   OnFuturesLimitChange = f_nil         --- изменение ограничений по срочному рынку
		 	   OnFuturesLimitDelete = f_nil            --- удаление лимита по срочному рынку
--		 	   OnInit = f_nil                                  --- инициализация функции main
		 	   OnMoneyLimit = f_nil                   --- изменение денежного лимита
		 	   OnMoneyLimitDelete = f_nil       --- удаление денежного лимита
		 	   OnNegDeal = f_nil                       --- новая заявка на внебиржевую сделку или изменение параметров существующей заявки на внебиржевую сделку
		 	   OnNegTrade = f_nil                     --- новая сделка для исполнения или изменение существующей сделки для исполнения
		 	   OnOrder = f_nil                             --- новая заявка или изменение параметров существующей заявки
		 	   OnParam = f_nil                           --- изменение текущих параметров 
		 	   OnQuote = f_nil                            --- изменение стакана котировок
--		 	   OnStop = f_nil                              --- остановка скрипта из диалога управления
		 	   OnStopOrder = f_nil                    --- новая стоп-заявка или изменение параметров существующей стоп-заявки
		 	   OnTrade = f_nil                            --- новая сделка или изменение параметров существующей сделки
		 	   OnTransReply = f_nil                   --- ответ на транзакцию
------			   			
            local  unsubscribe_tbl =    ---- Функции отписки  ----
		 	{
		 	     ['OnAccountBalance'] = function ()   OnAccountBalance = f_nil   end
		 	   , ['OnAccountPosition'] = function ()   OnAccountPosition = f_nil   end
		 	   , ['OnAllTrade'] = function ()   OnAllTrade = f_nil   end
		 	   , ['OnCleanUp'] = function ()   OnCleanUp = f_nil  end
		 	   , ['OnClose'] = function ()   OnClose = f_nil  end
		 	   , ['OnConnected'] = function ()   OnConnected = f_nil  end
		 	   , ['OnDepoLimit'] = function ()   OnDepoLimit = f_nil  end
		 	   , ['OnDepoLimitDelete'] = function ()   OnDepoLimitDelete = f_nil  end
		 	   , ['OnDisconnected'] = function ()   OnDisconnected = f_nil  end
		 	   , ['OnFirm'] = function ()   OnFirm = f_nil  end
		 	   , ['OnFuturesClientHolding'] = function ()   OnFuturesClientHolding = f_nil  end
		 	   , ['OnFuturesLimitChange'] = function ()   OnFuturesLimitChange = f_nil  end
		 	   , ['OnFuturesLimitDelete'] = function ()   OnFuturesLimitDelete = f_nil  end
--		 	   , ['OnInit'] = function ()   OnInit = f_nil  end
		 	   , ['OnMoneyLimit'] = function ()   OnMoneyLimit = f_nil  end
		 	   , ['OnMoneyLimitDelete'] = function ()   OnMoneyLimitDelete = f_nil  end
		 	   , ['OnNegDeal'] = function ()   OnNegDeal = f_nil  end
		 	   , ['OnNegTrade'] = function ()   OnNegTrade = f_nil  end
		 	   , ['OnOrder'] = function ()   OnOrder = f_nil  end
		 	   , ['OnParam'] = function ()   OnParam = f_nil  end
		 	   , ['OnQuote'] = function ()   OnQuote = f_nil  end
--		 	   , ['OnStop'] = function ()   OnStop = f_nil  end
		 	   , ['OnStopOrder'] = function ()   OnStopOrder = f_nil  end
		 	   , ['OnTrade'] = function ()   OnTrade = f_nil end
		 	   , ['OnTransReply'] = function ()   OnTransReply = f_nil  end
		 	}
 
			----------------
            local  subscribe_tbl =    ---- Функции подписки  ----
		 	{
		 	     ['OnAccountBalance'] = function ()   OnAccountBalance = function (...)  push (Queue_QUIK, {'OnAccountBalance', os.time(), {...}} ) end   end
		 	   , ['OnAccountPosition'] = function ()   OnAccountPosition = function (...)  push (Queue_QUIK, {'OnAccountPosition', os.time(), {...}} ) end    end
		 	   , ['OnAllTrade'] = function ()   OnAllTrade = function (...)  push (Queue_QUIK, {'OnAllTrade', os.time(), {...}} ) end     end
		 	   , ['OnCleanUp'] = function ()   OnCleanUp = function (...)  push (Queue_QUIK, {'OnCleanUp', os.time(), {...}} ) end  end
		 	   , ['OnClose'] = function ()   OnClose = function (...)  push (Queue_QUIK, {'OnClose', os.time(), {...}} ) end  end
		 	   , ['OnConnected'] = function ()   OnConnected = function (...)  push (Queue_QUIK, {'OnConnected', os.time(), {...}} ) end  end
		 	   , ['OnDepoLimit'] = function ()   OnDepoLimit = function (...)  push (Queue_QUIK, {'OnDepoLimit', os.time(), {...}} ) end  end
		 	   , ['OnDepoLimitDelete'] = function ()   OnDepoLimitDelete = function (...)  push (Queue_QUIK, {'OnDepoLimitDelete', os.time(), {...}} ) end  end
		 	   , ['OnDisconnected'] = function ()   OnDisconnected = function (...)  push (Queue_QUIK, {'OnDisconnected', os.time(), {...}} ) end  end
		 	   , ['OnFirm'] = function ()   OnFirm = function (...)  push (Queue_QUIK, {'OnFirm', os.time(), {...}} ) end  end
		 	   , ['OnFuturesClientHolding'] = function ()   OnFuturesClientHolding = function (...)  push (Queue_QUIK, {'OnFuturesClientHolding', os.time(), {...}} ) end  end
		 	   , ['OnFuturesLimitChange'] = function ()   OnFuturesLimitChange = function (...)  push (Queue_QUIK, {'OnFuturesLimitChange', os.time(), {...}} ) end  end
		 	   , ['OnFuturesLimitDelete'] = function ()   OnFuturesLimitDelete = function (...)  push (Queue_QUIK, {'OnFuturesLimitDelete', os.time(), {...}} ) end  end
--		 	   , ['OnInit'] = function ()   OnInit = function (...)  push (Queue_QUIK, {'OnInit', os.time(), {...}} ) end  end
		 	   , ['OnMoneyLimit'] = function ()   OnMoneyLimit = function (...)  push (Queue_QUIK, {'OnMoneyLimit', os.time(), {...}} ) end  end
		 	   , ['OnMoneyLimitDelete'] = function ()   OnMoneyLimitDelete = function (...)  push (Queue_QUIK, {'OnMoneyLimitDelete', os.time(), {...}} ) end  end
		 	   , ['OnNegDeal'] = function ()   OnNegDeal = function (...)  push (Queue_QUIK, {'OnNegDeal', os.time(), {...}} ) end  end
		 	   , ['OnNegTrade'] = function ()   OnNegTrade = function (...)  push (Queue_QUIK, {'OnNegTrade', os.time(), {...}} ) end  end
		 	   , ['OnOrder'] = function ()   OnOrder = function (...)  push (Queue_QUIK, {'OnOrder', os.time(), {...}} ) end  end
		 	   , ['OnParam'] = function ()   OnParam = function (...)  push (Queue_QUIK, {'OnParam', os.time(), {...}} ) end  end
		 	   , ['OnQuote'] = function ()   OnQuote = function (...)  push (Queue_QUIK, {'OnQuote', os.time(), {...}} ) end  end
--		 	   , ['OnStop'] = function ()   OnStop = function (...)  push (Queue_QUIK, {'OnStop', os.time(), {...}} ) end  end
		 	   , ['OnStopOrder'] = function ()   OnStopOrder = function (...)  push (Queue_QUIK, {'OnStopOrder', os.time(), {...}} ) end  end
		 	   , ['OnTrade'] = function ()   OnTrade = function (...)  push (Queue_QUIK, {'OnTrade', os.time(), {...}} ) end  end
		 	   , ['OnTransReply'] = function ()   OnTransReply = function (...)  push (Queue_QUIK, {'OnTransReply', os.time(), {...}} ) end  end
		 	}
			-----	
 
		--- Вывод данных о состоянии подписки на события фондового рынка  ---
		 local function  condition_subscribe ()
		       local tbl= {}
			   ---
		        tbl['OnAccountBalance'] = OnAccountBalance ~= f_nil or nil
		 	    tbl['OnAccountPosition'] = OnAccountPosition ~= f_nil or nil
		 	    tbl['OnAllTrade'] = OnAllTrade ~= f_nil or nil 
		 	    tbl['OnCleanUp'] = OnCleanUp  ~= f_nil or nil
		 	    tbl['OnClose'] = OnClose ~= f_nil or nil 
		 	    tbl['OnConnected'] = OnConnected ~= f_nil or nil 
		 	    tbl['OnDepoLimit'] = OnDepoLimit ~= f_nil or nil 
		 	    tbl['OnDepoLimitDelete'] = OnDepoLimitDelete ~= f_nil or nil
		 	    tbl['OnDisconnected'] = OnDisconnected ~= f_nil or nil 
		 	    tbl['OnFirm'] = OnFirm ~= f_nil or nil
		 	    tbl['OnFuturesClientHolding'] = OnFuturesClientHolding ~= f_nil or nil
		 	    tbl['OnFuturesLimitChange'] =  OnFuturesLimitChange ~= f_nil or nil
		 	    tbl['OnFuturesLimitDelete'] =OnFuturesLimitDelete ~= f_nil or nil
--		 	    tbl['OnInit'] = OnInit ~= f_nil or nil 
		 	    tbl['OnMoneyLimit'] = OnMoneyLimit ~= f_nil or nil
		 	    tbl['OnMoneyLimitDelete'] = OnMoneyLimitDelete ~= f_nil or nil
		 	    tbl['OnNegDeal'] = OnNegDeal ~= f_nil or nil
		 	    tbl['OnNegTrade'] = OnNegTrade ~= f_nil or nil
		 	    tbl['OnOrder'] = OnOrder ~= f_nil or nil
		 	    tbl['OnParam'] = OnParam ~= f_nil or nil
		 	    tbl['OnQuote'] = OnQuote ~= f_nil or nil
--		 	    tbl['OnStop'] = OnStop ~= f_nil or nil
		 	    tbl['OnStopOrder'] = OnStopOrder ~= f_nil or nil
		 	    tbl['OnTrade'] = OnTrade ~= f_nil or nil
		 	    tbl['OnTransReply'] = OnTransReply ~= f_nil or nil
 
		    return  tbl	
        end
 
        ----------------------------------------------------------------		
 
 ----   Обработка событий QUIK  ----		
 ----   
local tbl_QUIK_fun  --- Для хранения функции обработки событий таблиц QUIK ---
local tbl_fun            --- Для хранения таблицы функции обработки событий фондового рынка ---
local period = 500    -- период счетчика для выдачи сообшений о задержке обработки событий фондового рынка ---
local event_counter  = 0  -- текущее значение циклов "разгребания" очередей событий ---
local event_counter_max  = 0  -- максимальное количество событий обработанных за один вызов функции обработки очередей --
local event_counter_mess  = 0  -- счетчик для выдачи сообщений --
local read_latency_max  = 0   -- максимальная задержка чтения очередей событий (сек.)--
------------------------------------------------------------------
 
		local tbl_QUIK = {} ---- Общая таблица состояния обработки таблиц QUIK ---
		---- Функция обаботки колбеков таблиц QUIK ----
		local function callback_tbl_QUIK(...)  --- (NUMBER t_id, NUMBER msg, NUMBER par1, NUMBER par2
		    local par ={...}
			par = par[1]
		    if tbl_QUIK[par] then  -- Запись в очередь событий таблиц QUIK.  os.time () - дата_время записи ---
		        push (Queue_QUIK_tbl, {par, os.time(), {...}} )
		    end
		end 
 
	   -- Подписка на события таблицы QUIK  ---
		local function  subscribe_tbl_QUIK (t_id, f_t)
            if 	tbl_QUIK[t_id]  then return 1 end
			if  tbl_QUIK_fun == nil and f_t == nil  then error ('!!! Ошибка в вызове subscribe_tbl_QUIK. Не задана функция обработки таблиц QUIK' ) end
			if  f_t  and type (f_t) ~= 'function' then error ('!!! Ошибка в вызове subscribe_tbl_QUIK. Второй параметр не функция ' ) end
            if 	f_t  and tbl_QUIK_fun then 
                if f_t ~= tbl_QUIK_fun  then error ('!!! Ошибка в вызове subscribe_tbl_QUIK. Функция обработки таблиц QUIK отличается от ранее заданной ') end
            end
            ----			
		    tbl_QUIK_fun = tbl_QUIK_fun or f_t 
		    if SetTableNotificationCallback ( t_id,  callback_tbl_QUIK) == 1 then
		       tbl_QUIK[t_id] = 1
			   return 0 
			else
			   message('!!! Ошибка задания колбека для таблицы: ' ..  t_id)
			   error('!!! Ошибка задания колбека для таблицы: ' ..  t_id)
			   return nil 
			end
        end
         --   Отписка от событий таблицы QUIK ----
        local function  unsubscribe_tbl_QUIK (t_id)    -- Отписка от событий таблицы QUIK  ---
		    if tbl_QUIK [t_id]   then tbl_QUIK [t_id] = nil  end
			if  next (tbl_QUIK) == nil then  tbl_QUIK_fun = nil  end
        end
		--  Состояние подписки на события таблицы QUIK ----
		local function condition_subscribe_tbl_QUIK ()  --- Состояние подписки на события таблиц QUIK ---
		   return tbl_QUIK
		end
 
 
		---------------------------------------------------------------------------------------------------------
        -- Подписаться на события фондового рынка --			
        ---  Ключи таблицы - имена событий ----
        local function  subscribe (tbl)
		    if tbl_fun == nil then error ('!!! Ошибка в вызове subscribe. Следует установить таблицу функций обработки событий фондового ( в функции install_event_handling_functions ') end
			local err ={},  f
            for k, v in next, tbl do
			    f = subscribe_tbl [k]
                if f then f() else  err[#err +1] = k  end
            end	
            return err 			
        end
 
		-- Отписаться от событий фондового рынка --
        ---  Ключи таблицы - имена событий ----
        local function  unsubscribe (tbl)
			local err ={},  f
            for k, v in next, tbl do
			    f = unsubscribe_tbl [k]
                if f then f() else  err[#err +1] = k  end
            end	
            return err 		
        end
		----
---  Установить функции обработки событий  ----
        local function install_event_handling_functions(tbl_f, period_f)
           if  type (tbl_f) ~= 'table' then error ('!!! Ошибка в вызове install_event_handling_functions. Параметр не таблица ' ) end
		   period = period_f or 500  ---- по умолчанию 500  --
           tbl_fun = tbl_f
           event_counter  = 0  
           event_counter_mess  = 0 
           event_counter_max  = 0 
           read_latency_max  = 0
           --- Подписка на события фондового рынка ---
           --- ! Ключи таблицы - имена событий ---- 
           unsubscribe (unsubscribe_tbl)  ---- Отписаться от всех событий
           subscribe (tbl_f)   ---  Подписка на события всей таблицы tbl_fun (для записей с функциями обработки событий)
        end
 
---- Функция обработки всех событий QUIK:  function handle_events(lim)	-----
--  Параметр lim (по умолчанию равен 1000000)  определяет максимальное количестко событий, обрабатаваемых за один вызов функции --
--- Если очереди событий пусты, то функция завершается.
-- Результат (их три):  1) количество обработанных событий; 2) максимальное количество событий за один вызов функции; 
-- 3) максимальная задержка (в сек.) между записью и чтением событий
local function handle_events(lim)	
   lim = lim or 1000000
   local f_ms
	---  Чтение очередей событий -----
    local ms_tbl = pop (Queue_QUIK_tbl)  
	local ms, first_q = pop (Queue_QUIK) 
	local os_time, os_time_d
 
	repeat     if ( not (ms_tbl or ms) )   then  break  end    --- ### 2-ой вариант (! из-за ошибки в Lua 5.3 до 15.10.21). Нет событий.  ---
--    while (ms_tbl or ms  )   do                                      --- ### 1-ый вариант. Есть события --- 
	  	os_time = os.time()   -- os.time()  		
	    event_counter  = event_counter  + 1
		event_counter_mess = event_counter_mess + 1
		if event_counter_mess >= period then
		   event_counter_mess = 0
		   message ( '!! Длительный цикл обработки событий >= ' .. event_counter )
		end
	    --- Обработка событий таблиц QUIK ---
	    if ms_tbl then   
          os_time_d = os_time - ms_tbl [2]
          if read_latency_max < os_time_d then read_latency_max = os_time_d  end
          if tbl_QUIK_fun then  tbl_QUIK_fun (table.unpack (ms_tbl[3]))  end       -- Запуск функции обработки событий таблиц QUIK ---
	    end	
	  ----------- События  фондового рынка ---
	    if type (tbl_fun) == 'table' then 
	        if ms then  
			   os_time_d = os_time - ms [2] 
               if read_latency_max < os_time_d then read_latency_max = os_time_d end f_ms = tbl_fun[ms[1]] if f_ms then f_ms (table.unpack (ms[3])) end -- Запуск функции обработки событий фондового рынка --- end end if event_counter >= lim then break  end  
        ---  Чтение очередей событий -----		
         ms_tbl = pop (Queue_QUIK_tbl)  --Queue_QUIK_tbl: pop()   -- ms_tbl = {t_id,  os.time (), {NUMBER t_id, NUMBER msg, NUMBER par1, NUMBER par2}}
	     ms, first_q = pop (Queue_QUIK)  --Queue_QUIK: pop()  	 
--	end            --- ### 1-ый вариант. ---
	until false    --- ### 2-ой вариант. ----
 
    ---  Ведение счетчиков циклов обработки событий ----
	if event_counter_max < event_counter then
	   event_counter_max = event_counter
	end
	event_counter_rt = event_counter
    event_counter  = 0
    event_counter_mess  = 0
	read_latency_max  = 0	
	---------------------------------------------------------------------------------------
	return event_counter_rt, event_counter_max, read_latency_max
end
 
-----------------------------------------------
 return { 
   sizeQueue_evn_QUIK = sizeQueue_evn_QUIK    -- Текущий размер очереди событий рынка ---
 , sizeQueue_evn_QUIK_tbl = sizeQueue_evn_QUIK_tbl   -- Текущий размер очереди событий таблиц QUIK   ---
 , subscribe = subscribe                                       -- Подписаться на события фондового рынка --
 , unsubscribe = unsubscribe                                -- Отписаться от событий фондового рынка --
 , condition_subscribe = condition_subscribe         -- Сотояние подписки на события фондового рынка --
 , subscribe_tbl_QUIK = subscribe_tbl_QUIK          -- Подписаться на события таблиц QUIK --
 , unsubscribe_tbl_QUIK = unsubscribe_tbl_QUIK   -- Отписаться от событий таблиц QUIK --
 , condition_subscribe_tbl_QUIK = condition_subscribe_tbl_QUIK  -- Сотояние подписки на таблицы QUIK  --
 , install_event_handling_functions = install_event_handling_functions  --  Установить функции обработки событий  ----
 , handle_events = handle_events    -- Функция обработки всех событий QUIK  -----
 }  ---
--------------------------------------

============================================================================
Скрипт-пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
-- Шаблон использования модуля even_handling_module
 
local even_handling_module = require('even_handling_module')  -- Подключение модуля обработки событий QUIK  ---
---
local dump_tbl = even_handling_module.dump_tbl
local subscribe = even_handling_module.subscribe
local unsubscribe = even_handling_module.unsubscribe
local condition_subscribe = even_handling_module.condition_subscribe
local subscribe_tbl_QUIK = even_handling_module.subscribe_tbl_QUIK
local unsubscribe_tbl_QUIK = even_handling_module.unsubscribe_tbl_QUIK
local condition_subscribe_tbl_QUIK = even_handling_module.condition_subscribe_tbl_QUIK
local sizeQueue_evn_QUIK = even_handling_module.sizeQueue_evn_QUIK   
local sizeQueue_evn_QUIK_tbl = even_handling_module.sizeQueue_evn_QUIK_tbl
local install_event_handling_functions = even_handling_module.install_event_handling_functions
local handle_events = even_handling_module.handle_events
 
 
IsRun = true
 
function main()
----------	
		--- Работа с событиями таблиц QUIK -----
		----- Функция обработки событий таблиц QUIK  -----
		--  FUNCTION (NUMBER t_id, NUMBER msg, NUMBER par1, NUMBER par2)  ---
		--  t_id – идентификатор таблицы;  msg – код сообщения;  par1 и par2 – значения параметров определяются типом сообщения msg  ----
        local function tbl_QUIK_fun (t_id, msg, par1, par2) 
              message ( 'tbl_QUIK_fun (t_id: ' .. t_id .. ', msg: ' .. msg .. ', par1: ' .. tostring(par1) .. ', par2: ' .. tostring(par2) )
	    end	 	
		-----
	 TableQUIK=AllocTable()  -- Создание таблицы QUIK --
        AddColumn(TableQUIK, 1, "Легенда", true, QTABLE_STRING_TYPE, 6)
        AddColumn(TableQUIK, 2, "Год", true, QTABLE_STRING_TYPE, 6)
        AddColumn(TableQUIK, 3, "Месяц", true, QTABLE_STRING_TYPE, 6)
        AddColumn(TableQUIK, 4, "День", true, QTABLE_STRING_TYPE, 6)
        CreateWindow(TableQUIK)
        for i=1,10 do -- Цикл заполняет ячейки
                 InsertRow(TableQUIK,-1)
                 for j=1,4 do
                          SetCell(TableQUIK, i, j, tostring(i).."-"..tostring(j))
                 end
        end
 
	 TableQUIK1=AllocTable()  -- Создание таблицы QUIK --
        AddColumn(TableQUIK1, 1, "Легенда", true, QTABLE_STRING_TYPE, 6)
        AddColumn(TableQUIK1, 2, "Год", true, QTABLE_STRING_TYPE, 6)
        AddColumn(TableQUIK1, 3, "Месяц", true, QTABLE_STRING_TYPE, 6)
        AddColumn(TableQUIK1, 4, "День", true, QTABLE_STRING_TYPE, 6)
        CreateWindow(TableQUIK1)
        for i=1,10 do -- Цикл заполняет ячейки
                 InsertRow(TableQUIK1,-1)
                 for j=1,4 do
                          SetCell(TableQUIK1, i, j, tostring(i).."-"..tostring(j))
                 end
        end
		---
        subscribe_tbl_QUIK (TableQUIK, tbl_QUIK_fun)  --- Подписка на события TableQUIK
	    subscribe_tbl_QUIK (TableQUIK1, tbl_QUIK_fun)   --- Подписка на события TableQUIK1
--    	unsubscribe_tbl_QUIK (TableQUIK)    --- Отдписка от событий TableQUIK
--		unsubscribe_tbl_QUIK (TableQUIK1)    --- Отдписка от событий TableQUIK
   --- Конец  Работа с событиями таблиц QUIK  -----
 
 
   ---  Работа с событиями фондового рынка ----
    ---    Функции обработки событий фондового рынка ---
    local function F_OnParam(p1, p2)
 --      message ( 'Тикер: ' .. tostring(p2))
    end
	--  
    local function F_OnAllTrade(t)
       message ( 'F_OnAllTrade: ' )
    end	
 
	local function F_OnConnected(t)
       message ( 'F_OnConnected: ' .. tostring (t) )
    end	
 
	---- и т.д  ----
	-------------------------------------------------------------------
    ------  !!!  Для перехода на обработку событий в очередях, в скрипте достаточно к именам функций колбеков добавить префикс 'F_'
	---  Таблица функций обработки событий фондового рынка  (параметры как в соответствующих колбеках) -----
    local tbl_fun =  
		 	{    
		 	     ['OnAccountBalance'] = F_OnAccountBalance   --- Имя функции обработки события OnAccountBalance  --
		 	   , ['OnAccountPosition'] = F_OnAccountPosition   -- и т.д.  ---
		 	   , ['OnAllTrade'] = F_OnAllTrade
		 	   , ['OnCleanUp'] = F_OnCleanUp
		 	   , ['OnClose'] = F_OnClose
		 	   , ['OnConnected'] = F_OnConnected
		 	   , ['OnDepoLimit'] = F_OnDepoLimit
		 	   , ['OnDepoLimitDelete'] = F_OnDepoLimitDelete
		 	   , ['OnDisconnected'] = F_OnDisconnected
		 	   , ['OnFirm'] = F_OnFirm
		 	   , ['OnFuturesClientHolding'] = F_OnFuturesClientHolding
		 	   , ['OnFuturesLimitChange'] = F_OnFuturesLimitChange
		 	   , ['OnFuturesLimitDelete'] = F_OnFuturesLimitDelete
--		 	   , ['OnInit'] = F_OnInit
		 	   , ['OnMoneyLimit'] = F_OnMoneyLimit
		 	   , ['OnMoneyLimitDelete'] = F_OnMoneyLimitDelete
		 	   , ['OnNegDeal'] = F_OnNegDeal
		 	   , ['OnNegTrade'] = F_OnNegTrade
		 	   , ['OnOrder'] = F_OnOrder
		 	   , ['OnParam'] = F_OnParam
		 	   , ['OnQuote'] = F_OnQuote
--		 	   , ['OnStop'] = F_OnStop
		 	   , ['OnStopOrder'] = F_OnStopOrder
		 	   , ['OnTrade'] = F_OnTrade
		 	   , ['OnTransReply'] = F_OnTransReply
		 	}
 
	--- Второй параметр - период счетчика для выдачи сообшений о задержке обработки событий фондового рынка  --
    install_event_handling_functions (tbl_fun, 100)   -- Установка таблицы функций обработки событий фондового рынка   --- 
    ----
    for i, v in next, condition_subscribe() do
        message( 'Скрипт подписан на событие фондового рынка ' .. i )
    end
     ---  Работа с событиями фондового рынка ----
	local event_counter
	local T_OS_high_sleep
	local T_sleep = 30
    -------------------
    while IsRun do 
        ---- Функция обработки всех событий QUIK:  function handle_events(lim)	-----
        --  Параметр lim (по умолчанию равен 1000000)  определяет максимальное количестко событий, обрабатаваемых за один вызов функции --
        --- Если очереди событий пусты, то функция завершается.
        --  Результат (их три):  1) количество обработанных событий в вызове функции; 2) максимальное количество обработанных событий за один вызов функции; 
        -- 3) максимальная задержка (в сек.) между записью и чтением событий (в вызове функции).
        event_counter, event_counter_max, read_latency_max = handle_events()  ----   Обработка всех событий QUIK  ----
		if event_counter &gt; 0 then  -- В очередях были события ----
--            message ( 'event_counter: ' .. event_counter ..  ';  read_latency_max: ' .. read_latency_max  )
		end	
	    ----  Обработка в main остального -----
 
 
	    ----  Конец Обработка в main остального -----
        sleep(20)  		
  end
  ------------------
end	
 
function OnStop()
     IsRun = false
	 DestroyTable(TableQUIK)
     DestroyTable(TableQUIK1)
	 return 10000   --  !!  По истечении этого интервала времени (в млсек.), данного скрипту на завершение работы, функция main() завершается принудительно. 
	                             -- При этом возможна потеря системных ресурсов.   ! Если функция не выдает значение, то по умолчанию оно 5000 (5 сек.)
end

Добавить комментарий

Модуль реализации интерфейса обработки событий QUIK с использованием очередей событий: 10 комментариев

  1. Привет!
    Вряд ли малоопытные кодеры с разбегу разберутся 😉
    Всё гораздо проще, достаточно использовать библиотеку lua_share от тохи.
    Один скрипт собирает все события (на деле их не так много нужно) и раздает роботам, которым эти события нужны
    Примерно так:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    local sh = require'lua_share'
    local qs = sh.GetNameSpace('queues')
     
    function OnTransReply(t)
    	local key = tostring(t.account).."_"..string.gsub(tostring(t.brokerref), "/+", "_").."_"..tostring(t.sec_code)
    	qs[key] = 
    	{
    		key = "OnTransReply", --ключ callback-a
    		trans_id = t.trans_id,
    		status = t.status,
    		result_msg = t.result_msg,
    	}
    end
    function main()
    	while not exitflag do
    		sleep(1)
    	end
    end
    function OnStop()
    	exitflag = true
    end

    и в торговом боте как то так:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    local sh = require'lua_share'
    local qs = sh.GetNameSpace('queues')
    local account = "xzxzxz"
    local botname = "bot" --комментарий к заявке (brokerref)
    local sec_code = "SiZ1"
    local botkey = account.."_"..botname.."_"..sec_code
     
    events_fn = {}
    function events_fn.OnTransReply(t)
    	-- some code
    end
    function events_fn.OnOrder(t)
    	-- some code
    end
     
    local function events()
    	repeat
    		local t = qs[botkey]
    		if t then
    			events_fn[t.key](t)
    		end
    	until not t
    end
    function main()
    	while not exitflag do
    		events()
    		--some code
    		sleep(1)
    	end
    end
    function OnStop()
    	exitflag = true
    end
    1. 1. Я согласен, что можно делать и так как вы предлагаете.
      2. Мною было специально отмечено, что модуль написан на «чистом» QLua, без использования dll и без синхронизации. Причем код модуля доступен для анализа и модификации.
      3. Что касается «гораздо проще», то приведенный вами пример (из двух скриптов) при использовании обсуждаемого модуля имеет следующий эквивалентный код (один скрипт вместо двух; вы же не приводите в вашем примере код lua_share):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      
      local even_handling_module = require('even_handling_module')  -- Подключение модуля обработки событий QUIK  ---
      ---
      local condition_subscribe = even_handling_module.condition_subscribe
      local install_event_handling_functions = even_handling_module.install_event_handling_functions
      local handle_events = even_handling_module.handle_events
       
      IsRun = true
       
      function main()
      	local function F_OnTransReply(t)
             message ( 'F_OnConnected: ' .. tostring (t) )
          end	
       
          local tbl_fun =  
      		 	{    
      		 	   ['OnTransReply'] = F_OnTransReply
      		 	}
       
      	--- Второй параметр - период счетчика для выдачи сообшений о задержке обработки событий фондового рынка  --
          install_event_handling_functions (tbl_fun, 100)   -- Установка таблицы функций обработки событий фондового рынка   --- 
          ----
          for i, v in next, condition_subscribe() do
              message( 'Скрипт подписан на событие фондового рынка ' .. i )
          end
           ---  Работа с событиями фондового рынка ----
      	local event_counter
          -------------------
          while IsRun do 
                      event_counter, event_counter_max, read_latency_max = handle_events()  ----   Обработка всех событий QUIK  ----
      		if event_counter > 0 then  -- В очередях были события ----
      --            message ( 'event_counter: ' .. event_counter ..  ';  read_latency_max: ' .. read_latency_max  )
      		end	
      	    ----  Обработка в main остального -----
       
      	    ----  Конец Обработка в main остального -----
              sleep(20)  		
        end
        ------------------
      end	
       
      function OnStop()
           IsRun = false
      end
      1. На чистом lua, достаточно одной очереди, используя обычные sinsert и sremove - задача решается просто.
        В моем случае, только один! робот имеет функции обратного вызова, все остальные роботы их не имеют и не занимают основной поток - никак (OnInit, OnStop, OnClose не в счет).
        Попробуйте включить полсотни ботов с вашим решением, скорее всего, терминал будет работать только на ботов, а обновление данных покурит в сторонке.
        Преимущества использования lua_share очевидны, а реализацию библиотеки можно посмотреть у ее создателя.

        Так же нужно учитывать, что само по себе событие не гарантирует наличие обновления соответствующей таблицы, и принятое решение на основании события может быть отвергнуто терминалом.

        1. 1. Посмотрите код модуля и вы увидите, что очередь для колбеков одна. Вторая очередь используется для событий таблиц QUIK.
          2. По поводу сравнения обычных sinsert и sremove с приведенными в модуле функциями работы с очередями проведены следующие эксперименты:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          
          local T 
          local v
          --[[
           --  1.  Этот фрагмент (использование потокобезопасных функций QLua)   (100 000 записей и чтений)  выполняется за ~3560 милисекунд ---
          T = {}
          for j = 1, 100  do 
            for i = 1, 1000 do  --- запись в очередь
               table.sinsert (T, 'sd')   
            end
           
            for i = 1, 1000 do  --- чтение из очереди
               v = T[1]
              table.sremove (T, 1)
            end
          end
          do return end      ------
           -----------------------------------------------------------------
          --]]
           
           --  2. Этот фрагмент (использование T = new() )   (100 000 записей и чтений)  выполняется за ~55 милисекунд ---
          T = new() 
           
           for j = 1, 100  do 
            for i = 1, 1000 do  --- запись в очередь
               T: push('sd')
            end
           
            for i = 1, 1000 do  --- чтение из очереди
               v = T: pop()
            end
          end

          3. Я согласен с тем, что если есть возможность, то колбеки использовать не надо.
          4. Насчет очевидного преимущества использования lua_share надо проверять. Вы уже писали про «гораздо проще».

          1. 1. Тут ключевое слово - вторая очередь и использование пользовательских таблиц в торговом алгоритме - так себе затея, если робот каждый раз включается с кнопки, то сойдет, а если включается с запуском терминала, то может и не включиться, что чревато. lua_share позволяет определить отдельного робота для пользовательских табличек - не включился с утра, да и фик с ним, на торговлю не влияет, торговые боты просто не будут отправлять сообщения для визуальной таблицы.
            2. Согласен, что sinser/sremove как и стандартные insert/remove не отличаются скоростью выполнения. Сам когда-то делал нечто подобное для фондового рынка, только проще 😉
            3. На луа можно совсем отказаться от торговых колбеков, скорость принятия решения и квик - не совместимы. Чем проще торговый алгоритм, тем надежнее.
            4. Алгоритм построенный на sinsert/sremove гораздо проще для начинающего программиста и никакого параллельного программирования в рамках терминала квик - не нужно. И... 90% колбеков точно не пригодятся в торговом алгоритме.
            Со всем Уважением к проделанной Вами работе - не нужно пугать начинающих кодеров сложным кодом и анонимными функциями 😉

            1. 1. Вообще то, я сторонник простых решений. Вы написали: «Сам когда-то делал нечто по-добное для фондового рынка, только проще». Поэтому было бы интересно увидеть код такого решения, если вас это не затруднит.
              2. Я нигде не писал, что надо заниматься параллельным программированием в QUIK. Наоборот мною написано, что при использовании очередей между колбеками и main параллельным программированием заниматься не надо.
              3. Вы же сами понимаете, что начинающим кодерам не обязательно пугаться и сразу изучать код приведенного мною модуля. Достаточно его подключить. А интерфейс его использования проще чем при использовании lua_share. Если вы готовы предложить более простой интерфейс, то я готов его реализовать.

              1. 4. Я согласен с тем, что QUIK не предназначен для высокочастотной торговли и что событийная модель построения роботов в нем ни к чему и только усложняет их построение. Достаточно использовать более простую модель программирования по состоянию с циклическим опросом соответствующих таблиц QUIK. Но кому-то события важны ну и пусть их используют.

                1. Привет!
                  1. К сожалению, я не могу представить свое решение (не Fifo), оно уже реализовано на с++ и позволяет мне опережать конкурентов на 2+ мс. Был случай, единичный, когда ближайший конкурент купил акции всего лишь на 780 мкс позже меня, но тут хз, как получилось, видимо бывают исключения из правил.
                  2. Как это вы не писали про параллельное программирование? Да вы им просто ужастили начинающих алготрейдеров 😉
                  3. Тут прям не согласен! Никогда не использовал код, который не понимал как работает - тут батенька, деньги на кону. lua_share - интерфейс много проще, чем описанный вами. Мало того, его можно легко адаптировать под свои нужды(пожалуйста - ваши решения в рамках lua_share будут категорически одобрены). У меня, например, на основе lua_share построена БД, которая работает с 3-мя терминалами, которые подключены к разным серверам квик, если один сдох(дисконект), то тут же в работу включился следующий терминал, который не сдох и роботы продолжают свою работу. И это лишь толика возможностей lua_share.
                  4. Никак нет, умение работать с колбеками очень важный момент программирования. Lua - это язык сценариев, который подходит для быстрой! реализации торговых алгоритмов любой! сложности, т.к. торговый алгоритм и есть - сценарий. Событийная модель программирования торгового алгоритма - залог успеха, т.к. ее легко адаптировать на тот же с++ в ко-локации.

                  1. Здравствуйте!
                    Было интересно узнать ваше мнение о пакете lua_share. Вы меня убедили в том, что это действительно хороший, стабильный пакет межскриптового/межпроцессорного взаимодействия. Я знал, что он существует, но мне он до сих пор не потребовался, так как я использую свой пакет, и это, конечно же, не обсуждаемый здесь модуль, который не имеет никакого отношения к межскриптовому/межпроцессорному взаимодействию.

                    1. Здравствуйте!
                      Я пользуюсь lua_share с релиза и очень доволен библиотекой, спасибо Тохе.
                      lua_share исключительно стабильна и проста в использовании, к тому же можно в lua_share_boot.lua написать свой namespace для конкретной задачи.