Initial commit

This commit is contained in:
Игорь Чечет
2021-02-14 15:47:51 +05:00
commit 87425dfe9c
16 changed files with 3512 additions and 0 deletions

33
Examples/01 - Connect.py Normal file
View File

@@ -0,0 +1,33 @@
from QuikPy import QuikPy as qp # QuikPy = Работа с Quik из Python через LUA скрипты QuikSharp
def PrintCallback(data):
"""Пользовательский обработчик события"""
print(data) # Печатаем полученные данные
if __name__ == '__main__': # Точка входа при запуске этого скрипта
# qpProvider = qp.QuikPy() # Вызываем конструктор QuikPy с подключением к локальному компьютеру с QUIK
qpProvider = qp.QuikPy(Host='192.168.1.7') # Вызываем конструктор QuikPy с подключением к удаленному компьютеру с QUIK
print(f'Подключено к терминалу QUIK по адресу: {qpProvider.Host}:{qpProvider.RequestsPort},{qpProvider.CallbacksPort}')
# QuikPy - Singleton класс. Будет создан 1 экземпляр класса, на него будут все ссылки
# qpProvider2 = qp.QuikPy()
qpProvider2 = qp.QuikPy(Host='192.168.1.7') # QuikPy - это Singleton класс. При попытке создания нового экземпляра получим ссылку на уже имеющийся экземпляр
print(f'Экземпляры класса совпадают: {qpProvider2 == qpProvider}')
# Проверка соединения
print(f'Терминал QUIK подключен к серверу: {qpProvider.IsConnected()["data"] == 1}')
print(f'Отклик QUIK на команду Ping: {qpProvider.Ping()["data"]}')
# Сервисные функции
print(f'Дата на сервере: {qpProvider.GetInfoParam("TRADEDATE")["data"]}')
print(f'Время на сервере: {qpProvider.GetInfoParam("SERVERTIME")["data"]}')
msg = 'Hello from Python!'
print(f'Отправка сообщения в QUIK: {msg}{qpProvider.MessageInfo(msg)["data"]}')
# Просмотр изменений параметров
qpProvider.OnParam = PrintCallback # Текущие параметры изменяются постоянно. Будем их смотреть, пока не нажмем Enter в консоли
# Выход
input('Enter - выход')
qpProvider.CloseConnectionAndThread() # Перед выходом закрываем соединение и поток QuikPy из любого экземпляра

125
Examples/02 - Accounts.py Normal file
View File

@@ -0,0 +1,125 @@
from QuikPy import QuikPy as qp # QuikPy = Работа с Quik из Python через LUA скрипты QuikSharp
def GetAllAccounts():
"""Получение всех торговых счетов"""
classCodes = qpProvider.GetClassesList()['data'] # Список классов
classCodesList = classCodes[:-1].split(',') # Удаляем последнюю запятую, разбиваем значения по запятой
tradeAccounts = qpProvider.GetTradeAccounts()['data'] # Все торговые счета
moneyLimits = qpProvider.GetMoneyLimits()['data'] # Все денежные лимиты (остатки на счетах)
depoLimits = qpProvider.GetAllDepoLimits()['data'] # Все лимиты по бумагам (позиции по инструментам)
orders = qpProvider.GetAllOrders()['data'] # Все заявки
stopOrders = qpProvider.GetAllStopOrders()['data'] # Все стоп заявки
# Коды клиента / Фирмы / Счета
for tradeAccount in tradeAccounts: # Пробегаемся по всем счетам
firmId = tradeAccount['firmid'] # Фирма
tradeAccountId = tradeAccount['trdaccid'] # Счет
distinctClientCode = list(set([moneyLimit['client_code'] for moneyLimit in moneyLimits if moneyLimit['firmid'] == firmId])) # Уникальные коды клиента по фирме
print(f'Код клиента {distinctClientCode[0] if distinctClientCode else "не задан"}, Фирма {firmId}, Счет {tradeAccountId} ({tradeAccount["description"]})')
tradeAccountClassCodes = tradeAccount['class_codes'][1:-1].split('|') # Классы торгового счета. Удаляем последнюю вертикальную черту, разбиваем значения по вертикальной черте
intersectionClassCodes = list(set(tradeAccountClassCodes).intersection(classCodesList)) # Классы, которые есть и в списке и в торговом счете
# Классы
for classCode in intersectionClassCodes: # Пробегаемся по всем общим классам
classInfo = qpProvider.GetClassInfo(classCode)['data'] # Информация о классе
print(f'- Класс {classCode} ({classInfo["name"]}), Тикеров {classInfo["nsecs"]}')
# Инструменты. Если выводить на экран, то занимают много места. Поэтому, закомментировали
# classSecurities = qpProvider.GetClassSecurities(classCode)['data'][:-1].split(',') # Список инструментов класса. Удаляем последнюю запятую, разбиваем значения по запятой
# print(f' - Тикеры ({classSecurities})')
if firmId == 'SPBFUT': # Для фьючерсов свои расчеты
# Лимиты
print(f'- Фьючерсный лимит {qpProvider.GetFuturesLimit(firmId, tradeAccountId, 0, "SUR")["data"]["cbplimit"]} SUR')
# Позиции
futuresHoldings = qpProvider.GetFuturesHoldings()['data'] # Все фьючерсные позиции
activeFuturesHoldings = [futuresHolding for futuresHolding in futuresHoldings if futuresHolding['totalnet'] != 0] # Активные фьючерсные позиции
for activeFuturesHolding in activeFuturesHoldings:
print(f' - Фьючерсная позиция {activeFuturesHolding["sec_code"]} {activeFuturesHolding["totalnet"]} @ {activeFuturesHolding["cbplused"]}')
else: # Для остальных фирм
# Лимиты
firmMoneyLimits = [moneyLimit for moneyLimit in moneyLimits if moneyLimit['firmid'] == firmId] # Денежные лимиты по фирме
for firmMoneyLimit in firmMoneyLimits: # Пробегаемся по всем денежным лимитам
limitKind = firmMoneyLimit['limit_kind'] # День лимита
print(f'- Денежный лимит {firmMoneyLimit["tag"]} на T{limitKind}: {firmMoneyLimit["currentbal"]} {firmMoneyLimit["currcode"]}')
# Позиции
firmKindDepoLimits = [depoLimit for depoLimit in depoLimits if depoLimit['firmid'] == firmId and depoLimit['limit_kind'] == limitKind and depoLimit['currentbal'] != 0] # Берем только открытые позиции по фирме и дню
for firmKindDepoLimit in firmKindDepoLimits: # Пробегаемся по всем позициям
secCode = firmKindDepoLimit["sec_code"] # Код тикера
classCode = qpProvider.GetSecurityClass(classCodes, secCode)['data']
entryPrice = float(firmKindDepoLimit["wa_position_price"])
lastPrice = float(qpProvider.GetParamEx(classCode, secCode, 'LAST')['data']['param_value']) # Последняя цена сделки
if classCode == 'TQOB': # Для рынка облигаций
lastPrice *= 10 # Умножаем на 10
print(f' - Позиция {classCode}.{secCode} {firmKindDepoLimit["currentbal"]} @ {entryPrice:.2f}/{lastPrice:.2f}')
# Заявки
firmOrders = [order for order in orders if order['firmid'] == firmId and order['flags'] & 0b1 == 0b1] # Активные заявки по фирме
for firmOrder in firmOrders: # Пробегаемся по всем заявка
isBuy = firmOrder['flags'] & 0b100 != 0b100 # Заявка на покупку
print(f'- Заявка номер {firmOrder["order_num"]} {"Покупка" if isBuy else "Продажа"} {firmOrder["class_code"]}.{firmOrder["sec_code"]} {firmOrder["qty"]} @ {firmOrder["price"]}')
# Стоп заявки
firmStopOrders = [stopOrder for stopOrder in stopOrders if stopOrder['firmid'] == firmId and stopOrder['flags'] & 0b1 == 0b1] # Активные стоп заявки по фирме
for firmStopOrder in firmStopOrders: # Пробегаемся по всем стоп заявкам
isBuy = firmStopOrder['flags'] & 0b100 != 0b100 # Заявка на покупку
print(f'- Стоп заявка номер {firmStopOrder["order_num"]} {"Покупка" if isBuy else "Продажа"} {firmStopOrder["class_code"]}.{firmStopOrder["sec_code"]} {firmStopOrder["qty"]} @ {firmStopOrder["price"]}')
def GetAccount(ClientCode='', FirmId='SPBFUT', TradeAccountId='SPBFUT00PST', LimitKind=0, CurrencyCode='SUR'):
"""Получение торгового счета. По умолчанию, выдается счет срочного рынка"""
classCodes = qpProvider.GetClassesList()['data'] # Список классов
moneyLimits = qpProvider.GetMoneyLimits()['data'] # Все денежные лимиты (остатки на счетах)
depoLimits = qpProvider.GetAllDepoLimits()['data'] # Все лимиты по бумагам (позиции по инструментам)
orders = qpProvider.GetAllOrders()['data'] # Все заявки
stopOrders = qpProvider.GetAllStopOrders()['data'] # Все стоп заявки
print(f'Код клиента {ClientCode}, Фирма {FirmId}, Счет {TradeAccountId}, T{LimitKind}, {CurrencyCode}')
if FirmId == 'SPBFUT': # Для фьючерсов свои расчеты
print(f'- Фьючерсный лимит {qpProvider.GetFuturesLimit(FirmId, TradeAccountId, 0, "SUR")["data"]["cbplimit"]} SUR')
futuresHoldings = qpProvider.GetFuturesHoldings()['data'] # Все фьючерсные позиции
activeFuturesHoldings = [futuresHolding for futuresHolding in futuresHoldings if futuresHolding['totalnet'] != 0] # Активные фьючерсные позиции
for activeFuturesHolding in activeFuturesHoldings:
print(f'- Фьючерсная позиция {activeFuturesHolding["sec_code"]} {activeFuturesHolding["totalnet"]} @ {activeFuturesHolding["cbplused"]}')
else: # Для остальных фирм
accountMoneyLimit = [moneyLimit for moneyLimit in moneyLimits # Денежный лимит
if moneyLimit['client_code'] == ClientCode and # Выбираем по коду клиента
moneyLimit['firmid'] == FirmId and # Фирме
moneyLimit['limit_kind'] == LimitKind and # Дню лимита
moneyLimit["currcode"] == CurrencyCode][0] # Валюте
print(f'- Денежный лимит {accountMoneyLimit["currentbal"]}')
accountDepoLimits = [depoLimit for depoLimit in depoLimits # Бумажный лимит
if depoLimit['client_code'] == ClientCode and # Выбираем по коду клиента
depoLimit['firmid'] == FirmId and # Фирме
depoLimit['limit_kind'] == LimitKind and # Дню лимита
depoLimit['currentbal'] != 0] # Берем только открытые позиции по фирме и дню
for firmKindDepoLimit in accountDepoLimits: # Пробегаемся по всем позициям
secCode = firmKindDepoLimit["sec_code"] # Код тикера
entryPrice = float(firmKindDepoLimit["wa_position_price"])
classCode = qpProvider.GetSecurityClass(classCodes, secCode)['data']
lastPrice = float(qpProvider.GetParamEx(classCode, secCode, 'LAST')['data']['param_value']) # Последняя цена сделки
if classCode == 'TQOB': # Для рынка облигаций
lastPrice *= 10 # Умножаем на 10
print(f'- Позиция {classCode}.{secCode} {firmKindDepoLimit["currentbal"]} @ {entryPrice:.2f}/{lastPrice:.2f}')
accountOrders = [order for order in orders # Заявки
if (order['client_code'] == ClientCode or ClientCode == '') and # Выбираем по коду клиента
order['firmid'] == FirmId and # Фирме
order['account'] == TradeAccountId and # Счету
order['flags'] & 0b1 == 0b1] # Активные заявки
for accountOrder in accountOrders: # Пробегаемся по всем заявка
isBuy = accountOrder['flags'] & 0b100 != 0b100 # Заявка на покупку
print(f'- Заявка номер {accountOrder["order_num"]} {"Покупка" if isBuy else "Продажа"} {accountOrder["class_code"]}.{accountOrder["sec_code"]} {accountOrder["qty"]} @ {accountOrder["price"]}')
accountStopOrders = [stopOrder for stopOrder in stopOrders # Стоп заявки
if (stopOrder['client_code'] == ClientCode or ClientCode == '') and # Выбираем по коду клиента
stopOrder['firmid'] == FirmId and # Фирме
stopOrder['account'] == TradeAccountId and # Счету
stopOrder['flags'] & 0b1 == 0b1] # Активные стоп заявки
for accountStopOrder in accountStopOrders: # Пробегаемся по всем стоп заявкам
isBuy = accountStopOrder['flags'] & 0b100 != 0b100 # Заявка на покупку
print(f'- Стоп заявка номер {accountStopOrder["order_num"]} {"Покупка" if isBuy else "Продажа"} {accountStopOrder["class_code"]}.{accountStopOrder["sec_code"]} {accountStopOrder["qty"]} @ {accountStopOrder["price"]}')
if __name__ == '__main__': # Точка входа при запуске этого скрипта
# qpProvider = qp.QuikPy() # Вызываем конструктор QuikPy с подключением к локальному компьютеру с QUIK
qpProvider = qp.QuikPy(Host='192.168.1.7') # Вызываем конструктор QuikPy с подключением к удаленному компьютеру с QUIK
GetAllAccounts() # Получаем все счета. По ним можно будет сформировать список счетов для торговли
print()
GetAccount() # Российские фьючерсы и опционы (счет по умолчанию)
# Выход
qpProvider.CloseConnectionAndThread() # Перед выходом закрываем соединение и поток QuikPy из любого экземпляра

38
Examples/03 - Ticker.py Normal file
View File

