возможно Вы правы пока не проверял. Но тогда это прикольно. По крайней мере для меня. ------------------------------- Полагаю ,что правильный скрипт запускается при любой погоде. ------------------------- А эти тупо молчат нет никаких сообщений об ошибках или о нелетной погоде .
Пользователь
Сообщений: Регистрация: 30.01.2015
27.07.2026 07:25:42
сделал свой тест на демо сервере --------------- Так как скрипт рабочий и большой, то расскажу основные моменты, чтобы было понятно , что и как -------------------- У меня все колбеки сделаны одинаков таким образом:
Код
OnOr der=function(t) setQ(4,t); end
Функция setQ помещает таблицу параметров колбека t и ключ колбека в очередь. В этой функции я добавил в параметры значение точного счетчика Windows Этот счетчик считает с квантом 0.1 мкс -------------------------------- В функции main я в цикле обрабатываю элементы очереди В данном тесте я из значения точного счетчика вычитаю сохраненное его значения при записи в очередь Результат пишу в лог файл ------------------------------------------- Таким образом в лог файле я получаю время задержки от вызова любого колбека до начала обработки параметров этого колбека в main Т е фактически это время задержки от момента записи в очередь до момента начала обработки элемента из очереди
Тест был направлен не на измерение скорости работы скрипта, а на влияние работы скрипта на работу терминала, его интерфейса в первую очередь. Какой смысл показать, да ещё на демо, что main работает быстро. У меня на демо тоже всё прекрасно. Переключаешься на реальный и во время открытия рынка - терминал вкладки переключает по 30 секунд. Была бы возможность инструментально измерить работу самого терминала - так и вопросов не было бы.
Вот прямо сейчас смотрю на один из терминалов - банально время сервера в строке состояния изменяется скачками, замирая на длительный период. И так каждый раз при старте, что, в теории, нормально, т.к. приходят пакеты. Но не 15 минут же. На терминалах других брокеров это же занимает минуту, 5-ять иногда. И в это время отчетливо видно как с трудом совершаются любые операции с интерфейсом.
Аналогично и в периоды высокой ликвидности. В это время транзакции могут исполнятся минуты - это ожидаемо, но почему опять интерфейс также "умирает".
Я готов это списать на влияние работы скриптов. Но тогда хотелось бы понять, что именно и какие методы оказывают максимальное влияние. Иначе делаешь банальный скрипт, а эффект как будто модель на 100b считаешь.
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 10:13:45
Ок. Теперь исключим колбеки.
Берем скрипт и выкидываем их, будем просто читать раз в секунду цены и класть их в ту же очередь. Логи тоже уберем.
Код
local path = _G.getScriptPath()
package.path = path.."/?.lua;"
local bl1 = require('big_lib')
local bl2 = require('big_lib2')
local bl3 = require('big_lib3')
local bl4 = require('big_lib4')
local id = 'script1'
local logFile
local function log_tostring(...)
local args = table.pack(...)
if args.n == 1 then
return tostring(args[1])
end
for i = 1, args.n do
args[i] = tostring(args[i])
end
return table.concat(args, " ", 1, args.n)
end
local function log(...)
if logFile==nil then return end
logFile:write(log_tostring(...).."\n");
logFile:flush();
end
local sec_list = {
{'SPBFUT', 'MMU6'},
{'SPBFUT', 'SiU6'},
{'SPBFUT', 'RIU6'},
{'SPBFUT', 'USDRUBF'},
{'SPBFUT', 'CNYRUBF'},
{'TQBR', 'SBER'},
{'TQBR', 'GAZP'}
}
local last_data = {}
local rev_lis = {}
for i = 1, #sec_list do
rev_lis[sec_list[i][2]] = i
last_data[sec_list[i][2]] = {}
end
local sleep = _G.sleep
local isRun = true
local t_id = nil
-- local SeaGreen = 12713921 -- RGB(193, 255, 193) нежно-зеленый
-- local RosyBrown = 12698111 -- RGB(255, 193, 193) нежно-розовый
-- local GetCell = _G.GetCell
-- local Highlight = _G.Highlight
local SetCell = _G.SetCell
local getParamEx = _G.getParamEx
local getInfoParam = _G.getInfoParam
local IsWindowClosed = _G.IsWindowClosed
local Queue = {}
Queue.__index = Queue
function Queue.new()
return setmetatable({first = 1, last = 0 }, Queue)
end
--- Записать в очередь --
function Queue:push(v)
if v == nil then error('!!! Ошибка. Параметр v функции QueuePush nil') end
local last = self.last + 1
self[last] = v
self.last = last
return last - self.first + 1 -- количество элементов в очереди ---
end
--- Читать очередь --
function Queue:pop()
local first = self.first
if first > self.last then return nil end
local v, v1 = self[first], self[first + 1]
self.first = first + 1
self[first] = nil
v1 = type (v1) == 'string' and #v1 or 0
return v, v1 -- v1 -длина следующего элемента (если строка) ---
end
-- Текущий размер очереди ---
function Queue:size()
return self.last - self.first + 1
end
local queue = Queue.new()
local function process_queue()
local n = 0
local res = queue:pop()
while res do
n = n + 1
local last_price = res[3]
-- local lp = GetCell(t_id, 1, 0).value or last_price
-- if lp < last_price then
-- Highlight(t_id, 1, 0, SeaGreen, 0, 500)
-- elseif lp > last_price then
-- Highlight(t_id, 1, 0, RosyBrown, 0, 500)
-- end
SetCell(t_id, rev_lis[res[2]], 1, tostring(last_price), last_price)
-- log(id, ' --- set pop value', n, rev_lis[res[2]], last_price)
res = queue:pop()
end
return n
end
local function GetPrice(class_code, sec_code)
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value) or nil
if last_data[sec_code][1] == last_price then return end
local res = {}
res.last_time = tonumber((getParamEx(class_code, sec_code, 'TIME') or {}).param_value) or 0
if res.last_time == 0 then return end
res.last_price = last_price
if not res.last_price then return end
local bid_depth = tonumber((getParamEx(class_code, sec_code, 'BIDDEPTHT') or {}).param_value) or 0
res.best_bid = bid_depth > 0 and tonumber((getParamEx(class_code, sec_code, 'BID') or {}).param_value) or nil
local ask_depth = tonumber((getParamEx(class_code, sec_code, 'OFFERDEPTHT') or {}).param_value) or 0
res.best_ask = ask_depth > 0 and tonumber((getParamEx(class_code, sec_code, 'OFFER') or {}).param_value) or nil
res.last_rec = tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
res.local_time = (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
res.time_diff = math.abs(res.local_time - res.last_rec) + 10
last_data[sec_code][1] = last_price
queue:push{class_code, sec_code, last_price}
end
local function event_callback(_, msg, par1, par2)
if (msg == _G.QTABLE_CLOSE) then
isRun = false
end
if msg == _G.QTABLE_CHAR then
SetCell(t_id, par1, 2, tostring(par2), par2)
end
end
local function CreateTable()
t_id = _G.AllocTable()
_G.AddColumn(t_id, 0, "sec", true, _G.QTABLE_STRING_TYPE, 15)
_G.AddColumn(t_id, 1, "price", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.AddColumn(t_id, 2, "edit", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.CreateWindow(t_id)
_G.SetWindowPos(t_id, 90, 120, 470, 300)
for i = 1, #sec_list do
_G.InsertRow(t_id, i)
SetCell(t_id, i, 0, sec_list[i][2])
end
_G.SetTableNotificationCallback(t_id, event_callback)
end
function _G.main()
CreateTable()
-- logFile = io.open(path.."\\load_"..id.."txt", "w")
local lt = 0
local req_int = 1
while isRun do
local cwt = os.time()
if cwt - lt > req_int then
lt = cwt
for i = 1, #sec_list do
GetPrice(sec_list[i][1], sec_list[i][2])
end
end
-- local s = os.clock()
-- log(id, 'Enter main loop', os.date())
IsWindowClosed(t_id)
local n = process_queue()
bl1.func1()
bl2.func100()
bl3.func200()
bl4.func300()
-- log(id, 'Exit main loop', os.clock()-s, 'in queue', n)
-- log('---------------------------------------------------------------------------')
sleep(60)
end
end
function _G.OnStop()
isRun = false
if t_id and not IsWindowClosed(t_id) then
_G.DestroyTable(t_id)
end
end
Запускаем скрипты и нет влияния. Хоть 5 скриптов запускай. Но как только подключаем OnParam и пишем в очередь, то уже на трех заметно влияение на работу интерфейса теримнала.
Пользователь
Сообщений: Регистрация: 12.05.2020
27.07.2026 10:58:30
Цитата
Nikolay написал: Но как только подключаем OnParam и пишем в очередь, то уже на трех заметно влияение на работу интерфейса теримнала.
Можно запустить скрипт из одной строки (хоть в песочнице, хоть в продуктиве):
Код
for i = 1, 5000000000 do end
и любоваться на то, как весь интерфейс QUIK зависнет пока будет выполняться эта строка в потоке обрабатывающем: 1) все коллбеки (всех скриптов и всех пользовательских таблиц); 2) все графики; 3) весь графический интерфейс QUIK; 4) возможно что-то еще (например, трафик данных?). Разработчикам QUIK не стыдно наблюдать такое который год подряд?
Пользователь
Сообщений: Регистрация: 30.01.2015
27.07.2026 11:42:37
Цитата
Nikolay написал: Вот прямо сейчас смотрю на один из терминалов - банально время сервера в строке состояния изменяется скачками, замирая на длительный период. И так каждый раз при старте, что, в теории, нормально, т.к. приходят пакеты. Но не 15 минут же. На терминалах других брокеров это же занимает минуту, 5-ять иногда. И в это время отчетливо видно как с трудом совершаются любые операции с интерфейсом.
Аналогично и в периоды высокой ликвидности. В это время транзакции могут исполнятся минуты - это ожидаемо, но почему опять интерфейс также "умирает".
Я готов это списать на влияние работы скриптов. Но тогда хотелось бы понять, что именно и какие методы оказывают максимальное влияние. Иначе делаешь банальный скрипт, а эффект как будто модель на 100b считаешь.
О скачках времени сервера читайте здесь:
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 11:50:13
Сами скачки волне ожидаемы в клиент-серверном взаимодействии. Ну не пришли ещё пакеты, бывает, если оставить в сторону реализацию обмена. Но вот почему в эти периоды терминал становится "тупым" - уже не столь очевидно и вызывает вопросы. Как будто сделали блокирующий цикл, ожидающий пакеты, а интерфейс - да кому он нужен.
Пользователь
Сообщений: Регистрация: 20.03.2023
27.07.2026 12:37:16
Цитата
Nikolay написал: Но как только подключаем OnParam и пишем в очередь
А если какой-нибудь другой колбэк заюзать? Любой достаточно частый.
Пользователь
Сообщений: Регистрация: 15.06.2023
27.07.2026 13:19:16
Просто мысли вслух. Таблицы обезличенных сделок и левел2 это тиковые данные, в то время как OnParam приходит пакетами примерно раз в секунду. Я кто тому что и очереди и буфер и тайм фреймы у них ме могут быть одинаковы. OnParam требует индивидуального подхода и подписок на параметры. Может собака в сомой обработке зарыта?
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 14:09:50
OnParam и OnQuote самые быстрые. Остальные не так часто.
Поэтому добавим пустой OnParam. Почти нет влияния. Хотя есть уже ощущение, что не так, как без скриптов. Опять же - только визуальный контроль.
Добавим внутрь колбека одну строку
tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value)
И уже явно что-то есть.
Также, чисто субъективно, кажется, что если дать ностояться, т.е. поработать скриптам, то уже как-то сильнее влияние.
Еще из странного - зависит от сервера брокера. Я тестирую на ВТБ, как одном из "тормозных", переключаешь на другой сервер - и уже не так заметно влияние.
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 14:16:40
Цитата
VPM написал: Просто мысли вслух. Таблицы обезличенных сделок и левел2 это тиковые данные, в то время как OnParam приходит пакетами примерно раз в секунду. Я кто тому что и очереди и буфер и тайм фреймы у них ме могут быть одинаковы. OnParam требует индивидуального подхода и подписок на параметры. Может собака в сомой обработке зарыта?
Я довольно редко использую обезличенные сделки и getQuoteLevel2. В данном тесте getQuoteLevel2 просто для создания какой-то нагрузки на qlua API. Простые адекватные расчёты в main не особо влияют. Тот же ВТБ и без скриптов стартует, да и работает, очень медленно. Я думал Кит - медленный (а там большие потоки данных). Но нет ВТБ выдает результаты в информационном окне типа такого:
средняя задержка данных = 1.877. Максимальная задержка данных = 29.656.
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 15:01:42
Оставил скрипты тестовые с колбеками запущенными. В итоге:
задержка данных при обмене с сервером = 11.109 средняя задержка данных = 26.384. Максимальная задержка данных = 175.063
Как только остановил скрипты - побежал счётчик полученных пакетов, отставших записей в информационном окне. И пока данные бегут, то терминал и без скриптов не особо спешит.
Пользователь
Сообщений: Регистрация: 30.01.2015
28.07.2026 05:11:28
Цитата
Nikolay написал: Оставил скрипты тестовые с колбеками запущенными. В итоге:
задержка данных при обмене с сервером = 11.109 средняя задержка данных = 26.384. Максимальная задержка данных = 175.063
Как только остановил скрипты - побежал счётчик полученных пакетов, отставших записей в информационном окне. И пока данные бегут, то терминал и без скриптов не особо спешит.
Вывод -тормозят Ваши скрипты. Измерьте время их исполнения. ------------------ Кроме того, тормозит сервер брокера . И это не зависит от скриптов. Последнее время у Сбера видны задержки в выставлении заявок Т е уходит заявка из терминала а в стакане она появляется с заметной задержкой визуально. Так как задержка реакция нашего глаза не менее 0.2 сек то можно говорить что заявка выставляется с задержкой сотни миллисекунд. ------------------------ Объяснить это можно лишь задержкой на стороне сервера. Два варианта 1) Умышленная задержка на стороне сервера. 2) Большие очереди заявок. ------------------------------------- Но так как эта задержка существует и при спокойном рынке, то скорее всего это 1 вариант
Пользователь
Сообщений: Регистрация: 30.01.2015
28.07.2026 05:48:47
Цитата
Nikolay написал: Тест был направлен не на измерение скорости работы скрипта, а на влияние работы скрипта на работу терминала, его интерфейса в первую очередь. Какой смысл показать, да ещё на демо, что main работает быстро. У меня на демо тоже всё прекрасно. Переключаешься на реальный и во время открытия рынка - терминал вкладки переключает по 30 секунд. Была бы возможность инструментально измерить работу самого терминала - так и вопросов не было бы.
Вы не поняли мой тест. Тест проверяет быстродействие выполнения колбеков, включая обработку очереди .
Пользователь
Сообщений: Регистрация: 27.01.2017
28.07.2026 08:01:41
Цитата
nikolz написал: Вывод -тормозят Ваши скрипты. Измерьте время их исполнения. ------------------ Но так как эта задержка существует и при спокойном рынке, то скорее всего это 1 вариант
Тексты скриптов выложены выше. Что там конкретно приводит к "тормозам"? Просто интересно, раз такое категоричное утверждение.
Цитата
nikolz написал: Тест проверяет быстродействие выполнения колбеков, включая обработку очереди .
У меня и не было задачи проверять работу колбеков, а влияние их наличия на скорость работы интерфейса терминала. Буду рад если предложите другой инструментальный тест.
Пользователь
Сообщений: Регистрация: 27.01.2017
28.07.2026 18:45:03
Ок. Чтобы наглядно показать как влияют колбеки на работу терминала запустил такой тест.
Запускаем скрипты, в которых есть только колбек OnParam с одной строкой внутри getParamEx(class_code, sec_code, 'LAST')
И смотрим как это повлияет на работу в период загрузки данных. Отключаем соединение с сервером, ждем некоторое время, чтобы накопились пакеты, восстанавливаем соединение. И смотрим в информационное окно терминала. Также визуально видна реакция на выключение и включение скрипта, особенно на вывод данных в таблицу.
Скрипт банальный. Раз в секунду запрашиваем getParamEx. Также для проверки влияния getInfoParam были добавлены запросы в main, но как показало - не влияет. Лог тоже закомментирован. Если есть колбек OnParam - влияние видно глазами, через скорость изменения данных в информационном окне. Если его нет, то наличие работающего скрипта визуально не заметно.
Судя по всему, вызывается OnParam на каждую пропущенную запись для какого-то служебного поля, что и приводит к такому. Но это просто догадка. Если так, то это совсем не корректно.
Отдельно также можно сказать, что если не было соединения достаточно долго, то брокер кидает OnCleanUp (это видно по логу и по параметру "число записей" в информационном окне, он скидывается в 0), судя по всему, срабатывает условие "смена сессии". И в итоге загрузка данных происходит долго, очень долго. По крайней мере у ВТБ. Другие бырокеры побыстрее.
Наблюдая за инфомационнгым окном у ВТБ - достатоно часто видно как бежит число "отставшая запись" и в это время терминал занят этим и будет такое же влияние.
Код
local path = _G.getScriptPath()
package.path = path.."/?.lua;"
local bl1 = require('big_lib')
local bl2 = require('big_lib2')
local bl3 = require('big_lib3')
local bl4 = require('big_lib4')
local id = 'script3'
local logFile
local function log_tostring(...)
local args = table.pack(...)
if args.n == 1 then
return tostring(args[1])
end
for i = 1, args.n do
args[i] = tostring(args[i])
end
return table.concat(args, " ", 1, args.n)
end
local function log(...)
if logFile==nil then return end
logFile:write(log_tostring(...).."\n");
logFile:flush();
end
local sec_list = {
{'SPBFUT', 'MMU6'},
{'SPBFUT', 'SiU6'},
{'SPBFUT', 'RIU6'},
{'SPBFUT', 'USDRUBF'},
{'SPBFUT', 'CNYRUBF'},
{'TQBR', 'SBER'},
{'TQBR', 'GAZP'}
}
local last_data = {}
local rev_lis = {}
for i = 1, #sec_list do
rev_lis[sec_list[i][2]] = i
last_data[sec_list[i][2]] = {}
end
local sleep = _G.sleep
local isRun = true
local t_id = nil
-- local SeaGreen = 12713921 -- RGB(193, 255, 193) нежно-зеленый
-- local RosyBrown = 12698111 -- RGB(255, 193, 193) нежно-розовый
-- local GetCell = _G.GetCell
-- local Highlight = _G.Highlight
local SetCell = _G.SetCell
local getParamEx = _G.getParamEx
local getInfoParam = _G.getInfoParam
local IsWindowClosed = _G.IsWindowClosed
local Queue = {}
Queue.__index = Queue
function Queue.new()
return setmetatable({first = 1, last = 0 }, Queue)
end
--- Записать в очередь --
function Queue:push(v)
if v == nil then error('!!! Ошибка. Параметр v функции QueuePush nil') end
local last = self.last + 1
self[last] = v
self.last = last
return last - self.first + 1 -- количество элементов в очереди ---
end
--- Читать очередь --
function Queue:pop()
local first = self.first
if first > self.last then return nil end
local v, v1 = self[first], self[first + 1]
self.first = first + 1
self[first] = nil
v1 = type (v1) == 'string' and #v1 or 0
return v, v1 -- v1 -длина следующего элемента (если строка) ---
end
-- Текущий размер очереди ---
function Queue:size()
return self.last - self.first + 1
end
local queue = Queue.new()
local function process_queue()
local n = 0
local res = queue:pop()
while res do
n = n + 1
local last_price = res[3]
-- local lp = GetCell(t_id, 1, 0).value or last_price
-- if lp < last_price then
-- Highlight(t_id, 1, 0, SeaGreen, 0, 500)
-- elseif lp > last_price then
-- Highlight(t_id, 1, 0, RosyBrown, 0, 500)
-- end
SetCell(t_id, rev_lis[res[2]], 1, tostring(last_price), last_price)
-- log(id, ' --- set pop value', n, rev_lis[res[2]], last_price)
res = queue:pop()
end
return n
end
local function GetPrice(class_code, sec_code)
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value) or nil
if last_data[sec_code][1] == last_price then return end
local res = {}
res.last_time = tonumber((getParamEx(class_code, sec_code, 'TIME') or {}).param_value) or 0
if res.last_time == 0 then return end
res.last_price = last_price
if not res.last_price then return end
local bid_depth = tonumber((getParamEx(class_code, sec_code, 'BIDDEPTHT') or {}).param_value) or 0
res.best_bid = bid_depth > 0 and tonumber((getParamEx(class_code, sec_code, 'BID') or {}).param_value) or nil
local ask_depth = tonumber((getParamEx(class_code, sec_code, 'OFFERDEPTHT') or {}).param_value) or 0
res.best_ask = ask_depth > 0 and tonumber((getParamEx(class_code, sec_code, 'OFFER') or {}).param_value) or nil
-- res.last_rec = tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
-- res.local_time = (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
-- res.time_diff = math.abs(res.local_time - res.last_rec) + 10
last_data[sec_code][1] = last_price
queue:push{class_code, sec_code, last_price}
end
local function event_callback(_, msg, par1, par2)
if (msg == _G.QTABLE_CLOSE) then
isRun = false
end
if msg == _G.QTABLE_CHAR then
SetCell(t_id, par1, 2, tostring(par2), par2)
end
end
local function CreateTable()
t_id = _G.AllocTable()
_G.AddColumn(t_id, 0, "sec", true, _G.QTABLE_STRING_TYPE, 15)
_G.AddColumn(t_id, 1, "price", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.AddColumn(t_id, 2, "edit", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.CreateWindow(t_id)
_G.SetWindowPos(t_id, 90, 120, 470, 300)
for i = 1, #sec_list do
_G.InsertRow(t_id, i)
SetCell(t_id, i, 0, sec_list[i][2])
end
_G.SetTableNotificationCallback(t_id, event_callback)
end
function _G.OnParam(class_code, sec_code)
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value) or nil
-- if rev_lis[sec_code] then
-- GetPrice(class_code, sec_code)
-- end
end
local last_rec, last_time, time_diff
function _G.main()
CreateTable()
logFile = io.open(path.."\\load_"..id..".txt", "w")
local lt = 0
local req_int = 1
while isRun do
local cwt = os.time()
if cwt - lt > req_int then
lt = cwt
for i = 1, #sec_list do
GetPrice(sec_list[i][1], sec_list[i][2])
end
end
last_rec = tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
local_time = (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
time_diff = math.abs(local_time - last_rec) + 10
-- log(id, 'Enter main loop')
IsWindowClosed(t_id)
local n = process_queue()
bl1.func1()
bl2.func100()
bl3.func200()
bl4.func300()
-- log(id, 'Exit main loop', 'in queue', n)
-- log('---------------------------------------------------------------------------')
sleep(60)
end
end
function _G.OnStop()
isRun = false
if t_id and not IsWindowClosed(t_id) then
_G.DestroyTable(t_id)
end
end
Пользователь
Сообщений: Регистрация: 15.06.2023
29.07.2026 13:50:03
В моем примере выше, архитектура устроена так что, все колбэки ни чего не делают, они "ТУПЫЕ", их задача сводится просигнализировать "Терминал сообщает, что параметры инструмента изменились". Это другой уровень абстракции. В этой архитектуре главное: QUIK callbacks нельзя превращать в бизнес-события напрямую
Код
function OnParam(class_code, sec_code)
if not Robot or not c.is_run or not c.IS_WARMED_UP or Robot.Is_Inside_HotPath then return end
if Robot.ProcessParamEvent and type(Robot.ProcessParamEvent) == "function" then
Robot:ProcessParamEvent(class_code, sec_code)
end
end
У Вас же идет вызов тяжеловеса, еще нужно понять где фильтруется:
Код
function _G.OnParam(class_code, sec_code)
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value) or nil
-- if rev_lis[sec_code] then
-- GetPrice(class_code, sec_code)
-- end
end
Пользователь
Сообщений: Регистрация: 27.01.2017
29.07.2026 15:11:39
Цитата
VPM написал: У Вас же идет вызов тяжеловеса, еще нужно понять где фильтруется
Это нагрузочный тест, фильтр закомментирован. В этом и смысл. Также, если getParamEx - это тяжелая функция, то тогда можно смело даже не думать о написании чего-то существенного на QLUA. Эта функция, по идее, максимально быстро выдает данные из кеша терминала. Никуда не ходит, просто берет из хранилища.
Почему-то кажется, что получение типа, да ещё сравнение с строкой (её бы в константу загнать, чтобы Lua каждый раз не бегал в пул строк), а потом ещё и вызов функции, потяжелее будет.
Код
if not Robot or not c.is_run or not c.IS_WARMED_UP or Robot.Is_Inside_HotPath then return end
if Robot.ProcessParamEvent and type(Robot.ProcessParamEvent) == "function" then
Robot:ProcessParamEvent(class_code, sec_code)
end
Также, если внутри колбека OnParam ничего не делать, то он становится бесполезным, т.к. он не говорит что изменилось, а просто сигнализирует - что-то там изменилось. Да, можно как и с OnQuote, как в тестах выше, фиксировать просто флаг изменения, и уже в main получать данные через getParamEx. Но это приведёт к существенной потере точности считывания данных, т.к. пока дойдём до этого участка кода, OnParam вызовется ещё тридцать раз, где были промежуточные изменения.
Т.о., если даже такое - это тяжело для Квика, то, как сказал выше, можно расслабиться.
Но почему-то кажется, что проблема все-же в архитектуре. OnParam - по идее, должен быть событием реального времени. Если он будет вызываться на все приходящие записи после OnCleanUp, да ещё вечером, когда этих записей много, то терминал, вместо работы, будет занимается разбором пакетов. Я такое второй день наблюдаю на ВТБ, и даже без скриптов в эти моменты терминалу плохо. При этом теперь OnCleanUp - это рядовое событие, стоит разорвать связь на не такое и длительное время и всё - получите всё с начала торгов. Данные баров, сделки, ордера - да. А OnParam зачем?
Пользователь
Сообщений: Регистрация: 27.01.2017
29.07.2026 16:16:02
И ещё из странного. Добавляем в скрипт такое:
local function ds_callback(ds, index) PrintDbgStr(log_tostring('ds_callback', index, 'last_price', ds:C(index))) end
function _G.main()
logFile = io.open(path.."\\"..id..".txt", "w")
local ds = _G.CreateDataSource('TQBR', 'SBER', 1) if not ds then isRun = false end ds:SetUpdateCallback(function(...) return ds_callback(ds, ...) end)
while isRun do sleep(10) end end
Что пробуем понять - что будет если остановить скрипт, брокер выдаст OnCleanUp и начнет передавать все пакеты заново. Предположение, что функция в SetUpdateCallback выдаст все, начиная с первого индекса.
И действительно, прерываем связь, ждем, получаем OnCleanUp, и получаем вызовы от первого индекса.
Т.о. массовая передача отставших пакетов или даже всех от старта сессии, приведет к вызову всех колбеков заново. И скрипт будет это разбирать, вместо работы. И нагрузка на терминал, соответственно, т.к. на каждый пакет будут вызваны все назначенные пакеты. Вот тебе и событийная модель. Казалось бы, событие давно прошло - но нет, получи заново.
Пользователь
Сообщений: Регистрация: 15.06.2023
29.07.2026 16:44:54
Вы абсолютно правы в своих рассуждениях, и Ваш анализ обнажает главные "болевые точки" архитектуры QUIK и его связки с Lua. Разработчики терминала создали механизм, который при определённых условиях начинает работать против стабильности всей системы. Ваше наблюдение за поведением после OnCleanUp (или при обрыве связи у ВТБ) абсолютно точно описывает проблему архитектуры QUIK. Вы в этом лучше меня разбираетесь. Я лишь хочу сказать о срытой проблеме выделение памяти (Garbage Collection). Каждый вызов getParamEx возвращает Lua-таблицу с полями param_value, param_image, param_type и result.На миллионах вызовов в секунду (при подгрузке истории) создаются миллионы временных таблиц и строк. Встроенный в QUIK сборщик мусора Lua не успевает их утилизировать. Происходит микро - зависание потока, обрабатывающего скрипт. Я не давно вывел т. обезличенных сделок, волосы дыбом, что сделали из ленты! Выведите посмотрите ради интереса. Думаю тут таже история. Можно бесконечно возмущаться, а воз и ныне там. Я о другом как быть в этой ситуации? Разберу мой код выше. Посмотрите в примере именно на этот момент:
Код
while c.is_run do
local cur_size = _G.Robot.ds:Size()
if cur_size > last_known_size then
last_known_size = cur_size
c.BAR_CACHE_IDX = cur_size
_G.Robot:ExecuteQuantumClosure(
_G.Robot.ds,
cur_size
)
end
if _G.Robot.ProcessLearningQueue then
_G.Robot:ProcessLearningQueue()
end
if _G.Robot.TriggerUIRender then
_G.Robot:TriggerUIRender()
end
local current_work_time = GetMilliseconds() - TT
c.LAST_EXECUTION_LAG = current_work_time
local calculated_sleep = 200 - current_work_time
if calculated_sleep <= 0 then
calculated_sleep = 1
end
sleep(calculated_sleep)
TT = GetMilliseconds()
end
Это не просто цикл. Это планировщик (scheduler).Что здесь уже правильно: 1. Баровый контур отделён от событий QUIK. Это главное. сейчас: QUIK callbacks | v накопление состояния | v main() | v ExecuteQuantumClosure()
То есть: * OnQuote() не запускает принятие решений; * OnAllTrade() не запускает MDP; * OnParam() не вызывает расчёты S/D. * DS не вызывает расчёт индикаторов. И это уже правильно, у всех разные периоды тик, сеунда, минута.
2. Изохронность. задан период: sleep(200) То есть: 5 Hz scheduler А внутри: баровая фиксация; очередь обучения; рендеринг пользовательского интерфейса; пульс. Это паттерн. Не, тик -> торговое решение -> ордер а, накопление событий + обработка, управляемая тактовым сигналом.
3. Выполнение квантового замыкания. Вот это фактически граница транзакции в MEO. Сейчас: new bar v ExecuteQuantumClosure() v DSP v State Encoder v MDP v Decision v Execution Именно здесь должен жить: НАЧАТЬ ТРАНЗАКЦИЮ; вычислить; подтвердить; совершить; хэш, а не в QUIK CALLBACK. Ну по крайней мере сейчас так представляется.
Пользователь
Сообщений: Регистрация: 27.01.2017
29.07.2026 17:20:03
Кто хочет поразвлекаться, вот тестовый скрипт. Запускам. Разрываем связь, ждем, запускаем. Когда в информационном окне закончится очередь отставших сообщений остановить. была возможность считать этот параметр оставили бы сами. Для наглядности можно включить PrintDbgStr в OnParam и посмотреть что приходит и с какой скоростью, после восстановления связи или в моменты когда терминал подгружает отставшие пакеты.
И почему все эти вызовы OnParam будут мусором, потому что если получить и вывести цену в колбеке, то она будет одинаковая, т.к. последняя. Т.е. можно предположить, что изменяется что-то служебное.
Также, включая PrintDbgStr в OnParam, увидел, что приходят данные даже не включенные в настройках. На том же ВТБ - включены только облигации, т.к. открыта ТТТ по ним. Но после разрыва связи получаю такое
И много другого мусора по всем классам и инструментам даже не включенных в поток данных. Т.е. это банальная DDOS атака колбеков в скриптах, т.к. приходят десятки тысяч вызовов, по всем классам инструментов. Зачем тогда эта настройка "умным заказом данных", если получаем всё...
Код
local path = _G.getScriptPath()
package.path = path.."/?.lua;"
local id = 'script1'
local logFile
function _G.OnInit(script_path)
id = ((script_path:match("[^/\\]+$")):gsub('.lua', ''))
end
local function log_tostring(...)
local args = table.pack(...)
if args.n == 1 then
return tostring(args[1])
end
for i = 1, args.n do
args[i] = tostring(args[i])
end
return table.concat(args, " ", 1, args.n)
end
local function log(...)
if logFile==nil then return end
logFile:write(log_tostring(...).."\n");
logFile:flush();
end
local sleep = _G.sleep
local message = _G.message
local isRun = true
local last_rec, last_time, time_diff
local start_rec = tonumber(getInfoParam('LASTRECORD') or '') or 0
local q = 0
local t = os.time()
local start
function _G.OnParam(class_code, sec_code)
if q == 0 then t = os.time() end
q = q + 1
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value)
-- PrintDbgStr(log_tostring('OnParam', q, class_code, sec_code, 'last_price', last_price))
end
local function log_stat()
local end_rec = tonumber(getInfoParam('LASTRECORD') or '') or 0
local loaded = end_rec - start_rec
t = os.time() - t
local str = log_tostring(id, ': done', ', processed callbacks: ', q, ', in: ', t, 'sec', ', avg: ', q == 0 and '---' or t/q)
message(str)
local str1 = log_tostring(id, ': start_rec: ', start_rec, ', end_rec: ', end_rec, ', loaded records: ', loaded, ', avg: ', loaded == 0 and '---' or t/loaded)
message(str1)
if io.type(logFile) == 'file' then
log(str)
log(str1)
logFile:close()
end
end
function _G.main()
logFile = io.open(path.."\\"..id..".txt", "w")
while isRun do
sleep(10)
end
end
function _G.OnDisconnected()
message('OnDisconnected, current_rec: '..tostring(tonumber(getInfoParam('LASTRECORD') or '') or 0))
q = 0
t = os.time()
end
function _G.OnConnected(flag)
if flag then
start_rec = tonumber(getInfoParam('LASTRECORD') or '') or 0
message('OnConnected, start_rec: '..tostring(start_rec))
q = 0
t = os.time()
start = true
end
end
function _G.OnStop()
isRun = false
log_stat()
end
Пользователь
Сообщений: Регистрация: 27.01.2017
30.07.2026 12:04:55
Ок, вопрос разработчикам:
Почему callback OnParam игнорирует настройки терминала в разделе "Получение данных"?
Берем чистый терминал, открываем только одну таблицу текущих торгов, в ней выбраны конкретные инструменты. Выбрана настройка "умным заказом данных". Никаких открытых графиков или других таблиц, только одна ТТТ. Нет запущенных скриптов, кроме тестового.
В тестовом скрипте есть только OnParam и вывод данных через PrintDbgStr, нет заказов каких-то данных.
Запускаем тест и видим вызов OnParam для инструментов, которых нет в открытой ТТТ, нет открытых позиций, заявок по этим инструментам. С точки зрения скрипта, где этот OnParam зарегистрирован (а нигде более этот метод и не используется) - это мусор, влияющий на работу всего терминала.
Также вопрос о самой настройке "умным заказом данных": если она допускает такой поток данных (а он явно есть, раз есть вызов OnParam), то о каком снижении потока передаваемых данных может быть речь?
Если в скрипте зарегистрировать OnParam, даже пустой:
function OnParam() end
то память, отображаемая в окне Доступные скрипты, почти не изменяется. А если же OnParam нет вовсе, то она бегает от начального значения до удвоения, и потом уже срабатывает GC, сбрасывая её. Особенно это заметно на "объёмных" скриптах с большим числом локальных переменных.
Т.е. наличие OnParam вызывает постоянный вызов GC, судя по всему при вызове OnParam. Это, конечно, наверно хорошо не расходовать память, но и агрессивный GC - это влияние на производительность.
Пользователь
Сообщений: Регистрация: 12.05.2020
31.07.2026 10:09:51
Цитата
Nikolay написал: Т.е. наличие OnParam вызывает постоянный вызов GC, судя по всему при вызове OnParam. Это, конечно, наверно хорошо не расходовать память, но и агрессивный GC - это влияние на производительность.
Насколько я понимаю, в текущем QUIK реализована следующая схема реализации QLua. 1. В исходники Lua добавлены функции QLua и выбран вариант использования кода Lua в нескольких потоках, запускаемых на отдельных стеках (основном и стеках сопрограмм). 2. Основной стек используется служебным основным потоком QUIK, обрабатывающим много чего (смотрите мой комментарий в этой ветке и в других тоже). 3. Поток main запускается на стеке сопрограммы. 4. Коды скрипта разделяются между потоками, использующими его. В выбранном варианте трансляции исходников Lua активирована синхронизация, обеспечивающая использование кода LUa только одним потоком, но на разных стеках. Сишные функции (не использующие управление память Lua) при их вызовах выполняются в потоке их вызова, и при этом разрешается выполнение потока на другом стеке. После звершения сишной функции, его поток блокируется до тех пор пока в потоке, исполняемом код Lua не будет вызвана сишная функция. 5. При обработке коллбеков в QUIK: 1) перед его вызовом: сохраняется состояние работы GC; 2) выполняется: collectgarbage("stop") с тем, чтобы запретить работу Gc, который обрабатывает все стеки Lua (зашита от возможных ошибок многопоточности); 3) запускается функция коллбека; 4) после завершения коллбека восстанавливается состояние работы GC. Если восстановление collectgarbage("restart"), то это короткая операция (10000 вызовов ~ 1,5 млс.), но если разработчик сделаи collectgarbage("collect"), то это тяжелая операция принудительной сборки мусора. ---- Я много писал, о том, что разработчиком QUIK устроен этот гемморой, в котором они захлебыватся сами и окунают в это пользователей, хотя есть давно известные решения, избавляющие от этого всех. Обработка в одном потоке почти всего это тоже ноу-хау разработчика QUIK .
Пользователь
Сообщений: Регистрация: 30.01.2015
31.07.2026 11:49:51
Цитата
TGB написал: 4. Коды скрипта разделяются между потоками, использующими его. В выбранном варианте трансляции исходников Lua активирована синхронизация, обеспечивающая использование кода LUa только одним потоком, но на разных стеках. Сишные функции (не использующие управление память Lua) при их вызовах выполняются в потоке их вызова, и при этом разрешается выполнение потока на другом стеке. После звершения сишной функции, его поток блокируется до тех пор пока в потоке, исполняемом код Lua не будет вызвана сишная функция.
Тут у Вас ошибка в понимании. Коды не надо разделять или синхронизировать Они могут исполнятся потоками одновременно . Разделяется обращение к памяти данных Это еще называется синхронизация потоков. Т е синхронизация потоков это не про коды а про данные.
Пользователь
Сообщений: Регистрация: 12.05.2020
31.07.2026 13:33:04
Цитата
nikolz написал: Тут у Вас ошибка в понимании. Коды не надо разделять или синхронизировать Они могут исполнятся потоками одновременно .
Это, например, из исходников Lua 5.4.1:
Код
LUA_API void lua_pushboolean (lua_State *L, int b) {
lua_lock(L);
if (b)
setbtvalue(s2v(L->top));
else
setbfvalue(s2v(L->top));
api_incr_top(L);
lua_unlock(L);
}
Протрите глаза. Как фрагмент между lua_lock(L); lua_unlock(L); может исполняться в двух потоках?
Пользователь
Сообщений: Регистрация: 27.01.2017
31.07.2026 13:36:43
Цитата
TGB написал: Как фрагмент между lua_lock(L); lua_unlock(L); может исполняться в двух потоках
А есть подтверждение, что в qlua эти макросы задействованы, а не оставлены пустыми как в чистом lua.
Пользователь
Сообщений: Регистрация: 12.05.2020
31.07.2026 14:17:55
Цитата
Nikolay написал: А есть подтверждение, что в qlua эти макросы задействованы, а не оставлены пустыми как в чистом lua.
Цитата
TGB написал: Коды скрипта разделяются между потоками, использующими его. В выбранном варианте трансляции исходников Lua активирована синхронизация, обеспечивающая использование кода LUa только одним потоком, но на разных стеках.
Пользователь
Сообщений: Регистрация: 12.05.2020
31.07.2026 14:37:04
Цитата
Nikolay написал: А есть подтверждение, что в qlua эти макросы задействованы, а не оставлены пустыми как в чистом lua.
Для экспериментального подтверждения запустите код:
Код
for i = 1, 5000000000 do end
в main и пока он будет выполняться ни один коллбек не будет выполнен, а интерфейс QUIK зависнет.
Пользователь
Сообщений: Регистрация: 27.01.2017
31.07.2026 16:09:10
Ок. Пока разработчики молчат, то в тех скриптах, где требовался колбек OnParam пришлось полностью его отключить, т.к. обрабатывать этот "мусор", подаваемый ему на вход, совсем странное занятие. Это был второй колбек, который я использовал, теперь остался только один - OnTransReply.
Пользователь
Сообщений: Регистрация: 12.05.2020
31.07.2026 17:57:34
Цитата
Nikolay написал: в тех скриптах, где требовался колбек OnParam пришлось полностью его отключить
В тех случаях, когда мне надо получать значения текущих торгов, я это делаю методом опроса, в цикле с фильтрацией повторений по параметру 'VOLTODAY'. Существует также параметр 'TIME' - время последней сделки, которое можно для чего то пригодится.
Пользователь
Сообщений: Регистрация: 15.06.2023
31.07.2026 18:28:53
Ну прямо какие то "Страсти Мордастей" написали. Обматываю не большую таблицу, первоначально задав ее: -- Реестр параметров MEO_Config.REQ_PARAMS_REGISTRY = {
Ничего страшного, все на хорошей скорости проходит если понимать с каким лагом обновляется сама таблица. Ведь дело не в оптимальности самой функции и использовании калбэка, А в архитектуре вашей программы и круге решаемых задач.
А зачем он нужен, я специально разделил таблицу на группы параметров. Просто посмотрите. Ну ведь понятно что для высоколиквидных инструментов метрики сделок из ТТТ не годятся, для этого есть тиковые колбэки. А как отследить если изменились параметры "PRICEMAX", "PRICEMIN" в течении сессии? А где взять "BIDDEPTH", "OFFERDEPTH", "NUMBIDS", "NUMOFFERS"? Опять опрос? А посчитайте лаги?
Пользователь
Сообщений: Регистрация: 27.01.2017
31.07.2026 20:17:21
Есть нюанс. Т.к. скорость движения цены заведомо выше скорости получения данных терминалом, то OnParam использовался для компромиссного быстрого получения последней цены, времени цены, времени пакета и проверки, что это не опоздавший пакет. И аггрегации этих данных между опросами в основном цикле. Можно и просто получать опросами, но только если не проблема пропустить выбросы цены.
Остальное параметры ТТТ не требует такой скорости и можно получать по месту требования. Тем более, что много параметров ТТТ вообще не изменяются в течении сессии и требуют однократного запроса.
Но теперь OnParam - это слишком накладно для терминала. Альтернатива - чтение таблицы обезличенных сделок (тики) и это будет самое точное (читать не через колбеки точно), но проблема что её не все брокеры выдают, поэтому это решение нельзя использовать как базовое.
Пользователь
Сообщений: Регистрация: 15.06.2023
31.07.2026 21:58:48
Nikolay, Так ведь и я про это. Есть круг задач решение которых, ну если не оптимальны то хотя бы лучший способ использовать OnParam. Просто потому что лучше ни чего нет. Вы подняли на само деле очень важную проблему. Возраст которой можно уже исчислять десятками лет. Я с Вами целиком согласен. Полное безобразие!
Но торгуем сегодня, и решения нужны сегодня они есть? Пусть не оптимальное. Обсуждение очень важное, у себя даже усложняю структуру у меня вообще срез по рынку (фаза), задумался дописал модули, посчитал лаги на стратегию на принятие решения на исполнение суммарный. Усложнил изохронность, разделил на 3 контура. Встал вопрос а какой ценой лимитный ордер заполнять? Ну точно не из ТТТ.
Пользователь
Сообщений: Регистрация: 30.01.2015
03.08.2026 08:30:21
Цитата
Nikolay написал: Альтернатива - чтение таблицы обезличенных сделок (тики) и это будет самое точное (читать не через колбеки точно), но проблема что её не все брокеры выдают, поэтому это решение нельзя использовать как базовое.
Но колбек вызывается перед записью в таблицу обезличенных сделок. Если читать из таблицы то будет вызов колбека, запись в таблицу, и потом чтение из нее.
Пользователь
Сообщений: Регистрация: 27.01.2017
03.08.2026 10:16:05
Цитата
nikolz написал: Но колбек вызывается перед записью в таблицу обезличенных сделок. Если читать из таблицы то будет вызов колбека, запись в таблицу, и потом чтение из нее.
Здесь есть сомнения, т.к. колбек на таблицу - это событие на изменение данных, которые приходят в пакетах. Что произойдет первым - это вопрос, т.к. я не раз ловил в логах ситуацию когда запись в таблице появлялась ранее чем колбек. Но даже если это такт, то не очень понятно, что это изменяет. Я бы сказал, наоборот, наличие такого частого колбека приведет к падению производительности. Организовать процедуру чтения порций с опросом числа записей в таблице - довольно банальная задача. Сотни тысяч записей записываются в файл за пару секунд. А далее, по мере поступления уже новых, просто их обрабатываем. Зачем для этого колбек в потоке терминала - не ясно. Сама идея событийной модели - это абстракция, за которой все равно будет код, проверяющий что событие наступило. Поэтому я предпочитаю сам это делать, с необходимой мне частотой.