Юрий написал: периодически вылетает во время торговли без видимых ошибок и причин. То есть вот он работает и в следующий момент он уже не запущен вообще в системе.
QUIK 8.8.1.5 Вылеты продолжаются
Надо делать так, как надо. А как не надо - делать не надо.
Есть скрипт, который стартует автоматически при запуске QUIK. В самом начале идёт поиск заданного торгового счета:
Код
function main()
local Index = SearchItems("trade_accounts", 0, getNumberOf("trade_accounts")-1, function(trdaccid) return trdaccid == Account end, "trdaccid")
...
end
В настройках стоит "Очищать данные после смены даты: На сервере (при установлении связи)". Т.е., при первичном старте скрипт берёт данные из кэша. Запуск QUIK происходит долго, открыто несколько тиковых графиков и индикаторов. Но обычно скрипт работает нормально. Но сегодня SearchItems не нашла торговый счёт и вернула nil. Повторный запуск скрипта вручную (до подключения к серверу) отработал корректно.
По какой причине данный код мог дать сбой?
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Действительно в ПО QLUA есть ошибка так же иногда приводящая к завистанию терминала при вызове Lua функции DestroyTable. Мы исправим её в очередном обновлении ПО.
Как скоро? Временное решение есть?
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: При вызове InsertRow / DeleteRow в какой момент происходит смещение индексов?
В смысле?
Что есть DeleteRow (InsertRow)? Это не только удаление (добавление) физической строки, но и смещение всех индексов строк во внутреннем представлении. Ожидается, что операция смещения строк - цельная. Т.е., если выполнить код в таком порядке:
Хотя есть от main и польза: перенос сложных расчётов в main при одновременной работе двух и более таких скриптов (при достаточном количестве процессоров и условии, что колбеки не будут мешать расчётам).
Надо делать так, как надо. А как не надо - делать не надо.
Добавим блокировку для "монопольного" вычисления в колбеке:
Код
local abs = math.abs
local function f()
local t = os.clock()
for i = 1, 100000000 do abs(-1234.56789) end
return os.clock() - t
end
function main()
message(string.format('Расчёт в одном потоке: %0.1f', f()))
run = true
while run do
if param then
message(string.format('Расчёт в двух потоках [main]: %0.1f', f()))
param = nil
else sleep(1) end
end
end
function OnParam(class_code, sec_code)
if not run then return end
param = true
local t
table.ssort({0, 0}, function()
t = f()
return true
end)
message(string.format('Расчёт в двух потоках [OnParam]: %0.1f', t))
run = nil
end
Результат:
Цитата
Расчёт в одном потоке: 7.4 Расчёт в двух потоках [OnParam]: 6.7 Расчёт в двух потоках [main]: 7.3
Т.ч. вреда от main больше, чем пользы. И, если уж угораздило переносить расчёты в main, то то, что осталось в колбеках, лучше делать под блокировкой, чтобы main ни в коем случае не делал вычислений одновременно с колбеками. Последовательная работа потоков положительно скажется на общей производительности.
Надо делать так, как надо. А как не надо - делать не надо.
local abs = math.abs
local function f()
local t = os.clock()
for i = 1, 100000000 do abs(-1234.56789) end
return os.clock() - t
end
function main()
message(string.format('Расчёт в одном потоке: %0.1f', f()))
run = true
while run do
if param then
message(string.format('Расчёт в двух потоках [main]: %0.1f', f()))
param = nil
else sleep(1) end
end
end
function OnParam(class_code, sec_code)
if not run then return end
param = true
message(string.format('Расчёт в двух потоках [OnParam]: %0.1f', f()))
run = nil
end
Результат:
Цитата
Расчёт в одном потоке: 7.5 Расчёт в двух потоках [OnParam]: 32.2 Расчёт в двух потоках [main]: 31.9
Выводы делайте сами.
Надо делать так, как надо. А как не надо - делать не надо.
Игорь М написал: Если искать по идентификатору сделки порядковый номер в таблице
Сравнение в скорости линейного поиска с SearchItems:
Скрытый текст
Код
local function linSearch (t, id, num_1, num_2) -- функция линейного поиска
while num_1 <= num_2 do
local t_trade_num = getItem(t, num_1).trade_num -- номер сделки в таблице
if id == t_trade_num then -- сравнение идентификационных номеров
return num_1
else
num_1 = num_1 + 1
end
end
return nil
end
local os_clock = os.clock
local function test1(n, TradeNum)
local t = os_clock()
for i = 1, n do
NumTrade = linSearch("all_trades", TradeNum, 0, getNumberOf ("all_trades")-1)
end
t = os_clock() - t
message(tostring(NumTrade))
return t
end
local function test2(n, TradeNum)
local t = os_clock()
for i = 1, n do
local Index = SearchItems("all_trades", 0, getNumberOf("all_trades")-1, function(trade_num)
if trade_num == TradeNum then return nil else return false end
end, "trade_num")
NumTrade = Index and Index[1] or nil
end
t = os_clock() - t
message(tostring(NumTrade))
return t
end
function main()
local n = 100000
local TradeNum = getItem("all_trades", 0).trade_num -- Номер первой сделки
message(string.format("Искомый элемент в начале таблицы:\nlinSearch: %0.3f\nSearchItems: %0.3f", test1(n, TradeNum), test2(n, TradeNum)))
local numbers = getNumberOf("all_trades")
message(string.format("Numbers: %u", numbers))
TradeNum = getItem("all_trades", numbers-1).trade_num -- Номер последней сделки
message(string.format("Искомый элемент в конце таблицы:\nlinSearch: %0.3f\nSearchItems: %0.3f", test1(1, TradeNum), test2(1, TradeNum)))
end
Цитата
Искомый элемент в начале таблицы: linSearch: 0.660 SearchItems: 0.495
Цитата
Искомый элемент в конце таблицы: linSearch: 18.437 SearchItems: 4.919
Бинарный поиск можно использовать только по монотонно возрастающему (или убывающему) параметру.
Надо делать так, как надо. А как не надо - делать не надо.
Игорь М, добрый день. Спасибо за информацию. Но есть сомнения относительно верности результатов. По моим исследованиям SearchItems быстрее линейного поиска даже если искомый элемент находится первым в таблице. Если же искомый элемент глубже, то SearchItems может быть быстрее в несколько раз. Линейный поиск имеет преимущество только при поиске в обратном направлении, если искомый элемент находится ближе к концу таблицы. Что касается бинарного поиска, то, как верно заметил Anton, в общем случае его использовать не получится.
Применительно к тиковым индикаторам тут вот какая проблема. Ниже индикатор, показывающий время построения индикатора.
Скрытый текст
Код
Settings = {
Name = "Time",
search = 0,
line = {
{ Name = 'Time' }
}
}
function Init()
return 1
end
local search
local Clock
function OnCalculate(index)
if index == 1 then
if Settings.search == 1 then
function search()
getItem("all_trades", getNumberOf("all_trades")-1)
end
elseif Settings.search == 2 then
function search()
local function fn(qty,flags)
return nil
end
SearchItems("all_trades", 0, getNumberOf("all_trades")-1, fn, "qty,flags")
end
else
function search() end
end
Clock = os.clock()
end
local Volume = V(index)
if CandleExist(index) then
search()
end
return (os.clock() - Clock) * 1000
end
С вызовом getItem или SearchItems на каждый тик время увеличивается в 4-5 раз. И это только вызов функций, без поиска сделки.
Надо делать так, как надо. А как не надо - делать не надо.
Проблема явно не цикле repeat, а в том, что когда цикл заканчивается, вы возвращаетесь к следующей итерации цикла while, где проверяете позицию
Код
local asset = getFuturesHolding("SPBFUT", "SPBFUT****","SRU0",0).totalnet
которая отличается от нужной вам, т.к., вы только отправили транзакции, надо ещё дождаться, когда сделки исполнятся, сервер соизволит обновить позицию и отправит её клиенту.
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Ваше пожелание зарегистрировано. Мы постараемся рассмотреть его и сообщить Вам результаты анализа.
Удалось рассмотреть и проанализировать?
Или добавьте параметр flags к тиковым графикам, чтобы при построении пользовательских индикаторов не лазить в таблицу "all_trades" в поисках нужной сделки - очень сильно затормаживает расчёт: сам только вызов SearchItems увеличивает время расчёта в 10 и более раз.
Надо делать так, как надо. А как не надо - делать не надо.
Но чтобы она также давала информацию получен ли параметр с момента последней подписки: nil - подписка не включена false - подписка включена, но параметр ещё не получен true - подписка включена, параметр получен Или: 0, 1, 2
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Вам по сути нужен способ узнать включена ли подписка.
Пример с getQuoteLevel2 показателен. Если вы про аналог функции IsSubscribed_Level_II_Quotes, то она бесполезна, т.к. показывает, что подписка включена в то время когда, данные ещё не получены. Но дуэт Subscribe_Level_II_Quotes и getQuoteLevel2 самодостаточен и позволяет получать только актуальные данные. PS: Обратил внимание, что после остановки скрипта, подписка на стакан не закрывается автоматически. Надо бы исправить.
Для ParamRequest и getParamEx2 хотелось бы такой же работы.
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov, сможете привести однозначный алгоритм получения параметра, который работает всегда и надёжно, и который даёт гарантированно актуальное значение, а не старые данные из кеша? И чтобы не нужно было ждать полдня, пока ожидаемый параметр поменяет своё значение.
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Если параметр уже заказан, до запуска скрипта, то getParamEx2 и так получит "актуальные данные" без всяких ParamRequest и OnParam ждать нет нужды.
Вы поспорить или по делу? Как в скрипте узнать, что параметр уже заказан?
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Для понимания что заказанный параметр начал ехать нужно ждать коллбэк OnParam()
С первого сообщения пытаюсь объяснить. Если параметр уже заказан (а проверить мы это никак не можем) до запуска скрипта (в ТТТ али другим скриптом), то если брать данные только в OnParam(), придётся ждать следующего обновления параметров. А для неликвидного инструмента это может быть очень не скоро. Глядишь к концу сессий чё-нить дождёмся... А если вызывать getParamEx2 до прихода OnParam(), то нет гарантии, что там не старьё.
Надо делать так, как надо. А как не надо - делать не надо.
Alexander Kopyatkevich, мне даже не удобно вас об этом спрашивать: полагаете ваш код покажет актуальную котировку? А если я два часа назад добавлял параметр "BID" в ТТТ, а потом убрал его. То какую котировку мне покажет ваш код: текущую или двухчасовой давности?
Надо делать так, как надо. А как не надо - делать не надо.
Alexander Kopyatkevich, можете написать код для получения параметров через getParamEx2? С getQuoteLevel2 всё просто:
Код
if isConnected() ~= 1 then return end
function main()
-- Заказываем получение котировок:
Subscribe_Level_II_Quotes(class, sec)
-- Получаем актуальные котировки, если они есть:
Quotes = getQuoteLevel2(class, sec)
-- Если открыт стакан или котировки были раннее заказаны другим скриптом, то сразу получим актуальные данные
-- В противном случае в Quotes будет незаполненная таблица
...
end
-- Дальнейшие изменения в стакане можно ловить в OnQuote
function OnQuote(class_code, sec_code)
if sec_code == sec and class_code == class then
Quotes = getQuoteLevel2(class, sec)
...
end
end
Я уверен, что в Quotes будут актуальные котировки или незаполненная таблица. Таблицы с неактульными котировками не будет. Причём, даже если стакан по инструменту изменяется раз в час, получу текущие котировки, не дожидаясь следущего обновления стакана.
С getParamEx2 так не получается. Можете написать для следующего кейса: Получить зачение параметра для неликвидного инструмента с учётом того, что неизвестно был ли заказан параметр до запуска скрипта.
Надо делать так, как надо. А как не надо - делать не надо.
Alexander Kopyatkevich написал: Если речь идет исключительно об актуальности данных, то рекомендуем перед тем, как вызывать getParamEx (getParamEx2), сначала вызывать ParamRequest.Таким образом, после вызова getParamEx (getParamEx2) отобразятся только актуальные данные.
Вы уверены в том, что вы пишите? Актуальные данные клиент покажет только после того, как они попадут с сервера на клиента. И когда это будет нам неведомо. А до тех пор getParamEx2 может показывать что угодно.
Цитата
Старатель написал: Если getParamEx2 вернул данные, то они актуальные или старые? Может, пять часов назад их заказывали, потом подписку отменили, а данные остались в кеше.
Надо делать так, как надо. А как не надо - делать не надо.
Начну из далека. При заказе таблицы котировок используем
Код
Subscribe_Level_II_Quotes(class, sec) -- Для заказа котировок с сервера
getQuoteLevel2(class, sec) -- Для получения котировок
Мы либо получаем актуальные котировки, если стакан открыт, либо незаполненную таблицу, если стакан закрыт или котировки ещё не подгрузились с сервера. Если закрыть стакан (отменить заказ на получение котировок с сервера), то getQuoteLevel2 вернёт незаполненную таблицу. Т.о., мы понимаем, что, если getQuoteLevel2 вернул данные, то они актуальны. Использование простое:
Код
function main()
Subscribe_Level_II_Quotes(class, sec)
-- Получаем актуальные котировки, если они есть:
Quotes = getQuoteLevel2(class, sec)
end
-- Дальнейшие изменения в стакане можно ловить в OnQuote:
function OnQuote(class, sec)
Quotes = getQuoteLevel2(class, sec)
end
С getParamEx2 намного сложнее. Если getParamEx2 вернул данные, то они актуальные или старые? Может, пять часов назад их заказывали, потом подписку отменили, а данные остались в кеше. Если же брать парамтры только в OnParam, то по неликвидному инструменту их можно ждать бесконечно долго.
Надо делать так, как надо. А как не надо - делать не надо.
Ошибка плавающая. Причину установить не удалось. Но замечено, что виснет с большей вероятностью, если одновременно с DestroyTable происходит какое-то событие. Для воспроизведения написал следующий скрипт:
Код
function main()
ID = {}
for i = 1, 20 do
ID[i] = AllocTable()
CreateWindow(ID[i])
SetTableNotificationCallback(ID[i], function(id, event)
if event == QTABLE_CLOSE then
run = false
end
end)
end
run = true
while run do sleep(1) end
for _, id in pairs(ID) do
DestroyTable(id)
end
end
Использование: Запустить скрипт без подключения к серверу. Чтобы посыпались события, установить соединение с сервером, и сразу закрыть одно из окон скрипта.
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Вторая проблема, то что в свойствах индикатора, в уровне цены, нельзя указать число с точностью превышающую точность инструмента.
Проблема касается не только уровней, но и линий и отображения последнего значения на шкале. Этот баг добавили в какой-то версии 7.x
Надо делать так, как надо. А как не надо - делать не надо.
Как вариант, где-то в цикле main используется return Иван Ру, ваш вопрос звучит примерно так: "Угадайте, что в чёрном ящике"? Вряд ли кто-то сможет помочь.
Надо делать так, как надо. А как не надо - делать не надо.
Sergey Gorokhov написал: Если на инструменте точность цены не позволяет наличие дробных значений то форма ввода цены уровней действительно не даст указать дробную цену, и собственно это неявляется багом.
Точность значений индикаторов, например, таких, как VHF, не может ограничиваться точностью цены самого инструмента.
Надо делать так, как надо. А как не надо - делать не надо.
Anton написал: Тогда остается что, а остается ничего, сам факт приезда (первого) пакета на клиент только. Даже и функции много, флажок в датасорце, "ответ сервера получен". В сухом остатке что тогда от арки требуется: 1) убедиться, что сервер всегда что-нибудь отвечает на подписку, есть ли данные или нет; 2) на клиенте по получении первого ответа поставить флажок в датасорце.
Тоже хороший вариант.
Скрытый текст
Вообще такого флага не хватает во многих таблицах терминала. К примеру, имеем робота, выставляющего стоп-заявки по исполнению на активные заявки. Если работать в колбеках OnOrder, то получается: подключаемся к серверу, первой загружается таблица заявок. Робот смотрит в таблицу стоп-заявок, проверяет нет ли уже стопа на нашу заявку, там пусто. Соответственно, выставляет стопы. После грузится таблица стоп-заявок, где мы узнаём, что стопы задублировались. Приходится либо снимать дубликаты, либо работать в main с неопределённой задержкой. Так что, как ни крути, в Квик не все задачи эффективно решаются в колбеках.
Надо делать так, как надо. А как не надо - делать не надо.