Система принятия решений и/или Нечеткая логика(FuzzyLogic)

Страницы: Пред. 1 ... 8 9 10 11 12 След.
RSS
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
 
TGB,  Обо всем и по порядку.

1000 раз уже на форуме говорил "Не программист, и знания мои так себе". Это раз!
Форум - это площадка для обсуждений, а не выставления амбиций. Это два!

Теперь что касается последней темы:

 1. Не надо "Кусочничить".  Я тему веду последовательно, и рассуждения читать нужно также.
 2. А Вы то понимаете сами?  Пока не создан подход, то и оптимизировать НЕ ЧЕГО, от слова совсем?
 3. Ответ на Ваш вопрос. Оптимизация - это КОМПРОМИС. Поменяйте понимание и все встанет по местам.
 4. Повторюсь мне  не сложно:
Подход - Инвестиционный (означает тф. месячный и больше, в квик их нет).
Задача - возможность торговать и управлять 1 инструментом так и портфелем (универсальность).
Структура - модульная, много разового использования.
Интерфейс - наипростейший, понятный бабушке!

Про КПД, чего Вы рассуждаете? Все КПД моего старенького направленно на эффективность торговли = увеличению баланса счета. Тем более что подход описан, и можно воспользоваться функциями без корутин.

И последнее, Вы не сомневайтесь, сомнения нужны на стадии анализа, а когда решение принято, нужно действовать. Попробуйте. Ну или обосновано возражайте.
 
Цитата
VPM написал:
Попурри на тему "Три тополя на Плющихи" в исполнении ... .  ::

Зацените на сколько удобен интерфейс конечного пользователя в этом подходе.

Код

Преимущества MARKETDATA_PRO v1.0:

Вы не учитываете тот факт, что только Вы знаете это .
Мне как пользователю непонятно  зачем мне это.
--------------------------
Слишком.сложно, много букв. Трудно выявлять ошибки.
--------------------
Попробуйте не дублировать уже описанное.
--------------------
Все общее разместите в начале. Например так:
Код
-- Примеры использования универсальной системы: 
market  =   "TQBR"    
ticker  =   "SBER"  
 -- Глобальный экземпляр 
MDP  =  MarketData_Pro.Manager:new()

 -- 1. УНИВЕРСАЛЬНЫЙ ТЕХНИЧЕСКИЙ АНАЛИЗ ДЛЯ ЛЮБЫХ ДАННЫХ 

 -- Анализ цены последней сделки 
 local  price_analysis  =  MDP:getOrCreate{ interval  =  INTERVAL_H1,   param  =   "last" }
 print ( "Цена SMA20:" , price_analysis:sma( 20 ))
 print ( "Цена RSI14:" , price_analysis:rsi( 14 ))

 -- Анализ стакана (биды) 
 local  bid_analysis  =  MDP:getOrCreate{    interval  =  INTERVAL_M5,  param  =   "bid" }
 print ( "Биды EMA10:" , bid_analysis:ema( 10 ))
 print ( "Биды STDDEV20:" , bid_analysis:getIndicator( "STDDEV" ,  20 ))

 -- Анализ объема 
 local  volume_analysis  =  MDP:getOrCreate{  interval  =  INTERVAL_M15,  param  =   "volume" }
 print ( "Объем SMA30:" , volume_analysis:sma( 30 ))

 -- 2. КАСТОМНЫЕ ИНДИКАТОРЫ ДЛЯ ЛЮБЫХ ПАРАМЕТРОВ 

 -- Регистрация индикатора для спреда 
MDP:registerIndicator( "SPREAD_EMA" ,  function (buffer, period)
     if  buffer.size  <  period  then   return   nil   end 
     local  sum  =   0 
     for  i  =   1 , period  do  sum  =  sum  +  buffer:last(i).value   end 
     return  round(sum / period,  4 )
 end ,  "EMA for Spread" , {"numeric"})

 -- Анализ спреда bid/offer 
 local  spread_data  =  MDP:getOrCreate{    description  =   "Bid-Offer Spread" }
 -- Здесь можно обновлять буфер вручную с вычисленным спредом 
spread_data:updateBuffer( "value" ,  0.15 )  -- пример спреда 

 print ( "Спред EMA10:" , spread_data:getIndicator( "SPREAD_EMA" ,  10 ))

 -- 3. УНИВЕРСАЛЬНЫЙ АНАЛИЗ РАЗНЫХ ТИПОВ ДАННЫХ 

 local  analyses  =  {
    {param  =   "last" , desc  =   "Price Analysis" },
    {param  =   "bid" , desc  =   "Bid Analysis" }, 
    {param  =   "volume" , desc  =   "Volume Analysis" },
    {param  =   "value" , desc  =   "Value Analysis" }
}

 for  _, analysis  in  ipairs(analyses)  do 
     local  data  =  MDP:getOrCreate{ ticker  =   "GAZP" , interval  =  INTERVAL_D1, param  =  analysis.param  }
     local  sma  =  dat a:sma( 20 )
     local  rsi  =  dat a:rsi( 14 )
    print ( string.format ( "%s - SMA20: %.2f, RSI14: %.1f" ,  analysis.desc, sma  or   0 , rsi  or   0 ))
 end 

 -- 4. COMPLEX MULTI-PARAMETER ANALYSIS 

 interval  =  INTERVAL_H1

 -- Анализ нескольких параметров одновременно 
 local  multi_analysis  =   function ()
     local  price  =  MDP:getOrCreate{  param  =   "last"   }
     local  volume  =  MDP:getOrCreate{ param  =   "volume"      }
     local  bids  =  MDP:getOrCreate{ param  =   "bid"     }
     local  price_trend  =  price:ema( 10 )  >  price:ema( 20 )
     local  volume_spike  =  volume:sma( 5 )  >  volume:sma( 20 )  *   1.5 
     local  bid_strength  =  bids:ema( 5 )  >  bids:ema( 10 )  
     return  price_trend  and  volume_spike  and  bid_strength
 end 

 -- 5. DYNAMIC INDICATOR CREATION 

 -- Создание индикатора на лету 
 local  dynamic_indicator  =   function ()
    MDP:registerIndicator( "VOLUME_PRICE_RATIO" ,  function (buffer, priceBuffer, period)
         if   not  priceBuffer  or  buffer.size  <  period  then   return   nil   end 
        
         local  volume_avg  =   0 
         local  price_avg  =   0 
        
         for  i  =   1 , period  do 
             local  vol_item  =  buffer:last(i)
             local  price_item  =  priceBuffer:last(i)
             if  vol_item  and  price_item  then 
                volume_avg  =  volume_avg  +  vol_item.value
                price_avg  =  price_avg  +  price_item.value
             end 
         end 
        
        volume_avg  =  volume_avg / period
        price_avg  =  price_avg / period
        
         return  volume_avg /  math.max (price_avg,  0.001 )
     end ,  "Volume-Price Ratio" , {"volume"})
 end 

Универсальная система - это такая система, разработчик которой не имеет представления где и как ее будут применять.
-----------------------
Пользователь такой системы не в состоянии понять все, что включил в нее разработчик.
-------------------------
В результате реально либо используется 10 процентов  возможности такой системы, либо пользователь начинает делать свою - не универсальную.
 
nikolz,  На счет сложности, для меня очень сложно, здесь с Вами не поспоришь. Отсюда бывает и путаница.

Не могу согласиться: "Универсальная система - это такая система, разработчик которой не имеет представления где и как ее будут применять.".  На  все известно и источники и методы, а использование их на прямую зависит от сложности самой торговой системы. Изначально думал для удобства сделать единый интерфейс рыночных данных, как то само собой добавились индикаторы и вся остальная инфраструктура и все вылилось в целый фреймворк.   Конечно нужно тестирование и оптимизация.

