Блог им. jatotrade_com

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

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

13.6К | ★73
48 комментариев
Крутяк, спасибо! Буду смотреть.
avatar
Replikant_mih, спасибо, в «бою» попробовать можно в понедельник.
Евгений Шибаев, Мне б просто убедиться, что сигнал приходит, даже просто принтануть его) — наверно и на выхах такое смогу).
avatar
Replikant_mih, естественно, вот это все и на выхах работает

#По традиции поздороваемся с Квиком:
res = QLua('message(«Привет, Квик!», 1)')
res
#Проверим знание Квиком арифметики 1-го класса:
res = QLua('3+2')
res
#Проверим, подключен ли Квик к торгам, уже с помощью функции в Питоне(см.код выше)
isConnected()

Евгений Шибаев, ага, супер!
avatar
Ребятки, я дико извиняюсь, нужно на дачу, на комменты отвечу после 23:00 по МСК
Евгений Шибаев, мой личный рекорд времени поездки до дачи в субботу 4.5 часа.
avatar
3Qu, не, у меня так много времени нет, до платника с Сокола 8 минут, по М11 22 и после 5 минут. Итого 35 минут. А, забыл — еще 500р.
А чем эта схема лучше sql c#?
avatar
Susanin, скажите, чем лучше против Quik+QLua?
Rostislav Kudryashov, я ничего не имею против lua. это просто инструменты.
но вот я не смог придумать как отладить скрипт на lua в квике. если только через логи в фаил. где хранить данные робота? интерфейс с базой данных?

avatar
Susanin, Так в теме с сокетами, луа нужен только чтоб связаться с квиком — подписаться на и получать события и отправлять управляющие команды, остальная логика на стороне Python кода.
avatar
Replikant_mih, кстати а как подключить к квику пару не зависимых процессов с отправкой транзакций? dll не позволяет это делать.
avatar
Susanin, Ну, Евгений вот только выложил код, через сокеты наверняка можно и несколько процессов, если нужно. Просто через разные порты связываться. Надо будет затестить, хотя для начала и одного мне хватит).
avatar
Replikant_mih, а если строчка придет частично? или только может полностью придти?
avatar
Susanin, Не знаю)), я ещё не запускал ни разу).
avatar
Replikant_mih, я как то делал приложение на сокетах. при пришлось делать свой протокол с размером сообщений, crc  и т.д. т.е. собирать сообщение целиком из разных пришедших частей. а потом уже его обрабатывать.
avatar
Susanin, А, ну это, видимо, если сообщение не влезает в настроенный лимит, можно лимит расширить, либо, если сообщения по какой-то причине должны быть огромными — писать какую-то специфичную логику обработки. Но это вроде не проблема.
avatar
Susanin, не валяй дурочку. Каждый скрипт QLua — отдельный (независимый) поток thread. Если обработка события в callback'ах OnOrder, OnTrade и т.п. отнимает слишком много времени у главного потока, то данные события кладут в структуру «очередь» и  разгружают её в этих отдельных thread'ах.
Потокобезопасность обеспечивают функции table.sinsert(), sremove().
Rostislav Kudryashov, а причем тут потоки вообще? какое отношение это имеет к придаче двоичных данных по сокетам?
avatar
Susanin, сочувствую.
Rostislav Kudryashov, вот и я тоже. отвечать не по теме это странно.
avatar
Susanin, если не нужен сервер на сети, годится библиотека SQLua (SQLite). А ещё лучше vfpoledb.dll через COM-интерфейс luacom. FoxPro — чемпион по скорости. Команда SEEK быстрее в 10 раз любого SQL. Хотя  SQL и Rollback тоже есть.
Но мои роботы хранят торговые позиции для перехода на следующий день в Lua-скиптах, содержащих соответствующие таблицы.
А отлаживают по складам (через дебагер) и читают по складам только первоклашки. Хотя в Lua есть модуль debug.
Но роботов я отлаживаю на демо-счетах.
QLua не «просто инструмент». Это идеальный инструмент.
Rostislav Kudryashov, мда.
avatar
function OnInit(path)
  -- create a TCP socket and bind it to the local host, at port IPPort
  server = assert(socket.bind("*", IPPort))
