Изменения в Рабочем месте QUIK 11.3.2: ... В некоторых случаях после переподключения терминала к серверу QUIK не обновлялись статусы заявок, которые были активны на момент отключения от сервера.
Nikolay написал: Да, это 11.07.2024. Скрипты были включены. Снятие этих заявок приводило к ошибке о невозможности снять.
Был разрыв связи. Во время простоя заявки исполнились. Восстановление связи. Заявки остались активные. Пришли только колбеки OnTrade. Колбеки на ордера не пришли. Статусы ордеров остались активные.
Только обновились в интенсивно торгующих терминалах на версию 11.3.1.2, а тут такие тревожные симптомы. Мы сами пока ещё не попадали на такие ошибки.
Цитата
Andrey Golik написал: Nikolay, получили от вас письмо на почте, предлагаем продолжить обсуждение проблемы в почтовой переписке.
Просьба сообщить в этой теме о результатах анализа и том, в какой версии терминала будет исправление, если это не разовый глюк.
С увеличением количества торговых минут в торговом дне иногда 3000 свечей мало, хотелось бы хотя бы 10 000 штук.
Мы зарегистрировали Ваше пожелание. Постараемся рассмотреть его и сообщить Вам результаты анализа. Впоследствии, по результатам анализа, будет приниматься решение о реализации пожелания в будущих версиях ПО.
По ряду причин для сервера установлено ограничение в 3000 свечей. Соответственно именно поэтому для Рабочего места QUIK возможность локального накопления свечей была увеличена с 3000 до 65000.
Про локальное накопление я в курсе. Меня ряд причин для сервера интересует. Есть возможность более адекватного ответа на вопрос или вам политика компании не позволяет?
С увеличением количества торговых минут в торговом дне иногда 3000 свечей мало, хотелось бы хотя бы 10 000 штук.
поскольку сам сервер QUIK хранит всего 3000 свечей
Напомните, пожалуйста, почему нельзя на уровне сервера QUIK увеличить 3000 свечей, скажем, до 10 000 свечей. Места в хранилище жалко или есть какая-то более серьёзная причина программистского характера (например, какие-то биты заняты, и индекс ограничен, скажем, числом 4096)?
Net error "Удалённый хост принудительно разорвал существующее подключение", Такая ошибка ,как правило, возникает ближе к концу торгов.Подключиться после этой ошибки невозможно в этот день. На пк установлено ещё 2 квика,они при этом работают нормально. Подключение через USB-модем
NPlay написал: Автор тебе удалось решить проблему? У меня такие же симптомы при логине в квик происходит разрыв локального соединения и сеть отключается, брандмаузер, антивирь, все отрублено, папка квик в исключениях. Проблема возникает исключительно при коннекте с квиком! сетевая карта I-225v мат плата asus 550b-e gaming. Мучаюсь уже несколько месяцев, так и не смог победить. 1 из 10-15 раз все таки удается залогинится на сервер квика, но при этом приходится включать/выключать сетевое подключение.
У нас именно такое было на 2-х компах. Мы решили, что это косяк сетевой карты I225 (установка разных драйверов не помогла). В итоге купили внешние сетевые карты, после этого проблема ушла.
Реализация Lua-машины такова, что перед входом в главный цикл интерпретатора объект синхронизации захватывается и отпускается только в некоторых случаях, например, при вызове внешних функций (в частности, sleep). Рекомендуем использовать вызов функции sleep() внутри цикла, чтобы исключить зависание приложения.
Спасибо за пояснения о ттехподдержки. Хорошо, что они совпадают с теми, что высказали другие участники общения в этой теме.
Если код функции OnStop модифицировать следующим образом:
Код
function OnStop (flag)
interrupted = true
message ( "OnStop executed" , 1 )
pcall(closeWindow)
end
то зависания не будет и, наверное, понятно почему.
Да, так и есть. Я пробовал этот вариант, но уже не стал писать в теме.
В общем, получается, что неплохо бы со стороны разработчиков обеспечить переключение между потоками даже в случае "длинного чистого Lua-кода". Я видел Ваши предложения по этому поводу, на которые разработчики, к сожалению, не реагируют.
Конечно, большинство программ пользователей имеют главный цикл со sleep внутри, так что совсем уж в явном виде проблема не стоит. Но переключение между потоками может сделать как работу терминала более отзывчивой, а также зависание, с которым я столкнулся, исключит.
_sk_ написал: Тут с OnStop какая-то проблема, как мне кажется.
Причину я описал в своем комментарии. В вашем скрипте эта причина проявляется следующим образом. В цикле функции main есть два фрагмента кода с вызовами C-функций. Это sleep(0) и косвенно вызываемые (с помощью функции run()) стандартные функции работы с таблицами QUIK. При отключении sleep(0) остаются C-функции работы с таблицами QUIK. Поэтому OnStop запускается в потоке обработки колбеков (с выдачей сообщения "OnStop executed"). Но далее запускается pcall(closeWindow). В функции closeWindow отключается фрагмент работы с таблицами QUIK. Исполняемый код цикла функции main при этом представляет код на "чистом Lua". Внутри исполнения pcall(closeWindow) существует вызов C-функции (я с этим уже разбираться не буду), при котором блокируется поток обработки колбеков, но отпускается поток main, где в цикле выполнения кода на "чистом Lua" блокируется поток выполняемый функцию OnStop. QUIK виснет.
_sk_ написал: 2) Знает ли кто-то причину такого поведения?
Проверил у себя в песочнице 10.1. Ваш скрипт зависает при flag = false. Как известно, длинный участок фрагмента кода скрипта на "чистом Lua" в main (без вызова C-функций) блокирует выполнение потока обслуживающего колбеки. Функция sleep(0) - это C-функция. Когда вы исключаете ее исполнение (в цикле), то получается бесконечный фрагмент скрипта на "чистом Lua" и OnStop, являющийся колбеком не может быть выполненным.
Тут с OnStop какая-то проблема, как мне кажется.
Заменяем фрагмент с кодом OnStop() вот на такое:
Код
function OnAllTrade(t)
message("OnAllTrade", 1)
end
function OnStop(flag)
message("OnStop executed", 1)
pcall(closeWindow)
interrupted = true
end
Проверяем ещё раз и видим, что: 1) коллбэки OnAllTrade выполняются (есть такие сообщения, если терминал получает обезличенные сделки); 2) при остановке скрипта терминал зависает, но при этом видно последнее сообщение "OnStopExecuted".
В принципе, вместо OnAllTrade можно любой другой коллбэк поставить (OnQuote, OnParam).
Берём следующий скрипт. Если в первой строке стоит flag = true, то скрипт нормально запускается и останавливается из меню скриптов. Если в первой строке поставить flag = false, происходит зависание терминала при остановке скрипта. В зависимости от значения переменной flag либо имеется sleep(0) в функции main(), либо отсутствует на пути выполнения кода.
У меня зависание воспроизводится в терминалах версий 10.0.0 и 10.0.1.
Три вопроса: 1) Может ли кто-то ещё подтвердить, что зависание происходит (чтобы исключить особенность моего компьютера)? 2) Знает ли кто-то причину такого поведения? 3) Что разработчики терминала могут сказать по этому поводу?
Сам скрипт:
Код
local flag = true -- если установить в false, то при остановке скрипта происходит зависание (убирается sleep(0) в функции main)
local foregroundColor = RGB(0, 0, 0)
local backgroundColor = RGB(192, 255, 192)
local interrupted = false
local tableId
local function setTableColors(foregroundColor, backgroundColor)
local nRows, nCols = GetTableSize(tableId)
if nRows and nCols then
for row = 1, nRows do
for col = 1, nCols do
SetColor(tableId, row, col,
backgroundColor, foregroundColor,
backgroundColor, foregroundColor)
end
end
end
end
local function ensureWindowOpened()
if tableId == nil then
return
end
if IsWindowClosed(tableId) then
CreateWindow(tableId)
SetWindowPos(tableId, 0, 0, 300, 100)
SetWindowCaption(tableId, "hang_test")
InsertRow(tableId, 1)
InsertRow(tableId, 2)
SetCell(tableId, 1, 1, "11")
SetCell(tableId, 1, 2, "12")
SetCell(tableId, 2, 1, "21")
SetCell(tableId, 2, 2, "22")
SetSelectedRow(tableId, 1)
end
setTableColors(foregroundColor, backgroundColor)
end
local function closeWindow()
local t = tableId
tableId = nil
if t then
DestroyTable(t)
end
end
function OnInit(scriptPath)
tableId = AllocTable()
AddColumn(tableId, 1, "col1", true, QTABLE_STRING_TYPE, 5)
AddColumn(tableId, 2, "col2", true, QTABLE_STRING_TYPE, 5)
SetTableNotificationCallback(tableId, function(tId, msg, par1, _)
if msg == QTABLE_SELCHANGED then
SetSelectedRow(tId, 1)
end
end)
ensureWindowOpened()
end
local function run()
ensureWindowOpened()
if tableId then
SetSelectedRow(tableId, 2)
end
end
function OnStop(flag)
pcall(closeWindow)
interrupted = true
end
function main()
message("STARTED", 1)
while not interrupted do
run()
if flag then
sleep(0)
end
end
pcall(closeWindow)
message("SHUTDOWN", 1)
end
1) В числах разделитель разрядов должен быть точкой. 2) Если после знака равно стоит строка, её надо взять в кавычки. 3) В самом конце строк таблицы stop нужны запятые, а не точки с запятой, как сейчас в некоторых местах. 4) TRANS_ID должно быть числом.
Также надо учесть, что в таблице может быть две сделки с одинаковым номером. Такая ситуация возникнет, если это сделки между двумя клиентами одного брокера и терминал выводит в таблицу сделки для обоих клиентов. По-видимому, чтобы учесть такие случаи, нужно возвращать массив этих сделок. Обычно он будет либо пустой (если ничего не найдено) или содержать одну сделку, но возможен вариант с двумя сделками (для каждого из клиентов при этом будет свой номер заявки).
Уже есть функция getOrderByNumber, которая по коду класса и номеру заявки возвращает заявку из таблицы заявок. Предлагаю добавить аналогичную функцию для сделок. Сейчас для поиска используется
Может такое быть. Был некий стакан с неким значением ask, кто-то согласился на такую цену и ударил по ней рыночной заявкой. Потом участники рынка отозвали некоторые заявки из стакана и поставили новые; сделок при этом не произошло. В результате значение ask стало другим, уменьшилось. Потом кто-то из участников торговли согласился на такую цену и ударил по ней рыночной заявкой. Так может реализоваться описанный Вами выше сценарий.
По хорошему, надо, чтобы разработчики терминала ответили на поставленные выше вопросы. Пока что кажется, что это баг терминала, раз не проверяются null при освобождении ресурсов в Вашем случае.
Я обычно делаю цикл, который ждёт, пока ds:Size() не станет положительным, после чего работаю с данными, которые там появились. На практике кажется, что приходят сразу все свечи, которые есть в наличии у терминала.
и изменить код, начиная с OnStop(), в файле с кодом:
Код
local function isExportTime()
local dt = os.sysdate()
local hhmm = dt.hour * 100 + dt.min
return startExportHHMM <= hhmm and hhmm < stopExportHHMM and 1 <= dt.week_day and dt.week_day <= 5
end
function OnStop()
isInterrupted = true
end
function main()
message("Export started.", 1)
requestParams()
while not isInterrupted do
if isExportTime() then
exportQuotes()
end
pause(pauseDurationMillis)
end
message("Export stopped.", 1)
end
1) Создаём два файла. В одном описываются настройки и вызывается второй файл, содержащий код. Оба файла должны лежать в одной папке. Запускать надо ParamExportConfig.lua.
Файл ParamExportConfig.lua:
Код
--- Функция для условного форматирования: до момента hhmm используется format1, после -- format2
local function formatHHMM(hhmm, format1, format2)
return function()
local dt = os.sysdate()
if dt.hour * 100 + dt.min < hhmm then
return format1
else
return format2
end
end
end
-- Настройки для вывода котировок
securities = {
{
line = "GAZ ----\n", -- в конце перевод строки
},
{
classCode = "SPBFUT",
secCode = "BRF3",
params = {
{ param = "last", format = "BRENT %.2f ", }, -- в конце пробел-разделитель
{ param = "lastChange", format = "OIL %.1f\n", }, -- в конце перевод строки
},
},
{
classCode = "SPBFUT",
secCode = "GDZ2",
params = {
{ param = "last", format = "GOLD %.1f ", },
{ param = "lastChange", format = "%.2f\n", },
},
},
{
classCode = "TQBR",
secCode = "GAZP",
params = {
{ param = "last", format = "GAZP %.2f ", },
},
},
{
classCode = "TQBR",
secCode = "SBER",
params = {
{ param = "last", format = "SBER %.2f\n", },
},
},
{
classCode = "TQBR",
secCode = "AFLT",
params = {
{ param = "last", format = "AFLT %.2f ", },
},
},
{
classCode = "TQBR",
secCode = "GMKN",
params = {
{ param = "last", format = "GMKN %.0f\n", },
},
},
{
classCode = "TQBR",
secCode = "MGNT",
params = {
{ param = "last", format = formatHHMM(1837, "MGNT --\n", "MGNT %.1f\n"), },
},
},
{
classCode = "TQBR",
secCode = "MOEX",
params = {
{ param = "last", format = formatHHMM(1837, "", "MOEX %.2f\n"), },
},
},
}
filename = "D:/last_quotes.txt"
pauseDurationMillis = 10000
sleepDurationMillis = 250
dofile(getScriptPath() .. "/ParamExportCode.lua")
Файл ParamExportCode.lua:
Код
local isInterrupted = false
local file
local function valueOf(x)
return type(x) == "function" and x() or x
end
local function requestParams()
for _, security in ipairs(securities) do
if type(security.params) == "table" then
for _, securityParams in ipairs(security.params) do
if not ParamRequest(security.classCode, security.secCode, securityParams.param) then
message("Cannot request: "
.. tostring(security.classCode) .. ", "
.. tostring(security.secCode) .. ", "
.. tostring(securityParams.param), 2)
end
end
end
end
end
local function exportQuotesForSecurity(security)
if type(security.line) == "string" then
file:write(tostring(security.line))
elseif type(security.params) == "table" then
for i, securityParams in ipairs(security.params) do
local paramEx = getParamEx(security.classCode, security.secCode, securityParams.param)
if paramEx.result == "1" then
file:write(string.format(valueOf(securityParams.format), paramEx.param_value))
else
message("Cannot export: "
.. tostring(security.classCode) .. ", "
.. tostring(security.secCode) .. ", "
.. tostring(securityParams.param), 2)
end
end
end
end
local function exportQuotes()
file = io.open(filename, "w+")
if file then
for _, security in ipairs(securities) do
exportQuotesForSecurity(security)
end
file:close()
end
end
local function pause(durationMillis)
while durationMillis > 0 and not isInterrupted do
sleep(math.min(durationMillis, sleepDurationMillis))
durationMillis = durationMillis - sleepDurationMillis
end
end
function OnStop()
isInterrupted = true
end
function main()
message("Export started.", 1)
requestParams()
while not isInterrupted do
exportQuotes()
pause(pauseDurationMillis)
end
message("Export stopped.", 1)
end
2) Добавлена функция для реализации форматирования в зависимости от времени formatHHMM. В примере конфига для MGNT выводим прочерк до указанного момента времени и цену в остальное время, для MOEX не выводим ничего (пустая строка) до указанного момента и цену в остальное время. Возможно, придётся доработать эту функцию, чтобы указать время перехода с format2 на format1 (сейчас это полночь). В os.sysdate() используется локальное время компа.
3) По-моему, этого кода должно быть достаточно для реализации всех пожеланий. Пробуйте.
В предыдущем скрипте можно упростить описание массива инструментов: префиксы, суффиксы, разделители, переводы строки и название инструмента поместить в строку format. Так получается компактнее. Функционал не должен пострадать. Неудобство может возникнуть только если придётся экранировать какие-либо символы в названии инструмента (скажем, если в названии присутствует знак процента или ещё что-то в этом рода).
Код
local securities = {
{
classCode = "SPBFUT",
secCode = "BRF3",
params = {
{ param = "last", format = "BRENT %.2f ", }, -- название пишем прямо в строке format, в конце добавляем пробел
{ param = "lastChange", format = "OIL %.1f\n", }, -- тут в конце строки format добавляем перевод строки
},
},
{
classCode = "SPBFUT",
secCode = "GDZ2",
params = {
{ param = "last", format = "GOLD %.1f ", },
{ param = "lastChange", format = "%.2f\n", },
},
},
{
classCode = "TQBR",
secCode = "GAZP",
params = {
{ param = "last", format = "GAZP %.2f ", }, -- в конце пробел, чтобы отделить от следующего инструмента
},
},
{
classCode = "TQBR",
secCode = "SBER",
params = {
{ param = "last", format = "SBER %.2f\n", }, -- в конце перевод строки
},
},
{
classCode = "TQBR",
secCode = "AFLT",
params = {
{ param = "last", format = "AFLT %.2f ", },
},
},
{
classCode = "TQBR",
secCode = "GMKN",
params = {
{ param = "last", format = "GMKN %.0f\n", },
},
},
}
local filename = "D:/last_quotes.txt"
local pauseDurationMillis = 10000
local sleepDurationMillis = 250
local isInterrupted = false
local file
local function requestParams()
for _, security in ipairs(securities) do
for _, securityParams in ipairs(security.params) do
if not ParamRequest(security.classCode, security.secCode, securityParams.param) then
message("Не удалось заказать: "
.. tostring(security.classCode) .. ", "
.. tostring(security.secCode) .. ", "
.. tostring(securityParams.param), 2)
end
end
end
end
local function exportQuotesForSecurity(security)
for i, securityParams in ipairs(security.params) do
local paramEx = getParamEx(security.classCode, security.secCode, securityParams.param)
if paramEx.result == "1" then
file:write(string.format(securityParams.format, paramEx.param_value))
else
message("Не удалось экспортировать: "
.. tostring(security.classCode) .. ", "
.. tostring(security.secCode) .. ", "
.. tostring(securityParams.param), 2)
end
end
end
local function exportQuotes()
file = io.open(filename, "w+")
if file then
for _, security in ipairs(securities) do
exportQuotesForSecurity(security)
end
file:close()
end
end
local function pause(durationMillis)
while durationMillis > 0 and not isInterrupted do
sleep(math.min(durationMillis, sleepDurationMillis))
durationMillis = durationMillis - sleepDurationMillis
end
end
function OnStop()
isInterrupted = true
end
function main()
message("Скрипт экспорта котировок запущен.", 1)
requestParams()
while not isInterrupted do
exportQuotes()
pause(pauseDurationMillis)
end
message("Скрипт экспорта котировок остановлен.", 1)
end
При использовании скрипта надо обращать внимание, что в файле с кодом используется кодировка CP1251, иначе вместо русских букв будут кракозябры. Либо всё по-английски писать: Cannot request parameter: Cannot export: Script started. Script stopped.
Наблюдаем в прямом эфире рождение чёткого ТЗ от заказчика :)
Цитата
Как переформатировать вывод что бы бумаги с доп секцией lastchange оставить на отдельной строке, а "обычные" акции сделать по две на строку как в примере ниже:
Примерно так:
Код
local securities = {
{
classCode = "SPBFUT",
secCode = "BRF3",
params = {
{ prefix = "", name = "BRENT", delimiter = " ", param = "last", format = "%.2f", suffix = " ", },
{ prefix = "", name = "OIL", delimiter = " ", param = "lastChange", format = "%.1f", suffix = "\n", },
},
},
{
classCode = "SPBFUT",
secCode = "GDZ2",
params = {
{ prefix = "на всякий случай :) ", name = "GOLD", delimiter = " ", param = "last", format = "%.1f", suffix = " ", },
{ prefix = "", name = "золото", delimiter = " ", param = "lastChange", format = "%.2f", suffix = "\n", },
},
},
{
classCode = "TQBR",
secCode = "GAZP",
params = {
{ prefix = "", name = "GAZP", delimiter = " ", param = "last", format = "%.2f", suffix = " ", },
},
},
{
classCode = "TQBR",
secCode = "SBER",
params = {
{ prefix = "", name = "SBER", delimiter = " ", param = "last", format = "%.2f", suffix = "\n", },
},
},
{
classCode = "TQBR",
secCode = "AFLT",
params = {
{ prefix = "", name = "AFLT", delimiter = " ", param = "last", format = "%.2f", suffix = " ", },
},
},
{
classCode = "TQBR",
secCode = "GMKN",
params = {
{ prefix = "", name = "GMKN", delimiter = " ", param = "last", format = "%.0f", suffix = "\n", },
},
},
}
local filename = "D:/last_quotes.txt"
local pauseDurationMillis = 10000
local sleepDurationMillis = 250
local isInterrupted = false
local file
local function requestParams()
for _, security in ipairs(securities) do
for _, securityParams in ipairs(security.params) do
if not ParamRequest(security.classCode, security.secCode, securityParams.param) then
message("Не удалось заказать: "
.. tostring(security.classCode) .. ", "
.. tostring(security.secCode) .. ", "
.. tostring(securityParams.param), 2)
end
end
end
end
local function exportQuotesForSecurity(security)
for i, securityParams in ipairs(security.params) do
local paramEx = getParamEx(security.classCode, security.secCode, securityParams.param)
if paramEx.result == "1" then
file:write(
securityParams.prefix or "",
securityParams.name or "",
securityParams.delimiter or " ",
string.format(securityParams.format, paramEx.param_value),
securityParams.suffix or "")
else
message("Не удалось экспортировать: "
.. tostring(security.classCode) .. ", "
.. tostring(security.secCode) .. ", "
.. tostring(securityParams.param), 2)
end
end
end
local function exportQuotes()
file = io.open(filename, "w+")
if file then
for _, security in ipairs(securities) do
exportQuotesForSecurity(security)
end
file:close()
end
end
local function pause(durationMillis)
while durationMillis > 0 and not isInterrupted do
sleep(math.min(durationMillis, sleepDurationMillis))
durationMillis = durationMillis - sleepDurationMillis
end
end
function OnStop()
isInterrupted = true
end
function main()
message("Скрипт экспорта котировок запущен.", 1)
requestParams()
while not isInterrupted do
exportQuotes()
pause(pauseDurationMillis)
end
message("Скрипт экспорта котировок остановлен.", 1)
end
Скрипт в второй редакции просто отличный, компактный. Работает очень быстро.
С чего бы ему медленно работать, тут же все очень быстро происходит.
Цитата
Не такой популярный язык LUA, обзвонил всех знакомых кто в IT, ни кто этот язык не знает... интуитивно обычной логикой тоже наскоком его не понять.
По-хорошему, конечно, надо бы почитать книгу какую-то по этому языку. Жаль, что с IT окружением не задалось.
Код
local securities = {
{
classCode = "TQBR",
secCode = "SBER",
params = {
{ name = "SBER", param = "last", format = "%.2f", },
{ name = "Сбер", param = "lastChange", format = "%.1f", },
},
},
{
classCode = "TQBR",
secCode = "GAZP",
params = {
{ name = "GAZP", param = "last", format = "%.2f", },
},
},
{
classCode = "SPBFUT",
secCode = "BRF3",
params = {
{ name = "BR", param = "last", format = "%.2f", },
{ name = "OIL", param = "lastChange", format = "%.2f", },
},
},
}
local filename = "D:/last_quotes.txt"
local pauseDurationMillis = 10000
local sleepDurationMillis = 100
local isInterrupted = false
local file
local function requestParams()
for _, security in ipairs(securities) do
for _, securityParams in ipairs(security.params) do
ParamRequest(security.classCode, security.secCode, securityParams.param)
end
end
end
local function exportQuotesForSecurity(security)
for i, securityParams in ipairs(security.params) do
local paramEx = getParamEx(security.classCode, security.secCode, securityParams.param)
if paramEx.result == "1" then
if i > 1 then
file:write(" ")
end
file:write(securityParams.name, " ", string.format(securityParams.format, paramEx.param_value))
end
end
file:write("\n")
end
local function exportQuotes()
file = io.open(filename, "w+")
if file then
for _, security in ipairs(securities) do
exportQuotesForSecurity(security)
end
file:close()
end
end
local function pause(durationMillis)
while durationMillis > 0 and not isInterrupted do
sleep(math.min(durationMillis, sleepDurationMillis))
durationMillis = durationMillis - sleepDurationMillis
end
end
function OnStop()
isInterrupted = true
end
function main()
requestParams()
while not isInterrupted do
exportQuotes()
pause(pauseDurationMillis)
end
end
local securities = {
{ classCode = "TQBR", secCode = "SBER", },
{ classCode = "TQBR", secCode = "GAZP", },
{ classCode = "SPBFUT", secCode = "LKZ2", }
}
local param = "last"
local filename = "D:/last_quotes.txt"
local pauseDurationMillis = 10000
local sleepDurationMillis = 100
local isInterrupted = false
local file
local function requestParams()
for _, security in ipairs(securities) do
ParamRequest(security.classCode, security.secCode, param)
end
end
local function exportQuote(security)
local paramEx = getParamEx(security.classCode, security.secCode, param)
if paramEx.result == "1" then
file:write(security.secCode, " ", tostring(paramEx.param_value), "\n")
end
end
local function exportQuotes()
file = io.open(filename, "w+")
if file then
for _, security in ipairs(securities) do
exportQuote(security)
end
file:close()
end
end
local function pause(durationMillis)
while durationMillis > 0 and not isInterrupted do
sleep(math.min(durationMillis, sleepDurationMillis))
durationMillis = durationMillis - sleepDurationMillis
end
end
function OnStop()
isInterrupted = true
end
function main()
requestParams()
while not isInterrupted do
exportQuotes()
pause(pauseDurationMillis)
end
end
Можно начать вот с этого примера, который сохраняет цену last для GAZP в файл в корне диска D:
Код
local classCode = "TQBR"
local secCode = "GAZP"
local param = "last"
local filename = "D:/" .. classCode .. "_" .. secCode .. ".txt"
local pauseDurationMillis = 10000
local sleepDurationMillis = 100
local isInterrupted = false
local function printValue(value)
local file = io.open(filename, "w+")
if file then
file:write(tostring(value))
file:close()
end
end
local function exportParam()
local paramEx = getParamEx(classCode, secCode, param)
if paramEx.result == "1" then
printValue(paramEx.param_value)
end
end
local function pause(durationMillis)
while durationMillis > 0 and not isInterrupted do
sleep(math.min(durationMillis, sleepDurationMillis))
durationMillis = durationMillis - sleepDurationMillis
end
end
function OnStop()
isInterrupted = true
end
function main()
ParamRequest(classCode, secCode, param)
while not isInterrupted do
exportParam()
pause(pauseDurationMillis)
end
end
1) Производительность каких-то операций ввода-вывода терминала сильно падает (видно, как заметно медленнее прокачиваются все обезличенные сделки). Не знаю, эмулятор ли тут виноват или сам терминал так написано, но факт есть. Если будет не SSD, а HDD использоваться -- всё ещё хуже.
Уточните, пожалуйста, помимо проблем с обезличенными сделками, проявляется ли каким-либо еще образом снижение производительности? Рекомендуем Вам выполнить следующие настройки: - Основные настройки (F9) / Программа / Получение данных: Интервал обновления данных с текущим состоянием - Запрашивать данные раз в 1 сек.; - Основные настройки (F9) / Программа / Получение данных / Котировки : Формировать список обновляемых инструментов и параметров - "умным" заказом данных; - Основные настройки (F9) / Программа / Сохранение данных: Сохранять для получаемых инструментов и параметров - Только данные, отражающие текущее состояние. Просьба сообщить, помогут ли данные настройки решить проблему низкой производительности.
Цитата
2) Есть баг в функции os.sysdate(), который проявляется ТОЛЬКО под wine. Выглядит как будто иногда (раз на несколько тысяч вызовов) не учитывается временной пояс. Вроде, можно с этим программно бороться внутри QLua-скриптов, но не факт, что только в скриптах эта проблема вылезает.
Просим Вас подробнее описать данную проблему, а также прислать скриншоты и скрипт, при выполнении которого наблюдается указанное поведение. Фалйы можно направить на нашу почту quiksupport@arqatech.com .
Цитата
3) Иногда есть проблемы со свечными графиками. Они перестают получать новые свечи, пока не перезакажешь данные. Возможно, что причиной является п.2.
Уточните, пожалуйста, речь идет о графиках, созданных с помощью QLua, или об обычных графиках? Пришлите, пожалуйста, скриншот данной проблемы.
Цитата
4) Если одновременно запускать несколько скриптов, то заметно раньше появится проблема производительности. Она и под Windows есть, а под Linux вылезает ещё быстрее.
Уточните, пожалуйста, какие скрипты запускаются? Как именно проявляется проблема производительности?
Что касается разработки QUIK для ОС Linux, в настоящий момент мы не можем поделиться какими-либо новыми сведениями по этой теме. Если данное ПО будет выпущено, соответствующая новость появится на сайте: https://arqatech.com/ru/about/news/
Не знаю, зачем я это пишу, ведь вряд ли что-то изменится.
1) При запуске в терминале десятка торговых скриптов, которые примерно раз в секунду выводят данные в таблицы, созданные с помощью QLua имеющие размеры порядка 10 столбцов и 20-50 строк, приводят к тормозам. Проблема в том, что операции на отрисовку графического интерфейса встают в общую очередь в терминале, при этом возникает live lock.
2) Скриншот делать бессмысленно. Код примерно такой:
Код
function main() while true do local t1 = os.time() local dt = os.sysdate()
local t2 = os.time(dt) if math.abs(t1 - t2) > 1 then message(tostring(t1) .. "; " .. tostring(t2), 2)
end
end
end
Под Windows таких проблем нет. В описанном мною выше эмуляторе wine под Ubuntu 20.04 есть.
3) Речь идёт о свечных графиках внутри терминала. У нас такие ошибки были, в основном, на графиках акций СПБиржи.
4) См. п.1. Проблема производительности в том, что отзывчивость GUI падает.
Под "Астрой" не пробовал устанавливать связку wine + QUIK, а вот под Ubuntu 20.04 пробовал связку wine 7.0 + QUIK 9.5.0.42.
Использовалась виртуалка под QEMU/KVM с образом диска на SSD и относительно неплохим процессором Ryzen 3600 на хост-системе.
Есть следующие проблемы:
1) Производительность каких-то операций ввода-вывода терминала сильно падает (видно, как заметно медленнее прокачиваются все обезличенные сделки). Не знаю, эмулятор ли тут виноват или сам терминал так написано, но факт есть. Если будет не SSD, а HDD использоваться -- всё ещё хуже.
2) Есть баг в функции os.sysdate(), который проявляется ТОЛЬКО под wine. Выглядит как будто иногда (раз на несколько тысяч вызовов) не учитывается временной пояс. Вроде, можно с этим программно бороться внутри QLua-скриптов, но не факт, что только в скриптах эта проблема вылезает.
3) Иногда есть проблемы со свечными графиками. Они перестают получать новые свечи, пока не перезакажешь данные. Возможно, что причиной является п.2.
4) Если одновременно запускать несколько скриптов, то заметно раньше появится проблема производительности. Она и под Windows есть, а под Linux вылезает ещё быстрее.
По совокупности проблем мы не смогли запустить серьёзный алготрейдинг в связке linux + wine. Так, по мелочи можно, чтобы сэкономить на лицензиях для Windows.
В целом, конечно, хотелось бы терминал для Linux хоть в каком-то нативном виде.
Выскажу гипотезу, почему так получается с низкой скоростью отрисовки, и немного порассуждаю. Возможно, что я не прав.
Скрипты вызывают функцию SetCell в потоке main, а отрисовку терминал должен делать в потоке коллбэков, поэтому сразу несколько скриптов синхронизируются на одном мониторе. Вызовов SetCell, на самом деле, много (циклы и одновременно запущенные скрипты) и из-за этой постоянно требующейся синхронизации имеем тормоза.
Можно как-то снижать затраты на синхронизацию. Например, если бы была удобная возможность дать из потока main задачу для исполнения в потоке коллбэков, можно было бы реализовать отрисовку таблицы большим блоком, который выполнился бы быстрее, чем куча мелких задач. Что-то типа executeAsCallback на этом форуме уже обсуждали (https://forum.quik.ru/messages/forum10/message50396/topic5983/#message50396)
Но если просто починят SetCell, чтобы эта и, возможно, аналогичные функции стали существенно быстрее, тоже будет хорошо.
Раз вы смогли увидеть медленную отрисовку Lua-таблиц, это уже очень хорошо, т.к. обозначенную в посте проблему можно реально воспроизвести и постараться устранить.
Когда я писал про повышенную нагрузку на CPU, то имел в виду, что процесс терминала QUIK загружает ПО МАКСИМУМУ одно ядро процессора, хотя современное железо должно справляться с такой интенсивностью вывода на экран с меньшей нагрузкой. Я не знаю, сколько ядер в машине, на которой сделаны прилагаемые скриншоты, но 11.2% уже близко к границе для 8-ядерного компьютера (100% / 8 ядер = 12.5%).
Предлагаю считать, что если разобраться с медленной отрисовкой, то и проблема, которую я обозначил как повышенная нагрузка на CPU, будет решена.
При использовании приведённых ниже демонстрационных скриптов наблюдается повышенная нагрузка на CPU и медленный вывод содержимого в таблицы QLua. При этом вывод в заголовки окон этих таблиц производится нормально. Просьба проверить оптимальность реализации отрисовки содержимого таблиц. Используется светлая тема терминала 9.3.3.3, на тёмной не проверялось, наверное, ещё хуже будет.
Файлы RunDemo1.lua, ...m RunDemo9.lua каждый имеет свои настройки и запускают общий файл Demo.lua. Каждый скрипт выводит окно и периодически с некоторым таймаутом, заданным в Demo.lua, обновляет свою таблицу на экране. Скрипты надо запускать одновременно.
Просьба к разработчикам ответить на вопрос: считается ли такая скорость отрисовки нормальной или её стоит оптимизировать?
Файл Demo.lua:
Код
---
--- Настройки заданы в переменной config, которая к моменту запуска уже определена.
---
local interrupted = false
local tableId
local timeout = 20 -- можно уменьшать, чтобы проблема стала более явной
local nRow = 30 -- количество строк в таблице, можно увеличивать, чтобы проблема стала более явной
local nCol = 10 -- количество столбцов в таблице, можно увеличивать, чтобы проблема стала более явной
function OnInit(scriptPath)
tableId = AllocTable()
for colId = 1, nCol do
AddColumn(tableId, colId, "#" .. colId, true, QTABLE_STRING_TYPE, 20)
end
end
function OnStop()
interrupted = true
local tId = tableId
tableId = nil
if tId then
DestroyTable(tId)
end
end
function updateTable()
if tableId == nil then
return
end
if IsWindowClosed(tableId) then
CreateWindow(tableId)
SetWindowPos(tableId, config.x, config.y, config.dx, config.dy)
for rowId = 1, nRow do
InsertRow(tableId, rowId)
end
end
local dt = os.date(" %Y-%m-%d %X", os.time())
SetWindowCaption(tableId, config.name .. dt)
for rowId = 1, nRow do
for colId = 1, nCol do
SetCell(tableId, rowId, colId, dt)
end
end
end
function main()
while not interrupted do
updateTable()
sleep(timeout)
end
end
Файл RunDemo1.lua:
Код
---
--- Этот скрипт нужно запустить.
---
config = {
name = "RunDemo1",
x = 0,
y = 0,
dx = 1400,
dy = 100,
}
dofile(getScriptPath() .. "/Demo.lua")
Файл RunDemo2.lua:
Код
---
--- Этот скрипт нужно запустить.
---
config = {
name = "RunDemo2",
x = 0,
y = 100,
dx = 1400,
dy = 100,
}
dofile(getScriptPath() .. "/Demo.lua")
Содержимое файлов 3-9 аналогично, отличаются только y-координаты окна.
Упомянутый код демонстрирует проблему в QUIK 9.3.1.11. В QUIK 9.3.3.3 QLua 5.4 эта конкретная проблема устранена.
Это хорошо. Полагаю, что разработчики это тоже подтвердили бы, если бы провели разбор проблемы качественно.
Цитата
... если в колбеках, есть обращение к функциям API QLua, то все запущенные скрипты рабочего места QUIK, могут выстраиваться в очередь к синхронизуемым ресурсам QUIK (по которым в ранних версиях QUIK, возможно синхронизации вообще не было), что эквивалентно их работе в одном потоке.
Вот как раз похоже на такое поведение. Конкретно у меня в коллбэках минимальные действия и очереди, передающие данные в main, но в самом main остаётся периодический вывод информации в таблицы (имеются в виду окна, создаваемые из скриптов), а их отрисовка опять идёт через поток коллбэков и начинаются тормоза.
Цитата
Правильно я понимаю, что у Вас три терминала КВИК работают и каждый сделал по 12 потоков, а у Вас всего 8 ядер ?
Нет. В процессоре всего имеется 8 ядер. Каждый из 3-х терминалов грузит полностью одно ядро процессора (архитектура терминала такая). Дополнительная нагрузка собственно от Lua минимальна. Основная беда, похоже, с тем, как отрабатывают функции QLua, заставляющие терминал что-то отрисовывать. Что-то там внутри терминала с синхронизациями при доступе к общим ресурсам не так. Пока неясно, как бы это в явном виде продемонстрировать.
Для снижения нагрузки на терминал пришлось позакрывать все свечные графики (порядка 8 штук), собрать все инструменты в одну ТТТ, выключить там цветовые настройки, а также закрыть стаканы. Кажется, что несколько легче стало. Выходит, что отрисовка графики дополнительно сильно замедляет работу (похоже, что есть там какое бутылочное горлышко в виде одной общей очереди на всё). Трудно обеспечивать тысячи сделок в течение торгового дня.
Daniil Pozdnyakov, плохо, что не нашли причину. Конкретно в последние 2 дня из-за большой активности торгов на рынке у нас терминалы 9.3.3.3, где запущено много скриптов, тормозят: время сервера внизу в статус строке отстаёт от реального времени на несколько секунд. Ненормально это.
отсутствие linux версии Quik говорит лишь об адекватности кодеров в ARQA, ну или менеджмента хотя бы :)
Отсутствие линукс-версии терминала говорит о том, что не хватает ресурсов у разработчиков на это. Выбрали бы в самом начале разработки терминала язык Java -- имели бы кроссплатформенный вариант сегодня, правда непонятно с каким качеством.
На мой взгляд, актуальный терминал и под Windows работает так себе в смысле производительности для конкретно моих задач, связанных с алготрейдингом через lua-скрипты.
Под Wine 6.0.2 терминал как-то работает (с некоторыми глюками) и ещё более заметным падением производительности, из-за которого мне не удалось перейти на Linux с Windows в трейдинге. Может, Wine 7 как-то улучшит ситуацию (ещё не пробовал под ним запускать).
Антон выше прав, что наиболее реально -- это (попробовать) собрать терминал под ARM-версию Windows. Остальное нереально, у "Арки" нет на это ресурсов.
Жаль, что достойный конкурентов терминала нет на российском рынке, может от этого повысилось бы качество.