"Пользователь такой системы не в состоянии понять все, что включил в нее разработчик." Так ведь и задача стоит, не нужно пользователю начинка, подключил блок и пользуйся понятным простым интерфейсом. Меня "ушатала" постоянная переделка. Задача все таки, удобство получать в торговых подходах, а не упражнения в программировании.  Удобно и то что есть алгоритмы которые использует большинство, ATR, StDev, средниеи.
 
"Едем дальше, видим больше"! На мой взгляд данный подход, нужно распространить на получение "Серверных данных". Для понимания проблематики, архитектуру предлагается использовать общею для фондового и сросного рынков?  Задача все та же.

Задача - возможность торговать и управлять 1 инструментом так и портфелем (универсальность), с учетом рисков (как квиковских, так и собственно ручных), ну и конечно одной из основных задач управление позицией.
Структура - модульная, много разового использования.
Интерфейс - наипростейший, понятный бабушке!

Обобщенно свести квиковские в функции в удобную оболочку:

Функции взаимодействия скрипта Lua и Рабочего места QUIK

  • getDepoEx - функция для получения позиций по инструментам указанного типа
  • getMoneyEx - функция для получения информации по денежным позициям указанного типа
  • getFuturesLimit - функция для получения информации по фьючерсным лимитам
  • getFuturesHolding - функция для получения информации по фьючерсным позициям
  • getSecurityInfo - функция для получения информации по инструменту?
  • getTradeDate - функция для получения даты торговой сессии
  • CalcBuySell - функция для расчета максимально возможного количества лотов в заявке? (скорее для контроля, уж больно тяжелая?)
  • getPortfolioInfoEx - функция для получения значений параметров таблицы «Клиентский портфель» с учетом срока расчётов
  • getBuySellInfoEx - функция для получения параметров (включая срок расчётов) таблицы «Купить/Продать» (Важная для маржинальной торговли!)
 
Вариант реализации подхода, что скажите?
Код
QuickDataManager = {}
QuickDataManager.__index = QuickDataManager

-- Конструктор класса
function QuickDataManager:new(firmid, client_code)
    local obj = setmetatable({}, self)
    obj.firmid = firmid
    obj.client_code = client_code
    obj.data = {}  -- Для хранения кэшированных данных
    obj.loaded = {}  -- Для отслеживания загруженных параметров
    return obj
end

-- Функция для ленивой загрузки данных с учетом firmid и client_code
function QuickDataManager:_loadData(param, optional_params)
    -- Формируем уникальный ключ для кэширования (с учетом параметров)
    local cache_key = param .. "_" .. (optional_params or "")
    
    if self.loaded[cache_key] then
        return self.data[cache_key]  -- Возвращаем из кэша
    end

    -- Если данных нет в кэше, загружаем их
    local data
    if param == "money" then
        -- Вызов getMoneyEx для получения информации по деньгам
        data = getMoneyEx(self.firmid, self.client_code, optional_params.tag or "", optional_params.currcode or "", optional_params.limit_kind or 0)
    elseif param == "depo" then
        -- Вызов getDepoEx для получения позиций по инструментам
        data = getDepoEx(self.firmid, self.client_code, optional_params.sec_code or "", optional_params.trdaccid or "", optional_params.limit_kind or 0)
    elseif param == "portfolio" then
        -- Вызов getPortfolioInfoEx для получения информации по клиентскому портфелю
        data = getPortfolioInfoEx(self.firmid, self.client_code, optional_params.limit_kind or 0, optional_params.board_tag or "", optional_params.currency or "")
    elseif param == "buy_sell" then
        -- Вызов getBuySellInfoEx для получения параметров по заявкам
        data = getBuySellInfoEx(self.firmid, self.client_code, optional_params.class_code or "", optional_params.sec_code or "", optional_params.price or 0)
    elseif param == "futures_limit" then
        -- Вызов getFuturesLimit для получения фьючерсных лимитов
        data = getFuturesLimit(self.firmid, optional_params.trdaccid or "", optional_params.limit_type or 0, optional_params.currcode or "")
    elseif param == "futures_holding" then
        -- Вызов getFuturesHolding для получения позиций по фьючерсным счетам
        data = getFuturesHolding(self.firmid, optional_params.trdaccid or "", optional_params.sec_code or "", optional_params.type or 0)
    end
    
    -- Кэшируем загруженные данные
    self.data[cache_key] = data
    self.loaded[cache_key] = true

    return data or {}
end

-- Доступ к данным с ленивой загрузкой
function QuickDataManager:__index(key)
    if key == "money" then
        return self:_loadData("money", {tag = "default", currcode = "USD", limit_kind = 0})
    elseif key == "depo" then
        return self:_loadData("depo", {sec_code = "SBER", trdaccid = "1234", limit_kind = 0})
    elseif key == "portfolio" then
        return self:_loadData("portfolio", {limit_kind = 0, board_tag = "TQBR", currency = "RUB"})
    elseif key == "buy_sell" then
        return self:_loadData("buy_sell", {class_code = "TQBR", sec_code = "SBER", price = 1000})
    elseif key == "futures_limit" then
        return self:_loadData("futures_limit", {trdaccid = "TQBR", limit_type = 0, currcode = "USD"})
    elseif key == "futures_holding" then
        return self:_loadData("futures_holding", {trdaccid = "1234", sec_code = "SBER", type = 0})
    else
        -- Загружать другие параметры по необходимости
        return self:_loadData(key)
    end
end

-- Пример использования
local manager = QuickDataManager:new("Firm1", "Client1")

-- Ленивое обращение к данным
local money = manager.money  -- Получение информации по деньгам
local depo = manager.depo    -- Получение позиций по инструментам
local portfolio = manager.portfolio  -- Получение информации по портфелю
local buy_sell = manager.buy_sell  -- Получение информации по заявкам на покупку/продажу
 
Что делает:

QuickDataManager:new(firmid, client_code). Конструктор создаёт экземпляр менеджера с обязательными параметрами firmid и client_code.
Это основные параметры для работы с системой QUIK. Все остальные параметры передаются как опциональные в виде таблицы в функции.

QuickDataManager:_loadData(param, optional_params). Это основная функция для ленивой загрузки данных. В зависимости от параметра (например, "money", "depo", "portfolio", и т.д.), она делает запросы к соответствующим функциям QUIK. Функция также проверяет, есть ли уже кэшированные данные для этого параметра, и если они есть — возвращает их. Если данных нет, выполняется запрос и кэшируются результаты? Здесь основной вопрос как правильно организовать работу?

QuickDataManager:__index(key). Метатаблица __index отвечает за доступ к данным через ключи, такие как money, depo, portfolio и другие. Для каждого ключа вызывается соответствующая функция ленивой загрузки, которая получает необходимые данные и кэширует их.

Параметры (например, tag, currcode, limit_kind, sec_code и другие) передаются через опциональные параметры, что позволяет адаптировать запросы под различные нужды и сохранить гибкость подхода.

Пример использования. При первом обращении к данным (manager.money, manager.depo, manager.portfolio, и т.д.) данные будут загружены, затем они будут кэшироваться для последующего использования без лишних запросов к системе. Что позволяет уменьшить нагрузку на систему и ускорить последующие операции. Как альтернатива. Если нужно избежать повторного получения одинаковых данных, можно добавить механизм для "очистки" кэша по истечении времени или по запросу.
 
Цитата
VPM написал:
Что делает:

QuickDataManager:new(firmid, client_code).  Конструктор создаёт экземпляр менеджера с обязательными параметрами firmid и client_code.
Это основные параметры для работы с системой QUIK. Все остальные параметры передаются как опциональные в виде таблицы в функции.