Опасно! Сервер будет слушать на всех интерфейсах, а не только localhost.

avatar
Sergey Zaytchenko, А как надо?
avatar
Replikant_mih, либо защищать протокол обмена, либо заменить на:
server = assert(socket.bind(«127.0.0.1», IPPort))
и запускать клиент и сервер на одной и той же машине.
avatar
Sergey Zaytchenko, Мм, спасибо, попробую.
avatar
Replikant_mih, только скобки ёлочки надо заменить на двойные кавычки
avatar
Sergey Zaytchenko, ага, спсб, учту. В незнакомой среде такие уточнения могут стоить часов работы)).
avatar
Sergey Zaytchenko, вообще если вы административным ресурсом не откроете соответствующий порт, то к вам на локал хост никто не достучится.
 
Евгений Шибаев, к сожалению это не всегда так, за Вас это может сделать  malware, или можно ошибиться в конфигурации vpn сервиса. Но я просто обратил внимание на то что комментарий к коду не соответствует его поведению.
avatar
Sergey Zaytchenko, да, по-любому спасибо, что разобрались в коде, часто пишется все «на коленке», не за всем успеваешь уследить…

Может, хреновый из меня айтишник (но я и не полностью айтишник), но так приятно когда тебя за ручку проводят через сложные процессы где много где можно оступиться и завязнуть.

 

Всегда когда что-то подобное делаешь, всегда вылезет куча нюансов:

— чет не работает.

— а, ну тут библиотека с этой не совместима.

— чет не работает, а ну тут же файл надо переименовать, на стэковерфлоу в 2007 году пост на эту тему был, не читал что ли?

— чет не работает.

— а, ну ты в реестре запись, наверно, не прописал и конечно же не отключил системный процесс А.

 

А тут такое:

— вот тебе папочка, скопируй её сюда, да смотри не перепутай — содержимое, а не всю папку.

Расписано какие конкретно как и зачем ячейки в ноутбуке запускать.

 

Может, я сегодня какой-то особенно сентиментальный, но это праздник какой-то)), так иногда хочется чтоб тебя вот так юзер-френдли сопровождали при решении сложных задач, почему весь мир IT не такой?))).

avatar
Replikant_mih, «а разве так можно было?» — Спасибо за оценку! Просто олд скул, ничего лишнего...)))

Респект ещё раз!
Подружил с php (пока на тест) — то что давно хотел сделать )

Правда при клозе скрипт ваш по такой ошибке выпадает сам...



Свой Мужик, вообще зачет! Ну можно, в принципе с чем угодно подружить. А При клозе пока да, принудительный баг, чтобы скрипт останавливал, потом можно снова запустить. Руки не дошли…
WebQuik прекрасно принимает и передает всю необходимую информацию через Websocket. Рабочий пример https://github.com/DmitryPukhov/pytrade
WebQuik <--> Websocket <--> Python
avatar
luks sluk, Спасибо огромное за информацию. А для доступа через WebQuik есть где-то документация по запросам и т.д.? Благодарю.
avatar
luks sluk, только при этом требует докер. Не у всех есть возможность их устанавливать в силу корп политик безопасности.
avatar
Whispered, и без докера прекрасно работает, коннекшн совершается тут https://github.com/DmitryPukhov/pytrade/blob/master/pytrade/connector/quik/WebQuikConnector.py
avatar
luks sluk, Спасибо!
avatar

