В файловом архиве имеется файл ftp://ftp.quik.ru/public/INDICATORS.zip, который содержит код индикаторов технического анализа. Этот код новички могут рассматривать как пособие по созданию собственных индикаторов.
Однако, в связи с нововведением
Цитата
Изменен вывод информации функциями O, H, L, C, V, T по свечкам, сформированным на пустых интервалах. Теперь, для таких свечек, функция T возвращает время интервала, а функции O, H, L, C, V возвращают nil. Для корректной проверки существования свечи на графике добавлена новая функция CandleExist(). Подробное описание приведено в п. 7.2.5 Руководства пользователя Интерпретатора языка Lua.
в терминале QUIK 7.7, теперь на графиках бывают отсутствующие свечи со значениями nil в качестве цен. Соответственно, предложенные индикаторы перестают работать, т.к. раньше такого не было.
Не могли бы разработчики скорректировать исходный код этих индикаторов, чтобы они учитывали особенность нововведения?
Функции O(I), H(I), L(I), C(I), V(I) в качестве индекса I используют порядковый номер свечи на графике, включая "дыры".
Вопрос:data source ds имеет ту же нумерацию ds:O(I), ds:H(I), ds:L(I), ds:C(I), ds:V(I) или здесь индекс I относится к источнику данных, где нет "дыр" между свечками?
Ещё вопрос о правильности примера в документации QLua, содержащегося в разделе с описанием функции CandleExist. Там есть пример, вычисляющий MovingAverage. Может быть, я чего-то не понимаю, но OnCalculate(index) может вызываться с одним и тем же индексом index несколько раз подряд на формирующейся свече. Кажется, что в этом случае в примере из документации среднее будет неправильным.
Sergey Gorokhov написал: Здравствуйте, В будущем, возможно актуализируем.
Сергей Горохов.
Ваши индикаторы и ранее не работали (неправильно работали) в случае чтения значений с графиков, в которых есть пропущенные свечи. Раз уж выкладываете их от имени компании, уж приведите в порядок, чтобы их результаты соответствовали результатам встроенных в терминал. Иначе это выглядит странно - выкладывать в публичный доступ в качестве обучающего материала заведомо ущербные исходные тексты.
А заодно мы посмотрим, как компания ARQA предлагает нам работать с данными, получаемыми с таких графиков. Надеемся, что у вас есть рецепт ))) Сами-то индикаторы в терминале рассчитываете исходя из других данных, не с графиков.
Просьба ответить на мои вопросы из двух предыдущих постов. Если в документации неправильный пример, его точно надо оттуда убирать и, может быть, заменять на правильный.
s_mike@rambler.ru, Раньше, с этим были проблемы т.к. в разных ситуациях работало по разному. Теперь в 7.7 есть единый стандарт и надо придерживаться его. Если окажется что где-то стандарт не срабатывает это баг.
_sk_ написал: Просьба ответить на мои вопросы из двух предыдущих постов. Если в документации неправильный пример, его точно надо оттуда убирать и, может быть, заменять на правильный.
Пример не правильный, документация будет исправлена.
_sk_ написал: Функции O(I), H(I), L(I), C(I), V(I) в качестве индекса I используют порядковый номер свечи на графике, включая "дыры".
Вопрос:data source ds имеет ту же нумерацию ds:O(I), ds:H(I), ds:L(I), ds:C(I), ds:V(I) или здесь индекс I относится к источнику данных, где нет "дыр" между свечками?
нет, нумерация будет разной т.к. на графике пустые интервалы получаются из-за наложения других графиков, а в DS никакого наложения быть не может.
Интересно. Это совсем рушит логику кода из INDICATORS.ZIP.
Наверное, нужен некий способ понять, какому индексу J в DS соответствует индекс I на баре, для которого вызван OnCalculate.
Есть предложение вызывать OnCalculate() с двумя параметрами: номер индекса I бара на графике и номер индекса О в DS. Если бар пустой, пусть J будет nil. Или обеспечить функции трансляции I -> J, J -> I.
Есть у кого-нибудь опытного в индикаторостроении мысли на эту тему? Как сделать удобнее?
Пока кажется, что при вычислении каждого индикатора пользователю нужно будет сначала решать проблему компрессии (выбрасывания пустых баров), а потом уже вычисления индикатора. Несколько неудобно получилось, хоть и более гибко для терминала.
Sergey Gorokhov написал: s_mike@rambler.ru , Раньше, с этим были проблемы т.к. в разных ситуациях работало по разному. Теперь в 7.7 есть единый стандарт и надо придерживаться его. Если окажется что где-то стандарт не срабатывает это баг.
что говорит стандарт о времени пропущеннлй свечи? Оно nil или содеожит валидные значения?
а касается придерживаться - вот и расскажите, как это делать. На примере той же ema, построенной на минутеом графике от неликвидного инструмента, где пропуски идут чаще, чем период ema. Хотелось бы посмотреть на этот код от разработчиков в качестве руководства к действию.
Уже сейчас можно сделать вторую нумерацию. Просто добавив переменную счетчик, которая будет увеличиваться на +1 при CandleExist
ОК.
Я правильно понимаю, что индексы-аргументы OnCalculate() от одного вызова к другому: * либо повторяются; * либо увеличиваются на 1; * либо сбрасываются в 1.
Т.е., например, последовательности ..., 100, 101, 102, 101, 102, 103, ... быть не может?
Ответ на этот вопрос неплохо бы в документацию добавить.
Уже сейчас можно сделать вторую нумерацию. Просто добавив переменную счетчик, которая будет увеличиваться на +1 при CandleExist
ОК.
Я правильно понимаю, что индексы-аргументы OnCalculate() от одного вызова к другому: * либо повторяются; * либо увеличиваются на 1; * либо сбрасываются в 1.
Т.е., например, последовательности ..., 100, 101, 102, 101, 102, 103, ... быть не может?
Ответ на этот вопрос неплохо бы в документацию добавить.
обновления посередине быть не может, если Вы об этом.
обновления посередине быть не может, если Вы об этом.
Хорошо, если это так на самом деле. Только подтвердите, что в описанном ниже случае это тоже так.
Известно, что в данных об обезличенных сделках на фондовом и срочном рынке время задаётся разными часами, которые иногда могут разойтись на несколько миллисекунд. См., например, сегодняшнюю таблицу за 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?
Я понимаю, что это очень редко бывает, но всё равно интересно.
_sk_ написал: Известно, что в данных об обезличенных сделках на фондовом и срочном рынке время задаётся разными часами, которые иногда могут разойтись на несколько миллисекунд.
Что значит "задаётся разными часами"?
Надо делать так, как надо. А как не надо - делать не надо.
Возможно, что я неточно выразился. Имелось в виду, что упорядочивание сделок в квике в таблице всех сделок таково, что время сделки не является монотонно возрастающим. Пример я привёл в своём посте.
Квик как-то должен справляться в таких случаях, объединяя потоки сделок с разных рынков. Хотелось узнать у разработчиков, как именно это происходит в указанном мною случае. А они пока молчат.
Сергей, на примере индикатора RSI подскажите пожалуйста что нужно изменить, чтобы код работал на новой версии КВИКа? Спасибо
Код
Settings = {
Name = "*RSI (Relative Strength Index)",
round = "off",
Period = 14,
VType = "Close", --Open, High, Low, Close, Volume, Median, Typical, Weighted, Difference
line = {{
Name = "RSI",
Type = TYPE_LINE,
Color = RGB(255, 0, 0)
}
}
}
function Init()
func = RSI()
return #Settings.line
end
function OnCalculate(Index)
return func(Index, Settings)
end
function RSI() --Relative Strength I("RSI")
local Up = {}
local Down = {}
local val_Up = {}
local val_Down = {}
return function (I, Fsettings, ds)
local Out = nil
local Fsettings=(Fsettings or {})
local P = (Fsettings.Period or 14)
local VT = (Fsettings.VType or "Close")
local R = (Fsettings.round or "off")
if I == 1 then
Up[I] = 0
Down[I] = 0
end
if I>1 then
local Val = Value(I,VT,ds)
local ValPrev = Value(I-1,VT,ds)
if ValPrev < Val then
Up[I] = Val - ValPrev
else
Up[I] = 0
end
if ValPrev > Val then
Down[I] = ValPrev - Val
else
Down[I] = 0
end
if (I == P) or (I == P+1) then
local sumU = 0
local sumD = 0
for i = I-P+1, I do
sumU = sumU + Up[i]
sumD = sumD + Down[i]
end
val_Up[I] = sumU/P
val_Down[I] = sumD/P
end
if I > P+1 then
val_Up[I] = (val_Up[I-1] * (P-1) + Up[I]) / P
val_Down[I] = (val_Down[I-1] * (P-1) + Down[I]) / P
end
if I >= P then
Out = 100 / (1 + (val_Down[I] / val_Up[I]))
return rounding(Out, R)
end
end
end
end
function rounding(num, round)
if round and string.upper(round)== "ON" then round=0 end
if num and tonumber(round) then
local mult = 10^round
if num >= 0 then return math.floor(num * mult + 0.5) / mult
else return math.ceil(num * mult - 0.5) / mult end
else return num end
end
function Value(I,VType,ds)
local Out = nil
VType=(VType and string.upper(string.sub(VType,1,1))) or "A"
if VType == "O" then --Open
Out = (O and O(I)) or (ds and ds:O(I))
elseif VType == "H" then --High
Out = (H and H(I)) or (ds and ds:H(I))
elseif VType == "L" then --Low
Out = (L and L(I)) or (ds and ds:L(I))
elseif VType == "C" then --Close
Out = (C and C(I)) or (ds and ds:C(I))
elseif VType == "V" then --Volume
Out = (V and V(I)) or (ds and ds:V(I))
elseif VType == "M" then --Median
Out = ((Value(I,"H",ds) + Value(I,"L",ds)) / 2)
elseif VType == "T" then --Typical
Out = ((Value(I,"M",ds) * 2 + Value(I,"C",ds))/3)
elseif VType == "W" then --Weighted
Out = ((Value(I,"T",ds) * 3 + Value(I,"O",ds))/4)
elseif VType == "D" then --Difference
Out = (Value(I,"H",ds) - Value(I,"L",ds))
elseif VType == "A" then --Any
if ds then Out = ds[I] else Out = nil end
end
return Out
end
_sk_, На все вопросы вида "а что будет если" в большинстве случаев Вы сами легко можете найти ответ. И сейчас именно такой случай. Небольшой эксперимент покажет что индикатор по FEES даже не заметит появления значений по SRH7. Связано это с тем что по FEES изменений не было. В случае если в одном окне установить график по нелеквиду и по активному инструменту, Вы увидите что OnCalculate сработает по нелеквиду и CandleExist покажет false на пропущенных свечках только, после того как на нелеквиде появится нормальное значение.
Sergey Gorokhov написал: Небольшой эксперимент покажет что индикатор по FEES даже не заметит появления значений по SRH7. Связано это с тем что по FEES изменений не было. В случае если в одном окне установить график по нелеквиду и по активному инструменту, Вы увидите что OnCalculate сработает по нелеквиду и CandleExist покажет false на пропущенных свечках только, после того как на нелеквиде появится нормальное значение.
Хорошо. Этот момент неплохо бы отразить в документации, чтобы пользователям было понятно без дополнительных экспериментальных исследований.
на примере индикатора 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 написал: В случае если в одном окне установить график по нелеквиду и по активному инструменту, Вы увидите что OnCalculate сработает по нелеквиду и CandleExist покажет false на пропущенных свечках только, после того как на нелеквиде появится нормальное значение.
Не совсем так. OnCalculate на пустых интервалах вызывается только при пересчёте индикатора (если, например, в окне редактирования нажать "Применить" или "ОК"). В реалтайм OnCalculate на пустых интервалах не вызывается.
Надо делать так, как надо. А как не надо - делать не надо.
_sk_ написал: Потом приходит следующая сделка, а она, как оказалось, относится к предыдущей 5-минутке. Будет ли вызвана функция OnCalculate() для FEES с предыдущим значением I? Если нет, то окажется, что свеча подменилась, а мы не в курсе. Если да, то это нарушение "обновления посередине".
Вопрос о том, как поведёт себя QUIK, когда по одному инструменту уже начал рисоваться новый бар, а по другому инструменту - вдруг приходит сделка по предыдущему бару? У меня есть подозрения, что Квику без разницы когда и что он получил. Он просто нарисует, то что пришло.
Надо делать так, как надо. А как не надо - делать не надо.
Давно так не смеялся... Стандарт это когда выбираешь нужный размер одежды из набора стандартных, а кода юбка не выше колена... Еще хуже, когда ботинки только одного стандарта.
_sk_ написал: Потом приходит следующая сделка, а она, как оказалось, относится к предыдущей 5-минутке. Будет ли вызвана функция OnCalculate() для FEES с предыдущим значением I? Если нет, то окажется, что свеча подменилась, а мы не в курсе. Если да, то это нарушение "обновления посередине".
Вопрос о том, как поведёт себя QUIK, когда по одному инструменту уже начал рисоваться новый бар, а по другому инструменту - вдруг приходит сделка по предыдущему бару? У меня есть подозрения, что Квику без разницы когда и что он получил. Он просто нарисует, то что пришло.
Нужно, чтобы разработчики пояснили, что будет. Неплохо бы в явном виде опубликовать стандарт, про который Сергей Горохов говорил.
_sk_, Вы немного ушли от темы, я говорил ровно о том что написано в списке изменений.
Цитата
Изменен вывод информации функциями O, H, L, C, V, T по свечкам, сформированным на пустых интервалах. Теперь, для таких свечек, функция T возвращает время интервала, а функции O, H, L, C, V возвращают nil. Для корректной проверки существования свечи на графике добавлена новая функция CandleExist(). Подробное описание приведено в п. 7.2.5 Руководства пользователя Интерпретатора языка Lua.
Старатель написал: Не совсем так. OnCalculate на пустых интервалах вызывается только при пересчёте индикатора (если, например, в окне редактирования нажать "Применить" или "ОК"). В реалтайм OnCalculate на пустых интервалах не вызывается.
Старатель написал: Вопрос о том, как поведёт себя QUIK, когда по одному инструменту уже начал рисоваться новый бар, а по другому инструменту - вдруг приходит сделка по предыдущему бару?
Вопрос касается того как поведет себя OnCalculate. Он поведет себя ровно также как если бы был только 1 график, т.к. OnCalculate привязан к конкретному источнику данных.
Космонавт написал: Сергей, на примере индикатора RSI подскажите пожалуйста что нужно изменить, чтобы код работал на новой версии КВИКа?
Как уже говорилось, индикаторы будут переписаны. Не сейчас, но будут. А как именно, уже вопрос внутренний. Сейчас же Вы можете самостоятельно его исправить на Ваше усмотрение, т.к. индикаторы предоставляются "как есть" т.е. допускаются любые правки на свое усмотрение.
Sergey Gorokhov написал: Он поведет себя ровно также как если бы был только 1 график, т.к. OnCalculate привязан к конкретному источнику данных.
Опять не совсем "ровно". OnCalculate будет вызываться при изменении источника данных, к которому привязан, но индекс свечи будет передаваться с учётом всех графиков на диаграмме.
Даже не знаю, хорошо это или плохо Допустим, нам надо построить индикатор по двум графикам. И на "ведущем" графике в какой-то промежуток времени отсутствуют сделки. Тогда мы физически не сможем построить индикатор, пока не получим следующую сделку. Есть лекарство?
Надо делать так, как надо. А как не надо - делать не надо.
Вопрос не в том, что выводить - это зависит от индикатора, можно и вполне осмысленное значение. Вопрос в том, как выводить, если OnCalculate не вызывается?
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: Вопрос не в том, что выводить - это зависит от индикатора, можно и вполне осмысленное значение. Вопрос в том, как выводить, если OnCalculate не вызывается?
Такой возможности нет. Разве что на каждую из бумаг повесить по индикатору.
валерий написал: Вопрос тем кто пользует 77, сам я с большииим лагом обновляюсь. А на графиках как нововведение отражается? Пустые интервалы теперь убрать нельзя?
На графиках ничего в этом месте не менялось. Менялись конкретно те Lua функции о которых сказано в списке изменений.
Слава богу! Да еще, прошу зафиксировать предложение по стандарту. На графике клиент выбирает нужны пустые интервалы или нет. На сдвоенных графиках выбирает чем пустые интервалы заполняются: нулями, предыдущими значениями или только объем нулями, а остальное предыдущими. Ну а функции клуа - визивиг.
К сожалению мы не можем зарегистрировать пожелание в таком виде, т.к. оно противоречит концепции развития нашего ПО. Иными словами, мы не можем показать на графике то чего реально нет. Уточните зачем Вам это? Возможно есть другой путь решения.
Концепции Амиброкера это почемуто не противоречит и пользователей устраивает. Я же не сам это придумал. Любой индикатор это вообще то, чего реально нет. Это отражение реальности, а не сама реальность. Способов отражения, в том числе и пропусков, несчетное множество. А предложение позволяет пользователю выбрать или самое простое и ходовое zero-order hold или полную своду творчества или компромисс, когда по нулевым объемам можно выявить пропуски и как-то дополнить зох.
В quik 7.2.2.3 непосредственно после установки фильтра прогоняется цикл вызовов OnCalculate() c первой свечки, подавая на вход в т.ч. свечки, которые должны быть отфильтрованы, как нулевые (работает только T(I), остальное по нулям). При последующих перезапусках цикла OnCalculate() (например, после вызова окна настроек индикатора) эти отфильтрованные свечки уже не попадают в OnCalculate(). Также этой беды не происходит, если индикатор добавляется в окно графика с уже установленным фильтром. QUIK clients support в свое время писал, что в более поздних версиях QUIK этот баг был исправлен.
Но на самом деле, это не проблема. Существуют вполне логичные ситуации, когда в OnCalculate() должны попадать пустые свечки. Это стандартная ситуация, когда в одно окно графиков выводятся два разных инструмента, например, RIH7 и RTSI, или ликвид и неликвид на малом тайм-фрейме. Не важно какова причина попадания пустой свечки в OnCalculate() - вышеописанная или баг терминала. Пустые свечки должны либо игнорироваться (OnCalculate() возвращает nil, получаем дырку на графике, так поступает сам QUIK), либо OnCalculate() должен возвращать прежнее значение индикатора (получаем горизонтальную линию, если Вам так больше нравится). Что же касается расчета индикаторов, основанных на истории свечек, то выше уже было дано достаточно советов как организовать вычисления при наличии пустых свечек, главное, повторю, объявите счетчик непустых свечек и используйте его при обращении к массивам с историей.
валерий написал: Кстати, хотелось бы уточнить. Если на графике стоит фильтр по времени, то отсутствующие свечи все равно теперь будут подаваться в функцию индикатора?
Как связан фильтр по времени с наличием либо отсутствием пустых свечек?
валерий написал: Это в 7.2. А что в 7.7? Организовать вычисления при наличии пустых свечек конечно можно, но это накладно и в 99 случаях не нужно.
Никто Вас не заставляет "производить вычисления при наличии пустых свечек", просто пропускайте их и все. Именно это корректно позволяют сделать изменения в 7.7.