QuickDataManager:_loadData(param, optional_params). Это основная функция для ленивой загрузки данных. В зависимости от параметра (например, "money", "depo", "portfolio", и т.д.), она делает запросы к соответствующим функциям QUIK. Функция также проверяет, есть ли уже кэширов анные данные для этого параметра, и если они есть — возвращает их. Если данных нет, выполняется запрос и кэшируются результаты? Здесь основной вопрос как правильно организовать работу?

QuickDataManager:__index(key).  Метатаблица __index отвечает за доступ к данным через ключи, такие как money, depo, portfolio и другие. Для каждого ключа вызывается соответствующая функция ленивой загрузки, которая получает необходимые данные и кэширует их.

Параметры (например, tag, currcode, limit_kind, sec_code и другие) передаются через опциональные параметры, что позволяет адаптировать запросы под различные нужды и сохранить гибкость подхода.

Пример использования. При первом обращении к данным (manager.money, manager.depo, manager.portfolio, и т.д.) данные будут загружены, затем они будут кэшироваться для последующего использования без лишних запросов к системе. Что позволяет уменьшить нагрузку на систему и ускорить последующие операции. Как альтернатива. Если нужно избежать повторного получения одинаковых данных, можно добавить механизм для "очистки" кэша по истечении времени или по запросу.
Лучше не заставлять пользователя изучать Lua и не вводить без острой надобности специальные термены.
Например,
вместо:
Код
QuickDataManager:new(firmid, client_code). 
написал бы
Код
fimid=XXXX
client_code=EEEE
а в скрипте под капотом написал бы
Код
QuickDataManager:new(firmid, client_code). 
про термины:
"ленивая" загрузка  - пользователю обязательно это знать? Тогда что такое ленивая и какие еще есть ?
----------------------------
Т е сначала решите, для кого Вы пишите эту инструкцию. Т е какой у него уровень знаний должен быть, чтобы понять, что вы написали.
 
и еще
В своем скрипте все параметры типа firmid, client  получаю из QUIK.
Т е их не надо вводить пользователю.
 
nikolz,  Это не инструкция, это описные одного из вариантов подхода, если хотите мое виденье, выложено на обсуждение сообществу.  

"ленивая" загрузка - описание есть выше, такую возможность предоставляет ООП, если не очень понятно можно посмотреть фреймворк на который  я ссылаюсь.

Подход уже тоже устарел так как нашлись более удобные решения, например кэширует результаты и имеет авто обновление через корутину (с ограничением нагрузки),
 
Цитата
nikolz написал:
В своем скрипте все параметры типа firmid, client  получаю из QUIK. Т е их не надо вводить пользователю.
Не очень понимаю этот подход, ну если с firmid все еще понятно, зависит в чей терминал скипт загружен. То с client не все очевидно, все равно приходится в водить,  
 
Цитата
VPM написал:
Цитата
nikolz написал:
В своем скрипте все параметры типа firmid, client  получаю из QUIK. Т е их не надо вводить пользователю.
Не очень понимаю этот подход, ну если с firmid все еще понятно, зависит в чей терминал скипт загружен. То с client не все очевидно, все равно приходится в водить,  
можете вводить это не принципиально.
Для информации.
Вот что надо ввести пользователю в мой скрипт
Код
clas_list="QJSIM:0,SPBFUT:1,CETS:2"; --список классов счетов и тип инструментов 0 -акции 1-фьючерсы 2- валюта 3- опционы
sec_list="ROSN,GAZP,SBER,PLZL,GMKN,CHMF,HYDR,LKOH,MOEX,SNGS"  --список инструментов для портфеля, если нет, то все инструменты по заданным классам
task_list="task1,task2,task3,task4"  -- список существующих задач
sec_user={ --параметры устанавливаемые пользователем для инструментов по умолчанию
1, --интервал свечей
0, --подписка на стакан
0, --флаг разрешения коротких позиций
0,--флаг разрешения установки стопа  1 -обычный, 2-скользящий, 3 -take 4-stop и take
"task1",
"task2" --список задач по умолчанию для каждого инструмента
}
т е
clas_list - список торгуемых классов
sec_list -список торгуемых инструментов
task_list -- список существующих задач, которые ему дает разработчик или он пишет сам  
sec_user -- параметры для все инструментов
----------------------
Для каждого инструмента,задачи можно  через ':' указать индивидуальные параметры.
Пользователю не надо изучать луа, если он не хочет писать задачи.
===================
У меня тоже универсальная система.  
 
Код
    if self.loaded[cache_key] then
        return self.data[cache_key]  -- Возвращаем из кэша
    end

Т.к. я вижу в данных таблицы, которые постоянно изменяются, то не очень понятно о каком кеше идет речь. Кеш нужен для условно постоянно информации, а не для той, что изменяется каждую секунду.
Эти данные нужны для совершения торговых операций, например, проверить, что там с доступными средствами. Обращаться постоянно к ним особой нужды нет.
Также есть данные изменяемые при изменении статуса сессии, завершении клиринга - вот их можно помещать в кеш, и обновлять оный по этим событиям.

И да, делать для этого объекты - так себе затея.

Входящие параметры для скрипта - это код инструмента, торговый счет, субсчет (код клиента) если их несколько. Все. Остальное получается из терминала. Впрочем даже счет и субсчет можно подставить из данных для класса инструмента. Зачастую они и нужны.

Если Вы пишите скрипты для себя, то, конечно, делайте как хотите. Но если уже для других, то всегда отталкивайтесь, что максимум что может сделать пользователь - это изменить файл настроек, где все максимально просто и понятно. Не надо искать информацию в служебных окнах терминала. Либо делать интерфейс, через который тоже просто задаются параметры.
 
Цитата
VPM написал:
"Едем дальше, видим больше"! На мой взгляд данный подход, нужно распространить на получение "Серверных данных". Для понимания проблематики, архитектуру предлагается использовать общею для фондового и сросного рынков?  Задача все та же.

Задача - возможность торговать и управлять 1 инструментом так и портфелем (универсальность), с учетом рисков (как квиковских, так и собственно ручных), ну и конечно одной из основных задач управление позицией.
Структура - модульная, много разового использования.
Интерфейс - наипростейший, понятный бабушке!

Обобщенно свести квиковские в функции в удобную оболочку:

Функции взаимодействия скрипта Lua и Рабочего места QUIK  
getDepoEx - функция для получения позиций по инструментам указанного типа
 getMoneyEx - функция для получения информации по денежным позициям указанного типа
 getFuturesLimit - функция для получения информации по фьючерсным лимитам
 getFuturesHolding - функция для получения информации по фьючерсным позициям
 getSecurityInfo - функция для получения информации по инструменту?
 getTradeDate - функция для получения даты торговой сессии
 CalcBuySell - функция для расчета максимально возможного количества лотов в заявке? (скорее для контроля, уж больно тяжелая?)
 getPortfolioInfoEx - функция для получения значений параметров таблицы «Клиентский портфель» с учетом срока расчётов
 getBuySellInfoEx - функция для получения параметров (включая срок расчётов) таблицы «Купить/Продать» (Важная для маржинальной торговли!)
У меня эти функции спрятаны.
Пользователю доступ к ним не нужен и он про них ничего не знает и знать не хочет.
В задаче он просто читает нужную переменную скрипта.  
 
У меня данные обновляются лишь по колбекам.
Поэтому нет смысла их кэшировать.
 
