Всем привет!
То о чем так долго мечтали большевики — свершилось!
Представляю
QLua-сервер для управления
заявками в Квике Квиком. Как обычно, в несколько строк кода.
Сервер представляет собой пассивный tcp-сокет со стороны QLua, который принимает от клиента любой корректный
lua-код, «вычисляет» его на своей стороне и возвращает результат клиенту.
Если вы хотите усилить безопасность, (особенно если клиент не на локальном хосте) замените «вычислитель» собственным парсером.
В видео показано взаимодействие с Квиком со стороны Питона(клиент).
Нам понадобятся следующие ингредиенты:
1. Квик 8-й версии от 8.5.2
2. Питон. Jupyter Notebook (Anaconda 3)
Последовательность действий:
Установка: содержимое папки
Server (а не саму папку) скопируйте в папку Квика. Здесь сам луа-скрипт и библиотеки. Файл PythonClientQuikLuaServer.ipynb откройте в Питоне.
В Квике запустите QLua-сервер — луа-скрипт
LuaQuikServer.lua
В Питоне в файле
PythonClientQuikLuaServer.ipynb в переменную
account_forts запишите ваш аккаунт на ФОРТСе (можно получить из окна выставления заявки в Квике).
Вычислите первую ячейку(CTRL+Enter) — определения переменных, функций и подключение клиента к серверу.
Можно отправлять команды на сервер.
Начиная со второй ячейки ноутбука показаны примеры, в том числе запрос параметров инструмента, выставление и снятие лимитных и стоп-заявок по номеру заявки. Сервер работает даже с не подключенным Квиком (кроме выставления заявок). Так что, его можно опробовать уже сегодня.
Код lua-сервера:
stopped = false -- Остановка файла
socket = require("socket") -- Указатель для работы с sockets
json = require( "json" ) -- Указатель для работы с json
IPAddr = "127.0.0.1" --IP Адрес
IPPort = 3585 --IP Port
client = nil
-- Функция вызывается перед вызовом main
function OnInit(path)
-- create a TCP socket and bind it to the local host, at port IPPort
server = assert(socket.bind("*", IPPort))
message(string.format("Server started. IP: %s; Port: %d\n", IPAddr, IPPort), 1);
end;
-- Функция вызывается перед остановкой скрипта
function OnStop(signal)
if client then client:close() end
stopped = true; -- Остановили исполнение кода
end;
-- Функция вызывается перед закрытием квика
function OnClose()
if client then client:close() end
stopped = true; -- закрыли квик, надо остановить исполнение кода
end;
--Этой функцией заменен парсер. Саму ее нужно применять с осторожностью, т.к. это может нарушить безопасность системы.
--Она позволяет выполнять любой луа-код, поступивший со стороны клиента
function evalString (str)
return assert(loadstring( "return " .. str))()
end
-- Основная функция выполнения скрипта
function main()
-- wait for a connection from any client
client = server:accept()
sleep(1)
while not stopped do
local line, err = client:receive()
-- if there was no error, send it back to the client
local result = evalString(line)
if result == nil then result = "{}" end
if type(result) == "table" then result = json.encode(result) end
if type(result) == "boolean" then result = (result and 1 or 0) end
-- if not err then message(string.format("Message:%s Result: %s\n", line, result), 1) end
if not err then client:send(result .. "\n") end
end
end;
Код Python-клиента:
# 1.В Квике запускаем скрипт LuaQuikServer.lua
# 2.В Питоне эту ячейку CTRL+Enter
import socket
import json
import sys
from datetime import datetime
host = '127.0.0.1' #локальный компьютер
port = 3585 #порт для подключения к серверу
# Буфер для получения ответа от сервера QLUA. Будьте внимательны, если ответ длиннее buff, увеличивайте размер buff заранее
buff=2048
account_forts = '888097i' # Аккаунт на ФОРТСе
account_tplus = 'L01+00000F00' # Аккаунт на Мамбе
client_tplus = '888VB/888VB' # Код клиента на Мамбе
transid = 1 # Инкрементируемый ID транзакции
#Функция - пример того, как из Python (или любого другого клиента, в т.ч. удаленного) можно вычислить любое корректное
#выражение или функцию на стороне сервера QLua и вернуть результат в Python, например res=QLua("1+2").
#Аргументом является строка, содержащая корректное выражение QLua. Т.к. в Квике присутствует кириллица формат будет 'cp1251'
#Строка посылается через сокет на QLua-сервер, вычисляется на нем, а результат возвращается в Python.
#Всегда используйте это с осторожностью, особенно если КВИК находится не на локальном хосте а в сети. Т.к. подключаемый
#к QLua-серверу клиент получает полный контроль над Квиком. Чтобы избежать проблем с безопасностью, замените в QLua-сервере
#"вычислитель" на парсер или используйте SSL-соединение.
def QLua(any_eval_string):
s.send(f'{any_eval_string}\n'.encode('cp1251'))
return s.recv(buff).decode('cp1251').replace("\n","")
#Функция предназначена для определения состояния подключения клиентского места к серверу.
#Возвращает 1, если клиентское место подключено и 0, если не подключено.
def isConnected():
s.send('isConnected()\n'.encode('utf-8'))
return int(s.recv(buff).decode('utf-8'))
#Функция предназначена для получения информации по бумаге. Возвращает словарь, соответствующей таблице QLua с параметрами.
def getSecurityInfo(class_code, sec_code):
s.send(f'getSecurityInfo("{class_code}", "{sec_code}")\n'.encode('utf-8'))
return json.loads(s.recv(buff).decode('cp1251').replace("\n",""))
#Функция для выставления лимитных и стоп-заявок. Аргументы ticker - словарь, содержащий описание тикера после запроса
# getSecurityInfo(class_code, sec_code). Например SiU0=getSecurityInfo("SPBFUT", "SiU0"). price - цена, quant -количество
# контрактов(лотов) в заявке, oper 'B'(по умолчанию) или 'S' - покупка или продажа, stop_price - цена активации стоп-заявки,
# если указана идентифицирует заявку как стоп-заявку. acc - торговый счет клиента (по умолчанию на ФОРТСе)
# Примеры выставления заявок:
# Лимитная заявка на покупку 1 контракта SiU0 по цене 70059. NewOrder(SiU0, 70059, 1, oper = 'B')
# Лимитная заявка на продажу 1 контракта SiU0 по цене 70888. NewOrder(SiU0, 70888, 1, oper = 'S')
# Для выставления стоп-заявки, задайте явно аргумент stop_price. Это цена активации стоп-заявки.
# Стоп-заявка на покупку 10 контрактов SiU0. При достижении ценой 71008 Купить 10 контрактов по цене 71028.
# NewOrder(SiU0, 71028, 10, oper = 'B', 71008)
def NewOrder(ticker, price, quant, oper = 'B', stop_price = False, acc = account_forts):
global transid
transid += 1 #Увеличиваем счетчит транзакций
act = "NEW_STOP_ORDER" if stop_price else "NEW_ORDER" #Если указана цена активации заявки stop_price - это стоп-заявка
stop_price_str = f'["STOPPRICE"] = "{stop_price}",' if stop_price else "" #если заявка лимитная, то это строка пустая
s.send(('sendTransaction ({'+ f'["TRANS_ID"] = "{transid}",["ACTION"] = "{act}",["CLASSCODE"] = "{ticker["class_code"]}",["SECCODE"] = "{ticker["sec_code"]}",["OPERATION"] = "{oper}",["QUANTITY"] = "{quant}",["PRICE"] = "{price}",{stop_price_str}["ACCOUNT"] = "{acc}"' + '})\n').encode('utf-8'))
return s.recv(buff).decode('cp1251').replace("\n","")
#Функция для снятия лимитных заявок по идентификатору заявки order_id - число или строка.
# transid - необязательный (в Питоне) номер транзакции. acc - торговый счет клиента (по умолчанию на ФОРТСе)
def LimitOrderKill(ticker, order_id, transid = transid, acc = account_forts):
s.send(('sendTransaction ({'+ f'["TRANS_ID"] = "{transid}", ["ORDER_KEY"] = "{order_id}",["ACTION"] = "KILL_ORDER",["CLASSCODE"] = "{ticker["class_code"]}",["SECCODE"] = "{ticker["sec_code"]}",["ACCOUNT"] = "{acc}"' + '})\n').encode('utf-8'))
return s.recv(buff).decode('cp1251').replace("\n","")
#Функция для снятия стоп-заявок по идентификатору заявки order_id - число или строка.
def StopOrderKill(ticker, order_id, transid = transid, acc = account_forts):
s.send(('sendTransaction ({'+ f'["TRANS_ID"] = "{transid}", ["STOP_ORDER_KEY"] = "{order_id}",["ACTION"] = "KILL_STOP_ORDER",["CLASSCODE"] = "{ticker["class_code"]}",["SECCODE"] = "{ticker["sec_code"]}",["ACCOUNT"] = "{acc}"' + '})\n').encode('utf-8'))
return s.recv(buff).decode('cp1251').replace("\n","")
#################################################### Python Client Code ###############################################################
#Сокет для отправки комманд в КВИК и получения результатов исполнения (через сервер в луа-скрипте)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',3585))
#По традиции поздороваемся с Квиком:
res = QLua('message("Привет, Квик!", 1)')
res
#Проверим знание Квиком арифметики 1-го класса:
res = QLua('3+2')
res
#Проверим, подключен ли Квик к торгам, уже с помощью функции в Питоне(см.код выше)
isConnected()
#В переменную-словарь SiU0 запросим из Квика все параметры контракта SiU0."SPBFUT" - код площадки, "SiU0" - код инструмента
SiU0 = getSecurityInfo("SPBFUT", "SiU0")
SiU0
#Выставим лимитную заявку на покупку 1 контракта SiU0 по цене 70059
res=NewOrder(SiU0, 70059, 1, 'B')
res
#Выставим стоп на продажу 1 контракт с ценой активации стопа 69888 и ценой исполнения 69850
res=NewOrder(SiU0, 69850, 1, 'S', 69888)
res
#Снимем лимитную и стоп-заявки по номеру заявки
LimitOrderKill(SiU0, 26040408900600)
StopOrderKill(SiU0, 149077121)
#ОСТОРОЖНО!!! КОД ВЫСТАВЛЯЕТ В ЦИКЛЕ 10 КОНТРАКТОВ SiU0 НА ПОКУПКУ ПО ЦЕНЕ 70200 И НИЖЕ С ШАГОМ В 2 ПУНКТА
for price in range(70200, 70180 , -2):
NewOrder(SiU0, price, 1, 'B')
#После окончания успешной торговли не забывайте закрывать соединение, а в Квике остановите луа-скрипт.
s.close()
s
Клиент работает в режиме: отправка сообщения — получение ответа. Подключение постоянное. В конце работы закрывайте соединение c.close()
Скачать полный код можно со страницы
https://www.jatotrade.com/download (в конце страницы)
Подписаться на мой канал в ютьюбе можно
здесь.
Доброй охоты!
#По традиции поздороваемся с Квиком:
res = QLua('message(«Привет, Квик!», 1)')
res
#Проверим знание Квиком арифметики 1-го класса:
res = QLua('3+2')
res
#Проверим, подключен ли Квик к торгам, уже с помощью функции в Питоне(см.код выше)
isConnected()
но вот я не смог придумать как отладить скрипт на lua в квике. если только через логи в фаил. где хранить данные робота? интерфейс с базой данных?
Потокобезопасность обеспечивают функции table.sinsert(), sremove().
Но мои роботы хранят торговые позиции для перехода на следующий день в Lua-скиптах, содержащих соответствующие таблицы.
А отлаживают по складам (через дебагер) и читают по складам только первоклашки. Хотя в Lua есть модуль debug.
Но роботов я отлаживаю на демо-счетах.
QLua не «просто инструмент». Это идеальный инструмент.
server = assert(socket.bind(«127.0.0.1», IPPort))
и запускать клиент и сервер на одной и той же машине.
Может, хреновый из меня айтишник (но я и не полностью айтишник), но так приятно когда тебя за ручку проводят через сложные процессы где много где можно оступиться и завязнуть.
Всегда когда что-то подобное делаешь, всегда вылезет куча нюансов:
— чет не работает.
— а, ну тут библиотека с этой не совместима.
— чет не работает, а ну тут же файл надо переименовать, на стэковерфлоу в 2007 году пост на эту тему был, не читал что ли?
— чет не работает.
— а, ну ты в реестре запись, наверно, не прописал и конечно же не отключил системный процесс А.
А тут такое:
— вот тебе папочка, скопируй её сюда, да смотри не перепутай — содержимое, а не всю папку.
Расписано какие конкретно как и зачем ячейки в ноутбуке запускать.
Может, я сегодня какой-то особенно сентиментальный, но это праздник какой-то)), так иногда хочется чтоб тебя вот так юзер-френдли сопровождали при решении сложных задач, почему весь мир IT не такой?))).
Респект ещё раз!
Подружил с php (пока на тест) — то что давно хотел сделать )
Правда при клозе скрипт ваш по такой ошибке выпадает сам...
WebQuik <--> Websocket <--> Python
Messages ID 2xxxx — from server to client
https://junior.webquik.ru/classes/controller/CtrlWSocket.js
https://github.com/loktevsp/TraderQUIK/blob/master/dataServer.txt
github.com/loktevsp/TraderQUIK/blob/master/dataServer_заявки.txt
https://github.com/DmitryPukhov/pytrade/blob/master/pytrade/connector/quik/MsgId.py
https://github.com/m13oas/quik-connector
https://github.com/eSKond/WrapWebQuik
Господа, проконсультируйте, пожалуйста, по некоторым вопросам. Пробую использовать питон для написания простенькой автоматической торговой системы. Питон выбрал, так как уже имел с ним небольшой опыт, да и язык универсальный, может где ещё пригодится. Но, как я погляжу, не очень то распространено его использование в связке с торговыми терминалами, поэтому приходится собирать информацию по крупицам, и публикации Евгения Шибаева, похоже, самый лучший источник на смартлабе, если не сказать больше-в рунете! :) Для начала завёл демо-счёт у финама и установил предложенный Квик 7.25. Луа-серверы из постов «Управление заявками в Квике из Питона» и «Стакан к празднику» как-то можно между собой совместить? Можно ли запустить их одновременно разными потоками? (уже попробовал-можно). Что для этого нужно, использовать разные порты? Я смотрю, что в одном скрипте используется порт 3585, а в другом 3587, это имеет какое-либо значение? Или совместить эти скрипты в одном файле? Вообще, сейчас попробовал запускать скрипты, но никаких данных не получил, возможно это связано с тем, что это было в воскресенье, торги не ведутся.
Что за аккаунт на фортс и мамбе упоминается в посте? Это те, которые используются для подкачки исторических данных? В квике в окне заявки я вижу «торговый счёт» и «код клиента». Это оно?
Как на питоне скачать в рантайме неглубокие исторические данные, что бы строить, скажем, скользящую среднюю? Для оптимизации параметров алгоритма на большой истории я просто скачиваю csv с сайта финама, а как быть с рантаймом?
Или вообще не заморачиваться, и работать с каким-нибудь TSLab? Но хотелось бы с питоном поковыряться, интересно же..