Блог им. jatotrade_com
#1.Запускаем сервер (эту ячейку) CTRL+ENTER #2.В КВИКе запускаем луа-скрипт QuikLuaPython.lua import socket import threading import pandas as pd ticker = 'BRN0' #В Квике должен быть открыт стакан BRN0, а в таблице обезличенных сделок транслироваться тики BRN0 ticks=[] #Для примера, список обезличенных сделок BRN0 stakan = '' # строка формата '"имя тикера" {bid_price:bid_size,bid_price1:bid_size1...ask_price:-ask_size,ask_price1:-ask_size1}' def parser (res): parse = res.split(" ", 2) #первый элемент - идентификатор события, второй имя тикера if parse[0] == '2': # парсинг стакана (событие '2') if parse[1] == ticker: global stakan stakan = parse[2] if parse[0] == '1': # парсинг обезличенной сделки (событие '1') tail = res.split(" ") if tail[1] == ticker: #записываем цену текущего тика BRN0 в список ticks ticks.append(float(tail[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) #Здесь вызываете свой парсер. Для примера функция: parser (parse) sock.close() #Запускаем сервер в своем потоке t = threading.Thread(name='service', target = service) t.start()
#3.Запускаем отображение стакана и графика тиков (эту ячейку) CTRL+ENTER import matplotlib.pyplot as plt import numpy as np import matplotlib.animation %matplotlib notebook hi_lo = [0.0, 0.45] #здесь достаточно задать верхнюю и нижнюю границу цены стакана, чтобы сохранить заданный масштаб max_vol = 3000 #максимальное значение шкалы объема на стакане max_ticks = 1000 #Количество отображаемых тиков на графике fsize = 10 #размер шрифта в стакане fig = plt.figure(figsize=(12, 9), dpi= 80) #Определяем размер изображения ax_stakan = fig.add_subplot(1, 4, 3) #Расположение стакана ax_tick = fig.add_subplot(1, 2, 1) #Расположение графика сделок ax_stakan.tick_params(left = False, labelleft = False, labelright = True, labelsize=8) #Расположение осей #Функция для автосмещения шкал при приближении текущей цены к верхней или нижней границам стакана (20 процентов) #point - количество знаков после запятой, например для BR - 2, а для RI, SI, SR, GZ - 0, или можно не задавать def masht(price, point = 0): percent = 0.2 hi, lo = hi_lo[1], hi_lo[0] rn = hi - lo if price < lo + rn*percent or price > hi - rn*percent: hi_lo[0]=round(price - 0.5*rn, point) hi_lo[1]=round(price + 0.5*rn, point) #Функия обновления кадра графика def update(i): if ticks: price = ticks[-1] #Цена последней сделки ob_dict = eval(stakan) #читаем из строки стакан-словарь в ob_dict masht(price, 2) #проверяем необходимость смещения High,Low диаграммы при достижении ценой соответствующей границы ax_tick.clear() ax_stakan.clear() ax_tick.grid(which='major', color = 'gray', linestyle = ':') ax_stakan.grid(which='major', color = 'gray', linestyle = ':') ax_stakan.set_ylim(hi_lo) ax_stakan.set_xlim([0,max_vol]) ax_tick.set_ylim(hi_lo) ax_tick.set_xlim([0,max_ticks]) bid_price, bid_size, ask_price, ask_size = [],[],[],[] #Распределяем стакан-словарь на 4 списка: bid_price, bid_size, ask_price, ask_size for (pr, sz) in ob_dict.items(): if pr >= hi_lo[0] and pr <= hi_lo[1]: #отображаем только объемы в видимой области стакана if sz > 0: bid_price.append(pr) bid_size.append(sz) else: ask_price.append(pr) ask_size.append(-sz) x, y = np.array(bid_price), np.array(bid_size) x1, y1 = np.array(ask_price), np.array(ask_size) ax_stakan.autoscale(False, tight=False) #Отключаем автошкалу Питона, чтобы график не "прыгал" постоянно ax_stakan.barh(x, y, height = 0.009, color = 'seagreen', alpha=0.4) #tick_label = bid_size ax_stakan.barh(x1, y1, height = 0.009, color = 'lightcoral', alpha=0.4) for (pr, sz) in zip(bid_price[1:], bid_size[1:]): #Печать объемов ax_stakan.text(0, pr, sz, fontsize=fsize, va='center', color='green') #darkgreen for (pr, sz) in zip(ask_price[:-1], ask_size[:-1]): ax_stakan.text(0, pr, sz, fontsize=fsize, va='center', color='red') #darkred #Маркер текущей цены ax_tick.text(max_ticks*1.01, price, price, fontsize=10, va='center',ha='left') ax_tick.plot(ticks[-max_ticks:]) #Отображаем последние 1000(max_ticks) тиков # Включаем анимацию на 1000 кадров, раз в 400 мсек ani = matplotlib.animation.FuncAnimation(fig, update, frames=1000, repeat=False, interval=400, blit=True) plt.show()
stopped = false -- Остановка файла socket = require("socket") -- Указатель для работы с sockets json = require( "json" ) -- Указатель для работы с json 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 = " {" if type(level2["bid"]) == "table" then for index, bid in ipairs(level2["bid"]) do bidstr = bidstr..bid["price"]..":"..bid["quantity"].."," end end; local askstr = "" if type(level2["offer"]) == "table" then for index, ask in ipairs(level2["offer"]) do askstr = askstr..ask["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"]["day"].."."..order["datetime"]["month"].."."..order["datetime"]["year"].." "..order["datetime"]["hour"]..":"..order["datetime"]["min"]..":"..order["datetime"]["sec"].."\n" -- return status.." "..json.encode(order).."\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;
Продам на 50 звездах ★★★★★....
Потому, что пост про LUA))
Но у меня другая концепция:
— Питон для объемных вычислений,
— для всяких стаканов/сделок есть С++.
— для связи — Луа.
На самом деле, коллега, питон в этом варианте очень прожорлив, естессно его никто не собирается использовать для отображения быстрой графики.
Traceback (most recent call last):
File «C:\Users\User\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py», line 216, in process
func(*args, **kwargs)
File «C:\Users\User\Anaconda3\lib\site-packages\matplotlib\animation.py», line 1465, in _stop
self.event_source.remove_callback(self._loop_delay)
AttributeError: 'NoneType' object has no attribute 'remove_callback'
%matplotlib notebook в самое начало.
Comrade, saludos cordiales! El pueblo unido hamas sera vensido!
Сейчас вот это уточнение и статическое изображение.
Странно, может совпадение ?
А так, разумеется, спасибо!
Вам большой респект и дальнейших творческих успехов.
А где взять библиотеки для Lua:
Сделали бы лучше что-то типа скринера для акций для Квика. Например, чтоб в таблицу можно было выбрать интересующие инструменты и по ним отображалось по каждому инструменту изменение текущей цены по отношению к цене с задаваемым условием: за неделю, месяц, или особенно здорово например от последнего хай/лой за последние хх дней/ определенной даты.
Не сочтите за наглость, а можно ли еще к таблице добавить столбец, в котором пользователь мог бы просто заносить свои примечания к инструменту в произвольном формате?
Все это правильно было бы просить у разработчика. Но зайдя на форум Арки и полазив я увидел, что там годами висят предложения/просьбы к продукту от пользователей без всякого движения. Уж сколько лет народ просит добавить очевидные вещи типа автоматического выставления стопа/тэйк-профита по совершению сделки — все в пустоту.
А средствами только скрипта Lua задачи построения такой таблицы не решаются?
import requests
import csv
url='https://iss.moex.com/iss/engines/futures/markets/forts/securities/SIM0/candles/securities.json?&from=2020-03-20&start=0&interval=24&candles.columns=open,close,high,low,volume,end'
res=requests.get(url)
data=res.json()['candles']
data
Возвращает словарь типа:
Евгений Шибаев, Понял, спасибо, супер!)
Блин, мне бы просто посмотреть код которым команда из питона в квик прилетает) и как луашные функции цепляет. Я с луа никогда не работал, но думаю там много и не надо, чтоб функции на команды навесить, просто посмотреть шаблон).
Но конечно, если в вашем варианте уже что-то будет реализовано из мясца, не только скелет — вообще отлично).
Если что, я с пониманием отнесусь если будет и позже понедельника, а-то тоже, бывает, что-нить скажешь сгоряча, потом приходится делать что обещал))).
Там все просто — со стороны луа открывается потоковый сервер, который банально принимает строку от клиента и вычисляет ее. Конечно, небезопасно, если в сети кроме вас есть подключения. Для безопасности просто делается не вычислитель, а парсер на стороне сервера. Ну не будем забегать вперед.
При попытке запустить скрипт LUA (QuikLuaPython.lua) на QUIK версии 8.12.0.41 возникает ошибка:
C чем может быть связано?error loading module 'socket.core' from file 'C:\QUIK\socket\core.dll':
Не найден указанный модуль.