При написании своего специфического индикатора иногда возникает необходимость делать некоторые начальные настройки в его работе в зависимости от того, для какого инструмента он строится. Однако, из Init() вызывать getDataSourceInfo() бессмысленно (мы получим информацию только о тайм-фрейме). Перезапускать рабочее место Quik после добавления индикатора на график, как указано в "Руководстве пользователя QLua" как-то не хочется. (На самом, деле нужно как-то заставить Quik повторно вызвать Init() индикатора, что можно, впрочем, сделать и без перезапуска рабочего места, а просто некоторыми манипуляциями с самим окном графика. Но это тоже не выход).
Что посоветуете?
Первое, что мне самому приходит в голову, это выполнить нужные настройки при первом же вызове OnCalculate(). getDataSourceInfo() здесь уже будет работать корректно, т.к. к этому моменту Quik уже "привязал" новый индикатор к источнику данных. Но это тоже не самый красивый ход: каждый раз проверять индекс, отлавливая именно первую свечку, чтобы не заниматься настройками вообще при каждом вызове OnCalculate() и т.д.
Вопрос к разработчикам: я так понимаю, что, при добавлении Lua индикатора на график, Init() вызывается до "подключения" к источнику данных. А что мешает его вызвать после? Так ли важно знать количество линий на индикаторе до "подключения" к источнику данных? Если обеспечить корректную работу getDataSourceInfo() при первом после добавления индикатора на график вызове Init() все же нельзя, то хотел бы попросить разработчиков добавить дополнительный СallBack, типа Init2() после "подключения" к источнику данных, но до первого вызова OnCalculate(). Можно, например, это делать не для каждого индикатора, а только если Init() вернет "соответствующую просьбу".
Здравствуйте, Это то понятно, что решить задачу можно, отлавливая вызов для первой свечки, но это лишние накладные расходы, пускай и мизерные. А на медленных машинках, для процессов, исполняемых интерпретатором, если кто-то решит повесить много индикаторов, в каждом из которых по несколько линий ...:), уже можно заметить подтормаживания.
Но не в этом суть. Не прослеживается единообразия в логике вызовов Init() со стороны Quik: при исходном добавлении индикатора на график, Init() вызывается до привязки к источнику данных, а при замене инструмента Init(), почему-то, вызывается уже после привязки к новому инструменту.
Алексей написал: Не прослеживается единообразия в логике вызовов Init() со стороны Quik: при исходном добавлении индикатора на график, Init() вызывается до привязки к источнику данных, а при замене инструмента Init(), почему-то, вызывается уже после привязки к новому инструменту.
При добавлении индикатора сразу указывается Источник данных. Поэтому описанное поведение больше похоже на ошибку в логике.
Надо делать так, как надо. А как не надо - делать не надо.
Алексей написал: Не прослеживается единообразия в логике вызовов Init() со стороны Quik: при исходном добавлении индикатора на график, Init() вызывается до привязки к источнику данных, а при замене инструмента Init(), почему-то, вызывается уже после привязки к новому инструменту.
При добавлении индикатора сразу указывается Источник данных. Поэтому описанное поведение больше похоже на ошибку в логике.
В том-то и дело, что при вызове getDataSourceInfo() из Init() Вы не получите информацию об инструменте, на основе свечек которого должен строиться индикатор. На это даже сами разработчики Quik указывают в "Руководстве пользователя QLua". Имеется в виду самый первый вызов Init() непосредственно после добавления индикатора в окно графика.
To: Старатель. Под "привязкой" я имею в виду не действия пользователя, естественно указавшего для какого инструмента он желает видеть индикатор, а инициализацию внутренних служебных таблиц о самом инструменте, а также о ds (DataSource) с информацией о свечках, о методах H L O C V Size тд., которую Quik в вышеописанном случае производит уже только после вызова Init().
Алексей написал: Это то понятно, что решить задачу можно, отлавливая вызов для первой свечки, но это лишние накладные расходы, пускай и мизерные. А на медленных машинках, для процессов, исполняемых интерпретатором, если кто-то решит повесить много индикаторов, в каждом из которых по несколько линий ...:),
1) То что проверка первой свечи приводит к "накладным расходам" не более чем просто слова. 2) Гипотетическое создание Init2 совершенно никак не позволит изменить ситуацию. И даже если ее добавить, это будет ровно тоже самое что проверка первой свечи. 3) Решение в виде проверки первой свечи, в полной мере решает задачу и аргументов которые не позволят ее решить указанным способом, Вы так и не привели.
Алексей написал: Это то понятно, что решить задачу можно, отлавливая вызов для первой свечки, но это лишние накладные расходы, пускай и мизерные. А на медленных машинках, для процессов, исполняемых интерпретатором, если кто-то решит повесить много индикаторов, в каждом из которых по несколько линий ...:),
1) То что проверка первой свечи приводит к "накладным расходам" не более чем просто слова. 2) Гипотетическое создание Init2 совершенно никак не позволит изменить ситуацию. И даже если ее добавить, это будет ровно тоже самое что проверка первой свечи. 3) Решение в виде проверки первой свечи, в полной мере решает задачу и аргументов которые не позволят ее решить указанным способом, Вы так и не привели.
отлавливание первой свечи оператором if indx==1 then ... end на медленных машинах займет не более 50 мкс. А поступление очередных данных происходит не чаще, чем раз в 100 мс. Разница примерно в 2000 раз. А задержка реакции OC не менее 10 мс. Разница в 200 раз. А задержка отправки коротких сообщений с компа на сервер брокера типа заявок, может составить 200 мс Разница примерно в 4000 раз. За что боремся?
Алексей написал: Это то понятно, что решить задачу можно, отлавливая вызов для первой свечки, но это лишние накладные расходы, пускай и мизерные. А на медленных машинках, для процессов, исполняемых интерпретатором, если кто-то решит повесить много индикаторов, в каждом из которых по несколько линий ...:),
1) То что проверка первой свечи приводит к "накладным расходам" не более чем просто слова. 2) Гипотетическое создание Init2 совершенно никак не позволит изменить ситуацию. И даже если ее добавить, это будет ровно тоже самое что проверка первой свечи. 3) Решение в виде проверки первой свечи, в полной мере решает задачу и аргументов которые не позволят ее решить указанным способом, Вы так и не привели.
отлавливание первой свечи оператором if indx==1 then ... end на медленных машинах займет не более 50 мкс. А поступление очередных данных происходит не чаще, чем раз в 100 мс. Разница примерно в 2000 раз. А задержка реакции OC не менее 10 мс. Разница в 200 раз. А задержка отправки коротких сообщений с компа на сервер брокера типа заявок, может составить 200 мс Разница примерно в 4000 раз. За что боремся?
Я же нарисовал смайлик. И сразу далее написал, что естественно не в этом суть. А суть вопроса к quik в том, что:
Не прослеживается единообразия в логике вызовов Init() со стороны Quik: при исходном добавлении индикатора на график, Init() вызывается до привязки к источнику данных, а при замене инструмента Init(), почему-то, вызывается уже после привязки к новому инструменту.
Алексей написал: Это то понятно, что решить задачу можно, отлавливая вызов для первой свечки, но это лишние накладные расходы, пускай и мизерные. А на медленных машинках, для процессов, исполняемых интерпретатором, если кто-то решит повесить много индикаторов, в каждом из которых по несколько линий ... :) ,
1) То что проверка первой свечи приводит к "накладным расходам" не более чем просто слова. 2) Гипотетическое создание Init2 совершенно никак не позволит изменить ситуацию. И даже если ее добавить, это будет ровно тоже самое что проверка первой свечи. 3) Решение в виде проверки первой свечи, в полной мере решает задачу и аргументов которые не позволят ее решить указанным способом, Вы так и не привели.
отлавливание первой свечи оператором if indx==1 then ... end на медленных машинах займет не более 50 мкс. А поступление очередных данных происходит не чаще, чем раз в 100 мс. Разница примерно в 2000 раз. А задержка реакции OC не менее 10 мс. Разница в 200 раз. А задержка отправки коротких сообщений с компа на сервер брокера типа заявок, может составить 200 мс Разница примерно в 4000 раз. За что боремся?
Я же нарисовал смайлик. И сразу далее написал, что естественно не в этом суть. А суть вопроса к quik в том, что:
Не прослеживается единообразия в логике вызовов Init() со стороны Quik: при исходном добавлении индикатора на график, Init() вызывается до привязки к источнику данных, а при замене инструмента Init(), почему-то, вызывается уже после привязки к новому инструменту.
А в квике как мультике про простоквашено (Письмо дяди Федора). Начинал писать квик один писатель потом второй и т д. Стратегию построения КВИКА разработали еще в прошлом веке. Вот и нет однообразия. А читатели - это клиенты брокеров.
еще замечу, что в init ставить настройку каких либо параметров не имеет смысла, так как он не вызывается при изменении параметров индикаторов. Поэтому без проверки на 1 индекса Вы все рано не обойдетесь. И смысла делать как вы хотите нет никакого.
Николай Камынин написал: еще замечу, что в init ставить настройку каких либо параметров не имеет смысла, так как он не вызывается при изменении параметров индикаторов. Поэтому без проверки на 1 индекса Вы все рано не обойдетесь. И смысла делать как вы хотите нет никакого.
Init() вызывается при: 1. Первоначальном добавлении индикатора на график, 2. При каждой смене тайм-фрейма (смена набора свечек при сохранении инструмента), 3. При каждой смене инструмента (смена источника данных для свечек). 4. На старте quik и, что по сути для индикатора тоже самое, при загрузке настройки окон из *.wnd
Так вот, при событиях 2, 3 и 4 getDataSourceInfo(), вызванная из Init() выдаст все корректную информацию и о новом тайм-фрейме, и новом инструменте. Причем в "Руководстве пользователя QLua" прямо сказано, что только именно событие 4 гарантирует корректную работу getDataSourceInfo() при вызове из Init()
А вот в случае события 1, по непонятной причине, от getDataSourceInfo() мы можем получить только текущий тайм-фрейм окна графика.
Так чем же событие 1 так уж принципиально отличается от других, в особенности от события 4? Почему во 2-4 случаях quik "подключает" новый инструмент к индикатору до вызова Init(), в случае 1 - после?
А в квике как мультике про простоквашено (Письмо дяди Федора). Начинал писать квик один писатель потом второй и т д. Стратегию построения КВИКА разработали еще в прошлом веке. Вот и нет однообразия. А читатели - это клиенты брокеров.
Русский написал: Вы говорили не об этом, но правильно ли я понял, что если код такой:
Код
int = getDataSourceInfo ().interval
, то интервал будет получаться на каждой свечке. А если такой:
Код
if index = = 1 then
int = getDataSourceInfo ().interval
end
, то интервал будет получен единожды?
Да. Но уточню: getDataSourceInfo ().interval - это тайм-фрейм окна графика, в котором выводится индикатор. Это, строго говоря, не обязательно интервал по времени между предыдущей и текущей свечками, т.к. данные для каких-то моментов времени могут отсутствовать, и тогда предыдущая свечка может отстоять от текущей на больший, чем тайм-фрейм промежуток времени. Проверять тайм-фрейм для каждой свечки не имеет смысла, т.к. если Вы смените тайм-фрейм, то quik перезапустит цикл вызовов OnCalculate() и первым делом вызовет OnCalculate(index = 1)
Sergey Gorokhov написал: Здравствуйте, Задача вполне решается проверкой первой свечи в OnCalculate Из приведенного описания не вполне понятно, чем данный подход не устраивает.
Здравствуйте. Расскажу чем не устраивает.. или подскажите как такое реализовать: обновление Settings из кода OnCalculate.
хочу в настройки по своей внутренней формуле вывести номер свечи от которой буду производить отрисовку. При этом пользователь имеет право вручную изменить номер свечи. также хочу вывести в настройки некое значение рассчитанное из данных getDataSourceInfo (дать возможность пользователю изменить заранее рассчитанные значения) ,например, маржинальный диапазон или базовый интервал.
да можно рассчитать на первой свече все данные, но не записать их в Settings function OnCalculate(i)
-- определяем текущую бумагу и ее характеристики -- в Init можно разместить, но нужн перезапускать рабочее место if (i == 1) then sec_code = getDataSourceInfo().sec_code class_code = getDataSourceInfo().class_code interval = getDataSourceInfo().interval param = getDataSourceInfo().param -- не прокатит!!!! Settings.Fx= F(sec_code, class_code, interval, param)
на текущий момент не представляю как такое сделать. по все той же причине нельзя изменять Settings вне Init.
Подскажите как реализовать описанное выше в индикаторе.
п.с. перезагружать рабочее место или изменять настройки в индикаторе и затем еще раз его запускать... ну такое
Sergey Gorokhov написал: Здравствуйте, Задача вполне решается проверкой первой свечи в OnCalculate Из приведенного описания не вполне понятно, чем данный подход не устраивает.
Здравствуйте. Расскажу чем не устраивает.. или подскажите как такое реализовать: обновление Settings из кода OnCalculate.
хочу в настройки по своей внутренней формуле вывести номер свечи от которой буду производить отрисовку. При этом пользователь имеет право вручную изменить номер свечи. также хочу вывести в настройки некое значение рассчитанное из данных getDataSourceInfo (дать возможность пользователю изменить заранее рассчитанные значения) ,например, маржинальный диапазон или базовый интервал.
да можно рассчитать на первой свече все данные, но не записать их в Settings function OnCalculate(i)
-- определяем текущую бумагу и ее характеристики -- в Init можно разместить, но нужн перезапускать рабочее место if (i == 1) then sec_code = getDataSourceInfo().sec_code class_code = getDataSourceInfo().class_code interval = getDataSourceInfo().interval param = getDataSourceInfo().param -- не прокатит!!!! Settings.Fx= F(sec_code, class_code, interval, param)
на текущий момент не представляю как такое сделать. по все той же причине нельзя изменять Settings вне Init.
Подскажите как реализовать описанное выше в индикаторе.
п.с. перезагружать рабочее место или изменять настройки в индикаторе и затем еще раз его запускать... ну такое
Добрый день.
Извиняемся за долгий ответ. Такой возможности нет, готовы зарегистрировать пожелание на доработку.
Прошло 5 лет! Воз и ныне там, но это не точно. Пришлось изобрасти костыль. Итак, нам нужно в индикаторе в init получить sec_code. Для решения этого создал пустой индикатор, который в oncalculate на первой свече и единожды находит через getDataSourceInfo() class_code и sec_code и помещает их в файл, базу данных тут кому как нравится, это вспомогательный индикатор кидаем на график жмем применить, получаем названия инструмента в базе данных, затем кидаем на график уже основной индикатор, который в init считывает полученное в первом. ВСЕ! Ниже пример костыля. Может разработчики или кто подкажет как все-таки решать такой простой вопрос, без костылей.
Код
local sqlite3 = require("lsqlite3") -- Подключение SQLite (если доступно)
Settings =
{
Name = "!!!Current_Ticker",
line =
{
{
Name = "FixedLine",
Color = RGB(0, 255, 0),
Type = TYPE_LINE,
Width = 2
}
}
}
local db = nil
local saved = false -- Флаг, чтобы выполнить запись только один раз
function Init()
PrintDbgStr("!!!Current_Ticker: Init called")
return 1 -- Количество линий
end
function OnCalculate(index)
if index == 1 and not saved then
PrintDbgStr("!!!Current_Ticker: OnCalculate first bar")
local DSI = getDataSourceInfo()
if DSI == nil then
PrintDbgStr("!!!Current_Ticker: getDataSourceInfo() returned nil")
return 10
end
local class_code = DSI.class_code
local sec_code = DSI.sec_code
PrintDbgStr("!!!Current_Ticker: class_code=" .. tostring(class_code) .. ", sec_code=" .. tostring(sec_code))
local info = getSecurityInfo(class_code, sec_code)
if info == nil then
PrintDbgStr("!!!Current_Ticker: getSecurityInfo() returned nil")
return 10
end
PrintDbgStr("!!!Current_Ticker: short_name=" .. tostring(info.short_name))
db = sqlite3.open("C:\\QUIK\\LuaIndicators\\CurrentTicker.sqlite")
if not db then
PrintDbgStr("!!!Current_Ticker: Failed to open SQLite database")
return 10
end
local create_table_sql = [[
CRE ATE TABLE IF NOT EXISTS CurrentTicker (
id INTEGER PRIMARY KEY AUTOINCREMENT,
class_code TEXT,
sec_code TEXT,
short_name TEXT
);
]]
db:exec(create_table_sql)
PrintDbgStr("!!!Current_Ticker: Table CurrentTicker ensured")
local stmt = db:prepare([[
INS ERT IN TO CurrentTicker (class_code, sec_code, short_name)
VALUES (?, ?, ?);
]])
if stmt then
stmt:bind_values(class_code, sec_code, info.short_name or "")
stmt:step()
stmt:finalize()
PrintDbgStr("!!!Current_Ticker: Data inserted in CurrentTicker table")
else
PrintDbgStr("!!!Current_Ticker: Failed to prepare SQLite statement")
end
db:close()
PrintDbgStr("!!!Current_Ticker: Database closed")
saved = true
end
return 10
end
function OnDestroy()
PrintDbgStr("!!!Current_Ticker: OnDestroy called")
if db then
db:close()
PrintDbgStr("!!!Current_Ticker: Database closed on destroy")
end
end
nikolz написал: Артем , Можете пояснить, почему надо именно в Init найти код инструмента.
Потому что только в Init есть возможность программно изменять параметры Settings, далее во время нахождения индикатора на графике мы можем менять параметры вручную и программно только считывать их. Все бы ничего если бы в индикаторе было до 10 параметров, но если их 50-100, это превращается в издевательство.
nikolz написал: Артем , Можете пояснить, почему надо именно в Init найти код инструмента.
Потому что только в Init есть возможность программно изменять параметры Settings, далее во время нахождения индикатора на графике мы можем менять параметры вручную и программно только считывать их. Все бы ничего если бы в индикаторе было до 10 параметров, но если их 50-100, это превращается в издевательство.
Правильно Вас понял, что Вы меняете параметры у встроенных в QUIK индикаторов. Поэтому так извращаетесь?. вставьте функции нужных индикаторов в скрипт и меняйте все что хотите у них.
nikolz написал: Относительно изменения Setting. Это глобальная таблица и менять ее содержимое программно никто не запрещает. Не знаю зачем это надо.
Это здорово, но можно пример кода в студию, как вы в индикаторе после добавления его на график ПРОГРАММНО поменяете Settings. Именно программно, без каких либо ручных манипуляций, например в зависмости от изменения цены инструмента.
OnChangeSettings Функция вызывается при редактировании свойств индикатора после нажатия кнопок «Применить» или «OK». Функция вызывается также при перезагрузке Рабочего места QUIK и при загрузке wnd-файла либо tab-файла, в которых сохранен график с индикатором. Формат вызова: OnChangeSettings() Пример: Settings={Name="test1"} function Init() return 1 end function OnChangeSettings() message(Settings.Name) end
Тут так и написано, прям дословно, Settings можно изменить руками, ну или приведите пример кода, как программно поменять Settings и нажать кнопку применить или ок. Еще один бесплатый лайфхак к этому мануалу. - "Функция вызывается также при перезагрузке Рабочего места QUIK и при загрузке" и при "Cоздать копию" графика,да так можно делать, но если терминал грузится 10 минут, во время торговой сессии, это такое себе занятие, откровенная глупость, а если надо 10 раз поменять парметр (подобрать его, посмотреть результат), остается только удалить добавить индикатор...
nikolz написал: Артем , Можете пояснить, почему надо именно в Init найти код инструмента.
Потому что только в Init есть возможность программно изменять параметры Settings, далее во время нахождения индикатора на графике мы можем менять параметры вручную и программно только считывать их. Все бы ничего если бы в индикаторе было до 10 параметров, но если их 50-100, это превращается в издевательство.
Правильно Вас понял, что Вы меняете параметры у встроенных в QUIK индикаторов. Поэтому так извращаетесь?. вставьте функции нужных индикаторов в скрипт и меняйте все что хотите у них
Встроенные индикаторы, меня не интересуютот слова совсем, у меня своих достаточно. Рассмотрим простой пример, есть индикатор в котором используется дневной ATR , как параметр. Покажите как программно изменить этот параметр в индикаторе при наступлении следующего дня. Можно зайти и руками поправить (супер), но становится грустно когда графиков штук 50, нужно найти этот ATR для каждого инструмента и забить ручками, при этом человеческий фактор никто не отменял. Здесь становится намного проще просто удалить-добавить индикатор, но тоже проделать данную операцию 20 -30 раз, такое себе.
nikolz написал: Артем , Можете пояснить, почему надо именно в Init найти код инструмента.
Потому что только в Init есть возможность программно изменять параметры Settings, далее во время нахождения индикатора на графике мы можем менять параметры вручную и программно только считывать их. Все бы ничего если бы в индикаторе было до 10 параметров, но если их 50-100, это превращается в издевательство.
Правильно Вас понял, что Вы меняете параметры у встроенных в QUIK индикаторов. Поэтому так извращаетесь?. вставьте функции нужных индикаторов в скрипт и меняйте все что хотите у них
Встроенные индикаторы, меня не интересуютот слова совсем, у меня своих достаточно. Рассмотрим простой пример, есть индикатор в котором используется дневной ATR , как параметр. Покажите как программно изменить этот параметр в индикаторе при наступлении следующего дня. Можно зайти и руками поправить (супер), но становится грустно когда графиков штук 50, нужно найти этот ATR для каждого инструмента и забить ручками, при этом человеческий фактор никто не отменял. Здесь становится намного проще просто удалить-добавить индикатор, но тоже проделать данную операцию 20 -30 раз, такое себе.
Могу показать, если выложите скрипт И на примере подробно расскажите .
Итак, вот простой индикатор уровней, алгоритм обработки может быть любой в OnCalculate. Описание вопроса, индикатор имеет 40-50 параметров, параметры хранятся в базе данных для каждого конкретного тикера. Требуется при изменении цены или при срабанывании триггера получить параметры из базы и обратно записать в базу вычисленные параметры в индикаторе, сейчас это реализовано через OnChangeSettings (проверка параметра A1_SaveSettings установили в 1 нажали применить = записали в базу, скинули в 0 больше не пишется), это этап сохранения параметров в базу. А вот этап чтения параметров из базы тут все совсем грустно. Предположим простую ситуацию параметры поменялись в базе данных (пользователь внес данные в базу или сторонний софт провел расчет и внес параметры в базу данных). Нужно чтобы новые параметры были загружены индикатором. И я знаю только варианты 1) Удалить- добавить индикатор на график 2) Создать копию графика 3) Перезапустить Quik. Но что характерно, все эти манипуляции сводятся к одной цели запустить Init, согласитесь, это не дружественная ситуация, почему нет просто кнопки рестарт-реинит для конкретного графика? Но и тут нас ждет сюрприз просто так запустить инит не получится так как Quik забыл с каким инструментом работает! нужно полезть в базу данных и получить из псевдо курсора название инструмента, затем по полученному значению извлечь в инит параметры из основной базы для данного тикера! Итого два вопроса, 1) Как ПРОГРАММНО перевызвать Init или аналог его чтобы заполнить Settings ? (программный рестарт) 2) Почему нельзя прото добавить кнопку рестарт-реинит индикатора?(ручной рестарт, и избавить пользователей от опрации удали-добавь индикатор)
Код
local sqlite3 = require("lsqlite3")
if not PrintDbgStr then
function PrintDbgStr(s) print(s) end
end
Settings = {
Name = "!!!DB_Levels_v1",
A1_Ticker = "sec_code [class_code]",
A1_Enable = 1,
A1_SaveSettings = 0, -- Добавлен параметр
A1_target1 = 0.1, A1_target2 = 0.1, A1_target3 = 0.1, A1_target4 = 0.1, A1_target5 = 0.1, A1_target6 = 0.1,
Profile1 = 0.1, Profile2 = 0.1,
C1_Level1 = 0.1, C1_Level2 = 0.1, C1_Level3 = 0.1, C1_Level4 = 0.1, C1_Level5 = 0.1, C1_Level6 = 0.1, C1_Level7 = 0.1, C1_Level8 = 0.1, C1_Level9 = 0.1,
C1_Level10_AlertFlag = 0, C1_Level10 = 0.1, C1_Level11 = 0.1, C1_Level12 = 0.1, C1_Level13 = 0.1, C1_Level14 = 0.1, C1_Level15 = 0.1,
C1_Level16 = 0.1, C1_Level17 = 0.1, C1_Level18 = 0.1, C1_Level19 = 0.1, C1_Level20 = 0.1, C1_Level21 = 0.1, C1_Level22 = 0.1, C1_Level23 = 0.1,
C1_Level24 = 0.1, C1_Level25 = 0.1, C1_Level26 = 0.1, C1_Level27 = 0.1, C1_Level28 = 0.1, C1_Level29 = 0.1,
CD1_Level30 = 0.1, CD1_Level31 = 0.1, CD1_Level32 = 0.1, CD1_Level33 = 0.1, CD1_Level34 = 0.1, CD1_Level35 = 0.1, CD1_Level36 = 0.1, CD1_Level37 = 0.1, CD1_Level38 = 0.1, CD1_Level39 = 0.1,
CD1_Level30_2 = 0.1,C1_Level19_2 = 0.1, DD1_Volume = 0.1,
PER = "", DATE = "", TIME = "",
line = {
{ Name="A1_target1", Color = RGB(255,0, 0), Type = TYPE_LINE, Width = 6 },
{ Name="A1_target2", Color = RGB(255,0, 0), Type = TYPE_LINE, Width = 6 },
{ Name="A1_target3", Color = RGB(255,0, 0), Type = TYPE_LINE, Width = 6 },
{ Name="A1_target4", Color = RGB(0,128, 64), Type = TYPE_LINE, Width = 6 },
{ Name="A1_target5", Color = RGB(0,128, 64), Type = TYPE_LINE, Width = 6 },
{ Name="A1_target6", Color = RGB(0,128, 64), Type = TYPE_LINE, Width = 6 },
{ Name="Profile1", Color = RGB(149, 142, 14), Type = TYPE_BARS, Width = 2 },
{ Name="Profile2", Color = RGB(149, 142, 14), Type = TYPE_BARS, Width = 2 },
{ Name="C1_Level1", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level2", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level3", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level4", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level5", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level6", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level7", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level8", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level9", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level10_AlertFlag", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level10", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level11", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level12", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level13", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level14", Color = RGB(255,128, 64), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level15", Color = RGB(128,128, 192), Type = TYPE_DASHDOT, Width = 1 },
{ Name="C1_Level16", Color = RGB(128,128, 192), Type = TYPE_DASHDOT, Width = 1 },
{ Name="C1_Level17", Color = RGB(128,128, 192), Type = TYPE_DASHDOT, Width = 1 },
{ Name="C1_Level18", Color = RGB(128,128, 192), Type = TYPE_DASHDOT, Width = 1 },
{ Name="C1_Level19", Color = RGB(128,128, 192), Type = TYPE_DASHDOT, Width = 1 },
{ Name="C1_Level20", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level21", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level22", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level23", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level24", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level25", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level26", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level27", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level28", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level29", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level30", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level31", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level32", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level33", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level34", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level35", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level36", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level37", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level38", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level39", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="CD1_Level30_2", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="C1_Level19_2", Color = RGB(255,0, 255), Type = TYPE_LINE, Width = 2 },
{ Name="DD1_Volume", Color = RGB(128,64, 64), Type = TYPE_DASHDOT, Width = 3 }
}
}
local function get_db_path()
local script_path = getScriptPath and getScriptPath() or "."
local sep = package.config:sub(1,1)
if script_path:sub(-1) ~= sep then script_path = script_path .. sep end
local db_path = script_path .. "data.sqlite"
PrintDbgStr("get_db_path: db_path = " .. db_path)
return db_path
end
local db_fields = {
"TICKER", "PER", "DATE", "TIME",
"A1_target1", "A1_target2", "A1_target3", "A1_target4", "A1_target5", "A1_target6",
"Profile1", "Profile2",
"C1_Level1", "C1_Level2", "C1_Level3", "C1_Level4", "C1_Level5", "C1_Level6", "C1_Level7", "C1_Level8", "C1_Level9",
"C1_Level10_AlertFlag", "C1_Level10", "C1_Level11", "C1_Level12", "C1_Level13", "C1_Level14", "C1_Level15",
"C1_Level16", "C1_Level17", "C1_Level18", "C1_Level19", "C1_Level20", "C1_Level21", "C1_Level22", "C1_Level23",
"C1_Level24", "C1_Level25", "C1_Level26", "C1_Level27", "C1_Level28", "C1_Level29",
"CD1_Level30", "CD1_Level31", "CD1_Level32", "CD1_Level33", "CD1_Level34", "CD1_Level35", "CD1_Level36", "CD1_Level37", "CD1_Level38", "CD1_Level39",
"CD1_Level30_2", "C1_Level19_2","DD1_Volume"
}
function OnChangeSettings()
PrintDbgStr("OnChangeSettings: start")
if Settings.A1_SaveSettings ~= 1 then
PrintDbgStr("OnChangeSettings: A1_SaveSettings ~= 1, exiting")
return
end
local db_path = get_db_path()
PrintDbgStr("OnChangeSettings: opening database: " .. db_path)
local db = sqlite3.open(db_path)
if not db then
PrintDbgStr("OnChangeSettings: failed to open database: " .. db_path)
return
end
local ticker = Settings.A1_Ticker
Settings.TICKER = ticker
PrintDbgStr("OnChangeSettings: current ticker: " .. tostring(ticker))
-- Проверяем, есть ли запись с таким тикером
local sql_check = string.format("SEL ECT Id FR OM data WHERE TICKER = '%s' LIMIT 1", ticker)
local existing_id = nil
for row in db:nrows(sql_check) do
existing_id = row.Id
PrintDbgStr("OnChangeSettings: found existing record with Id = " .. tostring(existing_id))
break
end
if existing_id then
-- Обновляем существующую запись
PrintDbgStr("OnChangeSettings: updating record with Id = " .. tostring(existing_id))
local set_parts = {}
for _, field in ipairs(db_fields) do
if field ~= "TICKER" then
local val = Settings[field]
local val_str
if type(val) == "number" then
val_str = tostring(val)
else
val_str = "'" .. tostring(val):gsub("'", "''") .. "'"
end
table.insert(set_parts, string.format("%s = %s", field, val_str))
end
end
local sql_update = string.format("UPD ATE data SE T %s WHERE Id = %d", table.concat(set_parts, ", "), existing_id)
PrintDbgStr("OnChangeSettings: executing UPDATE: " .. sql_update)
local rc = db:exec(sql_update)
if rc ~= sqlite3.OK then
PrintDbgStr("OnChangeSettings: upd ate failed: " .. db:errmsg())
else
PrintDbgStr("OnChangeSettings: successfully updated record for ticker " .. ticker)
end
else
-- Вставляем новую запись
PrintDbgStr("OnChangeSettings: record not found, inserting new")
local columns = {}
local values = {}
for _, field in ipairs(db_fields) do
table.insert(columns, field)
local val = Settings[field]
if type(val) == "number" then
table.insert(values, tostring(val))
else
table.insert(values, "'" .. tostring(val):gsub("'", "''") .. "'")
end
end
local sql_insert = string.format("INS ERT INTO data (%s) VALUES (%s)", table.concat(columns, ", "), table.concat(values, ", "))
PrintDbgStr("OnChangeSettings: executing INSERT: " .. sql_insert)
local rc = db:exec(sql_insert)
if rc ~= sqlite3.OK then
PrintDbgStr("OnChangeSettings: insert failed: " .. db:errmsg())
else
PrintDbgStr("OnChangeSettings: successfully inserted new record for ticker " .. ticker)
end
end
PrintDbgStr("OnChangeSettings: finished")
db:close()
end
-- Остальной код (Init, LoadSettingsFromDB, OnCalculate) без изменений
function LoadSettingsFromDB(a1_ticker)
PrintDbgStr("LoadSettingsFromDB: start for A1_Ticker = " .. tostring(a1_ticker))
local db_path = get_db_path()
local db = sqlite3.open(db_path)
if not db then
PrintDbgStr("LoadSettingsFromDB: failed to open database: " .. db_path)
return
end
local sql = string.format("SEL ECT * FR OM data WH ERE TICKER = '%s' LIMIT 1", a1_ticker)
PrintDbgStr("LoadSettingsFromDB: sql = " .. sql)
for row in db:nrows(sql) do
for k, v in pairs(row) do
if Settings[k] ~= nil and type(Settings[k]) == "number" then
local num = tonumber(v)
if num then
Settings[k] = num
PrintDbgStr("LoadSettingsFromDB: loaded " .. k .. " = " .. tostring(Settings[k]))
end
end
end
break
end
db:close()
PrintDbgStr("LoadSettingsFromDB: done")
end
local function get_currentticker_db_path()
local script_path = getScriptPath and getScriptPath() or "."
local sep = package.config:sub(1,1)
if script_path:sub(-1) ~= sep then script_path = script_path .. sep end
return script_path .. "CurrentTicker.sqlite"
end
function GetLastTickerFromCurrentTicker()
local db_path = get_currentticker_db_path()
local db = sqlite3.open(db_path)
if not db then
PrintDbgStr("GetLastTickerFromCurrentTicker: failed to open database: " .. db_path)
return nil, nil
end
local sql = "SELE CT sec_code, class_code FR OM CurrentTicker ORDER BY id DESC LIMIT 1"
local sec_code, class_code = nil, nil
for row in db:nrows(sql) do
sec_code = row.sec_code
class_code = row.class_code
break
end
db:close()
return sec_code, class_code
end
function Init()
PrintDbgStr("Init: start (CurrentTicker version)")
local sec_code, class_code = GetLastTickerFromCurrentTicker()
if sec_code and class_code then
local template = sec_code .. " [" .. class_code .. "]"
Settings.A1_Ticker = template
PrintDbgStr("Init: A1_Ticker se t to: " .. Settings.A1_Ticker)
LoadSettingsFromDB(Settings.A1_Ticker)
else
PrintDbgStr("Init: No ticker found in CurrentTicker, fallback to default")
Settings.A1_Ticker = "sec_code [class_code]"
end
PrintDbgStr("Init: done")
return #Settings.line
end
function OnCalculate(index)
local E = Settings.A1_Enable or 1
return
E * Settings.A1_target1, E * Settings.A1_target2, E * Settings.A1_target3, E * Settings.A1_target4, E * Settings.A1_target5, E * Settings.A1_target6,
E * Settings.Profile1, E * Settings.Profile2,
E * Settings.C1_Level1, E * Settings.C1_Level2, E * Settings.C1_Level3, E * Settings.C1_Level4, E * Settings.C1_Level5, E * Settings.C1_Level6, E * Settings.C1_Level7, E * Settings.C1_Level8, E * Settings.C1_Level9,
E * Settings.C1_Level10_AlertFlag, E * Settings.C1_Level10, E * Settings.C1_Level11, E * Settings.C1_Level13, E * Settings.C1_Level19,
E * Settings.C1_Level15, E * Settings.C1_Level16, E * Settings.C1_Level17, E * Settings.C1_Level18, E * Settings.C1_Level19, E * Settings.C1_Level20,
E * Settings.C1_Level21, E * Settings.C1_Level22, E * Settings.C1_Level23, E * Settings.C1_Level24, E * Settings.C1_Level25, E * Settings.C1_Level26, E * Settings.C1_Level27, E * Settings.C1_Level28, E * Settings.C1_Level29,
E * Settings.CD1_Level30, E * Settings.CD1_Level31, E * Settings.CD1_Level32, E * Settings.CD1_Level33, E * Settings.CD1_Level34, E * Settings.CD1_Level35, E * Settings.CD1_Level36, E * Settings.CD1_Level37, E * Settings.CD1_Level38, E * Settings.CD1_Level39,
E * Settings.CD1_Level30_2, E * Settings.C1_Level19, E * Settings.DD1_Volume
end
У Вас уровни могут в любой момент поменяться что ли? Я их раз в час рассчитываю отдельным скриптом (сервер) и клиенты (индикаторы) по факту раз в час смотрят в хранилище уровней внутри OnCalculate и берут оттуда ближайшие 2-4 верхних и нижних. В моём случае хранение через C++ структуры данных внутри одной DLL, которую грузят серверный скрипт и клиентские индикаторы.
Артем, Пишу алгоритм своего решения вашей задачи: Если понял ее не правильно, то уточните, что не так. --------------------- Алгоритм: 1) Создаем файл , в котором записываем в строку : Имя инструмента, A1,A2,....An где A1,A... - параметры наших индикаторов (уровней) Т е в файле столько строк, сколько всего инструментов наблюдаем Прим: Можно сделать для каждого инструмента свой файл. Имя файла ==имя инструмента. -------------------------- В опCalculate на кажом тике: 1) читаем имя инструмента 2)Читаем из файла строку параметров индикаторов данного инструмента 3) Выводим на график индикаторы
funduk написал: У Вас уровни могут в любой момент поменяться что ли? Я их раз в час рассчитываю отдельным скриптом (сервер) и клиенты (индикаторы) по факту раз в час смотрят в хранилище уровней внутри OnCalculate и берут оттуда ближайшие 2-4 верхних и нижних. В моём случае хранение через C++ структуры данных внутри одной DLL, которую грузят серверный скрипт и клиентские индикаторы.
да, и у меня так, но в Settings у вас ничего не выводится , то есть налету поменять нет возможности, нужно менять в базе данных, и с лагом уровни прорисуются, вопрос в другом как программно изменить Settings, вне Init
nikolz написал: Артем , Пишу алгоритм своего решения вашей задачи: Если понял ее не правильно, то уточните, что не так. --------------------- Алгоритм: 1) Создаем файл , в котором записываем в строку : Имя инструмента, A1,A2,....An где A1,A... - параметры наших индикаторов (уровней) Т е в файле столько строк, сколько всего инструментов наблюдаем Прим: Можно сделать для каждого инструмента свой файл. Имя файла ==имя инструмента. -------------------------- В опCalculate на кажом тике: 1) читаем имя инструмента 2)Читаем из файла строку параметров индикаторов данного инструмента 3) Выводим на график индикаторы
совершенно не понятное решение, в моем скрипте все уже реализовано через базу данных, зачем какие то текстовые файлы? я предельно четко сформулировал 2 вопроса. Вот они : 1) Как ПРОГРАММНО перевызвать Init или аналог его чтобы заполнить Settings ? (программный рестарт) 2) Почему нельзя прото добавить кнопку рестарт-реинит индикатора?(ручной рестарт, и избавить пользователей от опрации удали-добавь индикатор)
nikolz написал: Артем , Пишу алгоритм своего решения вашей задачи: Если понял ее не правильно, то уточните, что не так. --------------------- Алгоритм: 1) Создаем файл , в котором записываем в строку : Имя инструмента, A1,A2,....An где A1,A... - параметры наших индикаторов (уровней) Т е в файле столько строк, сколько всего инструментов наблюдаем Прим: Можно сделать для каждого инструмента свой файл. Имя файла ==имя инструмента. -------------------------- В опCalculate на кажом тике: 1) читаем имя инструмента 2)Читаем из файла строку параметров индикаторов данного инструмента 3) Выводим на график индикаторы
совершенно не понятное решение, в моем скрипте все уже реализовано через базу данных, зачем какие то текстовые файлы? я предельно четко сформулировал 2 вопроса. Вот они : 1) Как ПРОГРАММНО перевызвать Init или аналог его чтобы заполнить Settings ? (программный рестарт) 2) Почему нельзя прото добавить кнопку рестарт-реинит индикатора?(ручной рестарт, и избавить пользователей от опрации удали-добавь индикатор)
Если не знаете, то файловая система это тоже база данных. ----------------- Вы спросили как сделать так чтобы не руками без Init. Я Вам написал как это сделать. ----------------------- В моем решении нет надобности что-то делать руками и нет надобности вызывать init. В моем решении нет Ваших проблем. -------------------------- Про Ваш скрипт я лучше промолчу, чтобы вас не обижать.
nikolz написал: Артем , Пишу алгоритм своего решения вашей задачи: Если понял ее не правильно, то уточните, что не так. --------------------- Алгоритм: 1) Создаем файл , в котором записываем в строку : Имя инструмента, A1,A2,....An где A1,A... - параметры наших индикаторов (уровней) Т е в файле столько строк, сколько всего инструментов наблюдаем Прим: Можно сделать для каждого инструмента свой файл. Имя файла ==имя инструмента. -------------------------- В опCalculate на кажом тике: 1) читаем имя инструмента 2)Читаем из файла строку параметров индикаторов данного инструмента 3) Выводим на график индикаторы
совершенно не понятное решение, в моем скрипте все уже реализовано через базу данных, зачем какие то текстовые файлы? я предельно четко сформулировал 2 вопроса. Вот они : 1) Как ПРОГРАММНО перевызвать Init или аналог его чтобы заполнить Settings ? (программный рестарт) 2) Почему нельзя прото добавить кнопку рестарт-реинит индикатора?(ручной рестарт, и избавить пользователей от опрации удали-добавь индикатор)
Если не знаете, то файловая система это тоже база данных. ----------------- Вы спросили как сделать так чтобы не руками без Init. Я Вам написал как это сделать. ----------------------- В моем решении нет надобности что-то делать руками и нет надобности вызывать init. В моем решении нет Ваших проблем. -------------------------- Про Ваш скрипт я лучше промолчу, чтобы вас не обижать.
"Если не знаете, то файловая система это тоже база данных." - это шедевр! А какой версии SQL там используется, СУБД тоже имеется? может, что то упустил за 25 лет в ИТ.
Вопрос остается прежним, есть Settings как туда программно записать значения, ваше решение, возможно, рабочее, но пользователь не видит изменений.
Далее возможно два варианта:
1 Вообще не выводим Settings пользователю - получаем черный ящик, все изменения через корректировку файлов в второннем софте (просто супер решение, но в этом случае хотя бы отсутствуют протеворечивость данных).
2 Settings выводим и даже разрешаем править значения, пользователь руками правит параметры, и жмет применить, параметры поменялись, затем через некоторое время Settings загружается из файлов и результат: пользователь видит одни значения, а по факт у значения совсем другие, или наоборот поменяли настройки в файле, а в окошке Settings ни чего не меняется. В этом варианте возникает противоречивость данных.
Я как раз про это и говорю, и прошу показать как можно решить этоту задачу. Устранить потенциальную опасность противоречия данных.
По второму вопросу вообще нет решения, как избавить пользователя от "удали-добавь индикатор, перезапусти Quik" ?
Артем написал: да, и у меня так, но в Settings у вас ничего не выводится , то есть налету поменять нет возможности, нужно менять в базе данных, и с лагом уровни прорисуются, вопрос в другом как программно изменить Settings, вне Init
Если на каком-то одном инструменте надо было вручную добавить несколько уровней, я использовал вкладку "Уровни" для цены инструмента в настройках графика.
В чём вопрос я понимаю, но считаю, что так делать - это противоестественно. Settings для статических настроек, туда ничего не выводится, динамические внутри скрипта должны работать. В TradingView так же, кстати. Не слышал о системах, где было бы иначе. Зачем в Settings выводить численно значения уровней, если они и так на графике видны? Если override нужен, ну так только его в Settings легко оставить - если в поле ноль, возвращать автоматический уровень (который берётся не из Settings, а внутри OnCalculate), если не ноль, то возвращать это ненулевое значение
Артем написал: да, и у меня так, но в Settings у вас ничего не выводится , то есть налету поменять нет возможности, нужно менять в базе данных, и с лагом уровни прорисуются, вопрос в другом как программно изменить Settings, вне Init
Если на каком-то одном инструменте надо было вручную добавить несколько уровней, я использовал вкладку "Уровни" для цены инструмента в настройках графика.
В чём вопрос я понимаю, но считаю, что так делать - это противоестественно. Settings для статических настроек, туда ничего не выводится, динамические внутри скрипта должны работать. В TradingView так же, кстати. Не слышал о системах, где было бы иначе. Зачем в Settings выводить численно значения уровней, если они и так на графике видны? Если override нужен, ну так только его в Settings легко оставить - если в поле ноль, возвращать автоматический уровень (который берётся не из Settings, а внутри OnCalculate), если не ноль, то возвращать это ненулевое значение
Да я же не спорю, я предлагаю сделать улучшение, расширить возможности Quik - избавить пользователя он коронного и разящего наповал далее цитата из мануала getDataSourceInfo Функция возвращает таблицу Lua с параметрами: ВАЖНО! Для корректной работы функции getDataSourceInfo, вызываемой из функции Init, необходимо перезапустить Рабочее место QUIK после добавления индикатора на график.
С этого и начался весь спор, нужно добавить в Quik кнопку рестарта текущего индикатора, тут я вообще не вижу сложностей (удали индикатор с графика и вставь снова с теми же параметрами выполнится инит), + решить вопрос по getDataSourceInfo в Init. Эти два простых действия сделают интерфейс дружественным.
Цитата: "В чём вопрос я понимаю, но считаю, что так делать - это противоестественно. Settings для статических настроек" Здесь как раз то и есть нюанс, что статистика не стоит на месте , и плавно изменяется, ежедневно а у когото и ежетиково , нужно руками корректировать уже приводил такой простой параметр ATR, да его можно рассчитывать в индикаторе, но можно просто хранить в базе параметром и тащить из базы, не показывая пользователю, а можно улучшить Quik, и расширить его возможности до изменять Setings программно. :)
Обойти отсутствие повторного запуска Init без ручных манипуляций я не смог, поэтому и прошу помощи. Мне прото жутко надоело каждый раз удали-добавь индикатор, может я чего-то не знаю... Может есть более протой путь.
Что есть реально другой путь кроме удали-добавь индикатор? Например, при написании кода того же индикатора, он меняется по 10 раз в день, какждый раз я делаю удалил-добавил, можно как то иначе?
В скрипте можно создать любые индикаторы (с полным контролем над ними) с использованием источников (DS), полученных функцией CreateDataSource. Кроме того в ветке https://forum.quik.ru/messages/forum10/message76052/topic5466/#message76052 представлен модуль подключения в скрипте к индикаторам папки LuaIndicators с готовыми индикаторами с сайта разработчика QUIK.
Артем написал: Что есть реально другой путь кроме удали-добавь индикатор? Например, при написании кода того же индикатора, он меняется по 10 раз в день, какждый раз я делаю удалил-добавил, можно как то иначе?
В режиме связанных окон (допустим таблица текущих торгов + график), при переходах с тикера на тикер, индикатор будет обновляться в месте с графиком, получая данные и пересчитывая значения. Также заметил в Вашем примере ошибки с типом данных, если на нем экспериментируете лучше поправить, пробелы в именах, линей меньше выводит.
"Если не знаете, то файловая система это тоже база данных." - это шедевр! А какой версии SQL там используется, СУБД тоже имеется? может, что то упустил за 25 лет в ИТ.
Ликбез: ------------------- Базы данных (БД) бывают разных типов, которые отличаются структурой и областью применения. Среди основных видов — реляционные, нереляционные, иерархические и сетевые, а также простейшие. ----------------------- Базы данных на основе файлов (flat-file databases) — это простой метод хранения данных в текстовом файле. В отличие от традиционных реляционных баз данных, в которых используются сложные структуры с таблицами, строками и столбцами, база данных с плоскими файлами организует данные линейным и последовательным образом. Особенности:
Каждая строка в файле представляет одну запись.
Отдельные поля внутри записи обычно разделяются разделителями, такими как запятые или табуляции.
Нет структур для индексирования или распознавания связей между записями.
Виды Базы данных с плоскими файлами подходят для небольших приложений и временного хранения данных. Некоторые случаи использования:
файлы конфигурации в программных приложениях;
обмен данными между различными системами;
файлы журналов, например, лог-файлы веб-сервера.
Форматы Для хранения данных в базах данных с плоскими файлами используются, например:
CSV (значения, разделённые запятыми) — каждое поле разделяется запятой.
TSV (значения, разделённые табуляцией) — в качестве разделителей используются табуляции, что полезно, когда запятые — часть самих данных.
Формат фиксированной ширины — каждое поле занимает заранее определённое количество символов, выравнивая данные по столбцам.
Инструменты Многие языки программирования и приложения имеют встроенную поддержку чтения и записи данных из баз данных с плоскими файлами. Например:
Программы для работы с электронными таблицами (Microsoft Excel, Google Sheets) — могут читать и работать с плоскими файлами.
Языки программирования (Python, Java) — могут анализировать и обрабатывать данные с помощью встроенных или сторонних библиотек.
Недостатки Базы данных с плоскими файлами имеют и ограничения. Некоторые из них:
Ограниченная масштабируемость — по мере роста объёма данных производительность базы данных может снижаться.
Отсутствие согласованности данных — если нужно обновить несколько записей, это становится утомительной задачей и увеличивает вероятность несоответствий.
Сложность извлечения данных — из-за отсутствия поддержки структурированного языка запросов (SQL) запросы часто требуют ручного сканирования и фильтрации через записи.
Ограниченный одновременный доступ — базы данных с плоскими файлами не предназначены для одновременного доступа нескольких пользователей или приложений.
------------------------------------------
SQL ( Structured Query Language — «язык структурированных запросов») — применяемый для создания, модификации и управления данными в реляционных базах данных. ----------------------- Нереляционные базы данных В нереляционных БД не используется табличная схема строк и столбцов. Применяется модель хранения, оптимизированная под конкретный тип данных. Некоторые типы нереляционных БД:
Документоориентированные. Данные хранятся в виде документов в форматах JSON, BSON или XML.
Колоночные. Информация хранится не в строках, а в столбцах.
Графовые. Данные представлены в виде графов, что упрощает их хранение и поиск.
- --------------------------- Для работы с нереляционными базами данных (NoSQL) используются специализированные языки запросов. Это связано с особенностями моделей данных в таких базах: данные могут храниться в виде документов, колонок, графов или на основе пар «ключ-значение». Ниже приведены примеры языков запросов для работы с базами данных MongoDB, Cassandra, BigTable и GraphDB.
MongoDB MongoDB Query Language (MQL) — язык запросов для документо-ориентированной базы данных MongoDB. Запросы формулируются как объекты JSON, что делает их интуитивно понятными. MQL поддерживает операции CRUD (создание, чтение, обновление и удаление), а также функции агрегирования для фильтрации, сортировки и группировки данных.
Cassandra Cassandra Query Language (CQL) — основной язык запросов для распределённой базы данных Apache Cassandra. Синтаксис похож на SQL, но оптимизирован для принципов NoSQL: горизонтальной масштабируемости, высокой доступности и партиционированного хранения данных. CQL не поддерживает традиционные SQL-соединения, а поощряет денормализацию — дублирование данных по таблицам для эффективного запроса.
BigTable Собственный язык запросов — BigTable не поддерживает язык запросов SQL. Запросы выполняются через API, который поддерживает ряд популярных языков программирования (Java, Python, C#, C++). Изначально нет схемы данных, но возможна поддержка пользовательской схемы.
GraphDB Cypher — язык запросов для графовых баз данных, например, Neo4j. Cypher — декларативный язык, позволяет создавать, обновлять и удалять вершины, рёбра, метки и свойства, а также управлять индексами и ограничениями. Другие языки запросов:
Gremlin — поддерживается базой данных Titan, позволяет выполнять базовые операции с элементами графа (создание, обновление и удаление вершин, рёбер, меток и свойств).