Я думал collectgarbage() оценивает память используемую всем терминалом, я не прав?
Нет, это память, используемая lua-машиной отдельного скрипта.
Я так понимаю, Вы вызываете сборщик мусора в цикле вложенным в main используя счетчик времени или используете setpause"и setstepmul. Можете привести элемент кода с этой операцией?
Создаём файл Timer.lua примерно такого содержания:
Код
--
-- Реализация таймера.
--
-- Пример использования:
-- local Timer = require("util.Timer")
-- local timer = Timer:new()
-- timer:setInterval(10)
-- ...
-- if timer:isTimeout() then ...
--
local Timer = {}
--- Конструктор.
-- @param self объект
local function new(self)
local timer = {
deadline = os.time()
}
setmetatable(timer, self)
self.__index = self
return timer
end
Timer.new = new
--- Задать интервал, по прошествии которого считается, что таймер сработал.
-- @param self объект
-- @param duration число секунд, через которое должен сработать таймер.
local function setInterval(self, duration)
self.deadline = os.time() + duration
end
Timer.setInterval = setInterval
--- Задать момент времени, по прошествии которого считается, что таймер сработал.
-- @param self объект
-- @param date таблица, задающая дату, когда должен сработать таймер.
local function setDate(self, date)
self.deadline = os.time(date)
end
Timer.setDate = setDate
--- Узнать, через сколько секунд осталось до момента срабатывания таймера.
-- @param self объект
-- @return количество секунд оставшееся до срабатывания таймера или 0, если время срабатывания прошло
local function getSecondsLeft(self)
return math.max(0, self.deadline - os.time())
end
Timer.getSecondsLeft = getSecondsLeft
--- Узнать, наступил ли момент срабатывания таймера.
-- @param self объект
-- @return true/false
local function isTimeout(self)
return os.time() >= self.deadline
end
Timer.isTimeout = isTimeout
return Timer
Используется примерно так:
Код
local function printMemoryUsed()
logger:debug("Memory used: " .. math.ceil(collectgarbage("count") / 1024) .. "M")
end
local function run()
while not interrupted do
...
if garbageTimer:isTimeout() then
printMemoryUsed()
logger:debug("Collecting garbage...")
collectgarbage()
printMemoryUsed()
garbageTimer:setInterval(600)
end
...
end
end
function main()
...
run()
...
end
Чтобы исчерпать всю память надо написать очень плохой скрипт.
Или использовать один терминал для интенсивной торговли по большому количеству счетов и инструментов. По-хорошему, надо переходить на что-то более адекватное, но пока (из-за дешевизны решения) приходится мириться с недостатками терминала.
У меня компьютер аналогичный, только памяти 16 Гб и так было несколько раз при запуске нескольких скриптов сразу.
Цитата
1. Подскажите, какова возможная причина проблемы? 2. У кого были похожие ситуации, с чем они связаны и как удалось избавиться от проблем?
Виртуальным Lua-машинам не хватает памяти. Например, сборщик мусора не успевает справляться или есть какая-то утечка памяти в скриптах. Иногда это из-за внутренних ошибок терминала.
Лучший вариант по надёжности -- это перезапускать терминал перед началом торгов (после смены торговой сессии), раз в 5-15 минут в каждом скрипте вызывать collectgarbage() для сборки мусора.
Цитата
3. Как самостоятельно попытаться идентифицировать источник проблемы и оптимизировать код? 4. Есть ли возможность использовать для LUA какие-либо программы отладки, которые анализируют.
Программы для отладки мне неизвестны. Можно периодически писать в лог потребление памяти каждым скриптом (функция collectgarbage("count") выдаёт количество выделенной памяти в килобайтах) до сборки мусора и после неё. Так, возможно, поймёте, в чём проблема.
Почему-то не видно комментариев разработчиков терминала. Несоответствие объёма в свече и суммарного объёма по сделкам этой свечи кажется критичным багом.
Пусть на совести биржи останется тот факт, что сделка прошла ровно в момент 23:50. Однако, вопрос к разработчикам терминала: почему на графике RIU7 свечка, начинающаяся в 23:50, имеет объём 2, когда в сделке был объём 1?
Где правда?
Если серьёзно, то это ошибка терминала, ведь свечи строит терминал, а не биржа.
При попытке закрыть диалоговое окно и остановить скрипты терминал упал (в папке dmp ничего нет), а Windows стала безуспешно искать способы устранения проблемы. После перезапуска и скачивания данных терминал занимает 300 Мб памяти.
Просьба к разработчикам: создайте у себя стенд для нагрузочного тестирования, включающий, скажем, * 20 вкладок с 50 графиками разных таймфреймов; * 20 скриптов, которые делают какую-то реальную работу с DataSource объектами, выводят инфу в таблицы и передают данные из потока коллбэков в main-поток с помощью очередей; * 50 разных таблиц (ТТП по 20 акциям+20 фьючерсам+валюте+индексам, истории параметров, всех обезличенных сделок по всем инструментам и т.п.). и погоняйте его в течение нескольких дней. Наверняка повылезают ошибки.
Операция не может быть выполнена, т.к. недостаточно памяти.
Диспетчер задач сейчас (через несколько часов после ночного падения скрипта) показывает, что терминал занимает 2.5Гб. Сколько он занимал перед падением -- неизвестно.
Остальные скрипты занимают: - до очередной сборки мусора: 19М+19М+20М+20М+10М+41М+7М+7М+7М+7М+0.2М=157.2М - после этой сборки мусора: 14М+8М+15М+11М+9М+28М+4М+4М+4М+4М+0.1М=101.4М
Все остальные скрипты как-то работают, но им доверия, понятное дело нет.
Пока что диагноз такой: при непрерывной работе в течение нескольких дней на пределе занимаемой памяти в терминале что-то переполняется и он начинает глючить. В скриптах lua это проявляется в появлении ошибок, которые перехватываются с помощью xpcall и показывают (наверное) неправильный адрес функции, которую попытались вызвать.
Похоже, что терминал не способен интенсивно работать несколько дней подряд без глюков. Придётся переходить к неудобному варианту эксплуатации, когда каждый день перед торгами терминал запускается заново.
Продолжение мониторинга после 2-х торговых дней без перезагрузки. Сейчас, за 3 часа до начала торгов, QUIK занимает в памяти 3 Гб. Работающие скрипты занимают: - до очередной сборки мусора: 29М+39М+49М+49М+22М+57М+21М+17М+16М+16М+0.2М+0.2М = 315.4М - после этой сборки мусора: 14М+19М+31М+21М+18М+54М+9М+9М+9М+9М+0.1М+0.1М = 193.2М
Так что ситуация более-менее стабильная и похожая на ситуацию предыдущего дня.
Вчера перед началом торгов (после смены торговой даты) терминал занимал в памяти 1 Гб.
Боевую систему, понятное дело, прислать нет возможности. С другой стороны, можно написать скрипты, похожие по своей логике на те, что работают в боевых системах. Если несколько пользователей пришлют вам свои примеры скриптов, то можно будет сделать что-то типа стенда нагрузочного тестирования (хотя, по идее, у вас уже должен такой быть). На этом стенде гонять скрипты в большом количестве несколько дней и смотреть, когда и что глючить начинает.
Вчера, за 30 минут до начала торгов, был перезаказ данных с очисткой всего. Сейчас, за 2 часа до начала торгов, QUIK занимает в памяти 2.6 Гб. Работающие скрипты занимают: - до очередной сборки мусора: 16М+29М+38М+52М+28М+62М+10М+15М+15М+14М+0.2М+0.2М = 279.4М - после этой сборки мусора: 12М+18М+28М+21М+17М+52М+8М+8М+8М+8М+0.1М+0.1М = 180.2М Получается, что основное потребление памяти идёт от внутренних структур терминала, а нет от самих lua-скриптов. Перед торгами будет сброс данных при смене торговой даты. Посмотрим, что там будет.
А вообще, кажется, что пределы по памяти у терминала (4 Гб) весьма близки. И стратегически решать проблемы можно только либо оптимизациями внутренних структур терминала, либо переходом на 64-битную версию. Предположим, что торговая активность вырастет в 2 раза. Терминал просто не переварит данные за один день, не говоря уж о работающих скриптах. Рост торговой активности наблюдаю с 2013 года: количество записей за день выросло с 6 миллионов до почти 20 миллионов, т.е. почти в 3 раза, примерно то же самое с количеством тиков по Si.
Интересно, что изменилось с момента, когда была активна вот эта тема: https://forum.quik.ru/forum8/topic432/ Особенно см. комментарии Виталия Скоробогатова.
Заведите себе объект, который вычисляет количество отправленных заявок в последнюю секунду (реализация через очередь). Если количество заявок больше, скажем, 45, не отправляйте новые. Понятно, что ограничение неприятное и неудобное, но спамить сервер тоже не правильно.
local Tt = tmpParam.DAYS_TO_MAT_DATE / YearLen --"T-t" время до истечения срока опциона (период опциона);
значение tmpParam.DAYS_TO_MAT_DATE является целым, то будет получаться погрешность при вычислении времени Tt до истечения срока опциона. Эта погрешность будет относительно небольшой, если до квартальной экспирации ещё долго (как сейчас), а непосредственно перед экспирацией это уже ощутимо. Биржа считает время до истечения точно, вычисляя разницу между текущим моментом и моментом экспирации и переводя в доли года. Давно исследовал этот вопрос, даже разбирался для RI момент экспирации считается 18:45 или 19:00.
Если стакан, который наблюдался на момент отправки заявки к моменту попадания заявки на биржу не изменится, то произойдут несколько сделок для тех ценовых уровней, где цена лимиток из стакана лучше или равна цене в Вашей заявке, после чего остаток останется стоять в виде лимитной заявки. Это в случае, если был указан параметр транзакции "PUT_IN_QUEUE". В случаях "FILL_OR_KILL", "KILL_BALANCE" будет по-другому.
Если же биржевой стакан как-то поменяется, то результаты (сделки и остаток в заявке) будут соответствовать обновлённому стакану. Может, например, что вообще сделок не будет, а Ваша заявка встанет в стакан, если рынок успеет "убежать".
Вопросы, по-видимому, к разработчикам и продвинутым пользователям.
В настоящий момент у меня в терминале идёт получение данных в ТТП по примерно 20 акциям и 20 фьючерсам, в таблицу обезличенных сделок идёт информация по всем акциям и фьючерсам, открыто 13 вкладок с графиками, стаканами и разного рода таблицами (всего 83 окна, включая созданные lua-скриптами), работает 12 торговых lua-скриптов (вызывают примерно раз в час функцию collectgarbage).
Перед началом вчерашней торговой сессии (дата уже сменилась на 21.06.2017) были перезаказаны данные со всеми отмеченными галочками. После завершения вчерашней торговой сессии на текущий момент (торговая дата ещё 21.06.2017) терминал занимает 3.1 Гб оперативной памяти.
Операционная система Windows 10 x64 версия 1607.
Вопросы:
1) насколько близки пределы по выделенной памяти, количеству lua-скриптов, после достижения которых будет выскакивать out of memory или появляться другие проблемы?
2) можно ли как-нибудь понять, сколько памяти тратится на работу lua-скриптов (чтобы решить, имеет ли смысл снижать её потребление)?
Один вариант реал-тайм проверки состоит в том, чтобы считать, что вечерняя сессия начинается в 19:00, но отслеживать, идут ли обезличенные сделки по классу SPBFUT. Признак того, что вечерняя сессия открыта -- время >= 19:00 и были сделки после этого времени.
У себя в коде, если по классу SPBFUT нет сделок в последние N секунд нет сделок, считаю, что сессия закрыта и торгов нет. Хорошо помогает, когда биржа глючит, торги остановлены, но статус, по-прежнему, пишет "торгуется".
Если хочется 100% надёжности, то, похоже, только задание расписания торгов поможет.
Andrei2016 написал: Дополнительно еще один вопрос к разработчикам:
возможна ли в штатном режиме работы ситуация, когда ответный вызов OnTransReply() на отправленную программным образом заявку не поступит вовсе? Если да, то прошу пояснить в каких конкретно случаях такое возможно.
Я хоть и не разработчик, но могу сказать, что в при штатной работе программы OnTransReply приходит всегда. На практике при проблемах с интернетом ответы OnTransReply могут пропадать (очень редко).
Optimus1 Optimus1 написал: Подскажите пожалуйста, я вывожу значение RSI в созданною таблицу, значение выводится и все бы хорошо, но значение выводится с 9 знаками после запятой, не очень удобно для восприятия, подскажите, как можно сократить до 1 знака после запятой ?
Код
local rsiFormatted = string.format("RSI=%.1f", rsiValue)
Поскольку команда разработчиков занималась встраиванием lua в терминал, то ей легче, чем простым пользователям, поставить диагноз, что именно пошло не так в lua. А с тем, что если квик в этом случае зависает намертво, то это ошибка квик, -- я согласен. Можно сделать более качественную обработку исключений, даже если они не в языке lua, а в реализации lua. Так что хотелось бы технических подробностей и фикса проблемы зависания терминала.
А не могли бы разработчики QUIK посмотреть, что происходит в этот момент в lua и терминале, и описать, что что конкретно там происходит? Интересно узнать.
Zoya Skvorcova написал: Светлана Серикова , Добрый день. Есть возможность запускать рабочее место QUIK из командной строки с ключом –clear . Для этого нажмите правой клавишей мыши по ярлыку и выберите свойства. В закладке Ярлык в поле Объект добавьте параметр -clear Строка должна выглядеть примерно так "C:\Квик\QUIK Junior\info.exe -clear"
Если ноутбук гарантийный и внезапно сломался, то выполнить указанную выше команду ДО отправки в мастерскую невозможно. Так что проблема с безопасностью действительно есть.
Цитата
А я в таком случае винчестер дома оставляю.
Ноутбук гарантийный, как было сказано в первом посте, так что вынимать винчестер означает потерять гарантию.
_sk_ написал: Потом приходит следующая сделка, а она, как оказалось, относится к предыдущей 5-минутке. Будет ли вызвана функция OnCalculate() для FEES с предыдущим значением I? Если нет, то окажется, что свеча подменилась, а мы не в курсе. Если да, то это нарушение "обновления посередине".
Вопрос о том, как поведёт себя QUIK, когда по одному инструменту уже начал рисоваться новый бар, а по другому инструменту - вдруг приходит сделка по предыдущему бару? У меня есть подозрения, что Квику без разницы когда и что он получил. Он просто нарисует, то что пришло.
Нужно, чтобы разработчики пояснили, что будет. Неплохо бы в явном виде опубликовать стандарт, про который Сергей Горохов говорил.
на примере индикатора RSI подскажите пожалуйста что нужно изменить, чтобы код работал на новой версии КВИКа?
Попробовал написать индикатор RSI, в котором используется простое сглаживание по окну (не экспоненциальное, как в коде QUIK) и не используется data source. Получился вариант, приведённый ниже.
Предлагаю обсудить, правильный он или нет, а также то, можно ли как-то этот пример улучшить.
Код
--
-- Реализация индикатора RSI_Simple.
--
Settings = {
Name = "RSI_Simple",
period = 14,
priceType = "Typical", -- Open, High, Low, Close, Volume, Median, Typical, Weighted
line = {
{ Name = "RSI_Simple", Type = TYPE_LINE, Color = RGB(255, 0, 0), },
}
}
function Init()
func = rsiSimple()
return #Settings.line
end
function OnCalculate(index)
return func(index, Settings)
end
local function getPrice(i, priceType)
local s = priceType and string.upper(string.sub(priceType, 1, 1)) or "C"
if s == "O" then
return O(i)
elseif s == "H" then
return H(i)
elseif s == "L" then
return L(i)
elseif s == "C" then
return C(i)
elseif s == "V" then
return V(i)
elseif s == "M" then
return (H(i) + L(i)) / 2
elseif s == "T" then
return (H(i) + L(i) + C(i)) / 3
elseif s == "W" then
return (O(i) + H(i) + L(i) + C(i)) / 4
else
return C(i)
end
end
function rsiSimple()
local window = {} -- окно с рядом цен, по которым строится индикатор
local from = 1 -- индекс начала данных в окне
local to = 0 -- индекс конца данных в окне (если to < from, данных нет)
local lastIndex = 0 -- номер свечи, имеющей цены, при последнем вызове OnCalculate()
local sumUp = 0 -- сумма всех положительных изменений цен за исключением приращения на формирующемся баре
local sumDn = 0 -- сумма всех отрицательных изменений цен за исключением приращения на формирующемся баре
local lastIndicatorValue -- значение индикатора при последнем вызове OnCalculate()
return function(index, settings)
local period = 14
local priceType = "Typical"
if settings then
period = settings.period or period
priceType = settings.priceType or priceType
end
if index == 1 then
window = {}
from = 1
to = 0
lastIndex = 0
sumUp = 0
sumDn = 0
lastIndicatorValue = nil
end
if CandleExist(index) then
local currPrice = getPrice(index, priceType)
if index ~= lastIndex then
-- Новая свеча
if to - from + 1 == period + 1 then
-- Окно заполнено, т.е. имеет длину period + 1, нужно удалить первый элемент и уменьшить суммы
local delta = window[from + 1] - window[from]
if delta > 0 then
sumUp = sumUp - delta
elseif delta < 0 then
sumDn = sumDn - (-delta)
end
window[from] = nil
from = from + 1
end
-- Учёт предыдущей свечи как сформированной, внесём вклад в суммы
if to - 1 >= from then
local delta = window[to] - window[to - 1]
if delta > 0 then
sumUp = sumUp + delta
elseif delta < 0 then
sumDn = sumDn + (-delta)
end
end
to = to + 1
end
lastIndex = index
-- Новая свеча или обновление старой свечи
window[to] = currPrice
if to > from then
local prevPrice = window[to - 1]
local delta = currPrice - prevPrice
local sumUp = sumUp
local sumDn = sumDn
if delta > 0 then
sumUp = sumUp + delta
elseif delta < 0 then
sumDn = sumDn + (-delta)
end
if to - from + 1 == period + 1 then
lastIndicatorValue = (sumUp + sumDn <= 0) and 50 or (100 * sumUp / (sumUp + sumDn))
else
lastIndicatorValue = nil
end
else
lastIndicatorValue = nil
end
end
return lastIndicatorValue
end
end
Sergey Gorokhov написал: Небольшой эксперимент покажет что индикатор по FEES даже не заметит появления значений по SRH7. Связано это с тем что по FEES изменений не было. В случае если в одном окне установить график по нелеквиду и по активному инструменту, Вы увидите что OnCalculate сработает по нелеквиду и CandleExist покажет false на пропущенных свечках только, после того как на нелеквиде появится нормальное значение.
Хорошо. Этот момент неплохо бы отразить в документации, чтобы пользователям было понятно без дополнительных экспериментальных исследований.
Возможно, что я неточно выразился. Имелось в виду, что упорядочивание сделок в квике в таблице всех сделок таково, что время сделки не является монотонно возрастающим. Пример я привёл в своём посте.
Квик как-то должен справляться в таких случаях, объединяя потоки сделок с разных рынков. Хотелось узнать у разработчиков, как именно это происходит в указанном мною случае. А они пока молчат.
обновления посередине быть не может, если Вы об этом.
Хорошо, если это так на самом деле. Только подтвердите, что в описанном ниже случае это тоже так.
Известно, что в данных об обезличенных сделках на фондовом и срочном рынке время задаётся разными часами, которые иногда могут разойтись на несколько миллисекунд. См., например, сегодняшнюю таблицу за 2-х секундный период 10:03:21 -- 10:03:22:
Допустим, что подобное произошло бы в 10:04:59 -- 10:05:00, а в терминале наложены графики SRH7 и FEES. Когда пришла сделка по SRH7, то началась новая 5-минутка. По идее, в этот момент квик может начать вызывать OnCalculate() для пары графиков, в результате видим, что на FEES случилась пустая свеча с T(I) = 10:05.
Потом приходит следующая сделка, а она, как оказалось, относится к предыдущей 5-минутке. Будет ли вызвана функция OnCalculate() для FEES с предыдущим значением I? Если нет, то окажется, что свеча подменилась, а мы не в курсе. Если да, то это нарушение "обновления посередине".
По идее, наиболее корректным действием было бы пересчитать весь индикатор, начиная с индекса 1.
А теперь вопрос: как реализована обработка таких ситуаций в QUIK?
Я понимаю, что это очень редко бывает, но всё равно интересно.
Уже сейчас можно сделать вторую нумерацию. Просто добавив переменную счетчик, которая будет увеличиваться на +1 при CandleExist
ОК.
Я правильно понимаю, что индексы-аргументы OnCalculate() от одного вызова к другому: * либо повторяются; * либо увеличиваются на 1; * либо сбрасываются в 1.
Т.е., например, последовательности ..., 100, 101, 102, 101, 102, 103, ... быть не может?
Ответ на этот вопрос неплохо бы в документацию добавить.
Интересно. Это совсем рушит логику кода из INDICATORS.ZIP.
Наверное, нужен некий способ понять, какому индексу J в DS соответствует индекс I на баре, для которого вызван OnCalculate.
Есть предложение вызывать OnCalculate() с двумя параметрами: номер индекса I бара на графике и номер индекса О в DS. Если бар пустой, пусть J будет nil. Или обеспечить функции трансляции I -> J, J -> I.
Есть у кого-нибудь опытного в индикаторостроении мысли на эту тему? Как сделать удобнее?
Пока кажется, что при вычислении каждого индикатора пользователю нужно будет сначала решать проблему компрессии (выбрасывания пустых баров), а потом уже вычисления индикатора. Несколько неудобно получилось, хоть и более гибко для терминала.