В последних версиях терминала если добавить индикатор где число линий, например, больше 50, то терминал просто "умирает". Этот же индикатор в 7-ой версии вполне себе работал, даже не на одном графике.
Вот простейший пример, ничего, по сути, не делающий, а просто выводит линии на график, демонстрирующий проблему.
Код
local lines = 100
Settings = {}
Settings.Name = "*test_lines"
Settings.price = 66960
Settings.delta = 1.0
function Init()
Settings.line = {}
for i = 1, lines do
Settings.line[i] = {}
Settings.line[i] = {Color = RGB(185, 185, 185), Type = TYPET_BAR, Width = 2}
end
return lines
end
function OnChangeSettings()
Init()
end
function OnCalculate(index)
if index < Size() then return end
for i = 1, lines do
SetRangeValue(i, index-100, index-1, Settings.price-i*Settings.delta)
end
return
end
Пример не совсем тот. В примере Вы выводите значения с функциями Lua. Это н совсем то, когда вывод делается с помощью return ... значения индикаторов. ----------------------- Для чистоты эксперимента сделайте вывод значений через return. ------------------------- У меня 42 индикатора. Отображаются практически мгновенно. Версия 8.7.1.3
Пример именно такой, какой нужен, т.к. я по большей части вывожу данные назад по барам, т.к. алгоритм не предполагает простого линейного расчёта. Иначе можно говорить, что теперь у методов установки данных на график есть ограничения. Или проблема, что более вероятно, т.к. при добавлении оного на график потребление ресурсов процессора повышается с 0% до 9-10%. Что тоже не много, но терминал уже не отвечает.
Nikolay написал: SetRangeValue(i, index-100, index-1, Settings.price-i*Settings.delta)
В примере каждый раз выводится 100 значений 100 линий. Это 10 000 вызовов функции SetRangeValue, в которой есть вычисления на луа. OnCalculate(index) вызывается на каждый тик. В результате у Вас цикл не успевает завершится до получения нового тика. -------------------------- Попробуйте измерить время вывода одного значения, чтобы понять где тормозит.
Поясняю. Вы стрите индикатор на 99 значений закрытых свечей плюс один тик текущей свечи. Какой в этом смысл. Полагаю, что у вас индикатор не изменяется назад на 99 свечей на каждом тике текущей свечи.
nikolz написал: Зачем строить индикатор назад на каждый тик?
Это для примера. Реальный строится только каждый бар. на демо счёте по Сбербанку, где сделка раз минуту - этот пример вполне показательный. Впрочем, добавить кеш уже обработанных индексов - не проблема, проблема в другом.
Не берусь судить о том, что в очередной раз "изобрели и наваяли" Разработчики, хочу лишь добавить, что в версиях 12.*.*.* при входе в терминал по долгу читаются самописные луа индикаторы (значительно по долгу). Хотя допускаю, что это и моих рук дело, может быть?
Тема написания индикаторов на луа многократно обсуждалась и разбиралась на форуме, и тем не менее, нет внутренней уверенности, что реализация луа индикаторов как минимум оптимальна? Вот и я в очередной раз столкнулся с проблемой создания надежной реализации индикаторов при реализации подхода по Wilder (реализация индикаторов и стратегий). Собственно трудности вызывает не сам алгоритм расчетов, а особенности реализации скриптов луа в QUIK.
Задачу ставил следующую: 1) Скрипт должен использоваться без переделок, как в OnCalculate, так и в потоке луа; 2) Одинаково быстро работать на разных тайм фреймах; 3) Технологическое исполнение (правила написания) должны быть едины, выполняться принцип создания "по Образу и Подобию". Код через класс луа, требует доработки, и вызывает у меня некоторые сложности в реализации, хотя интуитивно на мой взгляд и более предпочтителен, так как хранит свое внутреннее состояние (инкапсулирует). Вот что получилось при использовании подхода с замыканием, код привожу ниже, там же см. в комментариях и порядок написания, единая технология написания скрипта, на мой взгляд логична?
Код
function Wilder.RSI_Indicator()
-- 1. Локальные утилиты для оптимизации
local math_abs = math.abs
local math_max = math.max
local math_min = math.min
local math_floor = math.floor
local string_format = string.format
-- Форматирование даты и времени
local get_date = function(td)
return td and string_format("%.4d%.2d%.2d", td.year, td.month, td.day) or "00000000"
end
local get_time = function(td)
return td and string_format("%.2d%.2d%.2d", td.hour, td.min, td.sec) or "000000"
end
-- Функция округления
local round = function(x, n)
n = n or 0
local mult = 10 ^ n
return x >= 0 and math_floor(x * mult + 0.5) / mult or math_floor(x * mult - 0.5) / mult
end
-- 2. Функции сглаживания
local smoothing = {
EMA = function(new_val, prev, period)
local alpha = 2 / (period + 1)
return alpha * new_val + (1 - alpha) * prev
end,
SMMA = function(new_val, prev, period, bars_count)
if bars_count <= period then
return (prev * (bars_count - 1) + new_val) / bars_count
else
return (prev * (period - 1) + new_val) / period
end
end
}
-- 3. Состояние индикатора
local cache = {
initialized = false,
last_index = 0,
history = {}, -- Хранилище для исторических значений
Configs = {
period = 14,
method = "SMMA",
max_history = 100
},
prev_price = nil
}
return function(I, F, ds)
-- A. Обновление параметров
if F then
cache.Configs.period = F.Period or cache.Configs.period
cache.Configs.method = F.Method or cache.Configs.method
cache.Configs.max_history = F.Max_History or cache.Configs.max_history
end
local period = cache.Configs.period
local method = cache.Configs.method
-- B. Проверка нового бара
local is_new_bar = I > cache.last_index
-- C. Очистка старых данных при новом баре
if is_new_bar then
cache.last_index = I
-- Удаляем устаревшие данные
local min_index = I - cache.Configs.max_history
for i = min_index - 10, min_index do
if cache.history[i] then
cache.history[i] = nil
end
end
end
-- D. Получение текущей цены закрытия
local current_price = Value(I, "C", ds)
if not current_price then
return {
d = "00000000",
t = "000000",
rsi = 50,
rsi1 = 50,
rsi2 = 50,
rsi3 = 50,
gain = 0,
loss = 0,
avg_gain = 0,
avg_loss = 0
}
end
-- E. Инициализация записи для текущего индекса
if not cache.history[I] then
cache.history[I] = {
price = current_price,
change = 0,
gain = 0,
loss = 0,
avg_gain = 0,
avg_loss = 0,
rsi = 50
}
end
-- F. Расчет изменения цены
local prev_price = cache.prev_price
if not prev_price then
-- Для первого бара используем текущую цену как предыдущую
prev_price = current_price
end
local change = current_price - prev_price
cache.history[I].change = change
cache.prev_price = current_price -- Сохраняем для следующего вызова
-- G. Расчет положительных и отрицательных изменений
local gain = math_max(change, 0)
local loss = math_max(-change, 0)
cache.history[I].gain = gain
cache.history[I].loss = loss
-- H. Расчет средних значений
if I == 1 then
-- Первый бар
cache.history[I].avg_gain = gain
cache.history[I].avg_loss = loss
else
local prev_entry = cache.history[I-1] or cache.history[I]
local bars_count = math_min(I, period)
if method == "SMMA" then
cache.history[I].avg_gain = smoothing.SMMA(
gain,
prev_entry.avg_gain,
period,
bars_count
)
cache.history[I].avg_loss = smoothing.SMMA(
loss,
prev_entry.avg_loss,
period,
bars_count
)
else -- EMA
cache.history[I].avg_gain = smoothing.EMA(
gain,
prev_entry.avg_gain,
period
)
cache.history[I].avg_loss = smoothing.EMA(
loss,
prev_entry.avg_loss,
period
)
end
end
-- I. Расчет RSI
local avg_gain = cache.history[I].avg_gain
local avg_loss = cache.history[I].avg_loss
local rs = (avg_loss > 1e-5) and (avg_gain / avg_loss) or 0
local rsi = 100 - (100 / (1 + rs))
-- Корректировка граничных значений
rsi = math_max(0, math_min(100, rsi))
cache.history[I].rsi = rsi
-- J. Получение предыдущих значений
local prev1 = cache.history[I-1] or {rsi = 50}
local prev2 = cache.history[I-2] or {rsi = 50}
local prev3 = cache.history[I-3] or {rsi = 50}
-- K. Формирование результата
local td = ds:T(I)
local Out = {
d = get_date(td),
t = get_time(td),
rsi = round(rsi, 2),
rsi1 = round(prev1.rsi, 2),
rsi2 = round(prev2.rsi, 2),
rsi3 = round(prev3.rsi, 2),
gain = round(gain, 4),
loss = round(loss, 4),
avg_gain = round(avg_gain, 4),
avg_loss = round(avg_loss, 4),
_cache = cache.history[I]
}
Wilder.Log( tostring(I)..') '.. Out.d ..' / '.. Out.t
..'; rsi = ' .. tostring(Out.rsi)
..'; rsi1 = ' .. tostring(Out.rsi1)
..'; rsi2 = ' .. tostring(Out.rsi2)
..'; rsi3 = ' .. tostring(Out.rsi3)
..'; gain = ' .. tostring(Out.gain)
..'; loss = ' .. tostring(Out.loss)
--..'; atr = ' .. tostring(Out.atr)
..'; avg_gain = ' .. tostring(Out.avg_gain)
..'; avg_loss = ' .. tostring(Out.avg_loss)
--..'; trend = ' .. tostring(Out.trend)
--..'; trend1 = ' .. tostring(Out.trend1)
--..'; trend2 = ' .. tostring(Out.trend2)
--..'; signal = ' .. tostring(Out.signal)
--..'; signal1 = ' .. tostring(Out.signal1)
--..'; signal2 = ' .. tostring(Out.signal2)
)--[[--]]
cache.initialized = true
return Out
end
end
function Init() Settings.line = {} for i = 1, lines do Settings.line = {} Settings.line = {Color = RGB(185, 185, 185), Type = TYPET_BAR, Width = 2} end return lines end
function OnChangeSettings() Init() end
function OnCalculate(index) if index < Size() then return end for i = 1, lines do SetRangeValue(i, index-100, index-1, Settings.price-i*Settings.delta) end return end
Nikolay написал: Все же эта тема посвящена конкретной технической проблеме терминала.
Nikolay, Весь этот форум посвящён одной большой проблеме, если Вы думаете что устранив данную, не будет похожей, через некоторое время, я думаю мягко говоря Вы ошибаетесь.Думаю нужна профессиональная смекалка, или как тут выражался пользователь "покумекать", чтоб по крайней мере следующая проходила мимо.
Код который я выложил несет в себе две основные особенности. 1) Буферизация. (Зачем скажем 65 000 свечей если окно 14 бар?); 2) Своевременную очистку данных (Зачем скажем 65 000 свечей хранить, если окно 14 бар); ну и конечно момент обновлений и расчётов, зачем рассчитывать каждый тик, если квик с трудом обновляет данные за одну секунду?
Nikolay, Весь этот форум посвящён одной большой проблеме, если Вы думаете что устранив данную, не будет похожей, через некоторое время, я думаю мягко говоря Вы ошибаетесь.Думаю нужна профессиональная смекалка, или как тут выражался пользователь "покумекать", чтоб по крайней мере следующая проходила мимо.
Код который я выложил несет в себе две основные особенности. 1) Буферизация. (Зачем скажем 65 000 свечей если окно 14 бар?); 2) Своевременную очистку данных (Зачем скажем 65 000 свечей хранить, если окно 14 бар); ну и конечно момент обновлений и расчётов, зачем рассчитывать каждый тик, если квик с трудом обновляет данные за одну секунду?
Я уже говорил Вам, что я прекрасно знаю как и что делать для оптимизации кода индикатора. И рабочий код не рассчитывает ничего каждый тик, не хранит историю на все бары, т.к. это бессмысленно и т.д. Здесь предоставлен пример, воспроизводящий проблему. Эта тема посвящена только одному - большое число линий на графике. Все. Хотя, если подумать, странно ожидать, что терминал не может справится с такой простой нагрузкой даже каждый тик.
Впрочем, Вам явно необходимо высказаться. Вот пример без расчёта на каждый тик. И он точно также "убивает" терминал. У Вас работает, у меня нет. Поэтому мне интересно мнение разработчиков.
Код
local lines = 100
Settings = {}
Settings.Name = "*test_lines"
Settings.price = 313
Settings.delta = 0.01
local cache = {}
function Init()
Settings.line = {}
for i = 1, lines do
Settings.line[i] = {}
Settings.line[i] = {Color = RGB(185, 185, 185), Type = TYPET_BAR, Width = 2}
end
cache = {}
return lines
end
function OnChangeSettings()
Init()
end
function OnCalculate(index)
if index < Size() then return end
if cache[index] then return end
for i = 1, lines do
SetRangeValue(i, index-100, index-1, Settings.price-i*Settings.delta)
end
cache[index] = true
message('calc '..tostring(index))
return
end
Nikolay, версия с cache на SBER, 15 ничего не показывает у меня при любом delta (хотя SetRangeValue возвращает true), и не вешает терминал. Я тестил без подключения к серверу.