Господа, проконсультируйте, пожалуйста, по некоторым вопросам. Пробую использовать питон для написания простенькой автоматической торговой системы. Питон выбрал, так как уже имел с ним небольшой опыт, да и язык универсальный, может где ещё пригодится. Но, как я погляжу, не очень то распространено его использование в связке с торговыми терминалами, поэтому приходится собирать информацию по крупицам, и публикации Евгения Шибаева, похоже, самый лучший источник на смартлабе, если не сказать больше-в рунете! :) Для начала завёл демо-счёт у финама и установил предложенный Квик 7.25. Луа-серверы из постов «Управление заявками в Квике из Питона» и «Стакан к празднику» как-то можно между собой совместить? Можно ли запустить их одновременно разными потоками? (уже попробовал-можно). Что для этого нужно, использовать разные порты? Я смотрю, что в одном скрипте используется порт 3585, а в другом 3587, это имеет какое-либо значение? Или совместить эти скрипты в одном файле? Вообще, сейчас попробовал запускать скрипты, но никаких данных не получил, возможно это связано с тем, что это было в воскресенье, торги не ведутся.
Что за аккаунт на фортс и мамбе упоминается в посте? Это те, которые используются для подкачки исторических данных? В квике в окне заявки я вижу «торговый счёт» и «код клиента». Это оно?
Как на питоне скачать в рантайме неглубокие исторические данные, что бы строить, скажем, скользящую среднюю? Для оптимизации параметров алгоритма на большой истории я просто скачиваю csv с сайта финама, а как быть с рантаймом?
Или вообще не заморачиваться, и работать с каким-нибудь TSLab? Но хотелось бы с питоном поковыряться, интересно же..

 

avatar
Андрей, ставьте Квик 8.5.2 и выше. С семеркой уже ничего работать не будет, т.к. биржа перешла на 19-ти значные заявки. По вопросу совмещения портов, я постараюсь в ближайшее время написать топик про tcp-сокет, там можно будет через один порт работать с Квиком как на получение данных из Квика так и на отправку приказов в Квик. Аккаунт на фортсе свой это «торговый счёт» и «код клиента» и они одинаковые. А на ММВБ — разные, это нужно учитывать при формировании заявок. Это оно.
Как на питоне скачать в рантайме неглубокие исторические данные, что бы строить, скажем, скользящую среднюю? Для оптимизации параметров алгоритма на большой истории я просто скачиваю csv с сайта финама, а как быть с рантаймом?
В этом топике https://smart-lab.ru/blog/663093.php показано как тащить любые котировки по истории с сайта Финама, функция GetCandles (ticker, time_frame, period_days) берет котировки по тикеру за последние period_days начиная с текущего момента, т.е. с реалтайм (последняя котировка на момент запроса) Там же и показано как строить например ЕМА-шки.
Или вообще не заморачиваться, и работать с каким-нибудь TSLab? Но хотелось бы с питоном поковыряться, интересно же..
Это уже на ваш выбор.
avatar
Подскажите пожалуйста, как сделать с использованием данного скрипта Qlua, чтобы из QLUA автоматически приходили обезличенные сделки в пайтон, не из пайтона вызывать каждую секунду проверку, а всётаки чтобы потоком без пропусков.?
avatar

Читайте на SMART-LAB:
Фото
🧠 Ресейл и поколение Z: почему молодёжь выбирает разумное потребление
📱 Поколение Z относится к потреблению прагматичнее, чем остальные. Для них важны не громкие слова и статус, а понятная ценность покупки —...
5 идей в российских акциях. Индекс МосБиржи снова на грани 2700
Индекс МосБиржи опять торгуется на грани значимого уровня 2700 п. Сейчас не исключен очередной отскок от указанного уровня. Кроме того, рынок...
Фото
Сделки в портфеле ВДО
Если Индекс ОФЗ (RGBI) пробьет вниз 116,69 п., то в портфеле PRObonds ВДО увеличиваем короткую позицию во фьючерсе на него с ~1,5% до 2,5% от...
Фото
Хэдхантер. Ситуация на рынке труда в декабре идет ко дну - хуже не было никогда
Вышла статистика рынка труда за декабрь 2025 года, которую Хедхантер публикует ежемесячно, что же там интересного: Динамика...

теги блога Евгений Шибаев

....все тэги



UPDONW
Новый дизайн