05k.ru
← к скриптам

Скрипты QUIK — документация SDK

Скрипты пишутся на Lua 5.4 поверх нашего SDK и исполняются в вашем терминале QUIK. Вы пишете только логику через готовые функции — подключение, ACCOUNT и доступ к QUIK мы берём на себя. Текущая версия — мониторинг (только чтение): данные, события, вывод. Торговых функций пока нет.

Как это работает

  1. В ЛК → Скрипты создаёте скрипт, привязываете к подключению QUIK, пишете код и жмёте Сохранить.
  2. Запустить — код уходит в ваш шлюзовой скрипт QUIK, компилируется и начинает работать (вызываются ваши обработчики). Остановить — выгружает.
  3. Вывод alert(...) появляется в журнале под редактором. Статус (работает / ошибка / остановлен) виден там же.
  4. Если перезапустить QUIK — запущенные скрипты поднимутся автоматически при переподключении шлюза.

Требование: шлюзовой скрипт подключения должен быть запущен в QUIK (Lua 5.4, рядом обе dll).

Ограничения и песочница

  • Пишите через обработчики (on_timer и т.п.), а не свой бесконечный цикл: все скрипты работают в одном потоке шлюза, и while true do … end его подвесит.
  • Доступен только SDK и безопасная часть стандартной Lua. Недоступны сырой QUIK API (sendTransaction, getItem…), файлы (io), запуск процессов (os.execute).
  • Только чтение — заявки из скрипта пока выставлять нельзя (появится в следующей версии, под торговым PIN и лимитами).
  • Ошибка компиляции или выполнения показывается статусом «ошибка» с текстом.

Данные

price(sec [, class]) → number

Последняя цена инструмента sec (тикер). Класс определяется автоматически; можно указать явно ("TQBR", "SPBFUT"…). Вернёт 0, если цены ещё нет.

local p = price("GAZP")
log("Газпром: " .. p)
positions() → таблица позиций

Открытые позиции по всем счетам. Каждый элемент:

{ symbol="SBER", name="Сбербанк", classcode="TQBR",
  qty=10, avg_price=286.5, last=287.1, kind="stock", account="L01-..." }
-- account — торговый счёт (trdacc); у фьючерсов дополнительно: varmargin, kind="futures"
for _, p in ipairs(positions()) do
  log(p.symbol .. ": " .. p.qty .. " шт, ср.цена " .. p.avg_price)
end
money([kind]) → таблица денег

Деньги по счетам. kind — "stock" или "futures" (без аргумента — все). Форма зависит от рынка:

-- фондовый:  { kind="stock",   currency="SUR", balance=..., locked=..., account="..." }
-- срочный:   { kind="futures", currency="SUR", free=..., go=..., varmargin=..., account="..." }
--   free — свободно, go — ГО, всего = free + go; account — торговый счёт (trdacc)
for _, m in ipairs(money("futures")) do
  if m.free < 5000 then alert("Мало свободных: " .. m.free, "warn") end
end
orders() → таблица активных заявок

Активные (не исполненные, не снятые) заявки. Каждый элемент:

{ symbol="SBER", classcode="TQBR", side="B",   -- "B" покупка, "S" продажа
  price=280, qty=10, balance=10, order_num="123456789" }

События

Регистрируются один раз (обычно в начале скрипта). Обработчик вызывается при наступлении события.

on_timer(ms, fn)

Вызывать fn() каждые ms миллисекунд.

on_timer(10000, function()
  log("тик каждые 10 секунд")
end)
on_price(sec, ms, fn)

Каждые ms мс вызывать fn(price) с последней ценой инструмента.

on_price("SBER", 5000, function(p)
  if p > 300 then alert("SBER выше 300: " .. p) end
end)
on_order(fn)

fn(order) при любом изменении заявки. order = { symbol, classcode, side, price, qty, balance, active } (active — активна ли ещё).

on_order(function(o)
  if not o.active then log("заявка закрыта: " .. o.symbol) end
end)
on_trade(fn)

