############################################ ©2020 by Evgeny Shibaev ################################################### #1.Запускаем сервер (эту ячейку) CTRL+ENTER #2.В КВИКе запускаем луа-скрипт QuikLuaPython.lua import socket import threading ticks=[] def parser (parse): if parse[0] == '1' and parse[1] == 'RIM0': #записываем цену текущего тика SiM0 в список ticks ticks.append(float(parse[4])) def service(): sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sock.bind(('127.0.0.1',3587)) #Хост-этот компьютер, порт - 3587 while True: res = sock.recv(2048).decode('utf-8') if res == '<qstp>\n': #строка приходит от клиента при остановке луа-скрипта в КВИКе break else: parser(res.split(' ')) #Здесь вызываете свой парсер. Для примера функция: parser (parse) sock.close() #Запускаем сервер в своем потоке t = threading.Thread(name='service', target = service) t.start()
#3.Запускаем анимацию (эту ячейку) для проверки работы сервера import matplotlib.pyplot as plt import pandas as pd import matplotlib.animation %matplotlib notebook fig = plt.figure() ax = fig.add_subplot() def update(i): ax.clear() plt.plot(ticks[-1000:]) #Отображаем последние 1000 тиков ani = matplotlib.animation.FuncAnimation(fig, update, frames=1000, repeat=False) plt.show()У меня аниматор иногда запускается со второго раза. Но это не страшно...
В сокет Питона из луа-скрипта мы получаем строки, в которых элементы разделены пробелами.
Первый элемент строки — это номер события в КВИКЕ (далее описание событий с реальными примерами)
1 — Текущая обезличенная сделка (тик), Тикер, № в потоке, ID сделки, цена, 1026(1025) покупка(продажа), количество, дата, время.мс
«1 RIM0 777837 25971668199048 121660.0 1026 1.0 27.5.2020 15:49:48.122»
3 — Обезличенная сделка из истории (тик), Тикер, № в потоке, ID сделки, цена, 1026(1025) покупка(продажа), количество, дата, время.мс
«3 RIM0 762482 25971668178568 121320.0 1026 2.0 27.5.2020 15:34:54.35»
2 — Биржевой стакан, Тикер, количество бидов, цена последнего бида, количество последнего бида, цена следующего бида, количество следующего бида,... цена лучшего бида, количество лучшего бида, количество асков, цена лучшего аска, количество лучшего аска, цена следующего аска, количество следующего аска,… цена последнего аска, количество последнего аска. Т.е. стакан упорядочен по возрастанию цены.
«2 RIM0 50.000000 121190 5 121200 30 121210 10 121220 9 121230 9 121240 14 121250 28 121260 18 121270 10 121280 11 121290 11 121300 28 121310 19 121320 6 121330 42 121340 64 121350 28 121360 11 121370 34 121380 44 121390 33 121400 36 121410 106 121420 37 121430 38 121440 30 121450 74 121460 38 121470 46 121480 70 121490 83 121500 123 121510 53 121520 57 121530 109 121540 41 121550 62 121560 39 121570 43 121580 20 121590 29 121600 46 121610 30 121620 24 121630 40 121640 15 121650 33 121660 27 121670 19 121680 4 50.000000 121690 5 121700 22 121710 13 121720 16 121730 19 121740 28 121750 23 121760 34 121770 37 121780 23 121790 34 121800 116 121810 158 121820 41 121830 23 121840 27 121850 168 121860 63 121870 26 121880 24 121890 75 121900 130 121910 40 121920 52 121930 58 121940 34 121950 61 121960 78 121970 85 121980 66 121990 58 122000 177 122010 34 122020 18 122030 10 122040 22 122050 50 122060 51 122070 14 122080 4 122090 37 122100 21 122110 8 122120 16 122130 8 122140 216 122150 5 122160 29 122170 5 122180 10»
"<qsrt>" — Старт соединения
«4 888888» — количество тиков в истории на текущий момент
«5»- загрузка истории завершена
6 — заявка, Тикер, № транзакции, ID заявки, Флаг, Счет, цена, количество, объем, комментарий, время заявки
Флаги: 26 — «Снята», «Купля»; 30 — «Снята», «Продажа»; 28 — «Исполнена», «Продажа»; 24 — «Исполнена», «Купля»; и т.д.
«6 RIM0 120 25971670302591 30 „764088i“ 121190.0 1.0 1.0 171665.64 „764088i“ 23:3:51»
7 — стоп-заявка
8 — сделка, Тикер, ID сделки, ID заявки, Флаг, Счет, цена, количество, объем, комментарий, время заявки
Флаги: 64 — «Исполнена», «Купля»; 68 — «Исполнена», «Продажа»; и т.д.
«8 RIM0 25971667540438 25971671513360 68 „764096i“ 121470.0 1.0 172062.26 „764096i“ 27.5.2020 10:16:25 4»
На самом деле вы можете передавать из КВИКа любые данные, например о состоянии счета, портфеля и пр. Просто добавьте в луа-скриптstopped = false -- Остановка файла socket = require("socket") -- Указатель для работы с sockets IPAddr = "127.0.0.1" --IP Адрес IPPort = 3587 --IP Port sender = nil --Укзатель на коннектор send = nil --Указатель на процедуру отправки сообщения connect = nil --Указатель на процедуру подключения к серверу all_trd_indx = 0 --Текущий индекс для переданных записей таблицы всех сделок all_ord_indx = 0 all_stop_ord_indx = 0 all_my_trades_indx = 0 inited = false --Для проверки вызова OnAllTrade перед main -- Функция вызывается перед вызовом main function OnInit(path) -- sender = assert(socket.connect(IPAddr, IPPort)); sender = socket.udp() sender:setpeername(IPAddr, IPPort) --Выводим сообщение в квике, что есть подключение message(string.format("Connection success. IP: %s; Port: %d\n", IPAddr, IPPort), 1); sender:send("<qsrt>\n"); end; -- Функция вызывается перед остановкой скрипта function OnStop(signal) sender:send("<qstp>\n"); stopped = true; -- Остановили исполнение кода end; -- Функция вызывается перед закрытием квика function OnClose() sender:send("<qcls>\n"); stopped = true; -- закрыли квик, надо остановить исполнение кода end; -- Функция вызвается при изменении стакана function OnQuote(class, seccode) if stopped then return; end local sec = getSecurityInfo(class, seccode); local price = 0; local level2 = getQuoteLevel2(class, seccode); --Получаем стакан --сначала загружаем бид, потом аск local bidstr = " "..level2["bid_count"] if type(level2["bid"]) == "table" then for index, bid in ipairs(level2["bid"]) do price = bid["price"] bidstr=bidstr.." "..price.." "..bid["quantity"] end end; local askstr = " "..level2["offer_count"] if type(level2["offer"]) == "table" then for index, ask in ipairs(level2["offer"]) do price = ask["price"] askstr=askstr.." "..price.." "..ask["quantity"] end end; local str = "2 "..seccode..bidstr..askstr.."\n"; sender:send(str) end; --User trades functions function formatmytrade (status, trade) all_my_trades_indx = all_my_trades_indx + 1 return status.." "..trade["sec_code"].." "..trade["trade_num"].." "..trade["order_num"].." "..trade["flags"].." \""..trade["account"].."\" "..trade["price"].." "..trade["qty"].." "..trade["value"].." \""..trade["brokerref"].."\" "..trade["datetime"]["day"].."."..trade["datetime"]["month"].."."..trade["datetime"]["year"].." "..trade["datetime"]["hour"]..":"..trade["datetime"]["min"]..":"..trade["datetime"]["sec"].." "..trade["trans_id"].."\n" end; function OnTrade(trade) if (not inited) or (stopped) then return; end; sender:send (formatmytrade (8, trade)); end; function sendallmytrades() local count = getNumberOf("trades"); local index = 0 for index=0,count - 1 do if stopped then return false; end; sender:send (formatmytrade (8, getItem("trades", index))); end; return true; end; --User stop orders functions function formatstoporder (status, order) all_stop_ord_indx = all_stop_ord_indx + 1 return status.." "..order["sec_code"].." "..order["trans_id"].." "..order["order_num"].." "..order["flags"].." \""..order["account"].."\" "..order["price"].." "..order["condition_price"].." "..order["qty"].." "..order["balance"].." \""..order["brokerref"].."\" "..order["ordertime"].."\n" end; function OnStopOrder (order) if (not inited) or (stopped) then return; end; sender:send (formatstoporder (7, order)); end; function sendallstoporders() local count = getNumberOf ("stop_orders"); local index = 0 for index=0,count - 1 do if stopped then return false; end; sender:send (formatstoporder (7, getItem ("stop_orders", index))); end; return true; end; function formatorder (status, order) all_ord_indx = all_ord_indx + 1 return status.." "..order["sec_code"].." "..order["trans_id"].." "..order["order_num"].." "..order["flags"].." \""..order["account"].."\" "..order["price"].." "..order["qty"].." "..order["balance"].." "..order["value"].." \""..order["brokerref"].."\" "..order["datetime"]["hour"]..":"..order["datetime"]["min"]..":"..order["datetime"]["sec"].."\n" end; function OnOrder(order) if (not inited) or (stopped) then return; end; sender:send (formatorder (6, order)); end; function sendallorders() local count = getNumberOf("orders"); local index = 0 for index=0,count - 1 do if stopped then return false; end; sender:send (formatorder (6, getItem("orders", index))); end; return true; end; function OnAllTrade(trade) if (not inited) or (stopped) then return; end; --Отправляем сделку sender:send(formattrade1(1, trade)); end; function formattrade1(status, trade) all_trd_indx = all_trd_indx + 1 -- Увеличиваем счетчик кол-ва --Формируем запись для передачи return status.." "..trade["sec_code"].." "..all_trd_indx.." "..trade["trade_num"].." "..trade["price"].." "..trade["flags"].." "..trade["qty"].." "..trade["datetime"]["day"].."."..trade["datetime"]["month"].."."..trade["datetime"]["year"].." "..trade["datetime"]["hour"]..":"..trade["datetime"]["min"]..":"..trade["datetime"]["sec"].."."..trade["datetime"]["ms"].."\n" --[[ trade_num NUMBER Идентификатор сделки flags NUMBER Набор битовых флагов price NUMBER Цена qty NUMBER Количество value NUMBER Объем сделки accruedint NUMBER Накопленный купонный доход yield NUMBER Доходность settlecode STRING Код расчетов reporate NUMBER Ставка РЕПО repovalue NUMBER Сумма РЕПО repo2value NUMBER Объем сделки выкупа РЕПО repoterm NUMBER Срок РЕПО в днях sec_code STRING Код инструмента class_code STRING Код класса datetime TABLE Дата и время ]] end; --Загрузка обезличенных сделок function sendalltrades() local count = getNumberOf("all_trades"); sender:send("4 "..count.."\n"); local index = 0 for index=0,count - 1 do if stopped then return false; end; sender:send(formattrade1(3, getItem("all_trades", index))); end; sender:send("5\n"); return true; end; -- Основная функция выполнения скрипта function main() inited = true if not stopped then local start_time = os.clock() if sendalltrades() then message("Send all trades history success: " .. tonumber(os.clock() - start_time), 1); sendallorders() sendallstoporders() sendallmytrades() while not stopped do sleep(1); end; --while end; --if end; --if sender:send('<qbye>\n') sender:close() --закрываем соединение sender=nil end;
Вау, че-то прикольное! Спасибо. Я правда ещё не читал, точечно глянул — очень интересно. Чуть позже почитаю подробней.
А нету, случайно, в заначке решения чтобы так же из питона можно было заявки отправлять...
Евгений Шибаев, Посмотрел, первое впечатление не обмануло — очень крутая штука для меня! Запустилось).
Пойду попробую научить луа скрипт упаковывать послания в словари))). Хотя в луа вообще не шарю.
Ну и очень буду ждать пост про то, как с заявками работать.
Снес установщик, и завязал с этими экспериментами. А сокеты через Луа ->DLL делал. Но, тоже не глянулись. Перед сокетами надо данные упаковывать, а после них много парсинга. А если еще инфа разнородная, ее еще разделять и расставлять по полкам замотаешься.
если параметры функций и их сами не менять, то имхо самое то…
в lua еще есть функционал для вызова функций из внешних .dll
я набросал по аналогии несколько функций, в которые и сгружал все данные… и где-то за 2-3 года почти непрерывной работы этот самопальный экспорт из lua ни разу не сглючил… единственное что омрачает, так это подозрение что скоро 32-bit квик прикроют...
А как у вас статистика по соккетам? отрубаются? глючат? память жрут?
Это самое главное в системе. Очень много проблем с этим. С этого стоит начинать.
Вы случайно не знаете — сокеты Луа совместимы с сокетами С# или C++?
у меня с Питоном сразу не завелись «часики».
Правда, долго не копал почему.
не думали,
может быть лучше будет разделить на потоки, где каждая сущность пишется в свой сокет?
Или хотя бы AllTrades и Стаканы отделить от всего остального.
И еще можно в принципе там где много элементов передается (AllTrades и Стаканы) передавать не string, а string[]
Евгений Шибаев
только что обратил внимание — Протокол UDP в данном случае
А по TCP — протоколу сокеты Lua работают ?
Сделал по инструкции, Quik 8.5.2.11 выдает:
\QuikLuaPython.lua:3: module 'json' not found:
no field package.preload['json']
no file 'mypath\lua\json.lua'
no file 'mypath\lua\json\init.lua'
no file 'mypath\json.lua'
no file 'mypath\json\init.lua'
no file 'mypath\..\share\lua\5.3\json.lua'
no file 'mypath\..\share\lua\5.3\json\init.lua'
no file '.\json.lua'
no file '.\json\init.lua'
no file 'mypath\json.dll'
no file 'mypath\..\lib\lua\5.3\json.dll'
no file 'mypath\loadall.dll'
no file '.\json.dll'
Где можно найти недостающие файлы?
Извиняюсь, забыл положить — сейчас все ок
Евгений Шибаев,
Спасибо, работает. (плюсануть не хватает кармы)
Спасибо большое!)
У заявок есть Флаги в зависимости от состояния. Не подскажите, где можно посмотреть какие флаги, каким статусам соответствуют?
надеюсь понятно выразился))