Евгений Шибаев
Евгений Шибаев личный блог
20 июня 2020, 11:47

Python-->Lua-->Квик. Управление заявками в Квике из Питона.

Всем привет!
То о чем так долго мечтали большевики — свершилось!
Представляю 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 (в конце страницы)
Подписаться на мой канал в ютьюбе можно здесь.

Доброй охоты!

48 Комментариев
  • Replikant_mih
    20 июня 2020, 11:54
    Крутяк, спасибо! Буду смотреть.
  • Susanin
    20 июня 2020, 13:23
    А чем эта схема лучше sql c#?
  • Sergey Zaytchenko
    20 июня 2020, 16:35
    function OnInit(path)
      -- create a TCP socket and bind it to the local host, at port IPPort
      server = assert(socket.bind("*", IPPort))
    Опасно! Сервер будет слушать на всех интерфейсах, а не только localhost.

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн