Enfernuz / quik-lua-rpc

RPC-сервис для вызова API Lua-библиотеки торгового терминала QUIK (ARQA Technologies)
Apache License 2.0
104 stars 43 forks source link

Асинхронные вызовы из Python (пример кода) и ACCESS_VIOLATION #61

Closed alexveden closed 2 years ago

alexveden commented 3 years ago

Пытаюсь экспериментировать с библиотекой, и сделать асинхронные вызовы. Сам на Linux Debian и пробую работать как через Wine так и на виртуальной машине под Win7.

В целом асинхронный код работает сносно, только возникает проблема ACCESS_VIOLATION в main.lua когда я запускаю этот запрос на квике в котором открыто много таблиц. В квике без открытых окон получается все ок.

Вопросы к разработчикам:

  1. Поддерживает ли lua в квике многопоточность / асинхронность (судя по всему да)
  2. Как можно отлаживать подобные ACCESS_VIOLATION проблемы? Debug Log? М.б. какие-нибудь логи включить?

Хочу поделиться примером кода как можно асинхронно получать данные из квика через QuikLua:

Тут для сравнения получаем последовательно и асинхронно. Разница для долгих запросов весьма существенна 13 запросов 1-мин истории асинхронно занимают 17сек, они же последовательно около 100сек.

import zmq
import json

print("Connecting to test server")
import time
import asyncio
import zmq
import zmq.asyncio

context = zmq.asyncio.Context()

HOST = 'tcp://127.0.0.1:5560'

async def recv_history(ticker):
    print(f'recv_history({ticker})')
    socket = context.socket(zmq.REQ)
    socket.connect(HOST)
    await socket.send_string(json.dumps({"method":"datasource.CreateDataSource",
                                         "args":{"class_code":"SPBFUT", "sec_code": ticker, "interval":"INTERVAL_M1", "param":""}}))
    datasource_uuid = json.loads(await socket.recv_string())['result']['datasource_uuid']
    socket.send_string('{"method":"datasource.Size","args":{"datasource_uuid":"%s"}}' % (datasource_uuid))
    num_candles = json.loads(await socket.recv_string())['result']['value']
    candle_close = None
    for i in range(0,num_candles):
        socket.send_string('{"method":"datasource.C","args":{"datasource_uuid":"%s","candle_index":%d}}' % (datasource_uuid, i))
        candle_close = json.loads(await socket.recv_string())['result']['value']
        socket.send_string('{"method":"datasource.O","args":{"datasource_uuid":"%s","candle_index":%d}}' % (datasource_uuid, i))
        candle_open = json.loads(await socket.recv_string())['result']['value']
        socket.send_string('{"method":"datasource.T","args":{"datasource_uuid":"%s","candle_index":%d}}' % (datasource_uuid, i))
        candle_time = json.loads(await socket.recv_string())['result'] #['time']

        #print(f'{candle_time}: {candle_close}')

    print(f'{ticker}: #{num_candles} bars, last close: {candle_close}')

async def recv_classes():

    socket = context.socket(zmq.REQ)
    socket.connect(HOST)
    await socket.send_string("{\"method\":\"getClassesList\"}")
    print('Sent recv_classes')
    message = await socket.recv()
    print("Received reply [ %s ]" % (message))

async def recv_and_process():
    socket = context.socket(zmq.REQ)
    socket.connect(HOST)

    print("\nSending request")
    await socket.send_string("{\"method\":\"message\", \"args\":{\"message\":\"Hello!\",\"icon_type\":\"WARNING\"}}")
    message = await socket.recv()
    print("Received reply [ %s ]\n" % (json.loads(message)))

loop = asyncio.get_event_loop()
t_begin = time.time()

print('Gather Async')
loop.run_until_complete(asyncio.gather(
        recv_history('SiH1'),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_classes(),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_classes(),
        recv_history('RIH1'),
        recv_history('SiH1'),
        recv_history('RIH1'),
    ))
print(f'Gather Async Finished in {time.time()-t_begin}')

t_begin = time.time()
print('Run sync')
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_classes())
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_classes())
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
loop.run_until_complete(recv_history('SiH1'))
loop.run_until_complete(recv_history('RIH1'))
print(f'Run sync Finished in {time.time()-t_begin}')
alexveden commented 3 years ago

Quik начинает нещадно кушать память и в конечном итоге выдает в JSON Lua ошибку с кодом 500 'not enough memory'

image

alexveden commented 3 years ago

Похоже разобрался, нужно вызывать, чтобы отчистить ресурсы, иначе квик создает кучу datasources и все это занимает много памяти.

socket.send_string('{"method":"datasource.Close","args":{"datasource_uuid":"%s"}}' % (datasource_uuid))
gojmpz commented 3 years ago

Какая у вас версия quik?