nikolz написал: Последнее время или потому, что версия КВИК 12 , но у Сбербанка вообще полный писец. Когда ему вздумается останавливает передачу данных на минуты и связь не разрывает. Теперь может принимать данные ,но не отображать в строке время сервера и принятые данные, а прыгать через несколько или даже десятки секунд. ------------------------------------- Я уже перестал эти приколы записывать в лог файлы. Смысла нет. ---------------------------------- Советую тем , кто начинает - не связывайтесь с этим брокером , чтобы потом не удивляться почему такое Г..но получается,
Удивительным образом, у меня Сбер один из лучших. ВТБ - полный кошмар. Альфа - лучше всех. Финам - такое... Очень часто рвет связь.
Но самое печальное на последних версиях - это если обрыв связи был продолжительное время, минут 10, то после восстановления будет вызван колбек OnCleanUp. По крайней мере я это вижу на двух брокерах.
До 10-ой версии я этот колбек видел может раз в год.
Периодические паузы в получении данных
Пользователь
Сообщений: Регистрация: 27.01.2017
03.11.2026 10:27:32
Да уже столько писал про это. В последних версиях явно что-то изменили в работе поток данных. В итоге они идут неспешно, при длительном отключении терминала от брокера, вызовется очистка данных терминала и вы получите все что было от начала дня снова. Также потоки данных игнорируют умный заказ данных... Но все как задумано. Правда, судя по всему, разработчики забыли или даже не пробовали 7-ую версию, которая работала стабильно столько лет. А теперь мажорные версии каждый квартал, но до сих пор древняя кодировка в текстах, нет поддержки HDPI мониторов, нет изменения размеров базовых окно терминала...
Каким запросом на Lua получить уровень риска?, Квик стал транслировать уровень риска и ГО по типам риска. Как их получить в Lua?
Пользователь
Сообщений: Регистрация: 27.01.2017
19.10.2026 14:13:11
Подниму вопрос: в каких случаях брокер не транслирует данный параметр в таблицу? ГО по разным уровням есть в ТТТ, а самого уровня нет в таблице "Ограничения по клиентским счетам"
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 27.01.2017
12.10.2026 09:16:23
Да, Вы правы в части теории о чувствительности к ставке. У меня в голове была другая мысль, и написал что 238 менее чувствительна.
Теория говорит "большей модифицированной дюрацией обычно изменяется в цене сильнее". Да. Но моя мысль была иной: в нашей реальности дальние облигации слабее реагируют на ставку и почти всегда торгуются хуже ближних. Для этого идете на сайт ЦБ и смотрите на кривую бескупонной доходности и станет видно, что для длинных сроков есть "премия за срок", что можно перевести как "премия за риск". Точнее сказать - они не обязаны реагировать на сиюминутные события, но могут, с этим не поспоришь.
У нас есть 15-и летний деньги. И теперь начнем инвестировать на срок 15 лет... Чтобы это было как в теории, надо чтобы хотя бы лет 10 было без "киндер сюрприза", не горя уже о кризисах.
Т.о. даже если ставка снизится, это не гарантирует вам рост тела. Да, 238 обладает очень большой временной стоимостью, но в этом и проблема, время - это риск.
Моя мысль была в этом. Думал об одном, написал другое.
Поэтому дальние - это хорошо, можно зафкисировать ставку, как сейчас любят говорить. Но инвестируя на 15 лет, вы должно точно понимать во что, без иллюзий.
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 27.01.2017
10.10.2026 10:41:10
С облигациям все не так.
Мы торгуем ожидания по ДКП, Ставке, климату в стране, киндер-сюрпризы. Покупая текущую доходность, вы ожидаете, что будут изменения в ту или иную сторону по ставке. Или будет на месте. В конечном итоге уже накопилось достаточно лет, чтобы понять куда и как все двигается.
Поэтому сравнение с другими бумагами - это не про облигации. Да, сравнение между разными сериями допустимо, но только в части расхождения по текущим параметрам, т.е. арбитраж.
Что касается ДДС, то никто не мешает подобрать выпуски, чтобы приходы были хоть каждый месяц. Также можно использовать "лестницу". А если хотите защиты, то есть фьючерс RGBI. Никто не мешает захеджировать позицию.
Поэтому покупая 238, Вы ожидаете снижения ставки - рост тела. Но из-за длинной дюрации, этот рост не будет столь сильным, как у более ближних выпусков. 254 же - это просто большой купон в моменте. Но рост тела будет еще меньше, чем у 238, т.к. она без дисконта.
Собственно облигации - это про экономику, а не про тех. анализ. Спекулировать облигациями можно, и нужно. Это даст доп. доход. Но опять же, покупаю облигацию, вы в первую очередь, анализируете эмитента, решаете что с ним будет, а не сравниваете с другим эмитентом. И уже точно не график, а строки баланса о задолженности, ДДС эмитента в будут важнее. Рейтинг - это перекладывать ответственность на других. Открой баланс и сам посмотри.
QUIK и жор памяти, Проблема с памятью скриптов Qlua
Пользователь
Сообщений: Регистрация: 27.01.2017
09.10.2026 16:06:42
Т.к. это в цикле, то получение данных таблицы - это каждый раз выделение памяти. local tab=getItem('orders',iord);
Выйдет из цикла и функции, делающее это, сборщик удалит и освободит память.
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 27.01.2017
05.10.2026 14:20:07
Если речь про правую клавишу в окне, то да, это начиная с 8-ой версии так, если не путаю. Просьбы убрать - только регистрация пожеланий.
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 27.01.2017
03.10.2026 10:33:04
Я акции особо не торгую, там торговать особо нечего. Да и нет никакого смысла торговать базовый актив спекулятивно. Облигации, наоборот, активно сеткой торгую, но на ИИС. Так что специфика налогов другая.
А вот фьючерсы и календарные спреды сеткой - много и часто.
Что касается налогов, то не все так печально, т.к. есть сальдирование и перенос убытков на будущее. Т.о. можно зачесть убытки, рассчитанные из-за разницы методологий учета. В целом, метод FIFO хорош для склада продуктов, т.к. есть сроки хранения. Для инвестирования - это очень спорное решение.
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 27.01.2017
03.10.2026 08:44:22
Я торгую уровни сетки. Последний открылся, первый закрылся. А что считает брокер меня не интересует, кроме его расчета налогов.
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Это зависит от алгоритма учета позиции. Я в основном торгую по LIFO, например. А брокер всегда считает по FIFO. Средняя - это для алгоритмов с единой ценой позиции, например чтобы стоп поставить от этого уровня. Но опять же она будет разная для разных режимов расчета по частичному закрытию.
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 27.01.2017
02.10.2026 10:53:31
Цитата
VPM написал: QUIK утром отдал среднюю цену как 2994 вместо 2.994
Если у инструмента изменится лотность, то такое может быть. Либо просто ошибка. Хотя, получать среднюю из терминала - это значит доверять расчетам по FIFO от брокера. Лучше рассчитывать среднюю по своим сделкам. Хранить эту информацию, чтобы всегда иметь возможность пересчитывать, рассчитывать прибыль и т.д.
А раз есть еще какие-то ошибки, то вероятно банально данные еще идет с сервера и работать скрипту в это время не стоит.
Скачивание данных свечей с Московской Биржи, Скачивание данных свечей с Московской Биржи
Пользователь
Сообщений: Регистрация: 27.01.2017
16.09.2026 11:41:32
Через API. Например
Вопросы про OnParam
Пользователь
Сообщений: Регистрация: 27.01.2017
15.09.2026 09:35:04
И почему это совсем не праздный вопрос только для писателя скриптов. Если посмотреть на дисковую активность терминала, то видно, что он постоянно пишет в файл info.log Ну пишет, в чем проблема. А проблема в том, что к концу дня этот файл становится 2+ гб. При это в терминала заказано всего сотня инструментов. И так каждый день. А диски SSD...
Т.е. можно предположить, что раз OnParam принимает всё, игнорируя фильтры, то и терминал сам по себе тащит все, даже что не просили. И при этом пишет всё это в лог. Зачем? Такое ощущение, что банально забыли отладочные контуры отключить.
Вопросы про OnParam
Пользователь
Сообщений: Регистрация: 27.01.2017
09.09.2026 10:37:59
Т.к. эти вопросы были проигнорированы в соответствующей теме
Вынесем в отдельную тему:
Почему callback OnParam игнорирует настройки терминала в разделе "Получение данных"?
Берем чистый терминал, открываем только одну таблицу текущих торгов, в ней выбраны конкретные инструменты. Выбрана настройка "умным заказом данных". Никаких открытых графиков или других таблиц, только одна ТТТ. Нет запущенных скриптов, кроме тестового. В логах теста видим вызов OnParam для инструментов, которых нет в открытой ТТТ, нет открытых позиций, заявок по этим инструментам. С точки зрения скрипта, где этот OnParam зарегистрирован (а нигде более этот метод и не используется) - это мусор, влияющий на работу всего терминала. Если же разорвать связь и получить OnCleanUp, то колбек будет вызван для всего от начала сессии, т.к. не учитывается фильтр "умного заказа данных".
Также вопрос по памяти: Если в скрипте зарегистрировать OnParam, даже пустой:
function OnParam() end
то память, отображаемая в окне Доступные скрипты, почти не изменяется. А если же OnParam нет вовсе, то она бегает от начального значения до удвоения, и потом уже срабатывает GC, сбрасывая её. Особенно это заметно на "объёмных" скриптах с большим числом локальных переменных.
Т.е. наличие OnParam вызывает постоянный вызов GC, судя по всему при вызове OnParam. Это, конечно, наверно хорошо не расходовать память, но и агрессивный GC - это влияние на производительность. ...................
Также вопрос про OnCleanUp, который уже задавался:
если не было соединения достаточно долго, то брокер кидает OnCleanUp (это видно по логу и по параметру "число записей" в информационном окне, он скидывается в 0). Судя по всему, срабатывает условие "смена сессии". И в итоге загрузка данных происходит долго, очень долго. Допустим, что разрывы связи - это хоть и неприятно, но ожидаемое событие. Правда частота этого явления тоже вызывает вопросы. Но почему внутри сессии вызывается OnCleanUp?
Уведомление о необходимости обновления торговых терминалов в связи с изменениями на срочном рынке Московской биржи, Список проблем при работе устаревших версий QUIK после обновления торговой системы срочного рынка МБ
Пользователь
Сообщений: Регистрация: 27.01.2017
06.09.2026 17:22:09
Было же подробное объяснение - биржа перешла на срочном рынке на числовые номера ордеров, которые уже не влезали в 32 бита. Судя по всему уже просто не хватало числа, чтобы вместить все номера ордеров. Поэтому собрать-то под 32 бита не сложно. Торговать на срочной секции будет невозможно. Да и странно жаловаться на 64 бита, если найти 32-битную ОС уже почти невозможно. Само качество работ по переходу на 64 бита вызывает вопросы, это да.
Не проходит заявка на сделку в QUIK на счёте *, Номер счёта на * через / ??
Пользователь
Сообщений: Регистрация: 27.01.2017
31.08.2026 17:48:39
Так создайте заявку в кармане транзакций, указав все значения. Потом выгрузите из кармана в файл и посмотрите как это выглядит.
Я не знаю как там у ВТБ с счетом, но у Альфа тоже счет с слешем - никаких проблем не наблюдается. Вы привели пример REQUEST, но не ясно с какой стороны этот лог - со стороны клиента или со стороны сервера уже после разбора полученного json. По идее, сервер не должен заниматься преобразованием данных, а просто транслировать их в таблицу транзакции к отправке.
Не проходит заявка на сделку в QUIK на счёте *, Номер счёта на * через / ??
Пользователь
Сообщений: Регистрация: 27.01.2017
31.08.2026 13:46:20
Это не система не принимает, а коннектор вполне может парсить так данные, что режет через /. Проверьте что принимает коннектор и что отправляет в транзакцию. Наверно есть уровень лога, позволяющий увидеть это.
Что происходит при экспирации опциона с точки зрения скрипта?
Пользователь
Сообщений: Регистрация: 27.01.2017
29.08.2026 09:51:28
Вопрос же про экспирацию, т.е. наступление право исполнить обязательства. Т.е. продавец опциона (контрагент по покупке опциона) обязан поставить фьючерсный контракт, а покупатель — принять его.
Что происходит при экспирации опциона с точки зрения скрипта?
Пользователь
Сообщений: Регистрация: 27.01.2017
29.08.2026 08:25:32
OnFuturesClientHolding - срабатывает на любые изменения в таблице, как и любой другой колбек в контексте Квика. Уж так сделано...
Сделки не должно быть, второго контрагента же нет.
Коннектор на Lua 5.1 переписать под Lua 5.4 &, Lua 5.1 -Lua 5.4
Пользователь
Сообщений: Регистрация: 27.01.2017
23.08.2026 08:43:12
Цитата
nikolz написал: Для декомпиляции не нужна отладочная информация в скрипте Декомпилируется байт-код.
Да. Но прочитать такой код будет сложно. Если это одна строка, то проблем нет. А если тысячи, что разбираться с этим будет сложно.
Коннектор на Lua 5.1 переписать под Lua 5.4 &, Lua 5.1 -Lua 5.4
Пользователь
Сообщений: Регистрация: 27.01.2017
22.08.2026 15:57:46
Необходимы исходные тексты коннектора. Luac - это скомпилированный файл. Возможно он состоит из одной строки загружающей dll. А может это код на тысячи строк.
Если это 5.1, то возможна декомпиляция, но без стопроцентной вероятности точного восстановления. Скрипт вполне могли скомпилировать с отключенной отладочной информацией.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
03.08.2026 10:16:05
Цитата
nikolz написал: Но колбек вызывается перед записью в таблицу обезличенных сделок. Если читать из таблицы то будет вызов колбека, запись в таблицу, и потом чтение из нее.
Здесь есть сомнения, т.к. колбек на таблицу - это событие на изменение данных, которые приходят в пакетах. Что произойдет первым - это вопрос, т.к. я не раз ловил в логах ситуацию когда запись в таблице появлялась ранее чем колбек. Но даже если это такт, то не очень понятно, что это изменяет. Я бы сказал, наоборот, наличие такого частого колбека приведет к падению производительности. Организовать процедуру чтения порций с опросом числа записей в таблице - довольно банальная задача. Сотни тысяч записей записываются в файл за пару секунд. А далее, по мере поступления уже новых, просто их обрабатываем. Зачем для этого колбек в потоке терминала - не ясно. Сама идея событийной модели - это абстракция, за которой все равно будет код, проверяющий что событие наступило. Поэтому я предпочитаю сам это делать, с необходимой мне частотой.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
31.07.2026 20:17:21
Есть нюанс. Т.к. скорость движения цены заведомо выше скорости получения данных терминалом, то OnParam использовался для компромиссного быстрого получения последней цены, времени цены, времени пакета и проверки, что это не опоздавший пакет. И аггрегации этих данных между опросами в основном цикле. Можно и просто получать опросами, но только если не проблема пропустить выбросы цены.
Остальное параметры ТТТ не требует такой скорости и можно получать по месту требования. Тем более, что много параметров ТТТ вообще не изменяются в течении сессии и требуют однократного запроса.
Но теперь OnParam - это слишком накладно для терминала. Альтернатива - чтение таблицы обезличенных сделок (тики) и это будет самое точное (читать не через колбеки точно), но проблема что её не все брокеры выдают, поэтому это решение нельзя использовать как базовое.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
31.07.2026 16:09:10
Ок. Пока разработчики молчат, то в тех скриптах, где требовался колбек OnParam пришлось полностью его отключить, т.к. обрабатывать этот "мусор", подаваемый ему на вход, совсем странное занятие. Это был второй колбек, который я использовал, теперь остался только один - OnTransReply.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
31.07.2026 13:36:43
Цитата
TGB написал: Как фрагмент между lua_lock(L); lua_unlock(L); может исполняться в двух потоках
А есть подтверждение, что в qlua эти макросы задействованы, а не оставлены пустыми как в чистом lua.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
30.07.2026 14:30:06
Также вопрос по памяти:
Если в скрипте зарегистрировать OnParam, даже пустой:
function OnParam() end
то память, отображаемая в окне Доступные скрипты, почти не изменяется. А если же OnParam нет вовсе, то она бегает от начального значения до удвоения, и потом уже срабатывает GC, сбрасывая её. Особенно это заметно на "объёмных" скриптах с большим числом локальных переменных.
Т.е. наличие OnParam вызывает постоянный вызов GC, судя по всему при вызове OnParam. Это, конечно, наверно хорошо не расходовать память, но и агрессивный GC - это влияние на производительность.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
30.07.2026 12:04:55
Ок, вопрос разработчикам:
Почему callback OnParam игнорирует настройки терминала в разделе "Получение данных"?
Берем чистый терминал, открываем только одну таблицу текущих торгов, в ней выбраны конкретные инструменты. Выбрана настройка "умным заказом данных". Никаких открытых графиков или других таблиц, только одна ТТТ. Нет запущенных скриптов, кроме тестового.
В тестовом скрипте есть только OnParam и вывод данных через PrintDbgStr, нет заказов каких-то данных.
Запускаем тест и видим вызов OnParam для инструментов, которых нет в открытой ТТТ, нет открытых позиций, заявок по этим инструментам. С точки зрения скрипта, где этот OnParam зарегистрирован (а нигде более этот метод и не используется) - это мусор, влияющий на работу всего терминала.
Также вопрос о самой настройке "умным заказом данных": если она допускает такой поток данных (а он явно есть, раз есть вызов OnParam), то о каком снижении потока передаваемых данных может быть речь?
Кто хочет поразвлекаться, вот тестовый скрипт. Запускам. Разрываем связь, ждем, запускаем. Когда в информационном окне закончится очередь отставших сообщений остановить. была возможность считать этот параметр оставили бы сами. Для наглядности можно включить PrintDbgStr в OnParam и посмотреть что приходит и с какой скоростью, после восстановления связи или в моменты когда терминал подгружает отставшие пакеты.
И почему все эти вызовы OnParam будут мусором, потому что если получить и вывести цену в колбеке, то она будет одинаковая, т.к. последняя. Т.е. можно предположить, что изменяется что-то служебное.
Также, включая PrintDbgStr в OnParam, увидел, что приходят данные даже не включенные в настройках. На том же ВТБ - включены только облигации, т.к. открыта ТТТ по ним. Но после разрыва связи получаю такое
И много другого мусора по всем классам и инструментам даже не включенных в поток данных. Т.е. это банальная DDOS атака колбеков в скриптах, т.к. приходят десятки тысяч вызовов, по всем классам инструментов. Зачем тогда эта настройка "умным заказом данных", если получаем всё...
Код
local path = _G.getScriptPath()
package.path = path.."/?.lua;"
local id = 'script1'
local logFile
function _G.OnInit(script_path)
id = ((script_path:match("[^/\\]+$")):gsub('.lua', ''))
end
local function log_tostring(...)
local args = table.pack(...)
if args.n == 1 then
return tostring(args[1])
end
for i = 1, args.n do
args[i] = tostring(args[i])
end
return table.concat(args, " ", 1, args.n)
end
local function log(...)
if logFile==nil then return end
logFile:write(log_tostring(...).."\n");
logFile:flush();
end
local sleep = _G.sleep
local message = _G.message
local isRun = true
local last_rec, last_time, time_diff
local start_rec = tonumber(getInfoParam('LASTRECORD') or '') or 0
local q = 0
local t = os.time()
local start
function _G.OnParam(class_code, sec_code)
if q == 0 then t = os.time() end
q = q + 1
local last_price = tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value)
-- PrintDbgStr(log_tostring('OnParam', q, class_code, sec_code, 'last_price', last_price))
end
local function log_stat()
local end_rec = tonumber(getInfoParam('LASTRECORD') or '') or 0
local loaded = end_rec - start_rec
t = os.time() - t
local str = log_tostring(id, ': done', ', processed callbacks: ', q, ', in: ', t, 'sec', ', avg: ', q == 0 and '---' or t/q)
message(str)
local str1 = log_tostring(id, ': start_rec: ', start_rec, ', end_rec: ', end_rec, ', loaded records: ', loaded, ', avg: ', loaded == 0 and '---' or t/loaded)
message(str1)
if io.type(logFile) == 'file' then
log(str)
log(str1)
logFile:close()
end
end
function _G.main()
logFile = io.open(path.."\\"..id..".txt", "w")
while isRun do
sleep(10)
end
end
function _G.OnDisconnected()
message('OnDisconnected, current_rec: '..tostring(tonumber(getInfoParam('LASTRECORD') or '') or 0))
q = 0
t = os.time()
end
function _G.OnConnected(flag)
if flag then
start_rec = tonumber(getInfoParam('LASTRECORD') or '') or 0
message('OnConnected, start_rec: '..tostring(start_rec))
q = 0
t = os.time()
start = true
end
end
function _G.OnStop()
isRun = false
log_stat()
end
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
29.07.2026 16:16:02
И ещё из странного. Добавляем в скрипт такое:
local function ds_callback(ds, index) PrintDbgStr(log_tostring('ds_callback', index, 'last_price', ds:C(index))) end
function _G.main()
logFile = io.open(path.."\\"..id..".txt", "w")
local ds = _G.CreateDataSource('TQBR', 'SBER', 1) if not ds then isRun = false end ds:SetUpdateCallback(function(...) return ds_callback(ds, ...) end)
while isRun do sleep(10) end end
Что пробуем понять - что будет если остановить скрипт, брокер выдаст OnCleanUp и начнет передавать все пакеты заново. Предположение, что функция в SetUpdateCallback выдаст все, начиная с первого индекса.
И действительно, прерываем связь, ждем, получаем OnCleanUp, и получаем вызовы от первого индекса.
Т.о. массовая передача отставших пакетов или даже всех от старта сессии, приведет к вызову всех колбеков заново. И скрипт будет это разбирать, вместо работы. И нагрузка на терминал, соответственно, т.к. на каждый пакет будут вызваны все назначенные пакеты. Вот тебе и событийная модель. Казалось бы, событие давно прошло - но нет, получи заново.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
29.07.2026 15:11:39
Цитата
VPM написал: У Вас же идет вызов тяжеловеса, еще нужно понять где фильтруется
Это нагрузочный тест, фильтр закомментирован. В этом и смысл. Также, если getParamEx - это тяжелая функция, то тогда можно смело даже не думать о написании чего-то существенного на QLUA. Эта функция, по идее, максимально быстро выдает данные из кеша терминала. Никуда не ходит, просто берет из хранилища.
Почему-то кажется, что получение типа, да ещё сравнение с строкой (её бы в константу загнать, чтобы Lua каждый раз не бегал в пул строк), а потом ещё и вызов функции, потяжелее будет.
Код
if not Robot or not c.is_run or not c.IS_WARMED_UP or Robot.Is_Inside_HotPath then return end
if Robot.ProcessParamEvent and type(Robot.ProcessParamEvent) == "function" then
Robot:ProcessParamEvent(class_code, sec_code)
end
Также, если внутри колбека OnParam ничего не делать, то он становится бесполезным, т.к. он не говорит что изменилось, а просто сигнализирует - что-то там изменилось. Да, можно как и с OnQuote, как в тестах выше, фиксировать просто флаг изменения, и уже в main получать данные через getParamEx. Но это приведёт к существенной потере точности считывания данных, т.к. пока дойдём до этого участка кода, OnParam вызовется ещё тридцать раз, где были промежуточные изменения.
Т.о., если даже такое - это тяжело для Квика, то, как сказал выше, можно расслабиться.
Но почему-то кажется, что проблема все-же в архитектуре. OnParam - по идее, должен быть событием реального времени. Если он будет вызываться на все приходящие записи после OnCleanUp, да ещё вечером, когда этих записей много, то терминал, вместо работы, будет занимается разбором пакетов. Я такое второй день наблюдаю на ВТБ, и даже без скриптов в эти моменты терминалу плохо. При этом теперь OnCleanUp - это рядовое событие, стоит разорвать связь на не такое и длительное время и всё - получите всё с начала торгов. Данные баров, сделки, ордера - да. А OnParam зачем?
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
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
Очередь сетевых сообщений
Пользователь
Сообщений: Регистрация: 27.01.2017
28.07.2026 15:41:25
Регистрируйте как пожелание, т.к. это единственный способ понять, что идет передача пропущенных данных.
Очередь сетевых сообщений
Пользователь
Сообщений: Регистрация: 27.01.2017
28.07.2026 14:39:14
Есть такой параметр в информационном окне. И он, на самом деле, полезен, т.к. после разрыва связи или запуска терминала позволяет косвенно увидеть, что идёт загрузка отставших сообщений.
Но его нет в описании к методу getInfoParam. Как его получить?
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
28.07.2026 08:01:41
Цитата
nikolz написал: Вывод -тормозят Ваши скрипты. Измерьте время их исполнения. ------------------ Но так как эта задержка существует и при спокойном рынке, то скорее всего это 1 вариант
Тексты скриптов выложены выше. Что там конкретно приводит к "тормозам"? Просто интересно, раз такое категоричное утверждение.
Цитата
nikolz написал: Тест проверяет быстродействие выполнения колбеков, включая обработку очереди .
У меня и не было задачи проверять работу колбеков, а влияние их наличия на скорость работы интерфейса терминала. Буду рад если предложите другой инструментальный тест.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 15:01:42
Оставил скрипты тестовые с колбеками запущенными. В итоге:
задержка данных при обмене с сервером = 11.109 средняя задержка данных = 26.384. Максимальная задержка данных = 175.063
Как только остановил скрипты - побежал счётчик полученных пакетов, отставших записей в информационном окне. И пока данные бегут, то терминал и без скриптов не особо спешит.
Работа колбеков
Пользователь
Сообщений: Регистрация: 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 14:09:50
OnParam и OnQuote самые быстрые. Остальные не так часто.
Поэтому добавим пустой OnParam. Почти нет влияния. Хотя есть уже ощущение, что не так, как без скриптов. Опять же - только визуальный контроль.
Добавим внутрь колбека одну строку
tonumber((getParamEx(class_code, sec_code, 'LAST') or {}).param_value)
И уже явно что-то есть.
Также, чисто субъективно, кажется, что если дать ностояться, т.е. поработать скриптам, то уже как-то сильнее влияние.
Еще из странного - зависит от сервера брокера. Я тестирую на ВТБ, как одном из "тормозных", переключаешь на другой сервер - и уже не так заметно влияние.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 11:50:13
Сами скачки волне ожидаемы в клиент-серверном взаимодействии. Ну не пришли ещё пакеты, бывает, если оставить в сторону реализацию обмена. Но вот почему в эти периоды терминал становится "тупым" - уже не столь очевидно и вызывает вопросы. Как будто сделали блокирующий цикл, ожидающий пакеты, а интерфейс - да кому он нужен.
Работа колбеков
Пользователь
Сообщений: Регистрация: 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 и пишем в очередь, то уже на трех заметно влияение на работу интерфейса теримнала.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
27.07.2026 08:27:18
Тест был направлен не на измерение скорости работы скрипта, а на влияние работы скрипта на работу терминала, его интерфейса в первую очередь. Какой смысл показать, да ещё на демо, что main работает быстро. У меня на демо тоже всё прекрасно. Переключаешься на реальный и во время открытия рынка - терминал вкладки переключает по 30 секунд. Была бы возможность инструментально измерить работу самого терминала - так и вопросов не было бы.
Вот прямо сейчас смотрю на один из терминалов - банально время сервера в строке состояния изменяется скачками, замирая на длительный период. И так каждый раз при старте, что, в теории, нормально, т.к. приходят пакеты. Но не 15 минут же. На терминалах других брокеров это же занимает минуту, 5-ять иногда. И в это время отчетливо видно как с трудом совершаются любые операции с интерфейсом.
Аналогично и в периоды высокой ликвидности. В это время транзакции могут исполнятся минуты - это ожидаемо, но почему опять интерфейс также "умирает".
Я готов это списать на влияние работы скриптов. Но тогда хотелось бы понять, что именно и какие методы оказывают максимальное влияние. Иначе делаешь банальный скрипт, а эффект как будто модель на 100b считаешь.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
26.07.2026 10:01:04
Текст скрипта выше, проблем с запуском на 4-ех терминалов нет. Библиотеки в архиве тоже есть.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
25.07.2026 09:07:08
Пока была задача воспроизвести "зависание" терминала. В реальности данные в таблицу не выводятся с такой частотой, да и в последнем тесте убрал дублирующие установки, что сильно снизило частоту вызовов. Колбеки используются только служебные OnConnect, OnClenUp, OnTransReply, но идут частые запросы к данным через getParamEx, getItem. Здесь просто модельный скрипт с набором действий, которые часто можно встретить в скриптах.
Я бы и рад их использовать, если писал только себе. Себе мне проще выдать данные по любому обмену, а всё написать вне Квика.
А чтобы найти надо всё отсекать и постепенно подключать, смотреть на результат. Проблема только в том, что работу скрипта ещё можно как-то измерить, а вот работу терминала только визуально. Ты вроде как SetCell вызвал, а данные в таблице появляются только если "пнуть" таблицу.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
24.07.2026 17:14:37
У меня 5 брокеров - 4-е терминала постоянно открыто. Сбер не самый "плохой" из них. Я бы даже сказал вполне нормально. Самый тормозной - Кит. Самый отзывчивый Альфа. Прямая корреляция с числом клиентов и мощностью серверов. Есть стойкое ощущение, что обмен данными сервер-клиенты - занимает всё время терминала.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
24.07.2026 16:32:17
Цитата
VPM написал: Если правильно понял то проблемы уже заметны на 7 инструментах
Иногда они заметны и на меньшем числе. Здесь основной вопрос - это деградация производительности всего терминала при достаточно простых, но частых действиях. Таблицы уже давно стали влиять на терминал, кажется с 8-ой версии. Но сейчас это уже выходит за рамки нормального. Вполне ожидаемо было бы квотирование процессорного времени скриптам, но сам терминал должен работать без влияния. Здесь же банальная задача - и терминал лежит.
Так что хотелось бы увидеть реакцию разработчиков. Правда сразу можно сказать - на демо Quik Junior такое не наблюдается, точнее, видимо, необходимо ещё больше приложить нагрузку. А вот на реальных брокерских терминалах, особенно если ещё есть поток данных по опционам - то уже даже один скрипт может "положить" терминал.
Поэтому я и не использую колбеки. Но и без них есть проблемы, просто с ними - это видно очень отчетливо.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
24.07.2026 12:27:21
Э... что же тогда большое.
У нас есть колбеки - настоятельно рекомендуемые к использованию. Есть окно скрипта - единственный способ вывести данные. Банальная задача - вывести данные, полученные через getParamEx. Также, для какой-то нагрузки - читаем 7 стаканов, при этом даже не разбирая их, а банально беря верхние значения, т.е. просто запрос данных.
И выводим это в таблицу. Всё. Ни транзакций, ни расчетов, ни запросов данных графиков и потока данных - только, по факту, запрос данных ТТТ из терминала.
Проверяем как это терминал переваривает, если таких скриптов несколько. Такое на 7-ой версии терминала даже не вызывало вопросов.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
24.07.2026 11:58:11
Ок. Чтобы показать влияние на работу интерфейса терминала еще сделал такой тест.
Терминал свежий, установлен не более двух недель назад. Брокер ВТБ. Остановил все скрипты, даже автологин.
В тестируемом скрипте добавил запоминание последних значений, чтобы выводились в окно только измененные данные. Лог через PrintDbgStr. В колбеках вывода в лог нет, только в main.
Запускаем три экземпляра скрипта и смотрим на реакцию терминала.
Один скрипт - почти нет влияния. Два - уже ТТТ "подвисла" и данные выводятся не так быстро. Три - всё встало, данные появляются только если "пнуть" таблицу
В архиве скрипты, лог с таймингом работы main, запись видео работы терминала.
Код тестового скрипта - по факту, он ничего не делает, кроме обработки колбеков, запроса данных через методы QLUA, и вывода данных в таблицу. Никаких расчётов, даже файловые операции убраны.
Код
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 = 'script 1:'
local logFile
local function log_tostring(...)
local n = select('#', ...)
if n == 1 then
return tostring(select(1, ...))
end
local t = {}
t[#t + 1] = id
for i = 1, n do
t[#t + 1] = tostring((select(i, ...)))
end
return table.concat(t, " ")
end
local function log(...)
-- Пишем в отладочные сообщения, вместо лога
PrintDbgStr(log_tostring(...))
-- Пишем в отладочные сообщения, вместо лога
-- 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 getQuoteLevel2 = _G.getQuoteLevel2
local IsWindowClosed = _G.IsWindowClosed
local DestroyTable = _G.DestroyTable
local message = _G.message
-- local table_sremove = _G.table.sremove
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 get_dom = {}
function _G.OnParam(class_code, sec_code)
local s = os.clock()
if t_id and rev_lis[sec_code] then
-- log('Enter OnParam', 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}
-- log('Exit OnParam', os.clock() - s, class_code, sec_code, res.last_rec, last_price)
-- log('---------------------------------------------------------------------------')
end
end
function _G.OnQuote(class_code, sec_code)
local s = os.clock()
if t_id and rev_lis[sec_code] and not get_dom[sec_code] then
-- log('Enter OnQuote', class_code, sec_code)
get_dom[sec_code] = {class_code, sec_code}
-- log('Exit OnQuote', os.clock() - s, class_code, sec_code)
-- log('---------------------------------------------------------------------------')
end
end
local function event_callback(_, msg, par1, par2)
-- log('Enter TableNotificationCallback', msg, par1, par2)
if (msg == _G.QTABLE_CLOSE) then
isRun = false
end
if msg == _G.QTABLE_CHAR then
SetCell(t_id, par1, 4, tostring(par2), par2)
end
-- log('Exit TableNotificationCallback', msg, par1, par2)
-- log('---------------------------------------------------------------------------')
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, "bid", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.AddColumn(t_id, 3, "ask", true, _G.QTABLE_DOUBLE_TYPE, 15)
_G.AddColumn(t_id, 4, "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
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(' --- set pop value', n, rev_lis[res[2]], last_price)
res = queue:pop()
end
return n
end
local function GetQuotes(class_code, sec_code)
local q = getQuoteLevel2(class_code, sec_code)
if not q then return end
local qbid = q["bid"]
local qask = q["offer"]
if not qbid and not qask then
return
end
local best_bid = tonumber(qbid[#qbid]["price"])
local best_ask = tonumber(qask[1]["price"])
if last_data[sec_code][2] ~= best_bid then
SetCell(t_id, rev_lis[sec_code], 2, tostring(best_bid), best_bid)
last_data[sec_code][2] = best_bid
end
if last_data[sec_code][3] ~= best_ask then
SetCell(t_id, rev_lis[sec_code], 3, tostring(best_ask), best_ask)
last_data[sec_code][3] = best_ask
end
get_dom[sec_code] = nil
end
function _G.main()
for i = 1, #sec_list do
if not _G.Subscribe_Level_II_Quotes(sec_list[i][1], sec_list[i][2]) then
return message('Не удалось подписаться на получение данных стакана по инструменту '..tostring(sec_list[i][1])..'|'..tostring(sec_list[i][2]))
end
end
CreateTable()
-- logFile = io.open(path.."\\load_test.txt", "w")
local lt = os.time()
local wnd_kill_interval = 120
while isRun do
local cwt = os.time()
if cwt - lt > wnd_kill_interval then
lt = cwt
DestroyTable(t_id)
CreateTable()
end
-- тест скорости локальной очереди
-- local list = Queue.new()
-- for i = 1, 10000 do
-- list:push(i)
-- end
-- local st = os.clock()
-- while list:pop() do end
-- log('process 10000 for time', os.clock() - st)
-- тест скорости локальной очереди
local s = os.clock()
log('Enter main loop', os.date())
IsWindowClosed(t_id)
local ct = os.clock()
local n = process_queue()
local qt = os.clock() - ct
ct = os.clock()
local qn = 0
for i = 1, #sec_list do
if get_dom[sec_list[i][2]] then
qn = qn + 1
GetQuotes(sec_list[i][1], sec_list[i][2])
end
end
local dt = os.clock() - ct
bl1.func1()
bl2.func100()
bl3.func200()
bl4.func300()
log('Exit main loop', os.clock()-s, 'in queue', n, 'queue time', qt, 'dom time', dt, 'for', qn)
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
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
23.07.2026 17:35:47
Цитата
nikolz написал: Наблюдал такой прикол ставишь индикатора регрессии на большой истории и привет ждешь старта квик очень долго. На демо все быстро потому что там история маленькая.
Это объяснимо. При старте, график инициализирует расчёт на всё истории дважды минимум (зачем? ответа так и нет). Если индикатор сделан так, что считает каждый бар с начала, что для регрессии очень редко нужно, и баров "до дури", ясно что будет так. У регрессии сложность же O квадрат от периода, а если не линейная, то ещё хуже. И так каждый бар...
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
23.07.2026 15:06:32
Иногда в логах работы скриптов видно, что банальная установка данных в таблицу может занимать 3 секунды.
Что-то типа такого:
fork,columninipairs(columns) do ifvalues[column]~=nilthen t:SetValue(line, k, values[column] andtostring(values[column]) or'', tonumber(values[column]) or0) end end
Банальный итератор на 20 элементов (в таблице скрипта редко больше), а время столько
Я уже давно перестал дергать таблицу, т.к. после 7-ой версии терминала она стала такой хрупкой. В реальных скриптах вывод данных только по необходимости, в лучшем случае раз в минуту.
Работа колбеков
Пользователь
Сообщений: Регистрация: 27.01.2017
23.07.2026 13:16:02
Ок. Переделал на очередь (по факту - не особо лучше чем с банальной table.sremove)
Итог. Худшие результаты:
Exit main loop 4.3549999999996 in queue 164 queue time 3.875 dom time 0.19399999999951
Exit main loop 2.5430000000015 in queue 116 queue time 2.4950000000008 dom time 0.04700000000048
Exit main loop 2.3759999999984 in queue 97 queue time 2.357 dom time 0.018999999998414
Exit main loop 1.018 in queue 57 queue time 0.97600000000057 dom time 0.040999999999258
Exit main loop 0.9429999999993 in queue 43 queue time 0.94100000000071 dom time 0.0019999999985885
Exit main loop 0.88700000000063 in queue 43 queue time 0.48700000000099 dom time 0.39899999999943
Exit main loop 0.85599999999977 in queue 78 queue time 0.76599999999962 dom time 0.090000000000146
Если убрать из колбеков PrintDbgStr, то немного быстрее, точнее меньше выбросов, и максимальный результат такой:
Exit main loop 1.0730000000003 in queue 54 queue time 1.0709999999999 dom time 0.0020000000004075
Но есть и такие выбросы по чтению стакана
Exit main loop 0.46200000000135 in queue 3 queue time 0.055000000000291 dom time 0.40600000000086
Exit main loop 0.47899999999936 in queue 10 queue time 0.16300000000047 dom time 0.31499999999869
Exit main loop 0.34699999999975 in queue 7 queue time 0.041999999999462 dom time 0.30400000000009
При этом число инструментов по которым читался стакан (а он читается один раз за цикл main, если были вызовы OnQuote), не особо влияет. Могут все 7 прочитаться за 50 млс, а могут всего 2 уже за 300.
Пока по ощущениям - это GC. Чем больше мусора, тем хуже main. Также прослеживается линейная зависимость от размера очереди - 100 элементов = 1 секунде работы main. Что, конечно, совершенно не сравнимо с чистым Lua где та же очередь из 10 тыс. элементов обрабатывается за 2 мкс.
При этом, если эту же очередь заполнить и разобрать внутри бесконечного цикла в main, то она и разберется за эти 2 мкс:
Код
local list = Queue.new()
for i = 1, 10000 do
list:push(i)
end
local st = os.clock()
while list:pop() do end
log('process 10000 for time', os.clock() - st)
Но та же очередь, заполняемая на колбеках, а разбираемая в main - уже на порядок медленней.
Также однозначно - в колбеках никаких операций ввода-вывода.
Но я так и не могу выявить что-то одно, что может так влиять на интерфейс терминала, кроме банально низкой скорости вообще.