Initial commit
This commit is contained in:
33
Examples/01 - Connect.py
Normal file
33
Examples/01 - Connect.py
Normal 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
125
Examples/02 - Accounts.py
Normal 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
38
Examples/03 - Ticker.py
Normal 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
51
Examples/04 - Stream.py
Normal 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 из любого экземпляра
|
||||||
90
Examples/05 - Transactions.py
Normal file
90
Examples/05 - Transactions.py
Normal 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
140
QUIK/lua/QuikSharp.lua
Normal 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
15
QUIK/lua/Quik_2.lua
Normal 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
18
QUIK/lua/config.json
Normal 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
714
QUIK/lua/dkjson.lua
Normal 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
235
QUIK/lua/qscallbacks.lua
Normal 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
940
QUIK/lua/qsfunctions.lua
Normal 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
295
QUIK/lua/qsutils.lua
Normal 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
149
QUIK/lua/socket.lua
Normal 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
BIN
QUIK/socket/core.dll
Normal file
Binary file not shown.
588
QuikPy.py
Normal file
588
QuikPy.py
Normal 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
81
README.md
Normal 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/).
|
||||||
Reference in New Issue
Block a user