fn(trade) при своей сделке. trade = { symbol, price, qty, is_buy, classcode } (is_buy — покупка ли).

on_trade(function(t)
  alert("Сделка: " .. t.symbol .. " " .. t.qty .. " @ " .. t.price)
end)

Вывод

alert(msg [, level])

Сообщение в журнал личного кабинета (видно в вебе). level — "info" (по умолч.), "warn", "error".

alert("Внимание: просадка", "warn")
log(msg)

Сообщение в окно отладки самого QUIK (в вебе не видно). Удобно для локальной отладки.

ACCOUNT

Константа — латинское имя подключения, к которому привязан скрипт.

Хранилища позиций

Хранилище — именованный журнал позиций (ЛК → Хранилища), независимый от подключения. Робот записывает в него сделки, а система ведёт стек открытий по каждому инструменту (по каким ценам открыли), закрытия и итоговый PnL. Хранилище нужно заранее создать в ЛК — его латинское имя робот и указывает в вызове.

store.fill(name, t)

Записать сделку в хранилище name. Поля таблицы t:

  • sec — тикер инструмента (обязательно).
  • op — "B" покупка / "S" продажа (или buy=true/false).
  • qty — объём в лотах, > 0 (обязательно).
  • price — цена сделки (нужна для расчёта PnL при закрытии).
  • class — класс инструмента (необязательно; определится сам).
  • price_step, step_price — шаг цены и его стоимость (для PnL фьючерсов; подтянутся сами, если не заданы).
  • writeoff — true для списания (см. ниже).
-- открыть/долить: копит стек открытий
store.fill("scalper", { sec="GAZP", op="B", price=145.20, qty=10 })

-- закрыть (конверсия): считает PnL и пишет в журнал закрытий
store.fill("scalper", { sec="GAZP", op="S", price=146.00, qty=10 })
Два вида закрытия
  • Конверсия (по умолчанию) — закрытие с расчётом прибыли; запись попадает в журнал закрытий и в итоговый PnL.
  • Списание (writeoff=true) — просто снимает лоты без расчёта PnL. Нужно для ручного переноса позиции (например, с фьючерса на спот), когда прибыль считать не надо.

Открытие (набор позиции) — всегда конверсия; на него writeoff не влияет.

-- списать 5 лотов без расчёта прибыли
store.fill("carry", { sec="SiH6", op="S", qty=5, writeoff=true })
Пример: писать свои сделки в хранилище
on_trade(function(t)
  store.fill("scalper", { sec=t.symbol, buy=t.is_buy,
                          price=t.price, qty=t.qty })
end)

Модули

Общий код можно вынести в модуль (ЛК → Модули) и подключать в скриптах через require("имя"). Модуль — это Lua-файл, который возвращает таблицу функций. Модули общие для всех ваших скриптов и не привязаны к подключению. Имя латинское — им и подключается модуль.

require(name) → значение модуля

Загружает модуль name и возвращает то, что модуль вернул (обычно таблицу). Модулю доступен тот же SDK (price, positions, money, store…). Результат кэшируется на время работы скрипта; модуль может require другие модули (циклы запрещены).

Модуль risk:
local M = {}

function M.low_free(limit)
  for _, m in ipairs(money("futures")) do
    if (m.free or 0) < limit then return true end
  end
  return false
end

return M
Скрипт, который его использует:
local risk = require("risk")

on_timer(15000, function()
  if risk.low_free(5000) then
    alert("Мало свободных средств на срочном!", "warn")
  end
end)
Публикация и подключение чужих модулей

Модуль можно опубликовать — тогда он доступен другим пользователям в каталоге и подключается по глобальному имени require("префикс.имя").

  • Публиковать может пользователь с ролью автор (выдаёт администратор) и заданным префиксом в профиле (3–10 латинских букв/цифр, уникален).
  • Свои приватные модули подключаются по короткому имени (require("risk")), опубликованные чужие — по полному (require("acme.risk")).
  • Внутри опубликованного модуля другие опубликованные модули тоже подключайте по полному имени (prefix.name).