VPM,
Возможно Вы не обратили внимание,
но у меня
ВСЯ УНИВЕРСАЛЬНАЯ СИСТЕМА это 380 операторов
из них 120 - это конечный автомат обработки колбеков
и 60 - конечный автомат обработки задач.
-----------------------------
+ скрипты задач.  
которые могут создать событие на выставление удаление перестановку заявок.
----------------------
Интерфейс к ней показал ранее.
 
сделал новый интерфейс еще проще:
Код
list={
--список инструментов для портфеля, если нет, то все инструменты по заданным классам
sec="ROSN,GAZP,SBER,PLZL,GMKN,CHMF,HYDR,LKOH,MOEX,SNGS"  
task="task1,task2,task3,task4"  -- список существующих задач
}
user_sec="   interval:1,quote:0,short:0,stop:0,takeprofit:0,task:task1:task2,quota:0"

--user_sec--параметры пользователя для sec  по умолчанию--interval --интервал свечей
--quote --подписка на стакан
--short -- разрешениe коротких позиций
--stop--флаг разрешения установки стопа  1 -обычный, 2-скользящий,
--takeprofit, -- флаг разрешения TakePrifit
--task---список задач по умолчанию для каждого инструмента
--quota--для средств для покупки в одной заявке
--можно задавать индивидуальные параметры для задач и инструментов
 
поправил комментарии
Код
list={
--список инструментов для портфеля
sec="ROSN,GAZP,SBER,PLZL,GMKN,CHMF,HYDR,LKOH,MOEX,SNGS"  
task="task1,task2,task3,task4"  -- список существующих задач
}
user_sec="   interval:1,quote:0,short:0,stop:0,takeprofit:0,task:task1:task2,quota:0"

 
В концептуальном примере выше, используется ленивый подход (lazy loading) для получения данных, когда они действительно необходимы, а не заранее.

В частности, для каждого параметра вызывается функция только при попытке обращения к этому параметру, что снижает избыточную загрузку данных. В примере уже используется ленивое получение данных для параметров через функцию __index. Это значит, что данные не загружаются, пока они явно не понадобятся (например, при обращении к ним в коде).

Производительность, хотя ленивый подход помогает избежать лишних запросов, важно следить за тем, чтобы не было слишком частых запросов к серверу, если объем данных большой.
Оптимизация, кэширование данных, чтобы не запрашивать данные каждый раз при обращении к параметрам, добавить кэширование результатов запросов через self.cache для параметров, чтобы избежать лишних вызовов.

Некоторые моменты, которые нужно улучшить для большей гибкости и упрощения, и настройки стабильной работы.

1. Обработчик ленивой загрузки для разных параметров. Вместо жесткого кода для каждого параметра, можно обрабатывать ленивую загрузку более универсально, используя механизм, который будет определять, какой тип данных нужно загружать, в зависимости от имени параметра.

2. Модификация функции __index. Нужно немного улучшить проверку, чтобы все параметры, можно было бы загружать лениво, а не только жестко прописанные. Например, можно динамически извлекать значения для всех типов данных через getParamEx.
 
Разбор полета.

Грамотная архитектура, для идеи "ленивого дата-менеджера" и улучшения её в духе реального продакшн-решения. Разберу вариант подробней, чтобы подтвердить, где попал в точку и что нужно отполировать.

1. Нормализация ключа кэша.
Сортировка ключей — идея для стабильности (иначе "a,b" и "b,a" могли бы быть разными), нужно ещё добавить firmid и client_code в начало ключа: local key_parts = {self.firmid, self.client_code, param}
Так один менеджер может обслуживать несколько клиентов без пересечений в кэше, теперь комбинация sec_code=SBER и sec_code=GAZP не будет путаться.

2. Кэш с TTL и проверкой актуальности.
self.cache_ttl = 5
self.cache_timestamps[cache_key] = os.time()

Это минимальный, но практичный шаг, QUIK не всегда требует мгновенной актуализации, но важно не держать stale-данные применим подход "time-based invalidation".
Также можно улучшить, если добавить опциональный параметр TTL при вызове?
function QuickDataManager:new(firmid, client_code, cache_ttl)
   obj.cache_ttl = cache_ttl or 5
end

3. Безопасная загрузка через pcall: local success, err = pcall(function()
QUIK-функции часто возвращают nil или бросают ошибки при нестабильных данных. Это гарантия, что менеджер не уронит основной поток.

4. Методы вместо __index. Переход от хардкода (manager.depo) к явным методам (manager:get_depo()) — это повышает читаемость и контроль над параметрами.

5. Управление кэшем.
QuickDataManager:clear_cache()
QuickDataManager:refresh()
QuickDataManager:get_cache_stats()

Просто и функционально — всё, что нужно для отладки и тонкой настройки. get_cache_stats() — инструмент для мониторинга эффективности.

Что можно слегка отшлифовать.

1. Унификация вызова QUIK-функций. сделано логично:
if param == "money" then
   data = getMoneyEx(...)

Но чтобы не плодить if-ветки, можно вынести маппинг функций в таблицу:
QuickDataManager.functions = {
   money = getMoneyEx,
   depo = getDepoEx,
   portfolio = getPortfolioInfoEx,
   buy_sell = getBuySellInfoEx,
   futures_limit = getFuturesLimit,
   futures_holding = getFuturesHolding
}

А потом универсально:
local func = self.functions[param]
if not func then error("Unknown data type: " .. param) end
data = func(table.unpack(args))

Чтобы args формировать автоматически — хранить шаблоны параметров?

2. Параметры по умолчанию.
Сейчас много or "", можно централизовать дефолты:
QuickDataManager.defaults = {
   money = { tag="", currcode="SUR", limit_kind=0 },
   depo = { sec_code="", trdaccid="", limit_kind=0 },
   portfolio = { limit_kind=0 },
   buy_sell = { class_code="", sec_code="", price=0 },
   futures_limit = { trdaccid="", limit_type=0, currcode="SUR" },
   futures_holding = { trdaccid="", sec_code="", type=0 }
}
Тогда метод _loadData может начинаться с: optional_params = table.merge(self.defaults[param] or {}, optional_params or {})

3. Асинхронность или очередь обновлений. Если  добавить корутину-обновление (например, периодическое refresh), то эта структура идеально готова — просто запускаешь фоновые обновления без блокировки QUIK.

4. Универсальный интерфейс доступа. Чтобы сохранить удобство "ленивого доступа", можно вернуть синтаксическую обёртку:

setmetatable(QuickDataManager, {
   __index = function(self, key)
       if self["get_" .. key] then
           return self["get_" .. key](self)
       end
   end
})
Тогда снова можно писать:
local depo = manager.depo  -- ленивый вызов get_depo()
но при этом параметры будут управляться методами?

 Финальный вариант (сбалансированный).

function QuickDataManager:new(firmid, client_code, cache_ttl)
   local obj = setmetatable({}, self)
   obj.firmid = firmid
   obj.client_code = client_code
   obj.cache = {}
   obj.cache_timestamps = {}
   obj.cache_ttl = cache_ttl or 5
   return obj
end

function QuickDataManager:_build_cache_key(param, optional_params)
   local key_parts = {self.firmid, self.client_code, param}
   if optional_params then
       local sorted = {}
       for k in pairs(optional_params) do table.insert(sorted, k) end
       table.sort(sorted)
       for _, k in ipairs(sorted) do
           table.insert(key_parts, k .. "=" .. tostring(optional_params[k]))
       end
   end
   return table.concat(key_parts, "|")
end
Просто добавить централизованные defaults и маппинг функций.

ИТОГ. Такая версия — уже достаточно зрелая. Она:
* Решает проблему дублирования и нагрева QUIK-API;
* Гибко масштабируется под любые источники данных;
* Предусматривает отказоустойчивость;
* Простая для использования в сценариях (ленивая инициализация).

К этому обновленному, менеджеру если добавить, автоматическое обновление данных через coroutine с учётом TTL и нагрузки (чтобы QUIK не тормозил). Это позволит, например, держать кэш свежим без прямых вызовов refresh().
 
Если добавить к этим модулям вспомогательную оболочку типа DataHub, которая объединит QuickDataManager и мой MarketData в одну унифицированную архитектуру (единый интерфейс “данные QUIK” и “данные с рынка”), то получим идеальную базу для торгового ядра?
Но в этом варианте в разы уваливается сложность, и требуется более профессиональный подход, в распределении ресурсов и нагрузок CPU, то о чем  нас постоянно предупреждают опытные разработчики.
 
Прислушаюсь к советам и замечания профи:
Цитата
Nikolay написал:
И да, делать для этого объекты - так себе затея. Входящие параметры для скрипта - это код инструмента, торговый счет, субсчет (код клиента) если их несколько. Все. Остальное получается из терминала. Впрочем даже счет и субсчет можно подставить из данных для класса инструмента. Зачастую они и нужны. Если Вы пишите скрипты для себя, то, конечно, делайте как хотите. Но если уже для других, то всегда отталкивайтесь, что максимум что может сделать пользователь - это изменить файл настроек, где все максимально просто и понятно. Не надо искать информацию в служебных окнах терминала. Либо делать интерфейс, через который тоже просто задаются параметры.
Разберусь с чем имеем дело.

1. Сила и мощь при  использовании луа - это его основная структура данных - таблица. Именно они позволяют нам структурировать код, так как нам нужно, создавать модули. Что в свою очередь приводит к многоразовому использованию этих модулей (принцип "Написал и Забыл") . Согласитесь - это просто удобно, если возникает необходимость что то исправить, мы просто работаем с этим модулем. И здесь возникает основной вопрос  по каким критериям, необходимо делить код на модули?

2. Луа позволяет легко создавать так называемый "Объект", создавая его необходимо понимать, а для чего и когда это удобно? Я не очень разобрался со всеми возможностями ООП, для меня важную роль играет фактор "таблица знает о себе все", то есть удобство управления, хранения и  получения данных. Именно здесь возникают профессиональные сложности.

3. Удобство конечного пользователя, просто необходимая. Автоматизация получения данных, утилиты, сервисные вещи и все прочее в библиотеку. Много  разовое модульное использования превращает нас из конструктора деталей и узлов в Главного конструктора нашего проекта "Робот". Один раз отладили, и используем в проверке наших торговых идей. Так что взялись за библиотеку, а идей еще очень много и не терпится их проверить!  :smile:  
 
Сформулирую подход — Разделение кода на модули. Критерии разделения кода в Lua для QUIK:

а) Функциональная логика – каждый модуль отвечает за конкретную задачу:

