admin@guru-net.biz

Phone : +7 903 662-35-39

Несинхронизированность потоков в QUIK

Терминал QUIK даёт разработчикам торговых роботов мощный инструмент — язык Lua и систему колбэков, через которые скрипт узнаёт о заявках, сделках, стоп-заявках и позициях. Однако есть подводный камень, о котором часто забывают: все колбэки работают в одном потоке с приёмом данных. Разберём, к каким проблемам это приводит и как построить надёжную обработку событий.

📡 Как QUIK доставляет данные в скрипт

Основные колбэки, которые использует торговый робот:

  • OnOrder(order) — новая заявка или изменение её состояния;
  • OnTrade(trade) — сделка;
  • OnStopOrder(stop_order) — стоп-заявка;
  • OnTransReply(trans_reply) — ответ на транзакцию;
  • OnFuturesClientHolding и OnDepoLimit — позиция.

⚠️ В чём проблема: один поток на всё

Главная особенность QLua в том, что колбэки выполняются в том же потоке, что и приём данных от брокера. Отсюда вытекает несколько неприятных следствий:

  1. Блокировка терминала. Если внутри колбэка выполняется долгая операция (sleep, тяжёлые вычисления, запись в базу), это блокирует обработку всех остальных колбэков во всех запущенных скриптах. Данные от брокера продолжают поступать в QUIK, но скрипты их не видят, пока поток не освободится.
  2. Задержка колбэков. Данные в таблице trades могут появиться позже, чем в orders. Это следствие того, что потоки данных о заявках и сделках не синхронизированы.
  3. Порядок не гарантирован. OnTrade, OnOrder и OnTransReply могут приходить в произвольном порядке, а не в логической последовательности.
  4. OnStopOrder может не работать в некоторых версиях терминала.

🧩 Что это значит на практике

Если писать логику робота прямо в колбэках, возникает классический набор проблем: потеря событий, дубли, рассинхронизация между заявкой и сделкой, а в худшем случае — «зависание» терминала на время записи в базу или отправки запросов.

💡 Решение: очередь событий

Идея простая и проверенная: колбэки должны делать только одно — мгновенно складывать событие в очередь. Вся тяжёлая работа (запись в базу, бизнес-логика) выполняется отдельно в главном цикле main().

  1. Колбэки только Push.
  2. main() периодически забирает пачку событий.
  3. Запись в базу — одной транзакцией на пачку.
  4. Бизнес-логика запускается только после успешной записи.

📥 Шаг 1. Колбэки делают только Push

function OnOrder(order)
    event_queue:Push({ type = "order", data = order, ts = os.time() })
end

function OnTrade(trade)
    event_queue:Push({ type = "trade", data = trade, ts = os.time() })
end

function OnStopOrder(stop_order)
    event_queue:Push({ type = "stop_order", data = stop_order, ts = os.time() })
end

⚙️ Шаг 2. main() атомарно забирает пачку

function ProcessEventQueue()
    -- атомарно забираем всю очередь разом
    local batch = event_queue:Drain()

    -- одна транзакция на всю пачку
    db:Begin()
    for _, event in ipairs(batch) do
        -- запись события в базу по его типу
    end
    db:Commit()

    -- бизнес-логика только после успешной записи
    RunTradingLogic()
end

function main()
    while true do
        ProcessEventQueue()
        sleep(100)
    end
end

📊 Сравнение: было и стало

Было (напрямую в колбэке) Стало (через очередь)
Колбэк блокирует QUIK на время записи в базу Колбэк выполняется мгновенно
Много мелких транзакций SQLite Одна транзакция на пачку событий
Логика робота в колбэке — блокировка Логика робота в main() — не блокирует
При сбое базы событие теряется Событие в очереди, можно повторить

🔍 Важные нюансы

  • Потеря событий при падении робота. Если робот упадёт между Push и Commit, события из очереди пропадут. Нужно либо мириться с этим, либо добавить персистентную очередь.
  • Размер очереди. Если обработчик не успевает за потоком событий, очередь растёт. Полезно мониторить её размер и логировать превышение порога.
  • Отдельный поток. В QLua работа с потоками ограничена. Но даже обработка в main() уже сильно разгружает колбэки.

✅ Выводы

Несинхронизированность потоков в QUIK — это не баг, а особенность архитектуры терминала. Зная её, достаточно соблюдать одно правило: колбэки должны быть максимально лёгкими, а вся работа с данными выносится в отдельный цикл через очередь событий. Так робот остаётся отзывчивым, данные не теряются, а логика становится предсказуемой.

Categories:

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Related Posts :-