"Если не знаете, то файловая система это тоже база данных." - это шедевр! А какой версии 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, позволяет выполнять базовые операции с элементами графа (создание, обновление и удаление вершин, рёбер, меток и свойств). SPARQL — используется в базах данных RDF.
Благодарю за ликбез, действительно, я все время работаю с реляционными базами. И, после вашего замечания, полностью согласен любой текстовый файл можно превратить в базу данных, так как у них одна сущность - хранение данных, а способы ее организации и обработки, конечно, могут быть различными.
Артем написал: да, и у меня так, но в 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 раз в день, какждый раз я делаю удалил-добавил, можно как то иначе?
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" ?
nikolz написал: Артем , Пишу алгоритм своего решения вашей задачи: Если понял ее не правильно, то уточните, что не так. --------------------- Алгоритм: 1) Создаем файл , в котором записываем в строку : Имя инструмента, A1,A2,....An где A1,A... - параметры наших индикаторов (уровней) Т е в файле столько строк, сколько всего инструментов наблюдаем Прим: Можно сделать для каждого инструмента свой файл. Имя файла ==имя инструмента. -------------------------- В опCalculate на кажом тике: 1) читаем имя инструмента 2)Читаем из файла строку параметров индикаторов данного инструмента 3) Выводим на график индикаторы
совершенно не понятное решение, в моем скрипте все уже реализовано через базу данных, зачем какие то текстовые файлы? я предельно четко сформулировал 2 вопроса. Вот они : 1) Как ПРОГРАММНО перевызвать Init или аналог его чтобы заполнить Settings ? (программный рестарт) 2) Почему нельзя прото добавить кнопку рестарт-реинит индикатора?(ручной рестарт, и избавить пользователей от опрации удали-добавь индикатор)
funduk написал: У Вас уровни могут в любой момент поменяться что ли? Я их раз в час рассчитываю отдельным скриптом (сервер) и клиенты (индикаторы) по факту раз в час смотрят в хранилище уровней внутри OnCalculate и берут оттуда ближайшие 2-4 верхних и нижних. В моём случае хранение через C++ структуры данных внутри одной DLL, которую грузят серверный скрипт и клиентские индикаторы.
да, и у меня так, но в Settings у вас ничего не выводится , то есть налету поменять нет возможности, нужно менять в базе данных, и с лагом уровни прорисуются, вопрос в другом как программно изменить Settings, вне Init
Итак, вот простой индикатор уровней, алгоритм обработки может быть любой в 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
nikolz написал: Артем , Можете пояснить, почему надо именно в Init найти код инструмента.
Потому что только в Init есть возможность программно изменять параметры Settings, далее во время нахождения индикатора на графике мы можем менять параметры вручную и программно только считывать их. Все бы ничего если бы в индикаторе было до 10 параметров, но если их 50-100, это превращается в издевательство.
Правильно Вас понял, что Вы меняете параметры у встроенных в QUIK индикаторов. Поэтому так извращаетесь?. вставьте функции нужных индикаторов в скрипт и меняйте все что хотите у них
Встроенные индикаторы, меня не интересуютот слова совсем, у меня своих достаточно. Рассмотрим простой пример, есть индикатор в котором используется дневной ATR , как параметр. Покажите как программно изменить этот параметр в индикаторе при наступлении следующего дня. Можно зайти и руками поправить (супер), но становится грустно когда графиков штук 50, нужно найти этот ATR для каждого инструмента и забить ручками, при этом человеческий фактор никто не отменял. Здесь становится намного проще просто удалить-добавить индикатор, но тоже проделать данную операцию 20 -30 раз, такое себе.
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 написал: Относительно изменения Setting. Это глобальная таблица и менять ее содержимое программно никто не запрещает. Не знаю зачем это надо.
Это здорово, но можно пример кода в студию, как вы в индикаторе после добавления его на график ПРОГРАММНО поменяете Settings. Именно программно, без каких либо ручных манипуляций, например в зависмости от изменения цены инструмента.
nikolz написал: Артем , Можете пояснить, почему надо именно в Init найти код инструмента.
Потому что только в Init есть возможность программно изменять параметры Settings, далее во время нахождения индикатора на графике мы можем менять параметры вручную и программно только считывать их. Все бы ничего если бы в индикаторе было до 10 параметров, но если их 50-100, это превращается в издевательство.
Прошло 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
Смотрите пост 80.Там есть ссылка, но там только lua 5.3, который не работает вместе с 5.4. Один индикатор psar я починил заменой unpack(arg) на table.unpack(arg), но не все можно так просто починить, нужно к автору этих индикаторов обращаться. И было бы здорово переделать их под 5.4, а не чтобы каждый сам их прилаживал под себя. Хотя и так тоже наверное можно. Вот переделанный мной Psar под lua 5.4
Код
Settings = {
Name = "*PSAR (Parabolic SAR)",
Step = 0.02,
MaxStep = 0.2,
line = {{
Name = "Horizontal line",
Type = TYPE_LINE,
Color = RGB(140, 140, 140)
},
{
Width = 3,
Name = "PSAR_Up",
Type = TYPE_POINT,
Color = RGB(221, 44, 44)
},
{
Width = 3,
Name = "PSAR_Down",
Type = TYPE_POINT,
Color = RGB(0, 206, 0)
}
},
Round = "off",
Multiply = 1,
Horizontal_line="off"
}
function Init()
func = PSAR()
return #Settings.line
end
function OnCalculate(Index)
local Out = ConvertValue(Settings, func(Index, Settings))
local HL = tonumber(Settings.Horizontal_line)
if Out then
if Out > ((H(Index)-L(Index))/2)+L(Index) then
return HL,Out,nil
else
return HL,nil,Out
end
else
return HL,nil,nil
end
end
function PSAR() --Parabolic SAR ("PSAR")
local tmp = {pp=nil, p=nil}
local it = {ppp=0, pp=0, p=0, l=0}
return function (I, Fsettings, ds)
local Out = nil
local Fsettings=(Fsettings or {})
local Step = (Fsettings.Step or 0.02)
local MaxStep = (Fsettings.MaxStep or 0.2)
if I == 1 then
tmp = {pp=nil, p=nil}
it = {ppp=0, pp=0, p=0, l=0}
end
if CandleExist(I,ds) then
if I~=it.p then
it={ppp=it.pp, pp=it.p, p=I, l=it.l+1}
tmp.pp = tmp.p
end
local cand = {ppp=nil, pp=nil, p=nil}
tmp.p = {Val = nil, Step = 0, Ext = 0, Long = true}
cand.p = {H = GetValueEX(it.p,HIGH,ds), L = GetValueEX(it.p,LOW,ds)}
if it.l==2 then
tmp.p = {Val = GetValueEX(it.p,HIGH,ds), Step = Step, Ext = cand.p.H, Long = true}
end
if it.l > 2 then
local Revers = false
tmp.p.Val = tmp.pp.Val + tmp.pp.Step * (tmp.pp.Ext - tmp.pp.Val)
tmp.p.Long = tmp.pp.Long
tmp.p.Ext = tmp.pp.Ext
tmp.p.Step = tmp.pp.Step
if tmp.pp.Long then
if cand.p.L < tmp.p.Val then
tmp.p = {Val = tmp.pp.Ext, Step = Step, Ext = cand.p.L, Long = false}
Revers = true
end
else
if cand.p.H > tmp.p.Val then
tmp.p = {Val = tmp.pp.Ext, Step = Step, Ext = cand.p.H, Long = true}
Revers = true
end
end
if not Revers then
cand.pp = {H = GetValueEX(it.pp,HIGH,ds), L = GetValueEX(it.pp,LOW,ds)}
cand.ppp = {H = GetValueEX(it.ppp,HIGH,ds), L = GetValueEX(it.ppp,LOW,ds)}
if tmp.pp.Long then
if cand.p.H > tmp.pp.Ext then
tmp.p.Ext = cand.p.H
tmp.p.Step = tmp.pp.Step + Step
if tmp.p.Step > MaxStep then tmp.p.Step = MaxStep end
end
if cand.pp.L < tmp.p.Val then tmp.p.Val = cand.pp.L end
if cand.ppp.L < tmp.p.Val then tmp.p.Val = cand.ppp.L end
else
if cand.p.L < tmp.pp.Ext then
tmp.p.Ext = cand.p.L
tmp.p.Step = tmp.pp.Step + Step
if tmp.p.Step > MaxStep then tmp.p.Step = MaxStep end
end
if cand.pp.H > tmp.p.Val then tmp.p.Val = cand.pp.H end
if cand.ppp.H > tmp.p.Val then tmp.p.Val = cand.ppp.H end
end
end
end
return tmp.p.Val
end
return nil
end
end
SMA,MMA,EMA,WMA,SMMA,VMA = "SMA","MMA","EMA","WMA","SMMA","VMA"
OPEN,HIGH,LOW,CLOSE,VOLUME,MEDIAN,TYPICAL,WEIGHTED,DIFFERENCE,ANY = "O","H","L","C","V","M","T","W","D","A"
function CandleExist(I,ds)
return (type(C)=="function" and C(I)~=nil) or
(type(ds)=="table" and (ds[I]~=nil or (type(ds.Size)=="function" and (I>0) and (I<=ds:Size()))))
end
function Squeeze(I,P)
return math.fmod(I-1,P+1)
end
function ConvertValue(T,...)
local function r(V, R)
if R and string.upper(R)== "ON" then R=0 end
if V and tonumber(R) then
if V >= 0 then return math.floor(V * 10^R + 0.5) / 10^R
else return math.ceil(V * 10^R - 0.5) / 10^R end
else return V end
end
local arg = {...}
arg.n = select('#', ...)
if arg.n > 0 then
for i = 1, arg.n do
arg[i]=arg[i] and r(arg[i] * ((T and T.Multiply) or 1), (T and T.Round) or "off")
end
return table.unpack(arg)
else return nil end
end
function GetValueEX(I,VT,ds)
VT=(VT and string.upper(string.sub(VT,1,1))) or ANY
if VT == OPEN then --Open
return (O and O(I)) or (ds and ds:O(I))
elseif VT == HIGH then --High
return (H and H(I)) or (ds and ds:H(I))
elseif VT == LOW then --Low
return (L and L(I)) or (ds and ds:L(I))
elseif VT == CLOSE then --Close
return (C and C(I)) or (ds and ds:C(I))
elseif VT == VOLUME then --Volume
return (V and V(I)) or (ds and ds:V(I))
elseif VT == MEDIAN then --Median
return ((GetValueEX(I,HIGH,ds) + GetValueEX(I,LOW,ds)) / 2)
elseif VT == TYPICAL then --Typical
return ((GetValueEX(I,MEDIAN,ds) * 2 + GetValueEX(I,CLOSE,ds))/3)
elseif VT == WEIGHTED then --Weighted
return ((GetValueEX(I,TYPICAL,ds) * 3 + GetValueEX(I,OPEN,ds))/4)
elseif VT == DIFFERENCE then --Difference
return (GetValueEX(I,HIGH,ds) - GetValueEX(I,LOW,ds))
else --Any
return (ds and ds[I])
end
return nil
end
function send_telegram() --если true, то слать телеграм local handler = io.popen("chcp 861 && ping -n 1 api.telegram.org") local response = handler:read("*a") start_pos, end_pos = string.find(response, "Lost = 0" ) if start_pos==nil then telegram=false else telegram=true end return telegram end
Все бы ничего, при вызове io.popen каждый раз кратковременно командное окошко всплывает и перехватывает фокус ввода, то есть Quik на 1 -2 сек становится не главным окном, что очень не хорошо. Как это можно победить запихать вызов пингов в фоновое окно? Может кто уже побеждал?
Суть кода выводить сообщения в телеграм. До недавного времени все работало успешно, теперь когда РКН блокирует сообщения. QUIK ПОЛНОСТЬЮ ВЫЛЕТАЕТ. Так как отсутствие коннекта к телеграму предсказать невозможно, то не возможно предсказать, когда вылетит терминал Quik. Нужно искать выход - обработать исключение!
if err==nil then err="" end -- до этого места доходит пока нет ошибок и в дебагер выдает нормальную информацию PrintDbgStr(tostring(status) .. " --- " .. err .. " --- " .. www) -- если ошибка в ссылке или тайаут, то квик вылетает и информации в дебагере нет!!!
Вопрос КАК заставить QLUA обработать исключение. Виды исключений : неправильная ссылка, не верный адрес, хост не доступен???
Проверял послюднюю часть кода в ZeroBrane Studio для Lua - успенно отрабатывает возвращает код ошибки и я могу его обработать. Помогите разобоаться