Функции обратного вызова, встроенные в QLua

Автор записи: Дмитрий (Admin)
1 звезда2 звезды3 звезды4 звезды5 звезд (Голосов 9, среднее: 5,00 из 5)
Загрузка...

Qlua-основы
Во время работы терминала QUIK в нем происходят различные события, такие, как приход новой обезличенной сделки, выставление заявки, сработал стоп-ордер и т.п. Для того, чтобы своевременно и определенным образом реагировать на эти события, в скрипте QLua можно использовать функции обратного вызова, которые будут выполнять блок кода, расположенного внутри них, в тот момент, когда это событие произойдет.

Для каждого определенного события есть своя предопределенная разработчиками QLua функция!

Функции OnInit(), main() и OnStop() рассматриваются в статье "База скрипта в QLua(Lua)".

Для использования функций в терминале QUIK должны быть открыты соответствующие им таблицы!

OnAccountBalance()
Функция вызывается терминалом QUIK при получении изменений текущей позиции по счету (ТОЛЬКО ДЛЯ БРОКЕРА).
OnAccountPosition()
Функция вызывается терминалом QUIK при изменении денежной позиции по счету (ТОЛЬКО ДЛЯ БРОКЕРА).
OnAllTrade()
Функция вызывается терминалом QUIK при получении обезличенной сделки (Таблица всех сделок).
OnCleanUp()
Функция вызывается терминалом QUIK при смене сессии и при выгрузке файла qlua.dll

OnClose()
Функция вызывается перед закрытием терминала QUIK.
OnConnected()
Функция вызывается терминалом QUIK при установлении связи с сервером QUIK.
OnDepoLimit()
Функция вызывается терминалом QUIK при получении изменений лимита по бумагам.
OnDepoLimitDelete()
Функция вызывается терминалом QUIK при удалении клиентского лимита по бумагам.
OnDisconnected()
Функция вызывается терминалом QUIK при отключении от сервера QUIK.
OnFirm()
Функция вызывается терминалом QUIK при получении описания новой фирмы от сервера.
OnFuturesClientHolding()
Функция вызывается терминалом QUIK при изменении позиции по срочному рынку (Позиции по клиентским счетам (фьючерсы)).
OnFuturesLimitChange()
Функция вызывается терминалом QUIK при получении изменений ограничений по срочному рынку (Ограничения по клиентским счетам).
OnFuturesLimitDelete()
Функция вызывается терминалом QUIK при удалении лимита по срочному рынку.
OnMoneyLimit()
Функция вызывается терминалом QUIK при получении изменений по денежному лимиту клиента (Таблица лимитов по денежным средствам).
OnMoneyLimitDelete()
Функция вызывается терминалом QUIK при удалении денежного лимита.
OnNegDeal()
Функция вызывается терминалом QUIK при получении внебиржевой заявки (Таблица заявок на внебиржевые сделки).
OnNegTrade()
Функция вызывается терминалом QUIK при получении внебиржевой сделки для исполнения (Таблица сделок для исполнения).
OnOrder()
Функция вызывается терминалом QUIK при получении новой заявки или при изменении параметров существующей заявки (Таблица заявок).
OnParam()
Функция вызывается терминалом QUIK при изменении текущих параметров (Текущая таблица параметров).
OnQuote()
Функция вызывается терминалом QUIK при получении изменения стакана котировок.
С примером использования данной функции можно ознакомиться в этой статье.

OnStopOrder()
Функция вызывается терминалом QUIK при получении новой стоп-заявки или при изменении параметров существующей стоп-заявки (Таблица стоп-заявок).
OnTrade()
Функция вызывается терминалом QUIK при получении сделки (Таблица сделок).
OnTransReply()
Функция вызывается терминалом QUIK при получении ответа на транзакцию пользователя (Таблица транзакций).

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

В функциях OnAllTrade(), OnParam() и OnQuote() вывод сообщений намеренно закомментирован, т.к. они вызываются очень часто и независимо от Ваших действий, если Вам будет нужно отследить их вызовы, раскомментируйте строки вывода сообщений в данных функциях.

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

Скрипт отслеживания работы функций обратного вызова
Для более удобного просмотра информации, выводимой при помощи функции message(), используйте таблицу сообщений (меню: "Сообщения"->"Системные сообщения"->"Таблица сообщений..."). А также, снимите флажок "Показывать окно сообщений" в меню: "Настройки"->"Основные..."->"Сообщения".

Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!

Добавить комментарий

Функции обратного вызова, встроенные в QLua: 137 комментариев

  1. Такой вопрос, вроде бы , где-то был ответ.
    Есть функция:

    1
    2
    3
    4
    5
    6
    
    function OnStop()											
        isRun = false
        DestroyTable(g_tableId)
    	KillActiveOrders()
    	LogInfo("OnStop(): Stop strategy") 	
    end

    Решил перенести все кроме g_isRun = false в функцию main, вот так

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    function main()															
    	while isRun do														
    		Body() -- торговый алгоритм
    		sleep(1000)
    	end	
    	DestroyTable(g_tableId)
    	KillActiveOrders()
    	LogInfo("OnStop(): Stop strategy")
    end

    Почему это код после цикла while не отрабатывается, окно робота не закрывается, заявки из системы не убираются?

    1. Потому что после вызова OnStop() скрипту дается всего 5 секунд на завершение работы, потом функция main принудительно завершается. Почитайте про функцию OnStop() в файле справки qlua.chm, который находится в папке с квиком. Можно вернуть из функции OnStop() количество миллисекунд, которые требуются для завершения.

  2. Добрый день. Пытаюсь разобраться с работой обратного вызова в Quik - Junior. Ничего не понятно пока.
    Запустил два "робота", один из этой стать, второй свой с таким кодом:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    g_schetchik1 = 0
    g_schetchik2 = 0
    g_schetchik3 = 0
     
    function OnTrade()
    	g_schetchik2 = g_schetchik2 + 1
    	LogInfo("g_schetchikOntrade="..tostring(g_schetchik2) )	
    end
    function OnOrder()
    	g_schetchik3 = g_schetchik3 + 1
    	LogInfo("g_schetchikOnordaer="..tostring(g_schetchik3) )
    end 
     
    function OnTransReply(trans_reply)
    	g_schetchik1 = g_schetchik1 + 1
    	LogInfo("g_schetchikReply="..tostring(g_schetchik1) )	
    end

    При совершении сделок вручную:
    В моем коде вызывается только OnOrder = 2 раза; OnTransReply и OnTrade не вызываются.
    Из "демонстрационного кода" OnTrade = 3 раза; OnOrder = 2 раза; Reply не вызывается

    При совершении сделки из кода моего робота:
    В моем коде вызывается только OnOrder = 2-6-7 раз; OnTransReply то 1, то 3 раза , а OnTrade не вызываются.
    Из "демонстрационного кода" OnOrder = 2-6-7 раз в точном соответствии с тем что выше; Reply также как и выше то 1 , то 3 раза, OnTrade = как всегда 3 раза;

    Вот не понимаю, почему такая разница может быть? т.е. при одновременном работе 2 "роботов" в коде которых прописаны функции обратного вызова, в одном коде они вызываются в другом нет?

    1. При работе нескольких роботов по хорошему в обоих роботах будет транслироваться инфа от всех сделок совершенном в квике, тут важно отсеить не нужные для робота=) для дальнейшей работе его, если конечно это вообще требуется для вашего робота.
      Как понимаю по одной сделке функции обратного вызова несколько раз логируют счетчик?
      Я думаю дело в том, что после того как код прогоняется 1 раз, он идет по второму кругу и снова пишет в лог, когда обращается к этой функции, чтобы избежать этого засорения мне кажется стоит добавить такую фичу:
      function main()
      while is_run do
      sleep(2000)
      end
      end

      1. Нет, проблема не в том что отрабатывается несколько раз. А в том что в моем коде на одно и тоже действие вызываются одни обработчики, а в коде из этой статьи немного другие. Нет полного совпадения, а должно быть. А точнее в моем "роботе" никогда не вызывается OnTrade, а в демонстрационном коде из этой статьи вызывается.

    2. Добрый день! Не вызываются они потому, что это 2 разных функции:

      1
      2
      3
      4
      5
      6
      7
      
      function OnOrder()
       
      end
       
      function OnOrder(order)
       
      end

      А по многу раз функции вызываются потому, что их вызов совпадает с разными соответствующими событиями, например, OnOrder, заявка выставлена, заявка частично исполнена, заявка полностью исполнена и т.д., событий много разных происходит, внутри одного , казалось бы, простого события "совершена сделка".

      1. Почему много раз вызываются понятно. Чтобы максимально упростить обсуждение - есть функция OnTrade() и в моем коде и в "из статьи", не OnTrade( trade ). Запущена оба бота "из статьи" и мой, делаю сделку руками, затем сделка из моего робота. OnTrade() "из статьи " отрабатывается, а в мой OnTrade() не приходит событие. При этом и OnOrder() в моем коде и в "из статьи" вызываются. А также "разные функции" OnTransReply(trans_reply) в моем коде и OnTransReply() в "коде из статьи" отрабатываются одинаково. Т.е. получается проблема в одном, в мой код не приходит событие OnTrade и OnTrade(trade) " проверял " оба

          1. Конечно скрипт работает, совершает сделки и т.д., записывает их без использования событийной модели, решил попробовать событийную модель.
            OnTrade(trade) я и использовал изначально, но решил привести к "одному знаменателю" OnTrade() , как в коде этой статьи под которой мы сейчас пишем комментарии, для чистоты эксперимента. К тому же события OnOrder() и OnTransReply() в моем коде отрабатывают идентично, как и в вашем. Это прям какой-то глюк.
            Статью читал пока только "по диагонали" https://quikluacsharp.ru/qlua-osnovy/baza-skripta-v-qlua/. Сейчас попробую на реальном квике от Открытия тогда

            1. Вот Вам скрипт "на поиграться" 🙂

              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
              21
              22
              23
              24
              25
              26
              27
              28
              29
              30
              31
              32
              33
              34
              35
              36
              37
              38
              39
              40
              41
              42
              43
              44
              45
              46
              47
              48
              49
              50
              51
              52
              53
              54
              55
              56
              57
              58
              59
              60
              61
              62
              63
              64
              65
              66
              67
              68
              69
              70
              71
              72
              73
              74
              75
              76
              77
              78
              79
              80
              81
              82
              83
              84
              85
              86
              87
              88
              89
              90
              91
              92
              93
              94
              95
              96
              97
              98
              99
              100
              101
              102
              103
              104
              105
              106
              107
              108
              109
              110
              111
              112
              113
              114
              115
              116
              117
              118
              119
              120
              121
              122
              123
              124
              125
              126
              127
              128
              129
              130
              131
              132
              133
              134
              135
              136
              137
              138
              139
              140
              141
              142
              143
              144
              145
              146
              147
              148
              149
              150
              151
              152
              153
              154
              155
              156
              157
              158
              159
              160
              161
              162
              163
              164
              165
              166
              167
              168
              169
              170
              171
              172
              173
              174
              
              ACCOUNT              = 'SPBFUT000nd'   -- Код счета
              CLASS_CODE           = 'SPBFUT'        -- Код класса
              SEC_CODE             = 'SRM8'          -- Код инструмента
               
              RUN = true
              trans_id = os.time()
               
              main = function()
                 -- Покупает по рынку 1 лот
                 SetMarketOrder('B', 1)
               
                 -- Ждет 10 секунд
                 sleep(10000)
               
                 -- Продает по рынку 1 лот
                 SetMarketOrder('S', 1)
               
                 -- Бесконечный цикл, поддерживает работу скрипта
                 while RUN do
                    sleep(100)
                 end
              end
               
              OnStop = function()
                 RUN = false
              end
               
              OnTransReply = function(trans_reply)
                 message('OnTransReply')
                 for key,value in pairs(trans_reply) do
                    if type(value) == 'table' then
                       message('   '..tostring(key)..':')
                       for key,value in pairs(value) do
                          message('      '..tostring(key)..':'..tostring(value))
                       end
                    else
                       message('   '..tostring(key)..':'..tostring(value))
                    end
                 end
              end
               
              OnOrder = function(order)
                 message('OnOrder')
                 for key,value in pairs(order) do
                    if type(value) == 'table' then
                       message('   '..tostring(key)..':')
                       for key,value in pairs(value) do
                          message('      '..tostring(key)..':'..tostring(value))
                       end
                    else
                       message('   '..tostring(key)..':'..tostring(value))
                    end
                 end
              end
               
              OnTrade = function(trade)
                 message('OnTrade')
                 for key,value in pairs(trade) do
                    if type(value) == 'table' then
                       message('   '..tostring(key)..':')
                       for key,value in pairs(value) do
                          message('      '..tostring(key)..':'..tostring(value))
                       end
                    else
                       message('   '..tostring(key)..':'..tostring(value))
                    end
                 end
              end
               
              -- Выставляет рыночную (по сути) заявку
              SetMarketOrder = function(
                 operation,  -- Операция ('B' - buy, 'S' - sell)
                 qty         -- Количество 
              )
                 -- Выставляет рыночную заявку
                 -- Получает ID для следующей транзакции
                 trans_id = trans_id + 1
                 -- Заполняет структуру для отправки транзакции
                 local T = {}
                 T['TRANS_ID']   = tostring(trans_id)    -- Номер транзакции
                 T['ACCOUNT']    = ACCOUNT               -- Код счета
                 T['CLASSCODE']  = CLASS_CODE            -- Код класса
                 T['SECCODE']    = SEC_CODE              -- Код инструмента
                 T['ACTION']     = 'NEW_ORDER'           -- Тип транзакции ('NEW_ORDER' - новая заявка)      
                 T['TYPE']       = 'L'                   -- Тип ('L' - лимитированная, 'M' - рыночная)
                 T['OPERATION']  = operation             -- Операция ('B' - buy, или 'S' - sell)
                 T['PRICE']      = GetPriceForMarketOrder(operation) -- Цена
                 T['QUANTITY']   = tostring(qty)         -- Количество
               
                 -- Отправляет транзакцию
                 local Res = sendTransaction(T)
                 -- Если при отправке транзакции возникла ошибка
                 if Res ~= '' then
                    -- Выводит сообщение об ошибке
                    message('Ошибка транзакции открытия/закрытия по рынку: '..Res)
                 end
              end
               
              -- Возвращает корректную цену для рыночной заявки по текущему инструменту (принимает 'S',или 'B')
              GetPriceForMarketOrder = function(operation) -- STRING
                 -- Получает минимальный шаг цены инструмента
                 local PriceStep = tonumber(getParamEx(CLASS_CODE, SEC_CODE, "SEC_PRICE_STEP").param_value)
                 -- В зависимости от направления
                 if operation == 'B' then -- BUY
                    -- Пытается получить максимально возможную цену для инструмента
                    local PriceMax = tonumber(getParamEx(CLASS_CODE,  SEC_CODE, 'PRICEMAX').param_value)
                    -- Если максимально возможная цена получена
                    if PriceMax ~= nil and PriceMax ~= 0 then
                       -- Возвращает ее в нужном для транзакции формате
                       return GetCorrectPrice(PriceMax)
                    -- Иначе, максимально возможная цена не получена
                    else
                       -- Получает цену последней сделки
                       local Last = tonumber(getParamEx(CLASS_CODE,  SEC_CODE, 'LAST').param_value)
                       -- Возвращает ее в нужном для транзакции формате, увеличив перед этим на 50 шагов цены
                       return GetCorrectPrice(Last + 50*PriceStep)
                    end
                 else                     -- SELL
                    -- Пытается получить минимально возможную цену для инструмента
                    local PriceMin = tonumber(getParamEx(CLASS_CODE,  SEC_CODE, 'PRICEMIN').param_value)
                    -- Если минимально возможная цена получена
                    if PriceMin ~= nil and PriceMin ~= 0 then
                       -- Возвращает ее в нужном для транзакции формате
                       return GetCorrectPrice(PriceMin)
                    -- Иначе, минимально возможная цена не получена
                    else
                       -- Получает цену последней сделки
                       local Last = tonumber(getParamEx(CLASS_CODE,  SEC_CODE, 'LAST').param_value)
                       -- Возвращает ее в нужном для транзакции формате, уменьшив перед этим на 50 шагов цены
                       return GetCorrectPrice(Last - 50*PriceStep)
                    end
                 end 
              end
               
              -- Приводит переданную цену к требуемому для транзакции по инструменту виду
              GetCorrectPrice = function(price) -- STRING
                 -- Получает точность цены по инструменту
                 local scale = getSecurityInfo(CLASS_CODE, SEC_CODE).scale
                 -- Получает минимальный шаг цены инструмента
                 local PriceStep = tonumber(getParamEx(CLASS_CODE, SEC_CODE, "SEC_PRICE_STEP").param_value)
                 -- Если после запятой должны быть цифры
                 if scale > 0 then
                    price = tostring(price)
                    -- Ищет в числе позицию запятой, или точки
                    local dot_pos = price:find('.')
                    local comma_pos = price:find(',')
                    -- Если передано целое число
                    if dot_pos == nil and comma_pos == nil then
                       -- Добавляет к числу ',' и необходимое количество нулей и возвращает результат
                       price = price..','
                       for i=1,scale do price = price..'0' end
                       return price
                    else -- передано вещественное число         
                       -- Если нужно, заменяет запятую на точку 
                       if comma_pos ~= nil then price:gsub(',', '.') end
                       -- Округляет число до необходимого количества знаков после запятой
                       price = math_round(tonumber(price), scale)
                       -- Корректирует на соответствие шагу цены
                       price = math_round(price/PriceStep)*PriceStep
                       price = string.gsub(tostring(price),'[\.]+', ',')
                       return price
                    end
                 else -- После запятой не должно быть цифр
                    -- Корректирует на соответствие шагу цены
                    price = math_round(price/PriceStep)*PriceStep
                    return tostring(math.floor(price))
                 end
              end
               
              -- Округляет число до указанной точности
              math_round = function(num, idp)
                local mult = 10^(idp or 0)
                return math.floor(num * mult + 0.5) / mult
              end
              1. Спасибо, все конечно постепенно изучу, пока не получается получить событие OnTrade и в реальном квике, как разберусь отпишусь, чувствую мелочь какая-то.

  3. Привет, если бот постоянно глядит в "Таблицу сделок" и параметры сделки из нее берет, то функция - OnTrade(trade) в принципе не нужна? Или я что то не так понимаю?

      1. Но все равно она же есть - значит есть в этом смысл какой-то. Я так понимаю сначала инфа по сделке прилетает в OnTrade(), и только потом инфа появляется в таблице сделок, так? Если так то сколько задержка по времени? Если не значительная то блин OnTrade() мне не нужна будет. Просто делаю функцию по "потеряшкам" (потерянные сделки) - так смотрю понятно в таблицу сделок и фильтровал по номерам - косяк выявил - у меня сделки по облигам прошли, а там с 2 млрд. номера идут (ну я чисто стратегически накосячил вот переделываю щас)

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

          1. "мне в цикле больше нравится." - в цикле main() перебираешь? я то щас примерно так и хочу сделать - ну получится 100% не потеряю сделку - ну я как то по своему это все вижу - ну ты понял. Просто потеряшки есть и бывают - или я туплю или что как то - но все равно если есть лимитка и связь потерялась и она прошла в этот момент то все бот ее по любому у меня щас ее не видит - ладно разберусь. PS показал твой сайт человеку (вчера) - хочет с нуля на Lua зайти, сам писать коды - ну вот говорю самый лучший сайт для старта

  4. Доброго времени суток.
    Подскажите, пожалуйста, как из таблиц Заявок и Стоп заявок можно получить Тип операции (купля/продажа), использую функции OnOrder, OnStopOrder.

    1. trans_id в OnTrade появился только с появлением 7-й версии терминала квик, еще совсем недавно его не было. Посмотреть можно в файле справки QLUA.chm раздел "Функции обратного вызова", файл находится в папке терминала квик. Нужно добавить, кстати, уже trans_id в статью.

  5. подскажите, почему OnTrade срабатывает 3 раза, думал есть отличия во флагах но нет, чем они отличаются и в каких случаях?
    и после какого OnTrade можно считать что сделка прошла и лимиты поменялись?
    спасибо что отвечаете.

    1. Я не знаю после какого раза меняются лимиты, но вызывается она по нескольку раз как раз потому, что меняется информация по сделке, если не ошибаюсь, то при первом вызове в сделке отсутствует номер заявки, потом он появляется, что еще меняется в процессе, даже не знаю, не было необходимости узнавать.

    2. Здравствуйте, если появилась соответствующая запись в таблице сделок, то лимиты точно поменялись. Можно ориентироваться и по таблице заявок - есть запись, лимиты поменялись, запись увидите раньше, чем в таблице сделок.