Cерия статей по языку QLua и алгоритмической торговле для тех, кто хочет автоматизировать свою работу на финансовых рынках, освоить написание скриптов, индикаторов, торговых советников и роботов для терминала Quik.
В 2022 году ЦБ выпустил презентацию «Портрет клиента брокера». В ней указано, что в РФ всего 0,03% клиентов используют алгоритмическую торговлю.
Поэтому я понимаю, что людей, которые будут интересоваться темой программирования в трейдинге, совсем немного (хотя с ростом популярности изучения программирования доля со временем может подрасти, но вряд ли существенно).
У меня нет задачи популяризировать эту тему, скорее помочь тем, кто будет идти той же дорогой. Дело в том, что открытой информации по qlua и алгоритмической торговле через Quik в сети немного: есть несколько сайтов энтузиастов, где кусочками выложены разные полезности, часть из этой информации порой уже устаревшая (работает только на более ранних версиях терминала), есть несколько коммерческих проектов (продажи роботов, либо обучения) там информация актуальная, но за неё нужно платить. Есть интересные библиотеки, но отдельные (например, какие-то библиотеки визуального интерфейса) могут отваливаться с появлением новых версий квика.
Благодаря наводке @quant_trader (за что отдельное спасибо!), переписал свой первый скрипт из поста https://smart-lab.ru/blog/916765.php по выгрузке из терминала всех торгуемых бумаг. Теперь всё выполняется штатными средствами с помощью getClassSecurities.
Далее второй скрипт (из поста выше) выгружает из торгового терминала под закрытие дня (под закрытие основной, либо вечерней сессии — можно устанавливать, я делаю обе выгрузки) необходимые данные по всем бумагам списка.
Особенности запроса. Если ввести:
sec_list = getClassSecurities("TQBR")<br />message(sec_list)
то терминал выдаст строку, где через запятую будут все тикеры, при этом видим, что список не полон, обрывается на RTSB:
Как выяснилось, это связано только с ограничением самого терминала на вывод строки (не более 899 символов).
При этом если посмотреть длину строки, то будет видно, что символов больше:
sec_list = getClassSecurities("TQBR") message(tostring(string.len(sec_list)))
выдаст 1281
Разбив строку по запятым получим весь массив тикеров для дальнейшей работы:
Иногда бывает необходимым проанализировать не отдельную бумагу, а рынок в целом.
Кто-то смотрит для этого индексы, кто-то различные сантименты, а мне удобнее проводить анализ по динамике всех бумаг (сколько на дату эмитентов в совокупности растет, сколько бумаг выше своих месячных, квартальных или годовых значений и пр.). Каждый по своему может это использовать далее (как общий фильтр принятия решения для входа в сделку, для составления своих индексов, для анализа динамики своего портфеля – особенно если счетов несколько у разных брокеров и пр.).
Получить котировки на конкретную дату можно через сайт Московской Биржи (https://www.moex.com/ru/marketdata/#/mode=groups&group=4&collection=3&boardgroup=57&data_type=history&date=2023-06-27&category=main), но это не очень удобно т.к. требуется либо парсить (для чего нужен уже нетривиальный уровень в программировании), либо вручную выдергивать эту страницу, например в excel (тем, кто попробует выгрузить всё по кнопкам скачать Excel / CSV биржа предложит воспользоваться платной подпиской для получения данных).
20220915,090000,61420,61497,61406,61464,241
20220915,090100,61460,61476,61420,61451,160
20220915,090200,61444,61489,61436,61479,185
Осмелюсь предположить, что эти строки ты заливаешь в массив с помощью string.match. Это готовый парсер строки с разделителем. Работает достаточно шустро. Я на нем сидел пару лет.
Когда данных не много, такой метод загрузки не напрягает. Но когда за день 20-30 раз загружаешь сотни тысяч или миллион строк, то потери времени становятся невыносимыми.
Стал искать способ ускорить этот процесс. И он таки нашелся. Выяснил следующее:
Если строки в файле истории сконвертировать в такой вид (делается 1 раз):
table.insert(MyTable,{«20220915»,«090000»,61420,61497,61406,61464,241})
table.insert(MyTable,{«20220915»,«090100»,61460,61476,61420,61451,160})
table.insert(MyTable,{«20220915»,«090200»,61444,61489,61436,61479,185})
Settings =
{
Name = «DHLM»,
line =
{
{
Name = «High»,
Color = RGB(0,200,64),
Type = TYPET_BAR,
Width = 1
},
{
Name = «Low»,
Color = RGB(200,0,64),
Type = TYPET_BAR,
Width = 1
},
{
Name = «Median»,
Color = RGB(0,64,200),
Type = TYPET_BAR,
Width = 1
}
}
}
local hlm = {}
local math_max = math.max
local math_min = math.minfunction Init()
return #Settings.line
end
function OnCalculate(index)
local dt = T(index)if O(index) then
if dt.day ~= hlm.day or
dt.month ~= hlm.month or
dt.year ~= hlm.year then
hlm.year = dt.year
hlm.day = dt.day
hlm.month = dt.month
hlm.high = H(index)
hlm.low = L(index)
else
hlm.high = math_max(hlm.high,H(index))
hlm.low = math_min(hlm.low,L(index))
hlm.median = (hlm.high + hlm.low)/2
end
end
return hlm.high,hlm.low,hlm.median
end
function round(number, znaq) -- функция округления числа num до знаков znaq local num = tonumber(number) local idp = tonumber(znaq) if num then local mult = 10 ^ (idp or 0) if num >= 0 then return math.floor(num * mult + 0.5) / mult else return math.ceil(num * mult - 0.5) / mult end else return num end end
function DaysToDie(class_code, sec_code) -- Получаем количество дней до погашения инструмента,<br />-- class_code - для фьючерсов SPBFUT<br />-- sec_code - код инструмента SiZ2, BRZ2, CRZ2 и т.д. -- если < 4, просим заменить инструмент -- для работы необходима ф-ция round (округляем до целого числа) -- is_run - глобальный флаг работы робота - false = отключаемся. --- local daysToDie = 0 -- количество дней до погашения инструмента ----- получаем количество дней до погашения, если < 4, рекомендуем перейти на новый инструмент ---------- daysToDie = round(getParamEx(class_code, sec_code, "DAYS_TO_MAT_DATE").param_value, 0) if daysToDie <= 4 and daysToDie > 0 then message("Количество дней до погашения инструмента " .. SEC_CODE .. " равно " .. tostring(daysToDie) .. ". Необходимо заменить инструмент в настройках робота") elseif daysToDie == 0 or daysToDie == nil then message("Инструмент больше не торгуется") is_run = false end return daysToDie end