* qlib_core.lua — системные и сервисные функции (isConnected, sleep, getTradeDate, логирование).
* qlib_data.lua — получение рыночной информации (getQuotes, getParamEx, getSecurityInfo).
* qlib_portfolio.lua — управление портфелем (initPortfolio, autoRebalancePortfolio, позиции).
* qlib_utils.lua — общие утилиты (tableKeys, форматирование, вспомогательные функции).

б) Повторное использование — если какая-то функция используется в разных местах, выносим её в отдельный модуль.
с) Лёгкость замены/обновления — чтобы исправления коснулись только конкретного функционала, без изменения всего робота.
д) Простота конечного пользователя — модули скрывают все сложности и предоставляют простой интерфейс: входные параметры минимальны (например, только тикер, счёт, субсчёт).

Итог. Модули разделяем
* по смыслу
* и повторяемости функций.
Цель - пользователь получает простую точку входа — скрипт робота.
 
2. Создание объектов в Lua (OOП-подход). Lua не имеет полноценного OOP, но таблицы + метатаблицы позволяют достаточно просто строить объекты.

Когда удобно использовать объект?
* Когда объект “знает о себе всё”, например, инструмент/портфель содержит всю информацию о классе, счёте, лоте, цене, позиции, статусе торгов.
* Когда нужно хранить состояние и методы, которые его изменяют.
* Когда удобно группировать методы вокруг данных, чтобы не передавать их постоянно в функции.

Когда объект может быть лишним?
* Если скрипт простой и все данные можно хранить в таблицах без методов.
* Если входные параметры минимальны, и основной сценарий — «инициализировал и забыл».

Итог.
*  ООП полезно для сложных инструментов, портфелей и роботов.
*  Но для конечного пользователя упрощённый интерфейс (модуль + таблицы данных) удобнее, просто и наглядно.

3. Удобство конечного пользователя. Ну здесь собственно и комментарии излишни, просто зафиксируем.

* Минимум входных параметров, тикер + счёт + субсчёт. Всё остальное берётся автоматически из QUIK.
* Автоопределение параметров, класс инструмента, шаг цены, масштаб, доступные позиции, счета.
* Модули как библиотека, пользователь не меняет код робота, а лишь занимается его конфигурацией.
* Принцип “Написал и Забыл”, всё сложное внутри библиотеки, робот только управляет потоком и вызывает нужные функции.

Ну как то так. Хорошего кода.
 
Код
Минимальный рабочий Скрипт для QUIK

local WORKING_FLAG = true
local class_code, sec_code = "TQBR", "SBER"--"SPBFUT", "RIZ5"

-- Простые переменные для данных
local last_bid = "N/A"
local last_offer = "N/A" 
local last_candle = nil
local ds = nil

-- Прямые callback функции без сложных систем
function OnParam(class_code, sec_code)
    if class_code == "SPBFUT" and sec_code == "RIZ5" then
        last_bid = getParamEx(class_code, sec_code, "bid").param_value or "N/A"
        last_offer = getParamEx(class_code, sec_code, "offer").param_value or "N/A"
    end
end

function OnQuote(class_code, sec_code)
    -- Просто получаем стакан, но не обрабатываем чтобы не нагружать
end

-- Callback для свечей
local function onCandleUpdate(index)
    if ds and index <= ds:Size() then
        last_candle = {
            open = ds:O(index),
            high = ds:H(index),
            low = ds:L(index),
            close = ds:C(index),
            volume = ds:V(index)
        }
        
        if index == 1 then  -- Новая свеча
            message(string.format("НОВАЯ СВЕЧА %s O=%.1f C=%.1f", 
                sec_code, last_candle.open, last_candle.close))
        end
    end
end

function main()
    message("=== ПРОСТОЙ РОБОТ ЗАПУЩЕН ===")
    
    -- Простые подписки
    local result = ParamRequest(class_code, sec_code, "bid")
    message( '1. bid '.. tostring(result) ..' '.. type(result))
    local result = ParamRequest(class_code, sec_code, "offer") 
    message( '2. offer '.. tostring(result) ..' '.. type(result))
    local result = ParamRequest(class_code, sec_code, "last")
    message( '3. last '.. tostring(result) ..' '.. type(result))
    
    local result = Subscribe_Level_II_Quotes(class_code, sec_code)
    message( '1. Level_II '.. tostring(result) ..' '.. type(result))
    
    -- Создаем источник данных свечей
    local error_desc = ''
    
    ds, error_desc  = CreateDataSource(class_code, sec_code, 60, "last")
    message( tostring(error_desc) ..' '.. type(ds))
    if ds then
        --sleep(1000)
        local result = ds:SetEmptyCallback() -- SetUpdateCallback(onCandleUpdate) --
        message("Источник свечей создан, размер: " .. ds:Size() ..' '..tostring(result) )
    else
        message("ОШИБКА: Не удалось создать источник свечей " .. tostring(error_desc))
        return
    end
    
    -- ОСНОВНОЙ ЦИКЛ - ПРОСТОЙ И ЧИСТЫЙ
    local iteration = 0
    local last_print_time = os.time()
    
    while WORKING_FLAG do
        iteration = iteration + 1
        
        -- Печатаем статус раз в 30 секунд
        local current_time = os.time()
        if current_time - last_print_time >= 30 then
            message(string.format("Статус: %d итераций, Bid=%s, Offer=%s", 
                iteration, last_bid, last_offer))
            
            local result = ds:SetEmptyCallback() -- SetUpdateCallback(onCandleUpdate) --
            message("получать данные с сервера, размер: " .. ds:Size() ..' '..tostring(result) )
            if ds:Size() and ds:Size()>0  then
                message(string.format("Свеча: O=%.1f H=%.1f L=%.1f C=%.1f", 
                    ds:O(ds:Size()), ds:H(ds:Size()), 
                    ds:L(ds:Size()), ds:C(ds:Size())
                    ))
            end
            
            last_print_time = current_time
        end
        
        -- ВАЖНО: Даем время QUIK обработать события
        sleep(1000)  -- 1 СЕКУНДА - достаточно для избежания "Превышения времени"
    end
    
    message("Робот остановлен")
