возможно Вы правы пока не проверял. Но тогда это прикольно. По крайней мере для меня. ------------------------------- Полагаю ,что правильный скрипт запускается при любой погоде. ------------------------- А эти тупо молчат нет никаких сообщений об ошибках или о нелетной погоде .
Пользователь
Сообщений: Регистрация: 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