Терминал QUIK даёт разработчикам торговых роботов мощный инструмент — язык Lua и систему колбэков, через которые скрипт узнаёт о заявках, сделках, стоп-заявках и позициях. Однако есть подводный камень, о котором часто забывают: все колбэки работают в одном потоке с приёмом данных. Разберём, к каким проблемам это приводит и как построить надёжную обработку событий.
📡 Как QUIK доставляет данные в скрипт
Основные колбэки, которые использует торговый робот:
OnOrder(order)— новая заявка или изменение её состояния;OnTrade(trade)— сделка;OnStopOrder(stop_order)— стоп-заявка;OnTransReply(trans_reply)— ответ на транзакцию;OnFuturesClientHoldingиOnDepoLimit— позиция.
⚠️ В чём проблема: один поток на всё
Главная особенность QLua в том, что колбэки выполняются в том же потоке, что и приём данных от брокера. Отсюда вытекает несколько неприятных следствий:
- Блокировка терминала. Если внутри колбэка выполняется долгая операция (sleep, тяжёлые вычисления, запись в базу), это блокирует обработку всех остальных колбэков во всех запущенных скриптах. Данные от брокера продолжают поступать в QUIK, но скрипты их не видят, пока поток не освободится.
- Задержка колбэков. Данные в таблице trades могут появиться позже, чем в orders. Это следствие того, что потоки данных о заявках и сделках не синхронизированы.
- Порядок не гарантирован. OnTrade, OnOrder и OnTransReply могут приходить в произвольном порядке, а не в логической последовательности.
- OnStopOrder может не работать в некоторых версиях терминала.
🧩 Что это значит на практике
Если писать логику робота прямо в колбэках, возникает классический набор проблем: потеря событий, дубли, рассинхронизация между заявкой и сделкой, а в худшем случае — «зависание» терминала на время записи в базу или отправки запросов.
💡 Решение: очередь событий
Идея простая и проверенная: колбэки должны делать только одно — мгновенно складывать событие в очередь. Вся тяжёлая работа (запись в базу, бизнес-логика) выполняется отдельно в главном цикле main().
- Колбэки только Push.
- main() периодически забирает пачку событий.
- Запись в базу — одной транзакцией на пачку.
- Бизнес-логика запускается только после успешной записи.
📥 Шаг 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 — это не баг, а особенность архитектуры терминала. Зная её, достаточно соблюдать одно правило: колбэки должны быть максимально лёгкими, а вся работа с данными выносится в отдельный цикл через очередь событий. Так робот остаётся отзывчивым, данные не теряются, а логика становится предсказуемой.




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