end

-- Простые обработчики остановки
function OnStop()
    WORKING_FLAG = false
    if ds then ds:Close() end
    message("Робот остановлен по команде")
end
function OnClose()
    WORKING_FLAG = false  
    if ds then ds:Close() end
end
function OnInit()
    message("Скрипт инициализирован")
end
Код
   TYPE   DATE   TIME   MESSAGE
1   1   17.11.2025   7:36:58   Скрипт инициализирован
2   1   17.11.2025   7:36:58   === ПРОСТОЙ РОБОТ ЗАПУЩЕН ===
3   1   17.11.2025   7:36:58   1. bid true boolean
4   1   17.11.2025   7:36:58   2. offer true boolean
5   1   17.11.2025   7:36:58   3. last true boolean
6   1   17.11.2025   7:36:58   1. Level_II true boolean
7   1   17.11.2025   7:36:58   nil table
8   1   17.11.2025   7:36:58   Источник свечей создан, размер: true 0
9   1   17.11.2025   7:37:28   Статус: 31 итераций, Bid=N/A, Offer=N/A
10   1   17.11.2025   7:37:28   Источник свечей создан, размер: true 0
11   1   17.11.2025   7:37:58   Статус: 61 итераций, Bid=N/A, Offer=N/A
12   1   17.11.2025   7:37:58   Источник свечей создан, размер: true 0
13   1   17.11.2025   7:38:28   Статус: 91 итераций, Bid=N/A, Offer=N/A
14   1   17.11.2025   7:38:28   Источник свечей создан, размер: true 0
15   1   17.11.2025   7:38:58   Статус: 121 итераций, Bid=N/A, Offer=N/A
16   1   17.11.2025   7:38:58   Источник свечей создан, размер: true 0
17   1   17.11.2025   7:39:28   Статус: 151 итераций, Bid=N/A, Offer=N/A
18   1   17.11.2025   7:39:28   Источник свечей создан, размер: true 0
19   1   17.11.2025   7:39:58   Статус: 181 итераций, Bid=N/A, Offer=N/A
20   1   17.11.2025   7:39:58   Источник свечей создан, размер: true 0
21   1   17.11.2025   7:40:28   Статус: 211 итераций, Bid=N/A, Offer=N/A
22   1   17.11.2025   7:40:28   Источник свечей создан, размер: true 0
23   1   17.11.2025   7:40:58   Статус: 241 итераций, Bid=N/A, Offer=N/A
24   1   17.11.2025   7:40:58   Источник свечей создан, размер: true 0
25   1   17.11.2025   7:41:28   Статус: 271 итераций, Bid=N/A, Offer=N/A
26   1   17.11.2025   7:41:28   Источник свечей создан, размер: true 0
27   1   17.11.2025   7:41:58   Статус: 301 итераций, Bid=N/A, Offer=N/A
28   1   17.11.2025   7:41:58   Источник свечей создан, размер: true 0
29   1   17.11.2025   7:42:28   Статус: 331 итераций, Bid=98610.000000, Offer=98650.000000
30   1   17.11.2025   7:42:28   Источник свечей создан, размер: true 0
31   1   17.11.2025   7:42:58   Статус: 360 итераций, Bid=98610.000000, Offer=98650.000000
32   1   17.11.2025   7:42:58   Источник свечей создан, размер: true 0
33   1   17.11.2025   7:43:28   Статус: 390 итераций, Bid=98610.000000, Offer=98650.000000
34   1   17.11.2025   7:43:28   Источник свечей создан, размер: true 0
35   1   17.11.2025   7:43:58   Статус: 420 итераций, Bid=98610.000000, Offer=98650.000000
36   1   17.11.2025   7:43:58   Источник свечей создан, размер: true 0
37   1   17.11.2025   7:44:28   Статус: 450 итераций, Bid=98610.000000, Offer=98650.000000
38   1   17.11.2025   7:44:28   Источник свечей создан, размер: true 0
39   1   17.11.2025   7:44:58   Статус: 480 итераций, Bid=98610.000000, Offer=98650.000000
40   1   17.11.2025   7:44:58   Источник свечей создан, размер: true 0
41   1   17.11.2025   7:45:28   Статус: 510 итераций, Bid=98610.000000, Offer=98650.000000
42   1   17.11.2025   7:45:28   Источник свечей создан, размер: true 0
43   1   17.11.2025   7:45:58   Статус: 540 итераций, Bid=98610.000000, Offer=98650.000000
44   1   17.11.2025   7:45:58   Источник свечей создан, размер: true 0
45   1   17.11.2025   7:46:28   Статус: 570 итераций, Bid=98610.000000, Offer=98650.000000
46   1   17.11.2025   7:46:28   Источник свечей создан, размер: true 0
47   1   17.11.2025   7:46:58   Статус: 600 итераций, Bid=98610.000000, Offer=98650.000000
48   1   17.11.2025   7:46:58   Источник свечей создан, размер: true 0
49   1   17.11.2025   7:47:28   Статус: 630 итераций, Bid=98610.000000, Offer=98650.000000
50   1   17.11.2025   7:47:28   Источник свечей создан, размер: true 0
51   1   17.11.2025   7:47:58   Статус: 660 итераций, Bid=98610.000000, Offer=98650.000000
52   1   17.11.2025   7:47:58   Источник свечей создан, размер: true 0
53   1   17.11.2025   7:48:28   Статус: 690 итераций, Bid=98610.000000, Offer=98650.000000
54   1   17.11.2025   7:48:28   Источник свечей создан, размер: true 0
55   1   17.11.2025   7:48:58   Статус: 720 итераций, Bid=98610.000000, Offer=98650.000000
56   1   17.11.2025   7:48:58   Источник свечей создан, размер: true 0
57   1   17.11.2025   7:49:28   Статус: 750 итераций, Bid=98610.000000, Offer=98650.000000
58   1   17.11.2025   7:49:28   Источник свечей создан, размер: true 0
59   1   17.11.2025   7:49:58   Статус: 780 итераций, Bid=98610.000000, Offer=98650.000000
60   1   17.11.2025   7:49:58   Источник свечей создан, размер: true 0
61   1   17.11.2025   7:50:28   Статус: 810 итераций, Bid=98610.000000, Offer=98650.000000
62   1   17.11.2025   7:50:28   Источник свечей создан, размер: true 0
63   1   17.11.2025   7:50:58   Статус: 840 итераций, Bid=98610.000000, Offer=98650.000000
64   1   17.11.2025   7:50:58   Источник свечей создан, размер: true 0
65   1   17.11.2025   7:51:28   Статус: 870 итераций, Bid=98610.000000, Offer=98650.000000
66   1   17.11.2025   7:51:28   Источник свечей создан, размер: true 0
67   1   17.11.2025   7:51:58   Статус: 900 итераций, Bid=98610.000000, Offer=98650.000000
68   1   17.11.2025   7:51:58   Источник свечей создан, размер: true 0
69   1   17.11.2025   7:52:15   Робот остановлен по команде
70   1   17.11.2025   7:52:15   Робот остановлен
 
Один простой ВОПРОС "Для кого все это???" . Ни покладая рук разработчики трудятся, а для кого все это? Или я что то делаю ни так?
 
VPM,
Если это у Вас универсальная система,
то покажите код для произвольного числа инструментов,
список которых будет задавать пользователь либо, этот список формирует сам робот исходя из состояния рынка.
 
VPM,
Если скрипт не осилите, то покажите фрагмент кода, как Вы будете обрабатывать свечи отдельного инструмента/
 
nikolz,  Весь скрип перед Вами, ниже лог его исполнения. На одном инструменте, подписывается на получение данных с сервера, ну а как выводит видно из лога. Если из ТТТ начинает получать (17.11.2025   7:42:28   Статус: 331 итераций, Bid=98610.000000, Offer=98650.000000) хотя и пол века прошло? То подписка на свечи не работает, от слова совсем (17.11.2025   7:51:28   Источник свечей создан, размер: true 0). Не падёт, ошибок не выводит, И НИ ЧЕГО не отдает ds:Size() =0?  Настройки: Умный заказ, терминал 12+ проверил на разных.

Вопрос что делаю не так?  
 
Возможность создания источника данных по данным ТТТ должна быть поддержана брокером. Многие брокеры не включают избыточные потоки данных по умолчанию. Например, те же обезличенные сделки, т.е. тиковые данные только по запросу. Также и для баров по данным ТТТ. Обычному пользователю это не надо. А кому надо запросит.
 
Если Вы просто хотели обычный поток баров-свечей, то не надо указывать имя доп. параметра ТТТ 'last' при создании потока.
 
Nikolay,  Спасибо Уразумел! Задумался на д тем что получая данные о свечах из таблицы обезличенных сделок, не понятно как обрабатывать свечи на низко ликвидных инструментах?
Цитата
Nikolay написал:
тиковые данные только по запросу
А что брокер под каждого клиента подстраивает сервер? Или как?
 
Цитата
VPM написал:
А что брокер под каждого клиента подстраивает сервер? Или как?
Нет, просто не включает многое. Сервера и каналы не резиновые. Тем более, что сейчас уже не модно сидеть перед монитором. А для экранов 5 дюймов много не надо, там и данные можно отдавать раз в секунду - никто не заметит.
 
Nikolay,  А подскажите такая практика:
ds1, error_desc  = CreateDataSource(class_code, sec_code, 60  ,"last")
ds2, error_desc  = CreateDataSource(class_code, sec_code, 60  ,"bid")
ds3, error_desc  = CreateDataSource(class_code, sec_code, 60  ,"offer")
dsn, error_desc  = CreateDataSource(class_code, sec_code, 60  ,"...")
Она "никудышняя"? Если допустим со стаканом сравнить, или опять же просто напрямую получить из ТТТ?
 
Практика нормальная если предполагать, что она работает всегда. Но т.к. это не так, то значит - не лучшая идея. У меня, например, один брокер отдает такие потоки, но только за прошлые сессии, а за текущую уже нет.
И зачем такой поток, спрашивается.

Здесь важно понимать, что брокер и биржа - это коммерческие организации. А данные - это их продукт. Хотите данных - покупайте. У биржи напрямую, через брокера - не важно. Соответственно свечи по истории изменения быстроизменющихся данных - это доп. нагрузка на инфраструктуру. Кто-то дает и так, кто-то нет. Не готовы мириться с качеством - готовьте сами. Данные есть - аггрегируйте сами, хоть по ТФ = 33 секунды. Если есть хоть какой-то смысл в этом действии. А если видите, что есть пропуски данных (а это будет точно) и это очень важно, то следующий шаг - заказать прямой доступ к серверам брокера через API. Но писать уже будете явно не на Lua. Если и этой скорости мало, то прямо к бирже и аренда серверов с прямым доступом к бирже.

Другой вопрос - а готовы ли к расходам. И стоит ли это вообще делать, т.к. обычно такие скорости нужны для HFT где нужны объемы, которые очень редко доступны для физ. лиц.
 
Проверил. Без доп. параметра работает. Даже задержка уменьшилась на получение из ТТТ.
Код
   TYPE   DATE   TIME   MESSAGE
1   1   17.11.2025   12:05:06   Скрипт инициализирован
2   1   17.11.2025   12:05:06   === ПРОСТОЙ РОБОТ ЗАПУЩЕН ===
3   1   17.11.2025   12:05:06   1. bid true boolean
4   1   17.11.2025   12:05:06   2. offer true boolean
5   1   17.11.2025   12:05:06   3. last true boolean
6   1   17.11.2025   12:05:06   1. Level_II true boolean
7   1   17.11.2025   12:05:06   nil table
8   1   17.11.2025   12:05:06   Источник свечей создан, размер: 6950 true
9   1   17.11.2025   12:05:36   Статус: 31 итераций, Bid=97970.000000, Offer=97980.000000
10   1   17.11.2025   12:05:36   получать данные с сервера, размер: 6950 true
11   1   17.11.2025   12:05:36   Свеча: O=295.1 H=296.0 L=295.1 C=295.6
12   1   17.11.2025   12:06:06   Статус: 61 итераций, Bid=97960.000000, Offer=97980.000000
13   1   17.11.2025   12:06:06   получать данные с сервера, размер: 6950 true
14   1   17.11.2025   12:06:06   Свеча: O=295.1 H=296.0 L=295.1 C=295.6
15   1   17.11.2025   12:06:34   Робот остановлен по команде
16   1   17.11.2025   12:06:35   Робот остановлен
 
Цитата
Nikolay написал:
И стоит ли это вообще делать, т.к. обычно такие скорости нужны для HFT где нужны объемы, которые очень редко доступны для физ. лиц.
Моя идея была получать разные параметры единообразным способом, в формате свечей, что в свою очередь позволило обрабатывать их едиными способами. Использовать единые фильтры, индикаторы, паттерны. Следовательно формировать единую логику, не только для цены, но и для OI, количества на покупку / продажу и так далее.
 
Цитата
VPM написал:
Моя идея была получать разные параметры единообразным способом, в формате свечей, что в свою очередь позволило обрабатывать их едиными способами. Использовать единые фильтры, индикаторы, паттерны. Следовательно формировать единую логику, не только для цены, но и для OI, количества на покупку / продажу и так далее.
По этому поводу я не могу ничего сказать, т.к. чтобы делать такие аггрегации необходимо доказать, что они имеют смысл. Единообразие - это хорошо, но не всегда.
 
Но все же осталось не понимание:      
Код
-- Создаем источник данных свечей
    local error_desc = ''
    ds, error_desc  = CreateDataSource(class_code, sec_code, 60 ) -- ,"last"
    message( tostring(error_desc) ..' '.. type(ds))
    if ds then
        --sleep(1000)
        local result = ds:SetEmptyCallback() -- SetUpdateCallback(onCandleUpdate) --
        message("Источник свечей создан, размер: " .. ds:Size() ..' '..tostring(result) )
    else
        message("ОШИБКА: Не удалось создать источник свечей " .. tostring(error_desc))
        return
    end

Цитата
VPM написал:
То подписка на свечи не работает, от слова совсем (17.11.2025   7:51:28   Источник свечей создан, размер: true 0). Не падёт, ошибок не выводит, И НИ ЧЕГО не отдает ds:Size() =0?  Настройки: Умный заказ, терминал 12+ проверил на разных.
А как же проверить? Убедиться что все работает?
 
Следовательно  ds:Size() = 0, есть показатель того что брокер не отдает данные?  
 
Цитата
VPM написал:
Следовательно  ds:Size() = 0, есть показатель того что брокер не отдает данные?  
К сожалению нет, т.к. это просто признак, что данных пока нет. Но они могут появится. В этом плане Квик очень скуден в плане определения статусов серверных запросов.
 
Цитата
VPM написал:
А как же проверить? Убедиться что все работает?
   Функция запроса данных свечей по тикеру с учетом возможных задержек:
https://forum.quik.ru/messages/forum10/message79730/topic9273/#message79730
 Ее легко изменить для запроса данных по многим тикерам:
  Параметром при этом может быть таблица со списком class_code, sec_code, tf.
  В цикле запрашиваются ds[i], а затем в цикле выполняется проверки поступления данных (как это делается в выложенной функции) и выдается результат в виде таблицы параметра с добавленным полем ds.
 
Цитата
TGB написал:
Функция запроса данных свечей по тикеру с учетом возможных задержек
Просто к слову - это блокирующая реализация. По опыту, данные могут приходить долго. Поэтому я предпочитаю подход через неблокирующие задачи. Сразу заказывается много потоков, хоть 1000. А потом просто проверяем, что данные приехали, опрашивая задачи.
 
Цитата
Nikolay написал:
Просто к слову - это блокирующая реализация. По опыту, данные могут приходить долго.
   Я написал как запрашивать сразу много тикеров, хоть 1000:
Цитата
TGB написал:
Ее легко изменить для запроса данных по многим тикерам:   Параметром при этом может быть таблица со списком class_code, sec_code, tf.   В цикле запрашиваются ds[i], а затем в цикле выполняется проверки поступления данных (как это делается в выложенной функции) и выдается результат в виде таблицы параметра с добавленным полем ds.
 
Цитата
TGB написал:
Ее легко изменить для запроса данных по многим тикерам:   Параметром при этом может быть таблица со списком class_code, sec_code, tf.   В цикле запрашиваются ds, а затем в цикле выполняется проверки поступления данных (как это делается в выложенной функции) и выдается результат в виде таблицы параметра с добавленным полем ds.
Это все равно будет блокирующая реализация, т.к. просто будете ждать в цикле. Закажете сразу, да. Но выйдете из метода только после ожидания. А по хорошему, заказ - это атомарное действие и проверка - это атомарное действие.
Не пришли данные - переходим к другим действиям, может по какому-то инструменту надо транзакцию отправить.
 
TGB,  Спасибо, да но этот случай для получения данных из таблицы обезличенных сделок, я использую так, если не получены то и делать дальше нечего (нечего обрабатывать):
Код
  local ds, Error = nil, ''
        local count = 0
        repeat

            ds, Error = CreateDataSource(class, symbol, interval)
            if ds == nil then
                count = count + 1
                sleep(100)
            end

        until (ds or count == 1000)
А что делать когда получаем из таблицы TTT как в примере выше? И задача стоит получить "LAST", "NUMCONTRACTS", "NUMBIDS", "NUMOFFERS", "BIDDEPTHT", "OFFERDEPTHT"? Один не пришел заблокировал все?
Цитата
VPM написал:
Моя идея была получать разные параметры единообразным способом, в формате свечей, что в свою очередь позволило обрабатывать их едиными способами. Использовать единые фильтры, индикаторы, паттерны. Следовательно формировать единую логику, не только для цены, но и для OI, количества на покупку / продажу и так далее.
 
Минимально безопасный вариант;
Код
function createDS(class, symbol, interval)
    for i = 1, 20 do
        local ds, err = CreateDataSource(class, symbol, interval)
        if ds then return ds end
        sleep(100)
    end
    return nil
end
Почему это безопасно?
  • Повтор не бесконечный;

  • Максимальная задержка – ~2 секунды, QUIK не убьёт скрипт;

  • Если DS не создалась — ошибка обработана, без падений;

  • Лаконично и безопасно.

 
Цитата
Nikolay написал:
Закажете сразу, да. Но выйдете из метода только после ожидания.
 Не обязательно написанное мной реализовывать в одном методе.
   Метод1:
Цитата
TGB написал:
 Параметром при этом может быть таблица со списком class_code, sec_code, tf.   В цикле запрашиваются ds[i],
  Метод2:
Цитата
TGB написал:
в цикле выполняется проверки поступления данных (как это делается в выложенной функции) и выдается результат в виде таблицы параметра с добавленным полем ds.
 И все делается, как вы описали, без блокировки.
 
Цитата
VPM написал:
Минимально безопасный вариант
Да причем здесь безопасность. Вы работаете а терминале - это клиент для сервера. Его задача показать данные, приходящие с сервера. И отправлять запросы на сервер. Все.

Можно абстрагироваться и сказать - есть база данных где-то в сети и есть клиент (браузер), показывающий данные. Задача показать данные из 1000 таблиц. Одна таблица - один запрос. Данные могут приехать за 10 млс., а могут за пару минут. Время не угадать.

Поэтому такого рода задачи решаются через запрос, запрос, запрос.... Ответ, ответ, ответ, таймаут, ответ... А не запрос-ждем, запрос-ждем... и последний запрос будет через бесконечность.

Т.е. кидаем запросы атомарно, создаем задачу для проверки ответа. В других языках можно было бы организовать AsynсAwait для ожидания ответа в фоне, пусть приходит ответ когда придет, а пока будем выполнять другие задачи. В Luа же проверяем ответ опросом созданных задач. Пришли данные - фиксируем, переходим к следующему. Не пришли - выполняем другую задачу. И так пока не придут все данные и мы не очистим очередь задач, опрашивая их. Вы же только что сами писали про магию корутин, а здесь забыли про это.

Т.е. атомароно запрос -> задача ответа. Проверка ответа тоже атомарно. Транзакцию мы также отправляем - команда транзакции -> задача проверки транзакции. Т.е. скрипт это по сути конечный автомат, создающий задачи разного рода. Мы их опрашиваем, проверяем что они выполнены, переходим по состояниям задачи. Переключаемся между ними. При этом запросы на данные могут быть параллельно с задачами на транзакции. Поэтому любое ожидание - это вред.

Такое ощущение, что все забыли как работала связь в 90-х. Тогда речи не было о млс. Минуты.
 
Цитата
VPM написал:
ds, error_desc  = CreateDataSource(class_code, sec_code, 60, "last")
пардон, не понял сначала .
Расскажу как я делаю подписку.   Число инструментов не имеет значения. Сейчас в разрабатываемом роботе 10 инструментов.
------------------------
Подписка делается один раз в функции получения параметров инструментов заданных списком в фале ini.
вот фрагмент подписки.
Код
local ds,err;  
if int>0 then --подписка на свечи
while ds==nil do ds,err=CreateDataSource(clas,sec,int); sleep(1); end
end
 
 
далее создаю файл для сохранения данных текущего дня  Для истории есть отдельная сжатая база глубиной 10 лет.
Создаю колбек для обработки данных
Код
local fD=pTD..sec.."/"..int.."/current.txt"; --файл данных текущего дня
local fe=io.open(fD,"w");
if ds then ds:SetUpdateCallback(cb_candle(sec,ds,fe)); else ds={} end
Страницы: Пред. 1 ... 8 9 10 11 12 След.
Читают тему
Наверх