_sk_
_sk_ личный блог
21 декабря 2022, 13:37

Ограничитель количества транзакций на чистом QLua

При торговле с помощью роботов в терминале QUIK рано или поздно встаёт вопрос об ограничении количества отправляемых в секунду транзакций, чтобы не начинались ошибки вида: «Превышен лимит отправки транзакций для данного логина».

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

Создаётся некая папка D:\throttle\, где работает ограничитель интенсивности. Во время работы QLua-скрипты формируют в этой папке столько файлов, сколько транзакций в секунду разрешено, например 10. При каждой попытке послать транзакцию скрипты, грубо говоря, конкурируют за эти файлы. Если ресурса хватило, то транзакция отправляется, если нет, то скрипт ждёт 10 мс и повторяет попытку заново.

Создание ограничителя, предоставляемого модулем Throttle.lua, производится командами

local Throttle = require("Throttle") -- указать путь к файлу модуля
local throttle = Throttle:new("D:/throttle/", 10)

Цикл проверки/ожидания ресурса и последующая отправка транзакции выглядит примерно так:
while not throttle:isAllowed() do
  sleep(10)
end
sendTransaction(...)
Приятным свойством данного подхода является то, что все торговые скрипты, работающие с общей папкой, будут иметь общее ограничение, вне зависимости от числа применяемых терминалов QUIK. В принципе, папку D:\throttle\ можно даже разместить на сетевом диске, тогда терминалы QUIK с роботами с разных компьютеров будут иметь общее ограничение по количеству транзакций.

Наконец, вот код модуля Throttle.lua:
---
--- Ограничитель интенсивности на базе файловой системы.
---
--- В папке folder создаются служебные файлы, содержащие штампы с идентификатором объекта и текущим временем.
--- Функция isAllowed() выдаёт разрешение или запрещает использование ресурса.
--- Количество используемых служебных файлов capacity обеспечивает ограничение интенсивности уровнем не более
--- чем capacity разрешений в секунду.
---
--- Алгоритм реализации использует датчик случайных чисел и не гарантирует, что на практике уровень интенсивности
--- будет в точности равен capacity разрешений в секунду при высоких нагрузках.
---

local M = {}

--- Конструктор.
-- @param self объект
-- @param folder имя папки со служебными файлами; оканчивается символом '/'
-- @param capacity количество используемых служебных файлов
local function new(self, folder, capacity)
    os.execute("mkdir \"" .. folder .. "\"")
    local object = {
        folder = folder,
        capacity = capacity,
        id = math.random(1000000, 9999999)
    }
    setmetatable(object, self)
    self.__index = self
    return object
end

M.new = new

local function getFilename(self)
    return self.folder .. string.format("%03d.dat", math.random(1, self.capacity))
end

local function getStamp(self)
    return self.id .. ":" .. os.time()
end

local function getIdTime(stamp)
    if type(stamp) ~= "string" then
        return nil, nil
    end
    local i = string.find(stamp, ":", 1, true)
    if i then
        return tonumber(string.sub(stamp, 1, i - 1)), tonumber(string.sub(stamp, i + 1))
    else
        return nil, nil
    end
end

--- Узнать, разрешено ли использование ресурса.
-- @param self объект
-- @return true/false
local function isAllowed(self)
    local filename = getFilename(self)
    local file = io.open(filename, "r")
    if file then
        local stamp = file:read()
        file:close()
        local id, time = getIdTime(stamp)
        if id == nil or time == nil then
            return false
        end
        if time == os.time() then
            return false
        end
    end
    file = io.open(filename, "w+")
    if file then
        local stamp1 = getStamp(self)
        file:write(stamp1)
        file:close()
        file = io.open(filename, "r")
        if file then
            local stamp2 = file:read()
            file:close()
            return stamp1 == stamp2
        else
            return false
        end
    else
        return false
    end
end

M.isAllowed = isAllowed

return M

Успехов в торговле!
16 Комментариев
  • Артур
    21 декабря 2022, 13:44
    привет.
    Lua реально выучить новичку самостоятельно? Просто тупо документацию из квика по языку читать?
    • Евгений
      26 декабря 2022, 16:58
      Артур, вполне реально. На ютубе есть ролики для обычного lua (понимание как код пишется), а для qlua есть справка внутри Quik.
  • Андрей К
    21 декабря 2022, 13:52
    мьютекс на базе файловой системы. прикольно )
      • nicknh
        21 декабря 2022, 15:13
        _sk_, Не понятно только зачем насиловать диск для решения такой задачи.
  • Егор NiKO
    21 декабря 2022, 13:58
    Какой лимит заявок у КВИКа?
  • 3Qu
    21 декабря 2022, 14:21
    Вы че, HFT занимаетесь, что вам транзакций не хватает? )
  • Replikant_mih
    21 декабря 2022, 15:58
    «Превышен лимит отправки транзакций для данного логина»

     

    А это на чьей стороне ограничение? И оно чем-то грозит, кроме того, что вылезающие за лимит в моменте транзакции отклонятся?

      • Replikant_mih
        21 декабря 2022, 16:11
        _sk_, Ага, понял, спасибо!
  • xSVPx
    21 декабря 2022, 20:33
    Мда, чего только не приходится изобретать народу.
    Мрак какой-то.
    Приоритетов итп похоже нету никаких, кто первый встал, того и тапки.
    Стало даже немножко страшно за тех, кто в таких инфраструктура бабки крутит…
  • Евгений
    26 декабря 2022, 17:02
     Интересное решение на базе записи на диск Действительно чего только не придумаешь если в руках только палка и верёвка

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн