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