@@ -0,0 +1,38 @@
from datetime import datetime
from QuikPy import QuikPy as qp # QuikPy = Работа с Quik из Python через LUA скрипты QuikSharp
if __name__ == '__main__': # Точка входа при запуске этого скрипта
# qpProvider = qp.QuikPy() # Вызываем конструктор QuikPy с подключением к локальному компьютеру с QUIK
qpProvider = qp.QuikPy(Host='192.168.1.7') # Вызываем конструктор QuikPy с подключением к удаленному компьютеру с QUIK
firmId = 'MC0063100000' # Фирма
classCode = 'TQBR' # Класс тикера
secCode = 'GAZP' # Тикер
# firmId = 'SPBFUT' # Фирма
# classCode = 'SPBFUT' # Класс тикера
# secCode = 'SiH1' # Для фьючерсов: <Код тикера><Месяц экспирации: 3-H, 6-M, 9-U, 12-Z><Последняя цифра года>
# Данные тикера и его торговый счет
securityInfo = qpProvider.GetSecurityInfo(classCode, secCode)["data"]
print(f'Информация о тикере {classCode}.{secCode} ({securityInfo["short_name"]}):')
print(f'Валюта: {securityInfo["face_unit"]}')
print(f'Кол-во десятичных знаков: {securityInfo["scale"]}')
print(f'Лот: {securityInfo["lot_size"]}')
print(f'Шаг цены: {securityInfo["min_price_step"]}')
print(f'Торговый счет для тикера класса {classCode}: {qpProvider.GetTradeAccount(classCode)["data"]}')
# Свечки
print(f'5-и минутные свечки {classCode}.{secCode}:')
bars = qpProvider.GetCandlesFromDataSource(classCode, secCode, 5, 0)["data"] # 5 минут, 0 = все свечки
print(bars)
# print(f'Дневные свечки {classCode}.{secCode}:')
# bars = qpProvider.GetCandlesFromDataSource(classCode, secCode, 1440, 0)['data'] # 1440 минут = 1 день, 0 = все свечки
# dtjs = [row['datetime'] for row in bars] # Получаем исходники даты и времени начала свчки (List comprehensions)
# dts = [datetime(dtj['year'], dtj['month'], dtj['day'], dtj['hour'], dtj['min']) for dtj in dtjs] # Получаем дату и время
# print(dts)
# Выход
qpProvider.CloseConnectionAndThread() # Перед выходом закрываем соединение и поток QuikPy из любого экземпляра

51
Examples/04 - Stream.py Normal file
View File

@@ -0,0 +1,51 @@
import time # Подписка на события по времени
from QuikPy import QuikPy as qp # QuikPy = Работа с Quik из Python через LUA скрипты QuikSharp
def PrintCallback(data):
"""Пользовательский обработчик событий:
- Изменение стакана котировок
- Получение обезличенной сделки
- Получение новой свечки
"""
print(data['data']) # Печатаем полученные данные
if __name__ == '__main__': # Точка входа при запуске этого скрипта
# qpProvider = qp.QuikPy() # Вызываем конструктор QuikPy с подключением к локальному компьютеру с QUIK
qpProvider = qp.QuikPy(Host='192.168.1.7') # Вызываем конструктор QuikPy с подключением к удаленному компьютеру с QUIK
firmId = 'MC0063100000' # Фирма
classCode = 'TQBR' # Класс тикера
secCode = 'GAZP' # Тикер
# firmId = 'SPBFUT' # Фирма
# classCode = 'SPBFUT' # Класс тикера
# secCode = 'SiH1' # Для фьючерсов: <Код тикера><Месяц экспирации: 3-H, 6-M, 9-U, 12-Z><Последняя цифра года>
# Стакан
print(f'Текущий стакан {classCode}.{secCode}: {qpProvider.GetQuoteLevel2(classCode, secCode)}')
qpProvider.OnQuote = PrintCallback # Обработчик изменения стакана котировок
print(f'Подписка на стакан {classCode}.{secCode}: {qpProvider.SubscribeLevel2Quotes(classCode, secCode)["data"]}')
sleepSec = 1 # Кол-во секунд получения котировок
print(f'{sleepSec} секунд котировок')
time.sleep(sleepSec) # Ждем кол-во секунд получения котировок
print(f'Отмена подписки на стакан: {qpProvider.UnsubscribeLevel2Quotes(classCode, secCode)["data"]}')
print(f'Статус подписки: {qpProvider.IsSubscribedLevel2Quotes(classCode, secCode)["data"]}')
qpProvider.OnQuote = qpProvider.DefaultHandler # Возвращаем обработчик по умолчанию
# Обезличенные сделки. Чтобы получать, в QUIK открыть Таблицу обезличенных сделок, указать тикер
qpProvider.OnAllTrade = PrintCallback # Обработчик получения обезличенной сделки
sleepSec = 3 # Кол-во секунд получения обезличенных сделок
print(f'{sleepSec} секунд обезличенных сделок')
time.sleep(sleepSec) # Ждем кол-во секунд получения обезличенных сделок
qpProvider.OnAllTrade = qpProvider.DefaultHandler # Возвращаем обработчик по умолчанию
# Подписка на новые свечки
qpProvider.OnNewCandle = PrintCallback # Обработчик получения новой свечки - В первый раз получим все свечки с начала прошлой сессии
print(f'Подписка на минутные свечки {qpProvider.SubscribeToCandles(classCode, secCode, 1)["data"]}')
input('Enter - отмена')
print(f'Отмена подписки на минутные свечки {qpProvider.UnsubscribeFromCandles(classCode, secCode, 1)["data"]}')
qpProvider.OnNewCandle = qpProvider.DefaultHandler # Возвращаем обработчик по умолчанию
# Выход
qpProvider.CloseConnectionAndThread() # Перед выходом закрываем соединение и поток QuikPy из любого экземпляра

View File

@@ -0,0 +1,90 @@
from QuikPy import QuikPy as qp # QuikPy = Работа с Quik из Python через LUA скрипты QuikSharp
def OnTransReply(data):
"""Обработчик события ответа на транзакцию пользователя"""
print('OnTransReply')
print(data['data']) # Печатаем полученные данные
def OnOrder(data):
"""Обработчик события получения новой / изменения существующей заявки"""
print('OnOrder')
print(data['data']) # Печатаем полученные данные
def OnTrade(data):
"""Обработчик события получения новой / изменения существующей сделки
Не вызывается при закрытии сделки
"""
print('OnTrade')
print(data['data']) # Печатаем полученные данные
def OnFuturesClientHolding(data):
"""Обработчик события изменения позиции по срочному рынку"""
print('OnFuturesClientHolding')
print(data['data']) # Печатаем полученные данные
def OnDepoLimit(data):
"""Обработчик события изменения позиции по инструментам"""
print('OnDepoLimit')
print(data['data']) # Печатаем полученные данные
def OnDepoLimitDelete(data):
"""Обработчик события удаления позиции по инструментам"""
print('OnDepoLimitDelete')
print(data['data']) # Печатаем полученные данные
if __name__ == '__main__': # Точка входа при запуске этого скрипта
# qpProvider = qp.QuikPy() # Вызываем конструктор QuikPy с подключением к локальному компьютеру с QUIK
qpProvider = qp.QuikPy(Host='192.168.1.7') # Вызываем конструктор QuikPy с подключением к удаленному компьютеру с QUIK
qpProvider.OnTransReply = OnTransReply # Ответ на транзакцию пользователя. Если транзакция выполняется из QUIK, то не вызывается
qpProvider.OnOrder = OnOrder # Получение новой / изменение существующей заявки
qpProvider.OnTrade = OnTrade # Получение новой / изменение существующей сделки
qpProvider.OnFuturesClientHolding = OnFuturesClientHolding # Изменение позиции по срочному рынку
qpProvider.OnDepoLimit = OnDepoLimit # Изменение позиции по инструментам
qpProvider.OnDepoLimitDelete = OnDepoLimitDelete # Удаление позиции по инструментам
TransId = 12345 # Номер транзакции
price = 74550 # Цена входа/выхода
quantity = 1 # Кол-во в лотах
# Новая лимитная/рыночная заявка
transaction = { # Все значения должны передаваться в виде строк
'TRANS_ID': str(TransId), # Номер транзакции задается клиентом
'CLIENT_CODE': '', # Код клиента. Для фьючерсов его нет
'ACCOUNT': 'SPBFUT00PST', # Счет
'ACTION': 'NEW_ORDER', # Тип заявки: Новая лимитная/рыночная заявка
'CLASSCODE': 'SPBFUT', # Код площадки
'SECCODE': 'SiH1', # Код тикера
'OPERATION': 'S', # B = покупка, S = продажа
'PRICE': str(price), # Цена исполнения. Для рыночных фьючерсных заявок наихудшая цена в зависимости от направления. Для остальных рыночных заявок цена = 0
'QUANTITY': str(quantity), # Кол-во в лотах
'TYPE': 'L'} # L = лимитная заявка (по умолчанию), M = рыночная заявка
print(f'Новая лимитная/рыночная заявка отправлена на рынок: {qpProvider.SendTransaction(transaction)["data"]}')
# Новая стоп заявка
# transaction = { # Все значения должны передаваться в виде строк
# 'TRANS_ID': str(TransId), # Номер транзакции задается клиентом
# 'CLIENT_CODE': '', # Код клиента. Для фьючерсов его нет
# 'ACCOUNT': 'SPBFUT00PST', # Счет
# 'ACTION': 'NEW_STOP_ORDER', # Тип заявки: Новая стоп заявка
# 'CLASSCODE': 'SPBFUT', # Код площадки
# 'SECCODE': 'SiH1', # Код тикера
# 'OPERATION': 'B', # B = покупка, S = продажа
# 'PRICE': str(price), # Цена исполнения
# 'QUANTITY': str(quantity), # Кол-во в лотах
# 'STOPPRICE': str(price), # Стоп цена исполнения
# 'EXPIRY_DATE': 'GTC'} # Срок действия до отмены
# print(f'Новая стоп заявка отправлена на рынок: {qpProvider.SendTransaction(transaction)["data"]}')
# Удаление существующей заявки
# orderNum = 1234567890123456789 # 19-и значный номер заявки
# transaction = {
# 'TRANS_ID': str(TransId), # Номер транзакции задается клиентом
# 'ACTION': 'KILL_ORDER', # Тип заявки: Удаление существующей заявки
# 'CLASSCODE': 'SPBFUT', # Код площадки
# 'SECCODE': 'SiH1', # Код тикера
# 'ORDER_KEY': str(orderNum)} # Номер заявки
# print(f'Удаление заявки отправлено на рынок: {qpProvider.SendTransaction(transaction)["data"]}')
input('Enter - отмена') # Ждем исполнение заявки
qpProvider.CloseConnectionAndThread() # Перед выходом закрываем соединение и поток QuikPy из любого экземпляра

140
QUIK/lua/QuikSharp.lua Normal file
View File

@@ -0,0 +1,140 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
-- is running from Quik
function is_quik()
if getScriptPath then return true else return false end
end
quikVersion = nil
script_path = "."
if is_quik() then
script_path = getScriptPath()
quikVersion = getInfoParam("VERSION")
if quikVersion ~= nil then
local t={}
for str in string.gmatch(quikVersion, "([^%.]+)") do
table.insert(t, str)
end
quikVersion = tonumber(t[1]) * 100 + tonumber(t[2])
end
if quikVersion == nil then
message("QUIK# cannot detect QUIK version", 3)
return
else
libPath = "\\clibs"
end
-- MD dynamic, requires MSVCRT
-- MT static, MSVCRT is linked statically with luasocket
-- package.cpath contains info.exe working directory, which has MSVCRT, so MT should not be needed in theory,
-- but in one issue someone said it doesn't work on machines that do not have Visual Studio.
local linkage = "MD"
if quikVersion >= 805 then
libPath = libPath .. "64\\53_"..linkage.."\\"
elseif quikVersion >= 800 then
libPath = libPath .. "64\\5.1_"..linkage.."\\"
else
libPath = "\\clibs\\5.1_"..linkage.."\\"
end
end
package.path = package.path .. ";" .. script_path .. "\\?.lua;" .. script_path .. "\\?.luac"..";"..".\\?.lua;"..".\\?.luac"
package.cpath = package.cpath .. ";" .. script_path .. libPath .. '?.dll'..";".. '.' .. libPath .. '?.dll'
local util = require("qsutils")
local qf = require("qsfunctions")
require("qscallbacks")
log("Detected Quik version: ".. quikVersion .." and using cpath: "..package.cpath , 0)
local is_started = true
-- we need two ports since callbacks and responses conflict and write to the same socket at the same time
-- I do not know how to make locking in Lua, it is just simpler to have two independent connections
-- To connect to a remote terminal - replace '127.0.0.1' with the terminal ip-address
-- All this values could be replaced with values from config.json
local response_host = '127.0.0.1'
local response_port = 34130
local callback_host = '127.0.0.1'
local callback_port = response_port + 1
function do_main()
log("Entered main function", 0)
while is_started do
-- if not connected, connect
util.connect(response_host, response_port, callback_host, callback_port)
-- when connected, process queue
-- receive message,
local requestMsg = receiveRequest()
if requestMsg then
-- if ok, process message
-- dispatch_and_process never throws, it returns lua errors wrapped as a message
local responseMsg, err = qf.dispatch_and_process(requestMsg)
if responseMsg then
-- send message
local res = sendResponse(responseMsg)
else
log("Could not dispatch and process request: " .. err, 3)
end
else
delay(1)
end
end
end
function main()
setup("QuikSharp")
run()
end
--- catch errors
function run()
local status, err = pcall(do_main)
if status then
log("finished")
else
log(err, 3)
end
end
function setup(script_name)
if not script_name then
log("File name of this script is unknown. Please, set it explicity instead of scriptFilename() call inside your custom file", 3)
return false
end
local list = paramsFromConfig(script_name)
if list then
response_host = list[1]
response_port = list[2]
callback_host = list[3]
callback_port = list[4]
printRunningMessage(script_name)
elseif script_name == "QuikSharp" then
-- use default values for this file in case no custom config found for it
printRunningMessage(script_name)
else -- do nothing when config is not found
log("File config.json is not found or contains no entries for this script name: " .. script_name, 3)
return false
end
return true
end
function printRunningMessage(script_name)
log("Running from ".. script_name .. ", params: response " .. response_host .. ":" .. response_port ..", callback ".. " ".. callback_host ..":".. callback_port)
end
if not is_quik() then
log("Hello, QUIK#! Running outside Quik.")
setup("QuikSharp")
do_main()
logfile:close()
end

15
QUIK/lua/Quik_2.lua Normal file
View File

