Egor Zaytsev написал: Просьба подсказать, претензия в том, что после подключения к серверу по настройке Восстанавливать связь автоматически QUIK долго получает данные? Если да, то на какие именно данные ориентируетесь? Проблема исключительно только утром?
Ситуации: 1) При проверке многократной (> 8 раз) остановки/запуска скрипта, в котором используется сторонний графический пакет IUP, QUIK (версия 12.6.1.2, Lua 5.4), после разного количества экспериментов, в моменты останова/запуска, падает: Дамп info_dmp_20251007_150748.dmp вышлю почтой. Могу наделать сколько нужно. Причина 0xc0000005 - Потоком была предпринята попытка прочитать или записать данные на виртуальный адрес, к которому он не имеет соответствующего доступа. Это происходит только в рабочем экземпляре QUIK. В песочнице с такой же версией этого нет. В работе проблем с пакетом IUP не обнаружено (использую давно, но ранее при проверке запуска/перезапуска делал это 3-4 раза.). Экспериментально выяснил, что перенос закрытия IUP (iup:Close()) из служебного потока OnStop в пользовательский поток main, ситуацию исправляет. QUIK падать перестает. Но почему iup:Close() "ломает" поток OnStop? Опять что-нибудь с синхронизацией? Похоже, это очередная ситуация, подтверждающая то, что мною написано в п. 2) комментария https://forum.quik.ru/messages/forum10/message74919/topic8563/#message74919. В комментарии https://forum.quik.ru/messages/forum10/message74928/topic8563/#message74928 предлагается вариант реализации п. 2). В комментарии https://forum.quik.ru/messages/forum10/message74960/topic8563/#message74960 есть предложение, как при реализации п. 2), сохранить существующий интерфейс QLua c QUIK. ------- При экспериментировании замучился ожидать перезапуски QUIK и об этом следующий пункт.
2) Задержка при запуске/перезапуске QUIK на моем ноутбуке, с ранее перечисленными характеристиками: ~1 мин. Это много и неоднократно в разные года обсуждалось в различных ветках. Мои комментарии трассировки запуска рабочего QUIK выделены символами ##.
Код
KW 08.10.25 16:50:36:220 ## Остановлен QUIK (16:50:36:220)
--- @ Остановлен по стоп бот D:\0 D\0 TGB\B_Q\Bots\B1 @ ---
## Через ~ 1 сек. QUIK запущен
==================================
KW 08.10.25 16:51:02:693 ## Начало работы скрипта (длительность от начала запуска 26 сек.)
Begin_Require: 08.10.25 16:51:02:693. End_Require: 08.10.25 16:51:02:696 ## Загружены все пакеты скрипта (длительность загрузки 3 млс.)
KW 08.10.25 16:51:02:717 ACCOUNT, CLASS_CODES, CLIENT_CODES, FIRM_ID: ## Получены реквизиты и скрипт готов продолжать работать
KW 08.10.25 16:51:02:717 #572:TRADER_lib Нет подключения к серверу
KW 08.10.25 16:51:02:717 #575:TRADER_lib С сервера не поступают необходимые данные
KW 08.10.25 16:51:02:717 #578:TRADER_lib Ждет возобновления связи
KW 08.10.25 16:51:25:197 #590:TRADER_lib Соединение с сервером восстановлено, ждет 10 сек. подгрузки данных
KW 08.10.25 16:51:35:201 #608:TRADER_lib Возобновил работу ## (16:51:35:201) Итого: длительность перезапуска ~ 1 мин.
Переформулирую два заданные ранее вопросы в один: Для взаимодействия сервера с терминалом QUIK существует протокол его быстрого восстановления при разрывах его по любой причине? Я не представляю, где проблемы с быстрым перезапуском QUIK с учетом того, что: 1) в ОС (Windows) многое кэшируется (на моем ноуте 32 Гб. и перезапуск выполняется сразу после падения QUIK); 2) реализован более менее приличный протокол быстрого восстановления взаимодействия.
VPM написал: Сопрограммы в Lua — это мост между двумя мирами: 1. Мир для программиста: Удобный, последовательный, понятный код. 2. Мир для машины: Гибкое, асинхронное, неблокирующее выполнение.
Написано так красиво , что я не удержался, чтобы что-нибудь не добавить: 3. Есть задачи, которые могут быть реализованы с использованием сопрограмм проще, чем с использованием только функций. Например, подключение IUP в выложенном вами скрипте. Однако, следует учитывать, что сопрограммы (нити - псевдопотоки) отличаются от потоков, в основном, только следующим: 1) они не могут выполняться параллельно; 2) переключение между ними программное (yield), а не внешнее как в потоках (по прерываниям в любом их месте); 3) для их синхронизации не требуются тяжеловесные примитивы (критические секции и т.д.), достаточно использования переменных (так как нет параллелизма). При использовании сопрограмм всегда надо помнить: если в нескольких сопрограммах изменяются общие данные, то нужно заниматься их синхронизацией. Таким образом, используя сопрограммы вы попадаете в пусть специфическую, но многопоточную среду, почти со всеми проблемами программирования потоков. Использование сопрограмм это моделирование многопоточного поведения скрипта в однопоточной среде исполнения. Программировать сопрограммы сложнее, чем функции. Чем сложнее программа, тем больше в ней ошибок. То что можно сделать просто в виде функций, вряд ли стоит делать на сопрограммах, если это не для развлечения.
Ростислав Дм. Кудряшов написал: Не поленись, а запусти на своём ПК пример переключения спорограмм-корутин между IUP и main(). И убедишься, что невытесняющая (кооперативная) многозадачность порой лучше вытесняющей.
С этого места, пожалуйста, поподробнее. Как вам удалось сумев запустить единственный готовый пример, определить что он порой лучше вытесняющей многозадачности? С чем вы его сравнивали? Или вы можете определять лучшее без сравнения? Откуда к вам приходят такие озарения ? Поделитесь.
Ростислав Дм. Кудряшов написал: параллельное исполнение скрипта main() и ручных манипуляций в IUP с непрерывной связью между этими нитями (threads) в обоих направлениях.
Где вы прочитали, что корутины в Lua выполняются параллельно? Интересно, как в одном потоке можно с помощью корутин реализовать параллелизм (одновременность) их выполнения? Но на всякий случай цитата Р. Иерузалимски: "в любой момент времени программа с сопрограммами выполняет только одну из своих сопрограмм".
Согласен, в работающем роботе достаточно возможностей пользовательских таблиц QUIK. Идеальный, зарабатывающий робот должен это делать без вводных и без лишнего вывода, только отчеты о прибыли в журнале с аналитикой, полезной пользователю. Реально, желателен вывод роботом сообщений о наступлении событий, требующих вмешательство пользователя. Все это без проблем реализуемо на таблицах QUIK. Графический пакет я использовал для реализации средств создания скриптов, что сделать на таблицах QUIK сложновато.
Nikolay написал: Просто к слову, т.к. используется неизвестная библиотека QluaUser.dll то запускать это не видя исходники будут сложно. По крайней мере мне, не знаю как другим.
Цитата
TGB написал: -- Вариант 1: запуск IUP в потоке main (надо подключать только IUP, но цикл обработки скрипта программировать -- в таймере IUP: помечено #### ). -- Вариант 2: запуск IUP в отдельном потоке отличном от main(кроме IUP, обязательно подключать пакет QluaUser). -- Вариант 1 более безопасный, чем 2. Так как однопоточный, и не требующий синхронизации диалога с -- основной работой скрипта.
--- Использование графического пакета IUP (Lua 5.4...) в QUIKе (! ограничение IUP:
-- только в одном из запущенном скрипте экземпляра QUIK).
-- Доступны все возможности пакета IUP (подключаемой версии).
-- Вариант 1: запуск IUP в потоке main (надо подключать только IUP, но цикл обработки скрипта программировать
-- в таймере IUP: помечено #### ).
-- Вариант 2: запуск IUP в отдельном потоке отличном от main(кроме IUP, обязательно подключать пакет QluaUser).
-- Вариант 1 более безопасный, чем 2. Так как однопоточный, и не требующий синхронизации диалога с
-- основной работой скрипта.
-- Ссылка для скачивания пакетов iup.dll, iuplua54.dll, QluaUser.dll (мой):
-- https://cloud.mail.ru/public/7xAm/jaCgULqGo Перед использованием пакетов, надо их разблокировать.
-- Пакеты переслать в папку экземпляра QUIK, хранящую файл info.exe.
_RUN_ = true
function main()
-- Пример формы IUP со двумя вкладками ---
local Pause = 1000
local thread = false -- false - выполнение диалога в потоке main; true - запуск IUP в отдельном потоке --
local cpath = (getWorkingFolder() .. '\\?54.dll;' .. getWorkingFolder() .. '\\?_5_4.dll;'.. getWorkingFolder() .. '\\?.dll;' .. package.cpath)
---
package.cpath = cpath
require("iuplua")
local QluaUser = require('QluaUser') -- Это для запуска диалога IUP в отдельном потоке ---
if thread then
QluaUser = require('QluaUser')
end
local function iup_form()
local timer = iup.timer { time = Pause } ---- Вместо sleep таймер формы ( млсек.) ---
timer.run = "NO"
function timer:action_cb()
if not _RUN_ then
timer.run = "NO"
iup:ExitLoop() -- Завершает текущий диалог
end
if not thread then -- Основной цикл обработки скрипта в таймере IUP ---
-- Тело основного цикла скрипта -- ####
message('iup выполняется в потоке main. Тело основного цикла скрипта должно выполняеться здесь (в таймере iup)')
-------------------
end
end
-- Creates boxes
local vboxA = iup.vbox{iup.fill{}, iup.label{title="TABS AAA", expand="HORIZONTAL"}, iup.button{title="AAA"}}
local vboxB = iup.vbox{iup.label{title="TABS BBB"}, iup.button{title="BBB"}}
-- Sets titles of the vboxes
vboxA.tabtitle = "AAAAAA"
vboxB.tabtitle = "BBBBBB"
-- Creates tabs
local tabs = iup.tabs{vboxA, vboxB}
-- Creates dialog
local dlg = iup.dialog{iup.vbox{tabs; margin="10x10"}; title="Test IupTabs", size="150x80"}
-- Shows dialog in the center of the screen
dlg:showxy(iup.CENTER, iup.CENTER)
timer.run = "YES"
if (iup.MainLoopLevel()==0) then
iup.MainLoop()
end
timer.run = "NO"
end
if thread then
QluaUser.ThreadNew(iup_form)
else
iup_form()
iup.Close()
iup = nil
end
while _RUN_ do -- Основной цикл обработки скрипта в main ---
-----------------------
if thread then
message('iup был запущен в отдельном потоке. Тело основного цикла скрипта выполняется в потоке main.')
else
message('После завершения потока iup, продолжается выполняется поток main.')
end
sleep(Pause)
-----------------------
end
end
---
function OnStop(flag)
_RUN_ = false
sleep(2000)
if iup then iup.Close() end
return 3000
end
Из текста фрагмента протокола видно, но, наверное, надо пояснить: мною в меню "Система > Соединения.. > Восстанавливать связь автоматически" было указано начало c 6.50.
На вопросы в общей постановке, ответа я не вижу. 1. Отвечать не отвечают, но реагируют . Протокол начала утренней сессии 29.09.25: KW 29.09.25 06:50:10:519 --- OnCleanUp (прилетел) ----------------- KW 29.09.25 06:50:32:746 #590:TRADER_lib Соединение с сервером восстановлено, ждет 10 сек. подгрузки данных KW 29.09.25 06:50:42:754 #608:TRADER_lib Возобновил работу
2. Проблема с длительностью запуска QUIK пока осталась. Для быстрого восстановления функционирования программ используются контрольные точки. Это значения переменных программ, возникшие при их выполнении и сохраненные в энергонезависимой памяти для возможности быстрого продолжения их работы, начиная с сохраненного состояния, после их перезапуска по любой причине (сбой аппаратуры, программная ошибка, и т.д.). Например: 1) Windows (программный монстр), используя контрольную точку, просыпается из режима сна за ~1сек. 2) Chrome (большая программа) c 50 вкладками открывается через ~2 сек. 3) В моих многоскриптовых роботах, со скриптами (в количестве:3-6), взаимодействующими между собой, есть режим, в котором после начала запуска/перезапуска, они готовы к продолжению прерванной работы менее чем через 15 млс. ---- На сервере есть контрольные точки терминалов (QUIKов) для быстрого продолжения работы с ними после любого его перезапуска? В QUIKе есть контрольная точка для быстрого продолжения работы с сервером после любого перезапуска QUIK?
Андрей написал: А уточните, пожалуйста, функция, которую вы упоминаете, финализирует только таблицы? открытые файлы лучше отдельно закрыть?
У меня все объекты и таблицы и файлы финализируются следующим образом (фрагмент моего кода, обеспечивающего финализацию):
Код
--- Стек объектов финализации (автоматическая финализация) ---
-- В этот стек помещать для финализируемых объектов функции их очистки (сразу при создании объектов) в формате:
-- {<Функция финализации объекта>, {<Параметры финализации объекта через запятую>}}; таблица с параметрами может отсутствовать
local FINALIZATION_META
FINALIZATION_STEK = {}
--- Реализована возможность финализации "вручную":
-- 1) mess = true - финализация без выдачи сообщения;
-- 2) no_mess = 'string' - финализация c выдачи сообщения no_mess;
function FINALIZATION_STEK_FUNFIN(stek_fin, no_mess) -- ! Функция вызывается при любом варианте завершения скрипта --
if type(stek_fin) ~= 'table' then return end
if not next(stek_fin) then return end
-------- Выдача сообщений различных вариантов финализации ---
if not no_mess or type(no_mess) == 'string' then
-- message('FINALIZATION_STEK_FUNFIN - здесь выдать сообщение ADMIN: *** Завершение с не перехваченной ситуацией')
if B_Q_DLL and OPEN_CONTROL_BOT then
local mess
if type(no_mess) == 'string' then
mess = no_mess
else
mess = '\n *** Завершение с не перехваченной ситуацией в скрипте: ' .. (SCRIPT_NAME or '??')
..'\n Дополнительные подробности смотрите в журнале скрипта и диагностике QUIK.'
end
message(mess, 3)
---
end
end
--------------------------------------------
local ob_l
for i = #stek_fin, 1, -1 do -- обработка стека очистки объектов финализации ---
ob_l = stek_fin[i] -- #### 15.12.24
stek_fin[i] = nil -- #### 15.12.24
if type(ob_l) == 'table' and type(ob_l[1]) == 'function' then
if ob_l[2] then
ob_l[1](table.unpack(ob_l[2]))
else
ob_l[1]()
end
end
end
FINALIZATION_META.__gc= 0 -- отключение финализации ---
end
----
-- При подключении финализирующей метатаблицы в ней обязательно должно быть поле __gc
-- (! значения отличные от функции не будут обрабатываться, но функцию можно присвоить позже),
-- иначе не будет пометки для финализации (список финализации формируется при подключении метатаблиц с полем __gc)
FINALIZATION_META = {__gc = FINALIZATION_STEK_FUNFIN}
setmetatable(FINALIZATION_STEK, FINALIZATION_META)
--- Запись в стек функции финализации ---
function FINALIZATION_STEK_PUSH(tbl_rez) -- tbl_rez = {<Функция финализации объекта>, {<Параметры финализации объекта>}}
FINALIZATION_STEK[#FINALIZATION_STEK + 1] = tbl_rez
end
Но, может для вас окажется достаточно добавить sleep(1000) или большую задержку.
Андрей написал: Видимо придется по одному действию возвращать в обработку остановки бота и смотреть, что приводит к его закрытию.
Мне пришлось столкнуться с похожей ситуацией когда в одном из скриптом стал использовать таблицы QUIK для визуализации. Посмотрите в эту сторону (код завершения моего скрипта, после добавления которого он стал перезапускаться вместе с QUIK):
Код
while _RUN_ do
< Тело основного цикла скрипта>
end
if TBL_QUIK then -- Если использовались таблицы
-- Финализация (не допускать одновременного выполнения в пользовательском и основном потоке QUIK) ---
FINALIZATION_STEK_FUNFIN(FINALIZATION_STEK, true)
----
-- #### Задержка sleep нужна чтобы установить признак запуска скрипта при перезапуске QUIK.
-- Иначе при перезапуске QUIK не будет перезапущен скрипт.--
-- При нормальном завершении QUIK, если есть таблицы QUIK, они удаляются (и вызываются коллбеки QTABLE_CLOSE)
-- Если при этом нет задержки, то скрипт завершается и не выставляется признак необходимости его перезапуска
-- при запуске QUIK. Признак TBL_QUIK устанавливается в основной пользовательской таблице QUIK.
-- Длительность задержки выбрана экспериментально.
sleep(sleep_TBL_QUIK_ or 1000)
end
FINALIZATION_STEK_FUNFIN(FINALIZATION_STEK, true) у меня финализирует по стековому принципу все открытые объекты, которые по-хорошему, требуют своего закрытия (в том числе созданные таблицы QUIK). Финализация выполняется при завершении скрипта по любой причине. Это делается с использованием метатаблицы финализации.
VPM написал: bit.band — чистая побитовая операция на целых, выполняется через С-библиотеку, работает на порядок быстрее, особенно если много итераций.
Вы это проверяли? bit.band это вызов функции, в теле которой выполняется битовая операция "И". Сам вызов до начала выполнения тела функции это довольно тяжелая операция, выполняющаяся дольше любой арифметической операции. В Lua 5.4 соотношение длительности выполнения приблизительно следующее: 1) % = 1 2) bit.band = 4,49 3) & (битовая операция "И" вместо bit.band , начиная c Lua 5.3) = 0,63
Можете попробовать использовать ранее выложенный мной, но слегка модифицированный код индикатора Kijun-sen, который работает в ~20 раз быстрее, чем чем то, что выложил Roman Koledin:
Код
Settings = {
Name = "*Kijun-sen",
kijun_period = 26, -- Период Kijun-sen (можно изменить)
line = {{
Name = "Kijun-sen",
Color = RGB(0, 0, 200),
Type = TYPE_LINE,
Width = 2
}}
}
local kijun_period = Settings.kijun_period
local QueueH, QueueL = {}, {}
local const_L = 999999999
function Init()
QueueH, QueueL = {}, {}
for i = 0, kijun_period - 1 do
QueueH[i] = 0; QueueL[i] = const_L
end
return 1
end
function OnChangeSettings()
kijun_period = Settings.kijun_period
QueueH, QueueL = {}, {}
for i = 0, kijun_period - 1 do
QueueH[i] = 0; QueueL[i] = const_L
end
end
---
local TT
local N_C = 500
local max_high, min_low
function OnCalculate(index)
-- -- Вычисление времени обработки свеч (500 свечкй за ~4 млс. в 5 раз быстрее моего же *Kijun-sen_opt)--
-- if index == kijun_period + 1 then
-- TT = os.clock()
-- end
-- if index == N_C + kijun_period then
-- message('Kijun-sen. Время обработки ' .. N_C .. ' свечей (млс.) = ' .. (os.clock() - TT) * 1000)
-- end
-- -----------------------------------
if index == 1 then
max_high = H(1) or 0
min_low = L(1) or const_L
QueueH[1], QueueL[1] = max_high, min_low
else
current_high = H(index) or 0
current_low = L(index) or const_L
local ind = index % kijun_period -- место в векторе --
local QueueH_end
local QueueL_end
if index > kijun_period then -- сохранение уходящих значений --
QueueH_end = QueueH[ind]
QueueL_end = QueueL[ind]
end
QueueH[ind], QueueL[ind] = current_high, current_low -- сохранение текущих в векторе --
-- --------
if current_high >= max_high then -- Пришел максимальный --
max_high = current_high
else
if index > kijun_period then -- начальный период завершен --
-- Максимум "ушел" из скользящего периода --
if QueueH_end >= max_high then -- поиск максимального в векторе
max_high = current_high
for j = 0, kijun_period - 1 do
current_high = QueueH[j]
if current_high > max_high then max_high = current_high end
end
end
end
end
---
if current_low <= min_low then -- Пришел минимальный
min_low = current_low
else
if index > kijun_period then
-- Ушел из скользящего периода минимальный --
if QueueL_end <= min_low then -- поиск минимального
min_low = current_low
for j = 0, kijun_period - 1 do
current_low = QueueL[j]
if current_low < min_low then min_low = current_low end
end
end
end
end
end
return (max_high + min_low) / 2
end
Протокол начала утренней сессии (типичный случай): KW 25.09.25 03:15:00:724 #578:TRADER Ждет возобновления связи KW 25.09.25 07:00:14:716 --- OnCleanUp (прилетел) ----------------- KW 25.09.25 07:00:38:873 #590:TRADER Соединение с сервером восстановлено, ждет 10 сек. подгрузки данных KW 25.09.25 07:00:48:873 #608:TRADER Возобновил работу --- Задержка возможности что-то делать 48 сек. ---- В этой ветке я написал о том, что существующая архитектуре QUIK порождает проблемы. И вот практическое подтверждение этому. Мне потребовалось что-то сделать в начале утренней сессии. Ноут 8 ядер 4 Ггц, SSD-скорость последовательного чтения 3 Гб. В пересчет на флагман советской индустрии БЭСМ-6 (~1000000 операций в сек.) задержка (48 * 1000 сек.) более 13 часов с начала утренней сессии . 1. Что делает QUIK 13 часов? ----- 2. Код в любом месте любого скрипта: for i = 1, 4000000000 do end "обездвиживает" QUIK на 10 секунд (длительность зависит от производительности ПК), он не отвечает. Я понимаю, что такой код писать нехорошо, но как написан QUIK, что зацикливание в пользовательском скрипте полностью его "обездвиживает"? 3. При переключении индикаторов, QUIK на какое-то время впадает в "ступпор". Можно ли сделать так, чтобы этого не было? Или это нерешаемая задача? 4. Перезапуск QUIKа выполняется долго (в пересчете на БЭСМ-6 ~13 часов :)). Как удалось этого добиться ?
nikolz написал: if i%Settings.kijun_period==0 then max_high = H(i); min_low = L(i) end
ошибка, так как Settings.kijun_period = nil. 2. Вместо скользящей вами предлагается "прыгающая" . Начальные значения каждого периода берутся в качестве экстремумов. Но может быть вы предлагаете свой прыгающий индикатор? ----------- Ниже выложен код реализации индикатора Kijun-sen приблизительно в 3,5 раза более эффективный по времени выполнения, чем то, что выложил Roman Koledin:
Код
Settings = {
Name = "*Kijun-sen_opt",
kijun_period = 26, -- Период Kijun-sen (можно изменить)
line = {{
Name = "Kijun-sen_opt",
Color = RGB(0, 0, 200),
Type = TYPE_LINE,
Width = 2
}}
}
function Init() return 1 end
local kijun_period = Settings.kijun_period
function OnChangeSettings()
kijun_period = Settings.kijun_period
end
---
local TT
local N_C = 500
function OnCalculate(index)
-- -- Вычисление времени обработки свеч --
-- if index == kijun_period + 1 then
-- TT = os.clock()
-- end
-- if index == N_C + kijun_period then
-- message('Kijun-sen. Время обработки ' .. N_C .. ' свечей (млс.) = ' .. (os.clock() - TT) * 1000)
-- end
if index==1 then
max_high = H(1)
min_low = L(1)
else
current_high = H(index)
current_low = L(index)
--------
if current_high > max_high then -- Пришел максимальный
max_high = current_high
else
if index > kijun_period then
-- Ушел из скользящего периода максимальный --
if H(index - kijun_period) >= max_high then -- поиск максимального
max_high = current_high
for j = index - kijun_period + 1, index - 1 do
current_high = H(j)
if current_high > max_high then max_high = current_high end
end
end
end
end
---
if current_low < min_low then -- Пришел минимальный
min_low = current_low
else
if index > kijun_period then
-- Ушел из скользящего периода минимальный --
if L(index - kijun_period) <= min_low then -- поиск минимального
min_low = current_low
for j = index - kijun_period + 1, index - 1 do
current_low = L(j)
if current_low < min_low then min_low = current_low end
end
end
end
end
end
return (max_high + min_low) / 2
end
Nikolay написал:. стоит принять за истину, что теперь терминал не гарантирует в таблицах сохранность порядка записей.
Они "дышат" . Это значит, что функция SearchItems работает некорректно. Она выдает таблицу индексов записей. В какой то момент (например через 3 секунды) эти индексы используются для обращения к записям, а записи могут "разбежаться" . ---- Предложение разработчику QUIK: 1) В таблицы QUIK добавить служебное поле номер записи в таблице и использовать его значение для обеспечения сохранности порядка в таблицах. 2)
Цитата
TGB написал: надо чтобы заявки в таблице появлялись с формированным номером заявки и trans_id;
Поддержка, пожалуйста, "донесите" предложение разработчику.
В скрипте можно создать любые индикаторы (с полным контролем над ними) с использованием источников (DS), полученных функцией CreateDataSource. Кроме того в ветке https://forum.quik.ru/messages/forum10/message76052/topic5466/#message76052 представлен модуль подключения в скрипте к индикаторам папки LuaIndicators с готовыми индикаторами с сайта разработчика QUIK.
Пользователям от таблицы заявок (стоп-заявок) надо чтобы: 1) заявки в таблице появлялись с формированным номером заявки и trans_id; 2) индекс заявки, пока она существует обеспечивает прямой доступ к ней и не меняется. Как это будет реализовано дело разработчика QUIK и, вообще, нас не интересует. Сколь угодно быстро, но неправильно работающие программы никому не интересны.
paluke написал: К примеру, если предполагается, что есть единственный скрипт, работающий с инструментом - все заявки его, trans_id вообще может быть неинтересен.
С этим частным случаем можно согласиться, но все таки QUIK не "живопырка", используемая только в этом частном случае.
Anton Belonogov написал: Описанные изменения негативным образом скажутся на скорости получения информации терминалом.
1. Зачем пользователю быстро видеть "сырые" данные, которые нельзя использовать непосредственно в своем скрипте? 2. Сам факт многократного обсуждения данной темы пользователями показывает, что в обработке заявок есть проблема. Пользователю все равно приходится выполнять ту работу, которая не реализована разработчиком QUIK, а именно, писать фрагмент учитывающий и обрабатывающий записи заявок без trans_id. Где тут скорость?
Anton Belonogov написал: Для ускорения передачи информации сервер изначально может отправлять заявки без заполненного trans_id, это не является ошибкой.
Что может делать в скрипте пользователь с заявкой без заполненного trans_id? ----- Есть по крайней мере два варианта реализации того, чтобы пользователь получал заявку всегда с trans_id. 1. Счетчик выдаваемый функцией getNumberOf увеличивается только после заполнения trans_id. 2. Для формирования записей заявок создается буфер, в котором все делается как сейчас в таблице, но записи из буфера сохраняются в таблице заявок только после заполнения trans_id.
Anton Belonogov написал: По предоставленному описанию не смогли воспроизвести подобную проблему при вызове функции getItem.
Эта ситуация "плавающая" (я ее увидел один раз, но с распечатанными параметрами доступа к таблице), связанная, возможно, с синхронизацией доступа к записям таблицы. У меня нет кода стабильно воспроизводящего эту ситуацию.
Цитата
Anton Belonogov написал: 1. Отсутствие trans_id на исходной записи заявки - корректное поведение. Причины обсуждались на форуме ранее, рекомендуем ознакомиться, например, с этой темой .
Я знаком с этим обсуждением. Там объясняется как сделано сейчас. Но зачем и на основании чего выполняется запись в таблицу заявок без trans_id? Если на основании созданной пользователем транзакции, то в ней trans_id есть.
Anton Belonogov написал: Это взаимоисключающие параметры, сервер транслирует на терминалы только один из них, поэтому смысла в дополнительном параметре для QLua нет.
Не согласен. Разве "Дата предстоящих расчетов" рассчитывается без учета "Режима торгов"? Пожалуйста, объясните какие проблемы возникают у брокера и пользователя, если дополнительно к тому как было (limit_kind - "Режим торгов") транслировалось бы поле (возможно пустое) "Дата предстоящих расчетов"? Может быть разработчик не стал создавать новое поле с целью большой экономии памяти ?
Anton Belonogov написал: limit_kind - это параметр позиции на сервере QUIK, а не параметр инструмента в Торговой системе.Торговые режимы остаются прежними, изменяется только ведение позиций на сервере, что позволяет для каждой позиции видеть даты предстоящих расчетов.
Почему бы не оставить в покое limit_kind (как было)? В чем была проблема завести новое поле "Дату предстоящих торгов"?
1. trans_id известен в момент подачи заявки, но в таблице orders поле trans_id в записи заявки формируется с задержкой. Это приводит к тому, что при обращении к существующей записи таблицы orders возможны случаи отсутствие значения trans_id. Не видно никаких проблем в том чтобы поле trans_id формировалось в момент создания записи в таблице orders. Почему так не сделано?
2. Бывали случаи (сразу после снятия заявки), когда при чтении существующей записи: order = getItem_wait('orders', Индекс_существующей_записи) значение order равно nil. "Костыль" на такие ситуации следующий:
Код
local function getItem_wait(tbl_quik, ind)
local ob = getItem(tbl_quik, ind)
local N = 500
while not ob do
sleep(10)
ob = getItem(tbl_quik, ind)
N = N - 1
if N <= 0 then error ('Не дождались результата чтения таблицы ' .. tbl_quik .. ' по индексу ' .. tostring(ind)) end
end
return ob
end
Предлагается тело функции getItem_wait в каком то виде реализовать в штатной функции getItem.
Anton Belonogov написал: В ПО терминала QUIK действительно есть ошибка в работе функций обратного вызова Lua скрипта, приводящая к рестарту сборщика мусора без учёта его предыдущего состояния. Ошибка будет исправлена в очередных обновлениях ПО.
Вышло очередное обновление версия 12.3.0. Ошибка не устранена.
paluke написал: Поддержка вместо 1. #### и 2.#### предлагала SetEmptyCallback() перед Close().
Да. Это работает. Но в документе: функция SetEmptyCallback позволяет получать данные с сервера. И нет описания, что Close не закрывает источник без предварительного вызова SetEmptyCallback.
VPM написал: На самом деле проблема стара как этот мир (в нашем случае квик)
Скрипты в QUIKе это программы реального времени, в которых существенен фактор времени. В коде, выложенном ниже, определена функция CreateDS, которая дожидается появления данных DS.
Код
stopped = false
function main()
-------------------------------------------------------
-- Создание параметризированной функции-замыкания коллбека (с дополнительными (кроме index)
-- параметрами ds и par, задаваемыми в момент создания).
-- Использование ds:SetUpdateCallback(CreateUpdateCallbackDS(func, ds, par))
-- параметр par необязателен, но если он fаlse, то тело функции не выполняется.
-- ! Исключения в функции коллбека перехватываются. Коллбек при этом отключается с выдачей сообщения об исключении.
-- Параметры:
-- func(index, ds, par) - функция обработки коллбеков (вызываемых по изменениям свечи) --
-- ds - открытый источник;
-- par - параметр (может отсутствовать) --
-- index - индекс свечи при выполнении коллбека;
-- Результат: параметризированная функция-замыкание - параметр для функции ds:SetUpdateCallback ---
local CreateUpdateCallbackDS = function(func, ds, par)
return
function(index) -- Обертка для func --
if par ~= false then
local kod, mes = pcall(func, index, ds, par) -- в защищенном режиме --
if mes then
message('Исключение в коллбеке (он отключен, его 3-й параметр : '.. tostring(par) .. '). Текст исключения: ' .. mes, 3)
par = false
end
end
end
end
--------------------------------------------------
--- Cl - класс, Sec - код бумаги, Int - таймфрейм.
--- T - ожидание поступления данных источника в сек. (по умолчанию 15 сек.)
local CreateDS = function(Cl, Sec, Int, T)
T = (T or 15) * 10
local ds, Error = CreateDataSource(Cl, Sec, Int)
for i = 1, T do
if not ((Error == "" or Error == nil) and ds:Size() == 0) then break end
if i == T then error('Не дождались DS: ' .. Sec .. '. Ошибка: ' .. tostring(Error)) end
sleep(100)
end
return ds
end
---------------------------------------------------
-- Вариант многократно используемой параметризированной заготовки для создания коллбеков в CreateUpdateCallbackDS --
local function cb_p(index, ds, sec_code)
local t = ds:T(index)
local _str = string.format("#%d of %d\t%.4f\t%.4f\t%.4f\t%.4f\t%.4f %02d.%02d.%04d %02d:%02d:%02d.%04d\n",
index, ds:Size(),ds:O(index), ds:H(index), ds:L(index),
ds:C(index), ds:V(index),
t.day, t.month, t.year, t.hour, t.min, t.sec, t.ms)
message(sec_code .. ': ' .. _str)
end
---------
local ds
local function cb(index) -- одноразовая функция коллбека -
local t = ds:T(index)
local _str = string.format("#%d of %d\t%.4f\t%.4f\t%.4f\t%.4f\t%.4f %02d.%02d.%04d %02d:%02d:%02d.%04d\n",
index, ds:Size(),ds:O(index), ds:H(index), ds:L(index),
ds:C(index), ds:V(index),
t.day, t.month, t.year, t.hour, t.min, t.sec, t.ms)
message(_str)
end
ds = CreateDS("QJSIM", "GAZP", INTERVAL_M1)
message('Size = ' .. ds:Size())
-- Вариант с созданием функции-замыкания, выдающей сообщение об исключениях во время ее выполнения ---
ds:SetUpdateCallback(CreateUpdateCallbackDS(cb))
-- ds:SetUpdateCallback() -- 1. #### это не помогает
-- ds:SetUpdateCallback(function() end) -- 2. #### и это не помогает
ds:Close() -- ?? После Close повторно установленные один раз коллбеки не срабатывают (
-- хотя источник открывается)
ds = CreateDS("QJSIM", "GAZP", INTERVAL_M1)
-- ds:SetUpdateCallback(function() end) -- 3. #### это тоже помогает вместо последующего ds:SetUpdateCallback(cb)
ds:SetUpdateCallback(cb) -- Обычный вариант (если в функции коллбека cb возникает исключение,
-- то коллбек перестает выполняться, но сообщения об этом нет)
ds:SetUpdateCallback(
CreateUpdateCallbackDS(cb_p, ds, "GAZP")) -- 4. #### Повторная установка параметризированного
-- коллбека. Если это закомментировать, то коллбек не запускается
-- ds:SetUpdateCallback(cb) -- 5. #### Это , вместо пункта 4, тоже проходит
while not stopped do
sleep(3000)
-- message('while Size = ' .. ds:Size())
end
end
function OnStop(row)
stopped = true
end
---
-- Резюме (смотрите комментарии, выделенные строкой ####).
--- После ds:Close(), ds = CreateDataSource(...) повторно установленный коллбек cb не срабатывает
-- (хотя источник открывается и свечи приходят).
-- Но если устанавливать два раза: ds:SetUpdateCallback(cb) ds:SetUpdateCallback(cb),то cb срабатывает --
2. В том же коде определена функция CreateUpdateCallbackDS - создание параметризированных замыканий-коллбеков на основе функций заготовок (в коде примера: функция cb_p). Функцию-заготовку можно использовать, создавая коллбеки для многих разных источников DS. При этом обеспечивается выдача сообщений об исключениях времени выполнения колбека (что не делается штатными средствами).
3. В коде комментариями, помеченными строкой ####, описываются "танцы с бубном" с тем, чтобы повторно подключить коллбек к повторно открытому DS. Вопрос к поддержке: что еще надо показать, чтобы стало ясно, что в ds:Close() есть ошибка?
Да, не потокобезопасна в ванильном режиме. Но разработчик Lua предусмотрел (и, возможно, напрасно) вариант использования инстанции Lua в нескольких потоках в режиме разделения, когда коды Lua могут быть использованы только одним потоком. Для этого варианта надо вносить изменения в исходники Lua, обеспечивающие синхронизацию разделения. С чем разработчик QUIK немало "покувыркался". Если исходить из того, что синхронизация сейчас реализована корректно, то перед выполнением коллбеков мусорщик можно было бы не останавливать. Отсутствие сборки мусора при выполнении коллбека обеспечивает: 1) сокращение времени его выполнения; 2) и что не менее важно, дополнительную страховку от возможных ошибок синхронизации, запрещая работу коллектора, который обрабатывает все стеки инстанции Lua. Но после выполнения коллбека должно быть восстановлено то состояние коллектора, которое было перед выполнением коллбека. Вообще то, я уже пару раз описывал, как можно изменить схему взаимодействия скриптов Lua с QUIK, чтобы уйти от "интересных" проблем синхронизации потоков. Более того это несколько бы ускорило выполнение скриптов (освобожденных от выполнения кода синхронизации).
nikolz написал: Т е в каком стеке Вы вызовите сборщик, тот стек он и будет чистить.
Цитата
TGB написал: В конце концов, можно почитать книгу Р. Иерузалимски
Вы умеете читать? Или только пишите ? Цитата из книги 32.2 Сборщик мусора: "Фаза очистки обходит все объекты Lua." Вы понимаете, что в Lua объектами являются и его стеки? В конце концов, детали сборки мусора вы можете посмотреть в исходниках Lua.
TGB написал: Надо: 1) сохранять состояние сборки мусора скрипта перед выполнением коллбека; 2) отключать сборку сборку мусора; 3) выполнять коллбек; 4) восстанавливать состояние сборки мусора.
Если это непонятно, то в переводе на коды это, примерно, следующее (три строки не считая комментариев и пробелов):
Код
--- Перед выполнением коллбека ---
local isrunning = collectgarbage('isrunning')
if isrunning then collectgarbage('stop') end
--- Выполнить коллбек ---
--- После выполнения коллбека ---
if isrunning then collectgarbage('restart') end
nikolz написал: сборщик мусора собирает мусор в в стеке основном VM.но при этом он не видит стек коррутины
Кто вам это рассказал или где вы это прочитали ?
Цитата
nikolz написал: Я полагаю что делается запуск сборщика для области стека корутины main.
Зачем что то предполагать, когда можно узнать точно как это устроено? И для этого на сайте разработчика существуют свободно доступные исходники Lua. Из них вы бы узнали, что управление памятью в Lua общее для всех его стеков и нет специального запуска сборщика для области стека корутины main. В конце концов, можно почитать книгу Р. Иерузалимски.