local risk = require("acme.risk")   -- чужой опубликованный модуль
local util = require("util")        -- свой приватный модуль

Изменили модуль — перезапустите скрипты, которые его подключают: код модулей уходит в шлюз в момент запуска скрипта.

Боты: входные данные

Бот (ЛК → Боты) — это скрипт со входными данными. Один и тот же код запускается с разными настройками. Входы задаются в интерфейсе бота и приходят в код таблицей params.

  • число → params.имя (number);
  • строка → params.имя (string);
  • хранилище → params.имя = имя хранилища (строка), сразу пригодно для store.fill.
-- входы бота: min_pct (число), depot (хранилище)
on_timer(10000, function()
  for _, m in ipairs(money("futures")) do
    local total = (m.free or 0) + (m.go or 0)
    if total > 0 and (m.free or 0) / total * 100 < (params.min_pct or 20) then
      alert("Мало свободных средств", "warn")
    end
  end
end)

on_trade(function(t)
  store.fill(params.depot, { sec=t.symbol, buy=t.is_buy, price=t.price, qty=t.qty })
end)

Изменили входы — перезапустите бота: params уходят в шлюз в момент запуска.

order.limit(sec, side, price, qty) → id / order.cancel(id)

Торговые функции доступны ботам. side — "B"/"S" (или buy=true/false). Возвращает id заявки; order.cancel(id) снимает её. Цена округляется к шагу цены. Исполнение приходит в on_trade/on_order (в событии есть order_num).

  • Песочница (по умолчанию) — заявки НЕ уходят на биржу: имитируются по последней цене; удобно отлаживать.
  • Реальная торговля — order.limit выставляет настоящую заявку. Режим включается у экземпляра бота, запуск требует торгового PIN.
-- простой пример: купить лимиткой на 0.5% ниже рынка, продать на 0.5% выше
local buy_id
on_timer(3000, function()
  local p = price(params.sec)
  if p > 0 and not buy_id then
    buy_id = order.limit(params.sec, "B", p * 0.995, params.qty)
  end
end)
on_trade(function(t)
  if t.is_buy then order.limit(t.symbol, "S", t.price * 1.005, t.qty) end
end)
Шаблоны и экземпляры
  • Шаблон (Конструктор) — код + объявленные входы. Не привязан к счёту. Можно опубликовать — попадёт в каталог ботов.
  • Экземпляр (Боты) — создаётся из шаблона (своего или из каталога): вы привязываете свой счёт и значения входов и запускаете. Код берётся из шаблона живьём.
  • Вход-хранилище в экземпляре указывает на ваше хранилище; в код приходит его имя (для store.fill).

Доступная часть Lua

Стандартные модули и функции, доступные в скрипте:

math, string, table, pairs, ipairs, next, select, tostring, tonumber, type, error, assert, pcall, os.time, os.clock, os.date

Примеры

Цена Газпрома каждые 10 секунд
on_timer(10000, function()
  alert("GAZP: " .. price("GAZP"))
end)
Сигнал при пробое уровня
local level, fired = 300, false
on_price("SBER", 3000, function(p)
  if p >= level and not fired then
    alert("SBER пробил " .. level .. ": " .. p, "warn")
    fired = true
  elseif p < level then
    fired = false
  end
end)
Контроль ГО на срочном счёте
on_timer(15000, function()
  for _, m in ipairs(money("futures")) do
    local total = (m.free or 0) + (m.go or 0)
    if total > 0 and (m.free / total) < 0.2 then
      alert("Свободно меньше 20% ГО: " .. m.free, "warn")
    end
  end
end)
Сводка по портфелю раз в минуту
on_timer(60000, function()
  local n = 0
  for _, p in ipairs(positions()) do n = n + 1 end
  alert("Позиций в портфеле: " .. n)
end)