@@ -0,0 +1,15 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
script_path = getScriptPath()
package.path = package.path .. ";" .. script_path .. "\\?.lua;" .. script_path .. "\\?.luac"..";"..".\\?.lua;"..".\\?.luac"
require("QuikSharp")
-- Do not edit this file. Just copy it and save with a different name. Then write required params for it inside config.json file
-- Не редактируйте этой файл. Просто скопируйте и сохраните под другим именем. После этого укажите настройки для него в файле config.json
function main()
if setup(scriptFilename()) then
run()
end
end

18
QUIK/lua/config.json Normal file
View File

@@ -0,0 +1,18 @@
{
"servers": [
{
"scriptName": "QuikSharp",
"responseHostname": "*",
"responsePort": 34130,
"callbackHostname": "*",
"callbackPort": 34131
},
{
"scriptName": "Quik_2",
"responseHostname": "*",
"responsePort": 34132,
"callbackHostname": "*",
"callbackPort": 34133
}
]
}

714
QUIK/lua/dkjson.lua Normal file
View File

@@ -0,0 +1,714 @@
-- Module options:
local always_try_using_lpeg = true
local register_global_module_table = false
local global_module_name = 'json'
--[==[
David Kolf's JSON module for Lua 5.1/5.2
Version 2.5
For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/src/dkjson-lua.fsl/>.
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
Copyright (C) 2010-2013 David Heiko Kolf
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.5" }
if register_global_module_table then
_G[global_module_name] = json
end
local _ENV = nil -- blocking globals in Lua 5.2
pcall (function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)
json.null = setmetatable ({}, {
__tojson = function () return "null" end
})
local function isarray (tbl)
local max, n, arraylen = 0, 0, 0
for k,v in pairs (tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
}
local function escapeutf8 (uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local function fsub (str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind (str, pattern) then
return gsub (str, pattern, repl)
else
return str
end
end
local function quotestring (value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind (value, "[\194\216\220\225\226\239]") then
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
value = fsub (value, "\216[\128-\132]", escapeutf8)
value = fsub (value, "\220\143", escapeutf8)
value = fsub (value, "\225\158[\180\181]", escapeutf8)
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
value = fsub (value, "\239\187\191", escapeutf8)
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring
local function replace(str, o, n)
local i, j = strfind (str, o, 1, true)
if i then
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
else
return str
end
end
-- locale independent num2str and str2num functions
local decpoint, numfilter
local function updatedecpoint ()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end
updatedecpoint()
local function num2str (num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end
local function str2num (str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end
local function addnewline2 (level, buffer, buflen)
buffer[buflen+1] = "\n"
buffer[buflen+2] = strrep (" ", level)
buflen = buflen + 2
return buflen
end
function json.addnewline (state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end
local encode2 -- forward declaration
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2 (level, buffer, buflen)
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler (reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson (value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str (value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray (value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2 (level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return buflen
end
function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error (msg, 2)
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat (buffer)
end
end
local function loc (str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind (str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
return "line " .. line .. ", column " .. (where - linepos)
end
local function unterminated (str, what, where)
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
end
local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
local sub2 = strsub (str, pos, pos + 1)
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind (str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind (str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
end
end
local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
}
local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
if not nextpos then
return unterminated (str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub (str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub (str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar (value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat (buffer), lastpos
else
return "", lastpos
end
end
local scanvalue -- forward declaration
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local len = strlen (str)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable (tbl, objectmeta)
else
setmetatable (tbl, arraymeta)
end
while true do
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
local char = strsub (str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
end
pos = scanwhite (str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
local val2
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite (str, pos)
if not pos then
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub (str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring (str, pos)
else
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num (strsub (str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind (str, "^%a%w*", pos)
if pstart then
local name = strsub (str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc (str, pos)
end
end
local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
end
end
function json.decode (str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
end
function json.use_lpeg ()
local g = require ("lpeg")
if g.version() == "0.11" then
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
end
local pegmatch = g.match
local P, S, R = g.P, g.S, g.R
local function ErrorCall (str, pos, msg, state)
if not state.msg then
state.msg = msg .. " at " .. loc (str, pos)
state.pos = pos
end
return false
end
local function Err (msg)
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
end
local SingleLineComment = P"//" * (1 - S"\n\r")^0
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
local HexDigit = R("09", "af", "AF")
local function UTF16Surrogate (match, pos, high, low)
high, low = tonumber (high, 16), tonumber (low, 16)
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
else
return false
end
end
local function UTF16BMP (hex)
return unichar (tonumber (hex, 16))
end
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
local Char = UnicodeEscape + EscapeSequence + PlainChar
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
local Fractal = P"." * R"09"^0
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
local SimpleValue = Number + String + Constant
local ArrayContent, ObjectContent
-- The functions parsearray and parseobject parse only a single value/pair
-- at a time and store them directly to avoid hitting the LPeg limits.
local function parsearray (str, pos, nullval, state)
local obj, cont
local npos
local t, nt = {}, 0
repeat
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
if not npos then break end
pos = npos
nt = nt + 1
t[nt] = obj
until cont == 'last'
return pos, setmetatable (t, state.arraymeta)
end
local function parseobject (str, pos, nullval, state)
local obj, key, cont
local npos
local t = {}
repeat
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
if not npos then break end
pos = npos
t[key] = obj
until cont == 'last'
return pos, setmetatable (t, state.objectmeta)
end
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
local Value = Space * (Array + Object + SimpleValue)
local ExpectedValue = Value + Space * Err "value expected"
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
local DecodeValue = ExpectedValue * g.Cp ()
function json.decode (str, pos, nullval, ...)
local state = {}
state.objectmeta, state.arraymeta = optionalmetatables(...)
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
if state.msg then
return nil, state.pos, state.msg
else
return obj, retpos
end
end
-- use this function only once:
json.use_lpeg = function () return json end
json.using_lpeg = true
return json -- so you can get the module using json = require "dkjson".use_lpeg()
end
if always_try_using_lpeg then
pcall (json.use_lpeg)
end
return json

235
QUIK/lua/qscallbacks.lua Normal file
View File

@@ -0,0 +1,235 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
package.path = package.path..";"..".\\?.lua;"..".\\?.luac"
local qscallbacks = {}
local function CleanUp()
closeLog()
end
function OnQuikSharpDisconnected()
-- TODO any recovery or risk management logic here
end
function OnError(message)
if is_connected then
local msg = {}
msg.t = timemsec()
msg.cmd = "lua_error"
msg.data = "Lua error: " .. message
sendCallback(msg)
end
end
function OnDisconnected()
local msg = {}
msg.cmd = "OnDisconnected"
msg.t = timemsec()
msg.data = ""
sendCallback(msg)
end
function OnConnected()
local msg = {}
msg.cmd = "OnConnected"
msg.t = timemsec()
msg.data = ""
sendCallback(msg)
end
function OnAllTrade(alltrade)
if is_connected then
local msg = {}
msg.t = timemsec()
msg.cmd = "OnAllTrade"
msg.data = alltrade
sendCallback(msg)
end
end
function OnClose()
if is_connected then
local msg = {}
msg.cmd = "OnClose"
msg.t = timemsec()
msg.data = ""
sendCallback(msg)
end
CleanUp()
end
function OnInit(script_path)
if is_connected then
local msg = {}
msg.cmd = "OnInit"
msg.t = timemsec()
msg.data = script_path
sendCallback(msg)
end
log("QUIK# is initialized from "..script_path, 0)
end
function OnOrder(order)
local msg = {}
msg.t = timemsec()
msg.id = nil
msg.data = order
msg.cmd = "OnOrder"
sendCallback(msg)
end
function OnQuote(class_code, sec_code)
if is_connected then
local msg = {}
msg.cmd = "OnQuote"
msg.t = timemsec()
local server_time = getInfoParam("SERVERTIME")
local status, ql2 = pcall(getQuoteLevel2, class_code, sec_code)
if status then
msg.data = ql2
msg.data.class_code = class_code
msg.data.sec_code = sec_code
msg.data.server_time = server_time
sendCallback(msg)
else
OnError(ql2)
end
end
end
function OnStop(s)
is_started = false
if is_connected then
local msg = {}
msg.cmd = "OnStop"
msg.t = timemsec()
msg.data = s
sendCallback(msg)
end
log("QUIK# stopped. You could keep script running when closing QUIK and the script will start automatically the next time you start QUIK", 1)
CleanUp()
-- send disconnect
return 1000
end
function OnTrade(trade)
local msg = {}
msg.t = timemsec()
msg.id = nil
msg.data = trade
msg.cmd = "OnTrade"
sendCallback(msg)
end
function OnTransReply(trans_reply)
local msg = {}
msg.t = timemsec()
msg.id = nil
msg.data = trans_reply
msg.cmd = "OnTransReply"
sendCallback(msg)
end
function OnStopOrder(stop_order)
local msg = {}
msg.t = timemsec()
msg.data = stop_order
msg.cmd = "OnStopOrder"
sendCallback(msg)
end
function OnParam(class_code, sec_code)
local msg = {}
msg.cmd = "OnParam"
msg.t = timemsec()
local dat = {}
dat.class_code = class_code
dat.sec_code = sec_code
msg.data = dat
sendCallback(msg)
end
function OnAccountBalance(acc_bal)
local msg = {}
msg.t = timemsec()
msg.data = acc_bal
msg.cmd = "OnAccountBalance"
sendCallback(msg)
end
function OnAccountPosition(acc_pos)
local msg = {}
msg.t = timemsec()
msg.data = acc_pos
msg.cmd = "OnAccountPosition"
sendCallback(msg)
end
function OnDepoLimit(dlimit)
local msg = {}
msg.t = timemsec()
msg.data = dlimit
msg.cmd = "OnDepoLimit"
sendCallback(msg)
end
function OnDepoLimitDelete(dlimit_del)
local msg = {}
msg.t = timemsec()
msg.data = dlimit_del
msg.cmd = "OnDepoLimitDelete"
sendCallback(msg)
end
function OnFirm(firm)
local msg = {}
msg.t = timemsec()
msg.data = firm
msg.cmd = "OnFirm"
sendCallback(msg)
end
function OnFuturesClientHolding(fut_pos)
local msg = {}
msg.t = timemsec()
msg.data = fut_pos
msg.cmd = "OnFuturesClientHolding"
sendCallback(msg)
end
function OnFuturesLimitChange(fut_limit)
local msg = {}
msg.t = timemsec()
msg.data = fut_limit
msg.cmd = "OnFuturesLimitChange"
sendCallback(msg)
end
function OnFuturesLimitDelete(lim_del)
local msg = {}
msg.t = timemsec()
msg.data = lim_del
msg.cmd = "OnFuturesLimitDelete"
sendCallback(msg)
end
function OnMoneyLimit(mlimit)
local msg = {}
msg.t = timemsec()
msg.data = mlimit
msg.cmd = "OnMoneyLimit"
sendCallback(msg)
end
function OnMoneyLimitDelete(mlimit_del)
local msg = {}
msg.t = timemsec()
msg.data = mlimit_del
msg.cmd = "OnMoneyLimitDelete"
sendCallback(msg)
end
return qscallbacks

940
QUIK/lua/qsfunctions.lua Normal file
View File

@@ -0,0 +1,940 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
local json = require ("dkjson")
local qsfunctions = {}
function qsfunctions.dispatch_and_process(msg)
if qsfunctions[msg.cmd] then
-- dispatch a command simply by a table lookup
-- in qsfunctions method names must match commands
local status, result = pcall(qsfunctions[msg.cmd], msg)
if status then
return result
else
msg.cmd = "lua_error"
msg.lua_error = "Lua error: " .. result
return msg
end
else
log(to_json(msg), 3)
msg.lua_error = "Command not implemented in Lua qsfunctions module: " .. msg.cmd
msg.cmd = "lua_error"
return msg
end
end
---------------------
-- Debug functions --
---------------------
--- Returns Pong to Ping
-- @param msg message table
-- @return same msg table
function qsfunctions.ping(msg)
-- need to know data structure the caller gives
msg.t = 0 -- avoid time generation. Could also leave original
if msg.data == "Ping" then
msg.data = "Pong"
return msg
else
msg.data = msg.data .. " is not Ping"
return msg
end
end
--- Echoes its message
function qsfunctions.echo(msg)
return msg
end
--- Test error handling
function qsfunctions.divide_string_by_zero(msg)
--noinspection LuaDivideByZero
msg.data = "asd" / 0
return msg
end
--- Is running inside quik
function qsfunctions.is_quik(msg)
if getScriptPath then msg.data = 1 else msg.data = 0 end
return msg
end
-----------------------
-- Service functions --
-----------------------
--- Функция предназначена для определения состояния подключения клиентского места к
-- серверу. Возвращает «1», если клиентское место подключено и «0», если не подключено.
function qsfunctions.isConnected(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = isConnected()
return msg
end
--- Функция возвращает путь, по которому находится файл info.exe, исполняющий данный
-- скрипт, без завершающего обратного слэша («\»). Например, C:\QuikFront.
function qsfunctions.getWorkingFolder(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = getWorkingFolder()
return msg
end
--- Функция возвращает путь, по которому находится запускаемый скрипт, без завершающего
-- обратного слэша («\»). Например, C:\QuikFront\Scripts.
function qsfunctions.getScriptPath(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = getScriptPath()
return msg
end
--- Функция возвращает значения параметров информационного окна (пункт меню
-- Связь / Информационное окно…).
function qsfunctions.getInfoParam(msg)
-- set time when function was called
msg.t = timemsec()
msg.data = getInfoParam(msg.data)
return msg
end
--- Функция отображает сообщения в терминале QUIK.
function qsfunctions.message(msg)
log(msg.data, 1)
msg.data = ""
return msg
end
function qsfunctions.warning_message(msg)
log(msg.data, 2)
msg.data = ""
return msg
end
function qsfunctions.error_message(msg)
log(msg.data, 3)
msg.data = ""
return msg
end
--- Функция приостанавливает выполнение скрипта.
function qsfunctions.sleep(msg)
delay(msg.data)
msg.data = ""
return msg
end
--- Функция для вывода отладочной информации.
function qsfunctions.PrintDbgStr(msg)
log(msg.data, 0)
msg.data = ""
return msg
end
-- Выводит на график метку
function qsfunctions.addLabel(msg)
local spl = split(msg.data, "|")
local price, curdate, curtime, qty, path, id, algmnt, bgnd = spl[1], spl[2], spl[3], spl[4], spl[5], spl[6], spl[7], spl[8]
local label = {
TEXT = "",
IMAGE_PATH = path,
ALIGNMENT = algmnt,
YVALUE = tostring(price),
DATE = tostring(curdate),
TIME = tostring(curtime),
R = 255,
G = 255,
B = 255,
TRANSPARENCY = 0,
TRANSPARENT_BACKGROUND = bgnd,
FONT_FACE_NAME = "Arial",
FONT_HEIGHT = "15",
HINT = " " .. tostring(price) .. " " .. tostring(qty)
}
local res = AddLabel(id, label)
msg.data = res
return msg
end
-- Удаляем выбранную метку
function qsfunctions.delLabel(msg)
local spl = split(msg.data, "|")
local tag, id = spl[1], spl[2]
DelLabel(tag, tonumber(id))
msg.data = ""
return msg
end
-- Удаляем все метки с графика
function qsfunctions.delAllLabels(msg)
local spl = split(msg.data, "|")
local id = spl[1]
DelAllLabels(id)
msg.data = ""
return msg
end
---------------------
-- Class functions --
---------------------
--- Функция предназначена для получения списка кодов классов, переданных с сервера в ходе сеанса связи.
function qsfunctions.getClassesList(msg)
msg.data = getClassesList()
-- if msg.data then log(msg.data) else log("getClassesList returned nil") end
return msg
end
--- Функция предназначена для получения информации о классе.
function qsfunctions.getClassInfo(msg)
msg.data = getClassInfo(msg.data)
-- if msg.data then log(msg.data.name) else log("getClassInfo returned nil") end
return msg
end
--- Функция предназначена для получения списка кодов бумаг для списка классов, заданного списком кодов.
function qsfunctions.getClassSecurities(msg)
msg.data = getClassSecurities(msg.data)
-- if msg.data then log(msg.data) else log("getClassSecurities returned nil") end
return msg
end
--- Функция получает информацию по указанному классу и бумаге.
function qsfunctions.getSecurityInfo(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = getSecurityInfo(class_code, sec_code)
return msg
end
--- Функция берет на вход список из элементов в формате class_code|sec_code и возвращает список ответов функции getSecurityInfo.
-- Если какая-то из бумаг не будет найдена, вместо ее значения придет null
function qsfunctions.getSecurityInfoBulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code = spl[1], spl[2]
local status, security = pcall(getSecurityInfo, class_code, sec_code)
if status and security then
table.insert(result, security)
else
if not status then
log("Error happened while calling getSecurityInfoBulk with ".. class_code .. "|".. sec_code .. ": ".. security)
end
table.insert(result, json.null)
end
end
msg.data = result
return msg
end
--- Функция предназначена для определения класса по коду инструмента из заданного списка классов.
function qsfunctions.getSecurityClass(msg)
local spl = split(msg.data, "|")
local classes_list, sec_code = spl[1], spl[2]
for class_code in string.gmatch(classes_list,"([^,]+)") do
if getSecurityInfo(class_code,sec_code) then
msg.data = class_code
return msg
end
end
msg.data = ""
return msg
end
--- Функция возвращает код клиента
function qsfunctions.getClientCode(msg)
for i=0,getNumberOf("MONEY_LIMITS")-1 do
local clientcode = getItem("MONEY_LIMITS",i).client_code
if clientcode ~= nil then
msg.data = clientcode
return msg
end
end
return msg
end
--- Функция возвращает все коды клиента
function qsfunctions.getClientCodes(msg)
local client_codes = {}
for i=0,getNumberOf("MONEY_LIMITS")-1 do
local clientcode = getItem("MONEY_LIMITS",i).client_code
if clientcode ~= nil then
table.insert(client_codes, clientcode)
end
end
msg.data = client_codes
return msg
end
--- Функция возвращает торговый счет для запрашиваемого кода класса
function qsfunctions.getTradeAccount(msg)
for i=0,getNumberOf("trade_accounts")-1 do
local trade_account = getItem("trade_accounts",i)
if string.find(trade_account.class_codes,'|' .. msg.data .. '|',1,1) then
msg.data = trade_account.trdaccid
return msg
end
end
return msg
end
--- Функция возвращает торговые счета в системе, у которых указаны поддерживаемые классы инструментов.
function qsfunctions.getTradeAccounts(msg)
local trade_accounts = {}
for i=0,getNumberOf("trade_accounts")-1 do
local trade_account = getItem("trade_accounts",i)
if trade_account.class_codes ~= "" then
table.insert(trade_accounts, trade_account)
end
end
msg.data = trade_accounts
return msg
end
---------------------------------------------------------------------
-- Order Book functions (Функции для работы со стаканом котировок) --
---------------------------------------------------------------------
--- Функция заказывает на сервер получение стакана по указанному классу и бумаге.
function qsfunctions.Subscribe_Level_II_Quotes(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = Subscribe_Level_II_Quotes(class_code, sec_code)
return msg
end
--- Функция отменяет заказ на получение с сервера стакана по указанному классу и бумаге.
function qsfunctions.Unsubscribe_Level_II_Quotes(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = Unsubscribe_Level_II_Quotes(class_code, sec_code)
return msg
end
--- Функция позволяет узнать, заказан ли с сервера стакан по указанному классу и бумаге.
function qsfunctions.IsSubscribed_Level_II_Quotes(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
msg.data = IsSubscribed_Level_II_Quotes(class_code, sec_code)
return msg
end
--- Функция предназначена для получения стакана по указанному классу и инструменту.
function qsfunctions.GetQuoteLevel2(msg)
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
local server_time = getInfoParam("SERVERTIME")
local status, ql2 = pcall(getQuoteLevel2, class_code, sec_code)
if status then
msg.data = ql2
msg.data.class_code = class_code
msg.data.sec_code = sec_code
msg.data.server_time = server_time
else
OnError(ql2)
end
return msg
end
-----------------------
-- Trading functions --
-----------------------
--- отправляет транзакцию на сервер и возвращает пустое сообщение, которое
-- будет проигноировано. Вместо него, отправитель будет ждать события
-- OnTransReply, из которого по TRANS_ID он получит результат отправленной транзакции
function qsfunctions.sendTransaction(msg)
local res = sendTransaction(msg.data)
if res~="" then
-- error handling
msg.cmd = "lua_transaction_error"
msg.lua_error = res
return msg
else
-- transaction sent
msg.data = true
return msg
end
end
--- Функция заказывает получение параметров Таблицы текущих торгов. В случае успешного завершения функция возвращает «true», иначе «false»
function qsfunctions.paramRequest(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = ParamRequest(class_code, sec_code, param_name)
return msg
end
--- Функция принимает список строк (JSON Array) в формате class_code|sec_code|param_name, вызывает функцию paramRequest для каждой строки.
-- Возвращает список ответов в том же порядке
function qsfunctions.paramRequestBulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
table.insert(result, ParamRequest(class_code, sec_code, param_name))
end
msg.data = result
return msg
end
--- Функция отменяет заказ на получение параметров Таблицы текущих торгов. В случае успешного завершения функция возвращает «true», иначе «false»
function qsfunctions.cancelParamRequest(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = CancelParamRequest(class_code, sec_code, param_name)
return msg
end
--- Функция принимает список строк (JSON Array) в формате class_code|sec_code|param_name, вызывает функцию CancelParamRequest для каждой строки.
-- Возвращает список ответов в том же порядке
function qsfunctions.cancelParamRequestBulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
table.insert(result, CancelParamRequest(class_code, sec_code, param_name))
end
msg.data = result
return msg
end
--- Функция предназначена для получения значений всех параметров биржевой информации из Таблицы текущих значений параметров.
-- С помощью этой функции можно получить любое из значений Таблицы текущих значений параметров для заданных кодов класса и бумаги.
function qsfunctions.getParamEx(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = getParamEx(class_code, sec_code, param_name)
return msg
end
--- Функция предназначена для получения значении? всех параметров биржевои? информации из Таблицы текущих торгов
-- с возможностью в дальнеи?шем отказаться от получения определенных параметров, заказанных с помощью функции ParamRequest.
-- Для отказа от получения какого-либо параметра воспользуи?тесь функциеи? CancelParamRequest.
-- Функция возвращает таблицу Lua с параметрами, аналогичными параметрам, возвращаемым функциеи? getParamEx
function qsfunctions.getParamEx2(msg)
local spl = split(msg.data, "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
msg.data = getParamEx2(class_code, sec_code, param_name)
return msg
end
--- Функция принимает список строк (JSON Array) в формате class_code|sec_code|param_name и возвращает результаты вызова
-- функции getParamEx2 для каждой строки запроса в виде списка в таком же порядке, как в запросе
function qsfunctions.getParamEx2Bulk(msg)
local result = {}
for i=1,#msg.data do
local spl = split(msg.data[i], "|")
local class_code, sec_code, param_name = spl[1], spl[2], spl[3]
table.insert(result, getParamEx2(class_code, sec_code, param_name))
end
msg.data = result
return msg
end
-- Функция предназначена для получения информации по бумажным лимитам.
function qsfunctions.getDepo(msg)
local spl = split(msg.data, "|")
local clientCode, firmId, secCode, account = spl[1], spl[2], spl[3], spl[4]
msg.data = getDepo(clientCode, firmId, secCode, account)
return msg
end
-- Функция предназначена для получения информации по бумажным лимитам.
function qsfunctions.getDepoEx(msg)
local spl = split(msg.data, "|")
local firmId, clientCode, secCode, account, limit_kind = spl[1], spl[2], spl[3], spl[4], spl[5]
msg.data = getDepoEx(firmId, clientCode, secCode, account, tonumber(limit_kind))
return msg
end
-- Функция для получения информации по денежным лимитам.
function qsfunctions.getMoney(msg)
local spl = split(msg.data, "|")
local client_code, firm_id, tag, curr_code = spl[1], spl[2], spl[3], spl[4]
msg.data = getMoney(client_code, firm_id, tag, curr_code)
return msg
end
-- Функция для получения информации по денежным лимитам указанного типа.
function qsfunctions.getMoneyEx(msg)
local spl = split(msg.data, "|")
local firm_id, client_code, tag, curr_code, limit_kind = spl[1], spl[2], spl[3], spl[4], spl[5]
msg.data = getMoneyEx(firm_id, client_code, tag, curr_code, tonumber(limit_kind))
return msg
end
-- Функция возвращает информацию по всем денежным лимитам.
function qsfunctions.getMoneyLimits(msg)
local limits = {}
for i=0,getNumberOf("money_limits")-1 do
local limit = getItem("money_limits",i)
table.insert(limits, limit)
end
msg.data = limits
return msg
end
-- Функция предназначена для получения информации по фьючерсным лимитам.
function qsfunctions.getFuturesLimit(msg)
local spl = split(msg.data, "|")
local firmId, accId, limitType, currCode = spl[1], spl[2], spl[3], spl[4]
local result, err = getFuturesLimit(firmId, accId, limitType*1, currCode)
if result then
msg.data = result
else
log("Futures limit returns nil", 3)
msg.data = nil
end
return msg
end
-- Функция возвращает информацию по фьючерсным лимитам для всех торговых счетов.
function qsfunctions.getFuturesClientLimits(msg)
local limits = {}
for i=0,getNumberOf("futures_client_limits")-1 do
local limit = getItem("futures_client_limits",i)
table.insert(limits, limit)
end
msg.data = limits
return msg
end
--- (ichechet) Через getFuturesHolding позиции не приходили. Пришлось сделать обработку таблицы futures_client_holding
function qsfunctions.getFuturesHolding(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local firmId, accId, secCode, posType = spl[1], spl[2], spl[3], spl[4]
end
local fchs = {}
for i = 0, getNumberOf("futures_client_holding") - 1 do
local fch = getItem("futures_client_holding", i)
if msg.data == "" or (fch.firmid == firmId and fch.trdaccid == accId and fch.sec_code == secCode and fch.type == posType*1) then
table.insert(fchs, fch)
end
end
msg.data = fchs
return msg
end
-- Функция возвращает таблицу заявок (всю или по заданному инструменту)
function qsfunctions.get_orders(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local orders = {}
for i = 0, getNumberOf("orders") - 1 do
local order = getItem("orders", i)
if msg.data == "" or (order.class_code == class_code and order.sec_code == sec_code) then
table.insert(orders, order)
end
end
msg.data = orders
return msg
end
-- Функция возвращает заявку по заданному инструменту и ID-транзакции
function qsfunctions.getOrder_by_ID(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code, trans_id = spl[1], spl[2], spl[3]
end
local order_num = 0
local res
for i = 0, getNumberOf("orders") - 1 do
local order = getItem("orders", i)
if order.class_code == class_code and order.sec_code == sec_code and order.trans_id == tonumber(trans_id) and order.order_num > order_num then
order_num = order.order_num
res = order
end
end
msg.data = res
return msg
end
---- Функция возвращает заявку по номеру
function qsfunctions.getOrder_by_Number(msg)
for i=0,getNumberOf("orders")-1 do
local order = getItem("orders",i)
if order.order_num == tonumber(msg.data) then
msg.data = order
return msg
end
end
return msg
end
--- Возвращает заявку по её номеру и классу инструмента ---
--- На основе http://help.qlua.org/ch4_5_1_1.htm ---
function qsfunctions.get_order_by_number(msg)
local spl = split(msg.data, "|")
local class_code = spl[1]
local order_id = tonumber(spl[2])
msg.data = getOrderByNumber(class_code, order_id)
return msg
end
--- Возвращает список записей из таблицы 'Лимиты по бумагам'
--- На основе http://help.qlua.org/ch4_6_11.htm и http://help.qlua.org/ch4_5_3.htm
function qsfunctions.get_depo_limits(msg)
local sec_code = msg.data
local count = getNumberOf("depo_limits")
local depo_limits = {}
for i = 0, count - 1 do
local depo_limit = getItem("depo_limits", i)
if msg.data == "" or depo_limit.sec_code == sec_code then
table.insert(depo_limits, depo_limit)
end
end
msg.data = depo_limits
return msg
end
-- Функция возвращает таблицу сделок (всю или по заданному инструменту)
function qsfunctions.get_trades(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local trades = {}
for i = 0, getNumberOf("trades") - 1 do
local trade = getItem("trades", i)
if msg.data == "" or (trade.class_code == class_code and trade.sec_code == sec_code) then
table.insert(trades, trade)
end
end
msg.data = trades
return msg
end
-- Функция возвращает таблицу сделок по номеру заявки
function qsfunctions.get_Trades_by_OrderNumber(msg)
local order_num = tonumber(msg.data)
local trades = {}
for i = 0, getNumberOf("trades") - 1 do
local trade = getItem("trades", i)
if trade.order_num == order_num then
table.insert(trades, trade)
end
end
msg.data = trades
return msg
end
-- Функция предназначена для получения значений параметров таблицы «Клиентский портфель», соответствующих идентификатору участника торгов «firmid» и коду клиента «client_code».
function qsfunctions.getPortfolioInfo(msg)
local spl = split(msg.data, "|")
local firmId, clientCode = spl[1], spl[2]
msg.data = getPortfolioInfo(firmId, clientCode)
return msg
end
-- Функция предназначена для получения значений параметров таблицы «Клиентский портфель», соответствующих идентификатору участника торгов «firmid», коду клиента «client_code» и виду лимита «limit_kind».
function qsfunctions.getPortfolioInfoEx(msg)
local spl = split(msg.data, "|")
local firmId, clientCode, limit_kind = spl[1], spl[2], spl[3]
msg.data = getPortfolioInfoEx(firmId, clientCode, tonumber(limit_kind))
return msg
end
-- Функция предназначена для получения таблицы обезличенных сделок по выбранному инструменту или всю целиком.
function qsfunctions.get_all_trades(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local trades = {}
for i = 0, getNumberOf("all_trades") - 1 do
local trade = getItem("all_trades", i)
if msg.data == "" or (trade.class_code == class_code and trade.sec_code == sec_code) then
table.insert(trades, trade)
end
end
msg.data = trades
return msg
end
--------------------------
-- OptionBoard functions --
--------------------------
function qsfunctions.getOptionBoard(msg)
local spl = split(msg.data, "|")
local classCode, secCode = spl[1], spl[2]
local result, err = getOptions(classCode, secCode)
if result then
msg.data = result
else
log("Option board returns nil", 3)
msg.data = nil
end
return msg
end
function getOptions(classCode,secCode)
--classCode = "SPBOPT"
--BaseSecList="RIZ6"
local SecList = getClassSecurities(classCode) --все сразу
local t={}
local p={}
for sec in string.gmatch(SecList, "([^,]+)") do --перебираем опционы по очереди.
local Optionbase=getParamEx(classCode,sec,"optionbase").param_image
local Optiontype=getParamEx(classCode,sec,"optiontype").param_image
if (string.find(secCode,Optionbase)~=nil) then
p={
["code"]=getParamEx(classCode,sec,"code").param_image,
["Name"]=getSecurityInfo(classCode,sec).name,
["DAYS_TO_MAT_DATE"]=getParamEx(classCode,sec,"DAYS_TO_MAT_DATE").param_value+0,
["BID"]=getParamEx(classCode,sec,"BID").param_value+0,
["OFFER"]=getParamEx(classCode,sec,"OFFER").param_value+0,
["OPTIONBASE"]=getParamEx(classCode,sec,"optionbase").param_image,
["OPTIONTYPE"]=getParamEx(classCode,sec,"optiontype").param_image,
["Longname"]=getParamEx(classCode,sec,"longname").param_image,
["shortname"]=getParamEx(classCode,sec,"shortname").param_image,
["Volatility"]=getParamEx(classCode,sec,"volatility").param_value+0,
["Strike"]=getParamEx(classCode,sec,"strike").param_value+0
}
table.insert( t, p )
end
end
return t
end
--------------------------
-- Stop order functions --
--------------------------
--- Возвращает список стоп-заявок
function qsfunctions.get_stop_orders(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local class_code, sec_code = spl[1], spl[2]
end
local count = getNumberOf("stop_orders")
local stop_orders = {}
for i = 0, count - 1 do
local stop_order = getItem("stop_orders", i)
if msg.data == "" or (stop_order.class_code == class_code and stop_order.sec_code == sec_code) then
table.insert(stop_orders, stop_order)
end
end
msg.data = stop_orders
return msg
end
-------------------------
--- Candles functions ---
-------------------------
--- Возвращаем количество свечей по тегу
function qsfunctions.get_num_candles(msg)
log("Called get_num_candles" .. msg.data, 2)
local spl = split(msg.data, "|")
local tag = spl[1]
msg.data = getNumCandles(tag) * 1
return msg
end
--- Возвращаем все свечи по идентификатору графика. График должен быть открыт
function qsfunctions.get_candles(msg)
log("Called get_candles" .. msg.data, 2)
local spl = split(msg.data, "|")
local tag = spl[1]
local line = tonumber(spl[2])
local first_candle = tonumber(spl[3])
local count = tonumber(spl[4])
if count == 0 then
count = getNumCandles(tag) * 1
end
log("Count: " .. count, 2)
local t,n,l = getCandlesByIndex(tag, line, first_candle, count)
log("Candles table size: " .. n, 2)
log("Label: " .. l, 2)
local candles = {}
for i = 0, count - 1 do
table.insert(candles, t[i])
end
msg.data = candles
return msg
end
--- Возвращаем все свечи по заданному инструменту и интервалу
--- (ichechet) Если исторические данные по тикеру не приходят, то QUIK блокируется. Чтобы это не происходило, вводим таймаут
function qsfunctions.get_candles_from_data_source(msg)
local ds, is_error = create_data_source(msg)
if not is_error then
--- Источник данных изначально приходит пустым. Нужно подождать пока он заполнится данными. Бесконечно ждать тоже нельзя. Вводим таймаут
local s = 0 --- Будем ждать 5 секунд, прежде чем вернем таймаут
repeat --- Ждем
sleep(100) --- 100 миллисекунд
s = s + 100 --- Запоминаем кол-во прошедших миллисекунд
until (ds:Size() > 0 or s > 5000) --- До тех пор, пока не придут данные или пока не наступит таймаут
local count = tonumber(split(msg.data, "|")[4]) --- возвращаем последние count свечей. Если равен 0, то возвращаем все доступные свечи.
local class, sec, interval = get_candles_param(msg)
local candles = {}
local start_i = count == 0 and 1 or math.max(1, ds:Size() - count + 1)
for i = start_i, ds:Size() do
local candle = fetch_candle(ds, i)
candle.sec = sec
candle.class = class
candle.interval = interval
table.insert(candles, candle)
end
ds:Close()
msg.data = candles
end
return msg
end
function create_data_source(msg)
local class, sec, interval = get_candles_param(msg)
local ds, error_descr = CreateDataSource(class, sec, interval)
local is_error = false
if(error_descr ~= nil) then
msg.cmd = "lua_create_data_source_error"
msg.lua_error = error_descr
is_error = true
elseif ds == nil then
msg.cmd = "lua_create_data_source_error"
msg.lua_error = "Can't create data source for " .. class .. ", " .. sec .. ", " .. tostring(interval)
is_error = true
end
return ds, is_error
end
function fetch_candle(data_source, index)
local candle = {}
candle.low = data_source:L(index)
candle.close = data_source:C(index)
candle.high = data_source:H(index)
candle.open = data_source:O(index)
candle.volume = data_source:V(index)
candle.datetime = data_source:T(index)
return candle
end
--- Словарь открытых подписок (datasources) на свечи
data_sources = {}
last_indexes = {}
--- Подписаться на получения свечей по заданному инструмент и интервалу
function qsfunctions.subscribe_to_candles(msg)
local ds, is_error = create_data_source(msg)
if not is_error then
local class, sec, interval = get_candles_param(msg)
local key = get_key(class, sec, interval)
data_sources[key] = ds
last_indexes[key] = ds:Size()
ds:SetUpdateCallback(
function(index)
data_source_callback(index, class, sec, interval)
end)
end
return msg
end
function data_source_callback(index, class, sec, interval)
local key = get_key(class, sec, interval)
if index ~= last_indexes[key] then
last_indexes[key] = index
local candle = fetch_candle(data_sources[key], index - 1)
candle.sec = sec
candle.class = class
candle.interval = interval
local msg = {}
msg.t = timemsec()
msg.cmd = "NewCandle"
msg.data = candle
sendCallback(msg)
end
end
--- Отписать от получения свечей по заданному инструменту и интервалу
function qsfunctions.unsubscribe_from_candles(msg)
local class, sec, interval = get_candles_param(msg)
local key = get_key(class, sec, interval)
data_sources[key]:Close()
data_sources[key] = nil
last_indexes[key] = nil
return msg
end
--- Проверить открыта ли подписка на заданный инструмент и интервал
function qsfunctions.is_subscribed(msg)
local class, sec, interval = get_candles_param(msg)
local key = get_key(class, sec, interval)
for k, v in pairs(data_sources) do
if key == k then
msg.data = true;
return msg
end
end
msg.data = false
return msg
end
--- Возвращает из msg информацию о инструменте на который подписываемся и интервале
function get_candles_param(msg)
local spl = split(msg.data, "|")
return spl[1], spl[2], tonumber(spl[3])
end
--- Возвращает уникальный ключ для инструмента на который подписываемся и инетрвала
function get_key(class, sec, interval)
return class .. "|" .. sec .. "|" .. tostring(interval)
end
-------------------------
--- UCP functions ---
-------------------------
--- Функция возвращает торговый счет срочного рынка, соответствующий коду клиента фондового рынка с единой денежной позицией
function qsfunctions.GetTrdAccByClientCode(msg)
local spl = split(msg.data, "|")
local firmId, clientCode = spl[1], spl[2]
msg.data = getTrdAccByClientCode(firmId, clientCode)
return msg
end
--- Функция возвращает код клиента фондового рынка с единой денежной позицией, соответствующий торговому счету срочного рынка
function qsfunctions.GetClientCodeByTrdAcc(msg)
local spl = split(msg.data, "|")
local firmId, trdAccId = spl[1], spl[2]
msg.data = getClientCodeByTrdAcc(firmId, trdAccId)
return msg
end
--- Функция предназначена для получения признака, указывающего имеет ли клиент единую денежную позицию
function qsfunctions.IsUcpClient(msg)
local spl = split(msg.data, "|")
local firmId, client = spl[1], spl[2]
msg.data = isUcpClient(firmId, client)
return msg
end
return qsfunctions

295
QUIK/lua/qsutils.lua Normal file
View File

@@ -0,0 +1,295 @@
--~ Copyright (c) 2014-2020 QUIKSharp Authors https://github.com/finsight/QUIKSharp/blob/master/AUTHORS.md. All rights reserved.
--~ Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
local socket = require ("socket")
local json = require ("dkjson")
local qsutils = {}
--- Sleep that always works
function delay(msec)
if sleep then
pcall(sleep, msec)
else
-- pcall(socket.select, nil, nil, msec / 1000)
end
end
-- high precision current time
function timemsec()
local st, res = pcall(socket.gettime)
if st then
return (res) * 1000
else
log("unexpected error in timemsec", 3)
error("unexpected error in timemsec")
end
end
-- Returns the name of the file that calls this function (without extension)
function scriptFilename()
-- Check that Lua runtime was built with debug information enabled
if not debug or not debug.getinfo then
return nil
end
local full_path = debug.getinfo(2, "S").source:sub(2)
return string.gsub(full_path, "^.*\\(.*)[.]lua[c]?$", "%1")
end
-- when true will show QUIK message for log(...,0)
is_debug = false
-- log files
function openLog()
os.execute("mkdir \""..script_path.."\\logs\"")
local lf = io.open (script_path.. "\\logs\\QUIK#_"..os.date("%Y%m%d")..".log", "a")
if not lf then
lf = io.open (script_path.. "\\QUIK#_"..os.date("%Y%m%d")..".log", "a")
end
return lf
end
-- Returns contents of config.json file or nil if no such file exists
function readConfigAsJson()
local conf = io.open (script_path.. "\\config.json", "r")
if not conf then
return nil
end
local content = conf:read "*a"
conf:close()
return from_json(content)
end
function paramsFromConfig(scriptName)
local params = {}
-- just default values
table.insert(params, "127.0.0.1") -- responseHostname
table.insert(params, 34130) -- responsePort
table.insert(params, "127.0.0.1") -- callbackHostname
table.insert(params, 34131) -- callbackPort
local config = readConfigAsJson()
if not config or not config.servers then
return nil
end
local found = false
for i=1,#config.servers do
local server = config.servers[i]
if server.scriptName == scriptName then
found = true
if server.responseHostname then
params[1] = server.responseHostname
end
if server.responsePort then
params[2] = server.responsePort
end
if server.callbackHostname then
params[3] = server.callbackHostname
end
if server.callbackPort then
params[4] = server.callbackPort
end
end
end
if found then
return params
else
return nil
end
end
-- closes log
function closeLog()
if logfile then
pcall(logfile:close(logfile))
end
end
logfile = openLog()
--- Write to log file and to Quik messages
function log(msg, level)
if not msg then msg = "" end
if level == 1 or level == 2 or level == 3 or is_debug then
-- only warnings and recoverable errors to Quik
if message then
pcall(message, msg, level)
end
end
if not level then level = 0 end
local logLine = "LOG "..level..": "..msg
print(logLine)
local msecs = math.floor(math.fmod(timemsec(), 1000));
if logfile then
pcall(logfile.write, logfile, os.date("%Y-%m-%d %H:%M:%S").."."..msecs.." "..logLine.."\n")
pcall(logfile.flush, logfile)
end
end
function split(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
local i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
function from_json(str)
local status, msg= pcall(json.decode, str, 1, json.null) -- dkjson
if status then
return msg
else
return nil, msg
end
end
function to_json(msg)
local status, str= pcall(json.encode, msg, { indent = false }) -- dkjson
if status then
return str
else
error(str)
end
end
-- current connection state
is_connected = false
local response_server
local callback_server
local response_client
local callback_client
--- accept client on server
local function getResponseServer()
log('Waiting for a response client...', 0)
if not response_server then
log("Cannot bind to response_server, probably the port is already in use", 3)
else
while true do
local status, client, err = pcall(response_server.accept, response_server )
if status and client then
return client
else
log(err, 3)
end
end
end
end
local function getCallbackClient()
log('Waiting for a callback client...', 0)
if not callback_server then
log("Cannot bind to callback_server, probably the port is already in use", 3)
else
while true do
local status, client, err = pcall(callback_server.accept, callback_server)
if status and client then
return client
else
log(err, 3)
end
end
end
end
function qsutils.connect(response_host, response_port, callback_host, callback_port)
if not response_server then
response_server = socket.bind(response_host, response_port, 1)
end
if not callback_server then
callback_server = socket.bind(callback_host, callback_port, 1)
end
if not is_connected then
log('QUIK# is waiting for client connection...', 1)
if response_client then
log("is_connected is false but the response client is not nil", 3)
-- Quik crashes without pcall
pcall(response_client.close, response_client)
end
if callback_client then
log("is_connected is false but the callback client is not nil", 3)
-- Quik crashes without pcall
pcall(callback_client.close, callback_client)
end
response_client = getResponseServer()
callback_client = getCallbackClient()
if response_client and callback_client then
is_connected = true
log('QUIK# client connected', 1)
end
end
end
local function disconnected()
is_connected = false
log('QUIK# client disconnected', 1)
if response_client then
pcall(response_client.close, response_client)
response_client = nil
end
if callback_client then
pcall(callback_client.close, callback_client)
callback_client = nil
end
OnQuikSharpDisconnected()
end
--- get a decoded message as a table
function receiveRequest()
if not is_connected then
return nil, "not conencted"
end
local status, requestString= pcall(response_client.receive, response_client)
if status and requestString then
local msg_table, err = from_json(requestString)
if err then
log(err, 3)
return nil, err
else
return msg_table
end
else
disconnected()
return nil, err
end
end
function sendResponse(msg_table)
-- if not set explicitly then set CreatedTime "t" property here
-- if not msg_table.t then msg_table.t = timemsec() end
local responseString = to_json(msg_table)
if is_connected then
local status, res = pcall(response_client.send, response_client, responseString..'\n')
if status and res then
return true
else
disconnected()
return nil, err
end
end
end
function sendCallback(msg_table)
-- if not set explicitly then set CreatedTime "t" property here
-- if not msg_table.t then msg_table.t = timemsec() end
local callback_string = to_json(msg_table)
if is_connected then
local status, res = pcall(callback_client.send, callback_client, callback_string..'\n')
if status and res then
return true
else
disconnected()
return nil, err
end
end
end
return qsutils

149
QUIK/lua/socket.lua Normal file
View File

@@ -0,0 +1,149 @@
-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")
local _M = socket
-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function _M.connect4(address, port, laddress, lport)
return socket.connect(address, port, laddress, lport, "inet")
end
function _M.connect6(address, port, laddress, lport)
return socket.connect(address, port, laddress, lport, "inet6")
end
function _M.bind(host, port, backlog)
if host == "*" then host = "0.0.0.0" end
local addrinfo, err = socket.dns.getaddrinfo(host);
if not addrinfo then return nil, err end
local sock, res
err = "no info on address"
for i, alt in base.ipairs(addrinfo) do
if alt.family == "inet" then
sock, err = socket.tcp4()
else
sock, err = socket.tcp6()
end
if not sock then return nil, err end
sock:setoption("reuseaddr", true)
res, err = sock:bind(alt.addr, port)
if not res then
sock:close()
else
res, err = sock:listen(backlog)
if not res then
sock:close()
else
return sock
end
end
end
return nil, err
end
_M.try = _M.newtry()
function _M.choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
else return f(opt1, opt2) end
end
end
-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
local sourcet, sinkt = {}, {}
_M.sourcet = sourcet
_M.sinkt = sinkt
_M.BLOCKSIZE = 2048
sinkt["close-when-done"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then
sock:close()
return 1
else return sock:send(chunk) end
end
})
end
sinkt["keep-open"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then return sock:send(chunk)
else return 1 end
end
})
end
sinkt["default"] = sinkt["keep-open"]
_M.sink = _M.choose(sinkt)
sourcet["by-length"] = function(sock, length)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if length <= 0 then return nil end
local size = math.min(socket.BLOCKSIZE, length)
local chunk, err = sock:receive(size)
if err then return nil, err end
length = length - string.len(chunk)
return chunk
end
})
end
sourcet["until-closed"] = function(sock)
local done
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else return nil, err end
end
})
end
sourcet["default"] = sourcet["until-closed"]
_M.source = _M.choose(sourcet)
return _M

BIN
QUIK/socket/core.dll Normal file

Binary file not shown.

588
QuikPy.py Normal file
View File

@@ -0,0 +1,588 @@
import socket # Обращаться к LUA скриптам QuikSharp будем через соединения
import threading # Результат работы функций обратного вызова будем получать в отдельном потоке
import json # Передавать и принимать данные в QUIK будем через JSON
import time # Задержка при чтении буфера
class Singleton(type):
"""Метакласс для создания Singleton классов"""
def __init__(cls, *args, **kwargs):
"""Инициализация класса"""
super(Singleton, cls).__init__(*args, **kwargs)
cls._singleton = None # Экземпляра класса еще нет
def __call__(cls, *args, **kwargs):
"""Вызов класса"""
if cls._singleton is None: # Если класса нет в экземплярах класса
cls._singleton = super(Singleton, cls).__call__(*args, **kwargs)
return cls._singleton # Возвращаем экземпляр класса
class QuikPy(metaclass=Singleton): # Singleton класс
"""Работа с Quik из Python через LUA скрипты QuikSharp https://github.com/finsight/QUIKSharp/tree/master/src/QuikSharp/lua
На основе Документации по языку LUA в QUIK из https://arqatech.com/ru/support/files/
"""
bufferSize = 4096 # Размер буфера приема в байтах
socketRequests = None # Соединение для запросов
callbackThread = None # Поток обработки функций обратного вызова
def DefaultHandler(self, data):
"""Пустой обработчик события по умолчанию. Его можно заменить на пользовательский"""
pass
def CallbackHandler(self):
"""Поток обработки результатов функций обратного вызова"""
# TODO Проверить работу при переходе на следующую сессию (отключение и включение QUIK)
socketCallbacks = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Соединение для функций обратного вызова
socketCallbacks.connect((self.Host, self.CallbacksPort)) # Открываем соединение для функций обратного вызова
currentThread = threading.currentThread() # Получаем текущий поток
while getattr(currentThread, 'process', True): # Пока поток нужен
fragments = [] # Гораздо быстрее получать ответ в виде списка фрагментов
while True: # Пока есть что-то в буфере ответов
fragment = socketCallbacks.recv(self.bufferSize) # Читаем фрагмент из буфера
fragments.append(fragment.decode('cp1251')) # Переводим фрагмент в Windows кодировку 1251, добавляем в список
if len(fragment) < self.bufferSize: # Если в принятом фрагменте данных меньше чем размер буфера
break # то это был последний фрагмент, выходим из чтения буфера
time.sleep(0.000001) # Может так получиться, что буфер будет считываться быстрее, чем заполняться. Поэтому, ставим небольшую задержку перед следующим чтением
data = ''.join(fragments) # Собираем список фрагментов в строку
dataList = data.split('\n') # Одновременно могут прийти несколько функций обратного вызова, разбираем их по одной
for data in dataList: # Пробегаемся по всем функциям обратного вызова
if data == '': # Если функция обратного вызова пустая
continue # то ее не разбираем, переходим на следующую функцию, дальше не продолжаем
data = json.loads(data) # Возвращаем полученный ответ в формате JSON
# Разбираем функцию обратного вызова QUIK LUA
if data['cmd'] == 'OnFirm': # 1. Новая фирма
self.OnFirm(data)
elif data['cmd'] == 'OnAllTrade': # 2. Получение обезличенной сделки
self.OnAllTrade(data)
elif data['cmd'] == 'OnTrade': # 3. Получение новой / изменение существующей сделки
self.OnTrade(data)
elif data['cmd'] == 'OnOrder': # 4. Получение новой / изменение существующей заявки
self.OnOrder(data)
elif data['cmd'] == 'OnAccountBalance': # 5. Изменение позиций по счету
self.OnAccountBalance(data)
elif data['cmd'] == 'OnFuturesLimitChange': # 6. Изменение ограничений по срочному рынку
self.OnFuturesLimitChange(data)
elif data['cmd'] == 'OnFuturesLimitDelete': # 7. Удаление ограничений по срочному рынку
self.OnFuturesLimitDelete(data)
elif data['cmd'] == 'OnFuturesClientHolding': # 8. Изменение позиции по срочному рынку
self.OnFuturesClientHolding(data)
elif data['cmd'] == 'OnMoneyLimit': # 9. Изменение денежной позиции
self.OnMoneyLimit(data)
elif data['cmd'] == 'OnMoneyLimitDelete': # 10. Удаление денежной позиции
self.OnMoneyLimitDelete(data)
elif data['cmd'] == 'OnDepoLimit': # 11. Изменение позиций по инструментам
self.OnDepoLimit(data)
elif data['cmd'] == 'OnDepoLimitDelete': # 12. Удаление позиции по инструментам
self.OnDepoLimitDelete(data)
elif data['cmd'] == 'OnAccountPosition': # 13. Изменение денежных средств
self.OnAccountPosition(data)
# OnNegDeal - 14. Получение новой / изменение существующей внебиржевой заявки
# OnNegTrade - 15. Получение новой / изменение существующей сделки для исполнения
elif data['cmd'] == 'OnStopOrder': # 16. Получение новой / изменение существующей стоп-заявки
self.OnStopOrder(data)
elif data['cmd'] == 'OnTransReply': # 17. Ответ на транзакцию пользователя
self.OnTransReply(data)
elif data['cmd'] == 'OnParam': # 18. Изменение текущих параметров
self.OnParam(data)
elif data['cmd'] == 'OnQuote': # 19. Изменение стакана котировок
self.OnQuote(data)
elif data['cmd'] == 'OnDisconnected': # 20. Отключение терминала от сервера QUIK
self.OnDisconnected(data)
elif data['cmd'] == 'OnConnected': # 21. Соединение терминала с сервером QUIK
self.OnConnected(data)
# OnCleanUp - 22. Смена сервера QUIK / Пользователя / Сессии
elif data['cmd'] == 'OnClose': # 23. Закрытие терминала QUIK
self.OnClose(data)
elif data['cmd'] == 'OnStop': # 24. Остановка LUA скрипта в терминале QUIK / закрытие терминала QUIK
self.OnStop(data)
elif data['cmd'] == 'OnInit': # 25. Запуск LUA скрипта в терминале QUIK
self.OnInit(data)
# Разбираем функции обратного вызова QuikSharp
elif data['cmd'] == 'NewCandle': # Получение новой свечки
self.OnNewCandle(data)
elif data['cmd'] == 'OnError': # Получено сообщение об ошибке
self.OnError(data)
socketCallbacks.close() # Закрываем соединение для ответов
def ProcessRequest(self, Request):
"""Отправляем запрос в QUIK, получаем ответ из QUIK"""
rawData = json.dumps(Request) # Переводим запрос в формат JSON
self.socketRequests.sendall(f'{rawData}\r\n'.encode()) # Отправляем запрос в QUIK
fragments = [] # Гораздо быстрее получать ответ в виде списка фрагментов
while True: # Пока фрагменты есть в буфере
fragment = self.socketRequests.recv(self.bufferSize) # Читаем фрагмент из буфера
fragments.append(fragment.decode('cp1251')) # Переводим фрагмент в Windows кодировку 1251, добавляем в список
if len(fragment) < self.bufferSize: # Если в принятом фрагменте данных меньше чем размер буфера
break # то это был последний фрагмент, выходим из чтения буфера
time.sleep(0.000001) # Может так получиться, что буфер будет считываться быстрее, чем заполняться. Поэтому, ставим небольшую задержку перед следующим чтением
data = ''.join(fragments) # Собираем список фрагментов в строку
return json.loads(data) # Возвращаем ответ в формате JSON в Windows кодировке 1251
# Инициализация и вход
def __init__(self, Host='127.0.0.1', RequestsPort=34130, CallbacksPort=34131):
"""Инициализация"""
# 2.2. Функции обратного вызова
self.OnFirm = self.DefaultHandler # 1. Новая фирма
self.OnAllTrade = self.DefaultHandler # 2. Получение обезличенной сделки
self.OnTrade = self.DefaultHandler # 3. Получение новой / изменение существующей сделки
self.OnOrder = self.DefaultHandler # 4. Получение новой / изменение существующей заявки
self.OnAccountBalance = self.DefaultHandler # 5. Изменение позиций
self.OnFuturesLimitChange = self.DefaultHandler # 6. Изменение ограничений по срочному рынку
self.OnFuturesLimitDelete = self.DefaultHandler # 7. Удаление ограничений по срочному рынку
self.OnFuturesClientHolding = self.DefaultHandler # 8. Изменение позиции по срочному рынку
self.OnMoneyLimit = self.DefaultHandler # 9. Изменение денежной позиции
self.OnMoneyLimitDelete = self.DefaultHandler # 10. Удаление денежной позиции
self.OnDepoLimit = self.DefaultHandler # 11. Изменение позиций по инструментам
self.OnDepoLimitDelete = self.DefaultHandler # 12. Удаление позиции по инструментам
self.OnAccountPosition = self.DefaultHandler # 13. Изменение денежных средств
# OnNegDeal - 14. Получение новой / изменение существующей внебиржевой заявки
# OnNegTrade - 15. Получение новой / изменение существующей сделки для исполнения
self.OnStopOrder = self.DefaultHandler # 16. Получение новой / изменение существующей стоп-заявки
self.OnTransReply = self.DefaultHandler # 17. Ответ на транзакцию пользователя
self.OnParam = self.DefaultHandler # 18. Изменение текущих параметров
self.OnQuote = self.DefaultHandler # 19. Изменение стакана котировок
self.OnDisconnected = self.DefaultHandler # 20. Отключение терминала от сервера QUIK
self.OnConnected = self.DefaultHandler # 21. Соединение терминала с сервером QUIK
# OnCleanUp - 22. Смена сервера QUIK / Пользователя / Сессии
self.OnClose = self.DefaultHandler # 23. Закрытие терминала QUIK
self.OnStop = self.DefaultHandler # 24. Остановка LUA скрипта в терминале QUIK / закрытие терминала QUIK
self.OnInit = self.DefaultHandler # 25. Запуск LUA скрипта в терминале QUIK
# Функции обратного вызова QuikSharp
self.OnNewCandle = self.DefaultHandler # Получение новой свечки
self.OnError = self.DefaultHandler # Получено сообщение об ошибке
self.Host = Host # IP адрес или название хоста
self.RequestsPort = RequestsPort # Порт для отправки запросов и получения ответов
self.CallbacksPort = CallbacksPort # Порт для функций обратного вызова
self.socketRequests = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Создаем соединение для запросов
self.socketRequests.connect((self.Host, self.RequestsPort)) # Открываем соединение для запросов
self.callbackThread = threading.Thread(target=self.CallbackHandler, name='CallbackThread') # Создаем поток обработки функций обратного вызова
self.callbackThread.start() # Запускаем поток
def __enter__(self):
"""Вход в класс, например, с with"""
return self
# Фукнции связи с QuikSharp
def Ping(self, TransId=0):
"""Проверка соединения. Отправка ping. Получение pong"""
return self.ProcessRequest({'data': 'Ping', 'id': TransId, 'cmd': 'ping', 't': ''})
def Echo(self, Message, TransId=0):
"""Эхо. Отправка и получение одного и того же сообщения"""
return self.ProcessRequest({'data': Message, 'id': TransId, 'cmd': 'echo', 't': ''})
def DivideStringByZero(self, TransId=0):
"""Тест обработки ошибок. Выполняется деление на 0 с выдачей ошибки"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'divide_string_by_zero', 't': ''})
def IsQuik(self, TransId=0):
"""Скрипт запущен в Квике"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'is_quik', 't': ''})
# 2.1 Сервисные функции
def IsConnected(self, TransId=0): # 1
"""Состояние подключения терминала к серверу QUIK. Возвращает 1 - подключено / 0 - не подключено"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'isConnected', 't': ''})
def GetScriptPath(self, TransId=0): # 2
"""Путь скрипта без завершающего обратного слэша"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getScriptPath', 't': ''})
def GetInfoParam(self, Params, TransId=0): # 3
"""Значения параметров информационного окна"""
return self.ProcessRequest({'data': Params, 'id': TransId, 'cmd': 'getInfoParam', 't': ''})
# message - 4. Сообщение в терминале QUIK. Реализовано в виде 3-х отдельных функций в QuikSharp
def Sleep(self, Time, TransId=0): # 5
"""Приостановка скрипта. Время в миллисекундах"""
return self.ProcessRequest({'data': Time, 'id': TransId, 'cmd': 'sleep', 't': ''})
def GetWorkingFolder(self, TransId=0): # 6
"""Путь к info.exe, исполняющего скрипт без завершающего обратного слэша"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getWorkingFolder', 't': ''})
def PrintDbgStr(self, Message, TransId=0): # 7
"""Вывод отладочной информации. Можно посмотреть с помощью DebugView"""
return self.ProcessRequest({'data': Message, 'id': TransId, 'cmd': 'PrintDbgStr', 't': ''})
# sysdate - 8. Системные дата и время
# isDarkTheme - 9. Тема оформления. true - тёмная, false - светлая
# Сервисные функции QuikSharp
def MessageInfo(self, Message, TransId=0): # В QUIK LUA message icon_type=1
"""Отправка информационного сообщения в терминал QUIK"""
return self.ProcessRequest({'data': Message, 'id': TransId, 'cmd': 'message', 't': ''})
def MessageWarning(self, Message, TransId=0): # В QUIK LUA message icon_type=2
"""Отправка сообщения с предупреждением в терминал QUIK"""
return self.ProcessRequest({'data': Message, 'id': TransId, 'cmd': 'warning_message', 't': ''})
def MessageError(self, Message, TransId=0): # В QUIK LUA message icon_type=3
"""Отправка сообщения об ошибке в терминал QUIK"""
return self.ProcessRequest({'data': Message, 'id': TransId, 'cmd': 'error_message', 't': ''})
# 3.1. Функции для обращения к строкам произвольных таблиц
# getItem - 1. Строка таблицы
# getOrderByNumber - 2. Заявка
# getNumberOf - 3. Кол-во записей в таблице
# SearchItems - 4. Быстрый поиск по таблице заданной функцией поиска
# Функции для обращения к строкам произвольных таблиц QuikSharp
def GetTradeAccounts(self, TransId=0):
"""Торговые счета, у которых указаны поддерживаемые классы инструментов"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getTradeAccounts', 't': ''})
def GetTradeAccount(self, ClassCode, TransId=0):
"""Торговый счет для запрашиваемого кода класса"""
return self.ProcessRequest({'data': ClassCode, 'id': TransId, 'cmd': 'getTradeAccount', 't': ''})
def GetAllOrders(self, TransId=0):
"""Таблица заявок (вся)"""
return self.ProcessRequest({'data': f'', 'id': TransId, 'cmd': 'get_orders', 't': ''})
def GetOrders(self, ClassCode, SecCode, TransId=0):
"""Таблица заявок (по инструменту)"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'get_orders', 't': ''})
def GetOrderByNumber(self, OrderId, TransId=0):
"""Заявка по номеру"""
return self.ProcessRequest({'data': OrderId, 'id': TransId, 'cmd': 'getOrder_by_Number', 't': ''})
def GetOrderById(self, ClassCode, SecCode, OrderTransId, TransId=0):
"""Заявка по инструменту и Id транзакции"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{OrderTransId}', 'id': TransId, 'cmd': 'getOrder_by_ID', 't': ''})
def GetOrderByClassNumber(self, ClassCode, OrderId, TransId=0):
"""Заявка по классу инструмента и номеру"""
return self.ProcessRequest({'data': f'{ClassCode}|{OrderId}', 'id': TransId, 'cmd': 'getOrder_by_Number', 't': ''})
def GetMoneyLimits(self, TransId=0):
"""Все денежные лимиты"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getMoneyLimits', 't': ''})
def GetClientCode(self, TransId=0):
"""Основной (первый) код клиента"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getClientCode', 't': ''})
def GetClientCodes(self, TransId=0):
"""Все коды клиента"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getClientCode', 't': ''})
def GetAllDepoLimits(self, TransId=0):
"""Лимиты по бумагам (всем)"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'get_depo_limits', 't': ''})
def GetDepoLimits(self, SecCode, TransId=0):
"""Лимиты по бумагам (по инструменту)"""
return self.ProcessRequest({'data': SecCode, 'id': TransId, 'cmd': 'get_depo_limits', 't': ''})
def GetAllTrades(self, TransId=0):
"""Таблица сделок (вся)"""
return self.ProcessRequest({'data': f'', 'id': TransId, 'cmd': 'get_trades', 't': ''})
def GetTrades(self, ClassCode, SecCode, TransId=0):
"""Таблица сделок (по инструменту)"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'get_trades', 't': ''})
def GetTradesByOrderNumber(self, OrderNum, TransId=0):
"""Таблица сделок по номеру заявки"""
return self.ProcessRequest({'data': OrderNum, 'id': TransId, 'cmd': 'get_Trades_by_OrderNumber', 't': ''})
def GetAllStopOrders(self, TransId=0):
"""Стоп заявки (все)"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'get_stop_orders', 't': ''})
def GetStopOrders(self, ClassCode, SecCode, TransId=0):
"""Стоп заявки (по инструменту)"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'get_stop_orders', 't': ''})
def GetAllTrade(self, TransId=0):
"""Таблица обезличенных сделок (вся)"""
return self.ProcessRequest({'data': f'', 'id': TransId, 'cmd': 'get_all_trades', 't': ''})
def GetTrade(self, ClassCode, SecCode, TransId=0):
"""Таблица обезличенных сделок (по инструменту)"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'get_all_trades', 't': ''})
# 3.2 Функции для обращения к спискам доступных параметров
def GetClassesList(self, TransId=0): # 1
"""Список классов"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getClassesList', 't': ''})
def GetClassInfo(self, ClassCode, TransId=0): # 2
"""Информация о классе"""
return self.ProcessRequest({'data': ClassCode, 'id': TransId, 'cmd': 'getClassInfo', 't': ''})
def GetClassSecurities(self, ClassCode, TransId=0): # 3
"""Список инструментов класса"""
return self.ProcessRequest({'data': ClassCode, 'id': TransId, 'cmd': 'getClassSecurities', 't': ''})
# Функции для обращения к спискам доступных параметров QuikSharp
def GetOptionBoard(self, ClassCode, SecCode, TransId=0):
"""Доска опционов"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'getOptionBoard', 't': ''})
# 3.3 Функции для получения информации по денежным средствам
def GetMoney(self, ClientCode, FirmId, Tag, CurrCode, TransId=0): # 1
"""Денежные позиции"""
return self.ProcessRequest({'data': f'{ClientCode}|{FirmId}|{Tag}|{CurrCode}', 'id': TransId, 'cmd': 'getMoney', 't': ''})
def GetMoneyEx(self, FirmId, ClientCode, Tag, CurrCode, LimitKind, TransId=0): # 2
"""Денежные позиции указанного типа"""
return self.ProcessRequest({'data': f'{FirmId}|{ClientCode}|{Tag}|{CurrCode}|{LimitKind}', 'id': TransId, 'cmd': 'getMoneyEx', 't': ''})
# 3.4 Функции для получения позиций по инструментам
def GetDepo(self, ClientCode, FirmId, SecCode, Account, TransId=0): # 1
"""Позиции по инструментам"""
return self.ProcessRequest({'data': f'{ClientCode}|{FirmId}|{SecCode}|{Account}', 'id': TransId, 'cmd': 'getDepo', 't': ''})
def GetDepoEx(self, FirmId, ClientCode, SecCode, Account, LimitKind, TransId=0): # 2
"""Позиции по инструментам указанного типа"""
return self.ProcessRequest({'data': f'{FirmId}|{ClientCode}|{SecCode}|{Account}|{LimitKind}', 'id': TransId, 'cmd': 'getDepoEx', 't': ''})
# 3.5 Функция для получения информации по фьючерсным лимитам
def GetFuturesLimit(self, FirmId, AccountId, LimitType, CurrCode, TransId=0): # 1
"""Фьючерсные лимиты"""
return self.ProcessRequest({'data': f'{FirmId}|{AccountId}|{LimitType}|{CurrCode}', 'id': TransId, 'cmd': 'getFuturesLimit', 't': ''})
# Функция для получения информации по фьючерсным лимитам QuikSharp
def GetFuturesClientLimits(self, TransId=0):
"""Все фьючерсные лимиты"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getFuturesClientLimits', 't': ''})
# 3.6 Функция для получения информации по фьючерсным позициям
def GetFuturesHolding(self, FirmId, AccountId, SecCode, PositionType, TransId=0): # 1
"""Фьючерсные позиции"""
return self.ProcessRequest({'data': f'{FirmId}|{AccountId}|{SecCode}|{PositionType}', 'id': TransId, 'cmd': 'getFuturesHolding', 't': ''})
# Функция для получения информации по фьючерсным позициям QuikSharp
def GetFuturesHoldings(self, TransId=0):
"""Все фьючерсные позиции"""
return self.ProcessRequest({'data': '', 'id': TransId, 'cmd': 'getFuturesHolding', 't': ''})
# 3.7 Функция для получения информации по инструменту
def GetSecurityInfo(self, ClassCode, SecCode, TransId=0): # 1
"""Информация по инструменту"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'getSecurityInfo', 't': ''})
# Функция для получения информации по инструменту QuikSharp
def GetSecurityInfoBulk(self, ClassCodes, SecCodes, TransId=0):
"""Информация по инструментам"""
return self.ProcessRequest({'data': f'{ClassCodes}|{SecCodes}', 'id': TransId, 'cmd': 'getSecurityInfoBulk', 't': ''})
def GetSecurityClass(self, ClassesList, SecCode, TransId=0):
"""Класс по коду инструмента из заданных классов"""
return self.ProcessRequest({'data': f'{ClassesList}|{SecCode}', 'id': TransId, 'cmd': 'getSecurityClass', 't': ''})
# 3.8 Функция для получения даты торговой сессии
# getTradeDate - 1. Дата текущей торговой сессии
# 3.9 Функция для получения стакана по указанному классу и инструменту
def GetQuoteLevel2(self, ClassCode, SecCode, TransId=0): # 1
"""Стакан по классу и инструменту"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'GetQuoteLevel2', 't': ''})
# 3.10 Функции для работы с графиками
# getLinesCount - 1. Кол-во линий в графике
def GetNumCandles(self, Tag, TransId=0): # 2
"""Кол-во свечей по тэгу"""
return self.ProcessRequest({'data': Tag, 'id': TransId, 'cmd': 'getNumCandles', 't': ''})
# getCandlesByIndex - 3. Информация о свечках (реализовано в get_candles)
# CreateDataSource - 4. Создание источника данных c функциями: (реализовано в get_candles_from_data_source)
# - SetUpdateCallback - Привязка функции обратного вызова на изменение свечи
# - O, H, L, C, V, T - Функции получения цен, объемов и времени
# - Size - Функция кол-ва свечек в источнике данных
# - Close - Функция закрытия источника данных. Терминал прекращает получать данные с сервера
# - SetEmptyCallback - Функция сброса функции обратного вызова на изменение свечи
# Функции для работы с графиками QuikSharp
def GetCandles(self, Tag, Line, FirstCandle, Count, TransId=0):
"""Свечки по идентификатору графика"""
return self.ProcessRequest({'data': f'{Tag}|{Line}|{FirstCandle}|{Count}', 'id': TransId, 'cmd': 'get_candles', 't': ''})
def GetCandlesFromDataSource(self, ClassCode, SecCode, Interval, Count): # ichechet - Добавлен выход по таймауту
"""Свечки"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{Interval}|{Count}', 'id': '1', 'cmd': 'get_candles_from_data_source', 't': ''})
def SubscribeToCandles(self, ClassCode, SecCode, Interval, TransId=0):
"""Подписка на свечки"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{Interval}', 'id': TransId, 'cmd': 'subscribe_to_candles', 't': ''})
def IsSubscribed(self, ClassCode, SecCode, Interval, TransId=0):
"""Есть ли подписка на свечки"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{Interval}', 'id': TransId, 'cmd': 'is_subscribed', 't': ''})
def UnsubscribeFromCandles(self, ClassCode, SecCode, Interval, TransId=0):
"""Отмена подписки на свечки"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{Interval}', 'id': TransId, 'cmd': 'unsubscribe_from_candles', 't': ''})
# 3.11 Функции для работы с заявками
def SendTransaction(self, Transaction, TransId=0): # 1
"""Отправка транзакции в торговую систему"""
return self.ProcessRequest({'data': Transaction, 'id': TransId, 'cmd': 'sendTransaction', 't': ''})
# CalcBuySell - 2. Максимальное кол-во лотов в заявке
# 3.12 Функции для получения значений таблицы "Текущие торги"
def GetParamEx(self, ClassCode, SecCode, ParamName, TransId=0): # 1
"""Таблица текущих торгов"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{ParamName}', 'id': TransId, 'cmd': 'getParamEx', 't': ''})
def GetParamEx2(self, ClassCode, SecCode, ParamName, TransId=0): # 2
"""Таблица текущих торгов по инструменту с возможностью отказа от получения"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{ParamName}', 'id': TransId, 'cmd': 'getParamEx2', 't': ''})
# Функция для получения значений таблицы "Текущие торги" QuikSharp
def GetParamEx2Bulk(self, ClassCodes, SecCodes, ParamNames, TransId=0):
"""Таблица текущих торгов по инструментам с возможностью отказа от получения"""
return self.ProcessRequest({'data': f'{ClassCodes}|{SecCodes}|{ParamNames}', 'id': TransId, 'cmd': 'getParamEx2Bulk', 't': ''})
# 3.13 Функции для получения параметров таблицы "Клиентский портфель"
def GetPortfolioInfo(self, FirmId, ClientCode, TransId=0): # 1
"""Клиентский портфель"""
return self.ProcessRequest({'data': f'{FirmId}|{ClientCode}', 'id': TransId, 'cmd': 'getPortfolioInfo', 't': ''})
def GetPortfolioInfoEx(self, FirmId, ClientCode, LimitKind, TransId=0): # 2
"""Клиентский портфель по сроку расчетов"""
return self.ProcessRequest({'data': f'{FirmId}|{ClientCode}|{LimitKind}', 'id': TransId, 'cmd': 'getPortfolioInfoEx', 't': ''})
# 3.14 Функции для получения параметров таблицы "Купить/Продать"
# getBuySellInfo - 1. Параметры таблицы купить/продать
# getBuySellInfoEx - 2. Параметры таблицы купить/продать с дополнительными полями вывода
# 3.15 Функции для работы с таблицами Рабочего места QUIK
# AddColumn - 1. Добавление колонки в таблицу
# AllocTable - 2. Структура, описывающая таблицу
# Clear - 3. Удаление содержимого таблицы
# CreateWindow - 4. Создание окна таблицы
# DeleteRow - 5. Удаление строки из таблицы
# DestroyTable - 6. Закрытие окна таблицы
# InsertRow - 7. Добавление строки в таблицу
# IsWindowClosed - 8. Закрыто ли окно с таблицей
# GetCell - 9. Данные ячейки таблицы
# GetTableSize - 10. Кол-во строк и столбцов таблицы
# GetWindowCaption - 11. Заголовок окна таблицы
# GetWindowRect - 12. Координаты верхнего левого и правого нижнего углов таблицы
# SetCell - 13. Установка значения ячейки таблицы
# SetWindowCaption - 14. Установка заголовка окна таблицы
# SetWindowPos - 15. Установка верхнего левого угла, и размеры таблицы
# SetTableNotificationCallback - 16. Установка функции обратного вызова для обработки событий в таблице
# RGB - 17. Преобразование каждого цвета в одно число для функци SetColor
# SetColor - 18. Установка цвета ячейки, столбца или строки таблицы
# Highlight - 19. Подсветка диапазона ячеек цветом фона и цветом текста на заданное время с плавным затуханием
# SetSelectedRow - 20. Выделение строки таблицы
# 3.16 Функции для работы с метками
def AddLabel(self, Price, CurDate, CurTime, Qty, Path, LabelId, Alignment, Background, TransId=0): # 1
"""Добавление метки на график"""
return self.ProcessRequest({'data': f'{Price}|{CurDate}|{CurTime}|{Qty}|{Path}|{LabelId}|{Alignment}|{Background}', 'id': TransId, 'cmd': 'AddLabel', 't': ''})
def DelLabel(self, ChartTag, LabelId, TransId=0): # 2
"""Удаление метки с графика"""
return self.ProcessRequest({'data': f'{ChartTag}|{LabelId}', 'id': TransId, 'cmd': 'DelLabel', 't': ''})
def DelAllLabels(self, ChartTag, TransId=0): # 3
"""Удаление всех меток с графика"""
return self.ProcessRequest({'data': ChartTag, 'id': TransId, 'cmd': 'DelAllLabels', 't': ''})
def GetLabelParams(self, ChartTag, LabelId, TransId=0): # 4
"""Получение параметров метки"""
return self.ProcessRequest({'data': f'{ChartTag}|{LabelId}', 'id': TransId, 'cmd': 'GetLabelParams', 't': ''})
# SetLabelParams - 5. Установка параметров метки
# 3.17 Функции для заказа стакана котировок
def SubscribeLevel2Quotes(self, ClassCode, SecCode, TransId=0): # 1
"""Подписка на стакан по Классу|Коду бумаги"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'Subscribe_Level_II_Quotes', 't': ''})
def UnsubscribeLevel2Quotes(self, ClassCode, SecCode, TransId=0): # 2
"""Отмена подписки на стакан по Классу|Коду бумаги"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'Unsubscribe_Level_II_Quotes', 't': ''})
def IsSubscribedLevel2Quotes(self, ClassCode, SecCode, TransId=0): # 3
"""Есть ли подписка на стакан по Классу|Коду бумаги"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}', 'id': TransId, 'cmd': 'IsSubscribed_Level_II_Quotes', 't': ''})
# 3.18 Функции для заказа параметров Таблицы текущих торгов
def ParamRequest(self, ClassCode, SecCode, ParamName, TransId=0): # 1
"""Заказ получения таблицы текущих торгов по инструменту"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{ParamName}', 'id': TransId, 'cmd': 'paramRequest', 't': ''})
def CancelParamRequest(self, ClassCode, SecCode, ParamName, TransId=0): # 2
"""Отмена заказа получения таблицы текущих торгов по инструменту"""
return self.ProcessRequest({'data': f'{ClassCode}|{SecCode}|{ParamName}', 'id': TransId, 'cmd': 'cancelParamRequest', 't': ''})
# Функции для заказа параметров Таблицы текущих торгов QuikSharp
def ParamRequestBulk(self, ClassCodes, SecCodes, ParamNames, TransId=0):
"""Заказ получения таблицы текущих торгов по инструментам"""
return self.ProcessRequest({'data': f'{ClassCodes}|{SecCodes}|{ParamNames}', 'id': TransId, 'cmd': 'paramRequestBulk', 't': ''})
def CancelParamRequestBulk(self, ClassCodes, SecCodes, ParamNames, TransId=0):
"""Отмена заказа получения таблицы текущих торгов по инструментам"""
return self.ProcessRequest({'data': f'{ClassCodes}|{SecCodes}|{ParamNames}', 'id': TransId, 'cmd': 'cancelParamRequestBulk', 't': ''})
# 3.19 Функции для получения информации по единой денежной позиции
def GetTrdAccByClientCode(self, FirmId, ClientCode, TransId=0): # 1
"""Торговый счет срочного рынка по коду клиента фондового рынка"""
return self.ProcessRequest({'data': f'{FirmId}|{ClientCode}', 'id': TransId, 'cmd': 'getTrdAccByClientCode', 't': ''})
def GetClientCodeByTrdAcc(self, FirmId, TradeAccountId, TransId=0): # 2
"""Код клиента фондового рынка с единой денежной позицией по торговому счету срочного рынка"""
return self.ProcessRequest({'data': f'{FirmId}|{TradeAccountId}', 'id': TransId, 'cmd': 'getClientCodeByTrdAcc', 't': ''})
def IsUcpClient(self, FirmId, Client, TransId=0): # 3
"""Имеет ли клиент единую денежную позицию"""
return self.ProcessRequest({'data': f'{FirmId}|{Client}', 'id': TransId, 'cmd': 'IsUcpClient', 't': ''})
# Выход и закрытие
def CloseConnectionAndThread(self):
"""Закрытие соединения для запросов и потока обработки функций обратного вызова"""
self.socketRequests.close() # Закрываем соединение для запросов
self.callbackThread.process = False # Поток обработки функций обратного вызова больше не нужен
def __exit__(self, exc_type, exc_val, exc_tb):
"""Выход из класса, например, с with"""
self.CloseConnectionAndThread() # Закрываем соединение для запросов и поток обработки функций обратного вызова
def __del__(self):
self.CloseConnectionAndThread() # Закрываем соединение для запросов и поток обработки функций обратного вызова

81
README.md Normal file
View File

@@ -0,0 +1,81 @@
# QuikPy
Библиотека-обертка, которая позволяет получить доступ к функционалу Quik на основе [Документации по языку LUA в QUIK](https://arqatech.com/ru/support/files/) из Python. В качестве коннектора используются lua-скрипты [проекта QUIKSharp](https://github.com/finsight/QUIKSharp).
### Для чего нужна
С помощью этой библиотеки можно создавать автоматические торговые системы любой сложности на Python для Quik. Также библиотека может быть использована для написания дополнений на Python к системам Технического Анализа. Например, для тестирования и автоматической торговли в [BackTrader](https://www.backtrader.com/).
### Установка коннектора. Метод 1. Из этого репозитория
1. Скопируйте папку **QUIK\lua** в папку установки Quik. В ней находятся скрипты LUA.
2. Скопируйте папку **QUIK\socket** в папку установки Quik.
3. Запустите Quik. Из меню **Сервисы** выберите **Lua скрипты**. Нажмите кнопку **Добавить**. Выберете скрипт **QuikSharp.lua** Нажмите кнопку **OK**. Выделите скрипт из списка. Нажмите кнопку **Запустить**.
Скрипт должен запуститься без ошибок, в окне сообщений Quik выдать **QUIK# is waiting for client connection...**
### Установка коннектора. Метод 2. Из оригинального [репозитория QuikSharp](https://github.com/finsight/QUIKSharp/tree/master/src/QuikSharp/lua)
1. В файле **config.json** замените строки "responseHostname": "127.0.0.1" на "responseHostname": "*" Иначе удаленный компьютер с Quik не будет отвечать на запросы.
2. В файле **qsfunctions.lua** замените функцию **qsfunctions.getFuturesHolding(msg)** на:
--- (ichechet) Через getFuturesHolding позиции не приходили. Пришлось сделать обработку таблицы futures_client_holding
function qsfunctions.getFuturesHolding(msg)
if msg.data ~= "" then
local spl = split(msg.data, "|")
local firmId, accId, secCode, posType = spl[1], spl[2], spl[3], spl[4]
end
local fchs = {}
for i = 0, getNumberOf("futures_client_holding") - 1 do
local fch = getItem("futures_client_holding", i)
if msg.data == "" or (fch.firmid == firmId and fch.trdaccid == accId and fch.sec_code == secCode and fch.type == posType*1) then
table.insert(fchs, fch)
end
end
msg.data = fchs
return msg
end
Иначе, фьючерсные позиции приходить не будут.
3. В файле **qsfunctions.lua** дополните функцию **qsfunctions.get_candles_from_data_source(msg)**
--- Возвращаем все свечи по заданному инструменту и интервалу
--- (ichechet) Если исторические данные по тикеру не приходят, то QUIK блокируется. Чтобы это не происходило, вводим таймаут
function qsfunctions.get_candles_from_data_source(msg)
local ds, is_error = create_data_source(msg)
if not is_error then
--- Источник данных изначально приходит пустым. Нужно подождать пока он заполнится данными. Бесконечно ждать тоже нельзя. Вводим таймаут
local s = 0 --- Будем ждать 5 секунд, прежде чем вернем таймаут
repeat --- Ждем
sleep(100) --- 100 миллисекунд
s = s + 100 --- Запоминаем кол-во прошедших миллисекунд
until (ds:Size() > 0 or s > 5000) --- До тех пор, пока не придут данные или пока не наступит таймаут
local count = tonumber(split(msg.data, "|")[4]) --- возвращаем последние count свечей. Если равен 0, то возвращаем все доступные свечи.
local class, sec, interval = get_candles_param(msg)
local candles = {}
local start_i = count == 0 and 1 or math.max(1, ds:Size() - count + 1)
for i = start_i, ds:Size() do
local candle = fetch_candle(ds, i)
candle.sec = sec
candle.class = class
candle.interval = interval
table.insert(candles, candle)
end
ds:Close()
msg.data = candles
end
return msg
end
Иначе, если исторические данные по тикеру Quik не возвращает, то он блокируется, дальнейшая работа невозможна.
### Начало работы
В папке Examples находится хорошо документированный код примеров. С них лучше начать разбираться с библиотекой.
1. **Connect.py** - Подключение, Singleton класс, проверка соединения, сервисные функции, пользователь обработчик событий.
2. **Accounts.py** - Список всех торговых счетов с лимитами, позициями, заявками и стоп заявками. Аналогично для заданного торгового счета.
3. **Ticker.py** - Информация о тикере, получение свечек.
4. **Stream.py** - Подписки на получение стакана, обезличенные сделки, новые свечки.
5. **Transactions.py** - Выставление новой лимитной/рыночной заявки, стоп заявки, отмена заявки.
### Авторство и право использования
Автор данной библиотеки Чечет Игорь Александрович. Библиотека написана в рамках проекта [Финансовая Лаборатория](https://chechet.org/) и предоставляется бесплатно. При распространении ссылка на автора и проект обязательны.
### Что дальше
Исправление ошибок, доработка и развитие библиотеки осуществляется как автором, так и сообществом проекта [Финансовая Лаборатория](https://chechet.org/).