16.14、异步任务:
16.14.1、使用协程任务:
函数create_task()用来创建协程任务,并将任务加入事件循环以实现异步并发。
wait_update()不能用在协程中,若在协程中等待业务更新,可调用register_update_notify函数把业务数据注册到TqChan,当业务数据有更新时会通知该TqChan,在协程里就可以用实时更新的业务数据运算。例如:
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("信易账号", "密码"))
quote1 = api.get_quote("CFFEX.T2103")
quote2 = api.get_quote("CFFEX.TF2103")
#带有业务更新的协程
async def demo(quote):
#将quote注册到TqChan命名为update_chan
async with api.register_update_notify(quote) as update_chan:
async for _ in update_chan: #遍历队列通知
print('品种:',quote.instrument_name,'最新价:',quote.last_price)
#无业务更新的协程
async def func():
return quote1.instrument_name,quote2.instrument_name
# 创建task1、task2,把quote1、quote2注册到TqChan
task1=api.create_task(demo(quote1))
task2=api.create_task(demo(quote2))
#把带有返回值的协程创建成task3
task3=api.create_task(func())
while True:
api.wait_update()
if task3.done(): #task3结束后,获取返回值
print(task3.result())
'''输出结果为:('债十2103', '债五2103')品种: 债十2103 最新价: 97.435('债十2103', '债五2103')品种: 债十2103 最新价: 97.435('债十2103', '债五2103')品种: 债十2103 最新价: 97.43('债十2103', '债五2103')品种: 债十2103 最新价: 97.43'''
16.14.2、使用多线程:
当用户策略实例很多,导致网络连接数无法容纳时,可以使用多线程。首先需要在主线程中创建一个 TqApi 实例 api_master,并用 TqApi.copy 函数创建多个slave副本,把slave副本用在多个线程中,主线程里的api_master 仍然需要持续调用 wait_update。
子线程和主线程其实是运行在同一个事件循环里,如果在子线程里调用api_slave.close()会引发主线程事件循环关闭的异常,如果主线程里调用api_master.close(),子线程可能因等待事件循环响应而阻塞,若想让子线程和主线程一起退出,可设置子线程为守护线程。
使用多线程需要自定义一个线程类,并重写run函数,在run函数里执行策略代码,例如:
import threading
from tqsdk import TqApi, TqAuth
#自定义线程类
class WorkerThread(threading.Thread):
def __init__(self, api, symbol):
threading.Thread.__init__(self)
self.api = api #初始化参数
self.symbol = symbol #初始化参数
#重写run函数,策略代码写在run函数中
def run(self):
SHORT = 30 # 短周期
LONG = 60 # 长周期
data_length = LONG + 2 # k线数据长度
klines = self.api.get_kline_serial(self.symbol, duration_seconds=60, data_length=data_length)
target_pos = TargetPosTask(self.api, self.symbol)
while True:
self.api.wait_update()
if self.api.is_changing(klines.iloc[-1], "datetime"): # 产生新k线:重新计算SMA
short_avg = ma(klines["close"], SHORT) # 短周期
long_avg = ma(klines["close"], LONG) # 长周期
if long_avg.iloc[-2] < short_avg.iloc[-2] and long_avg.iloc[-1] > short_avg.iloc[-1]:
target_pos.set_target_volume(-3)
print("均线下穿,做空")
if short_avg.iloc[-2] < long_avg.iloc[-2] and short_avg.iloc[-1] > long_avg.iloc[-1]:
target_pos.set_target_volume(3)
print("均线上穿,做多")
if __name__ == "__main__":
#主线程创建TqApi实例
api_master = TqApi(auth=TqAuth("信易账号", "密码"))
# 实例化线程类,传入TqApi实例的副本api_master.copy()
thread1 = WorkerThread(api_master.copy(), "SHFE.cu1901")
thread2 = WorkerThread(api_master.copy(), "SHFE.rb1901")
# 启动线程实例
thread1.start()
thread2.start()
while True:
api_master.wait_update() #主线程保持对wait_update()的调用
当线程太多时,操作系统因调度线程,可能把主要工作都用在了调度线程上,而降低了多线程的效率,更宜使用异步协程实现多策略。
16.14.3、使用多进程:
当程序比较耗CPU时,可以采用多进程,比如回测时,需要对大量的数据计算,可以用多个进程同时回测多个品种,注意: 由于服务器流控限制, 同时执行的回测任务请勿超过10个,例如:
from tqsdk import TqApi, TqAuth, TqSim, TargetPosTask, BacktestFinished, TqBacktest
from tqsdk.tafunc import ma
from datetime import date
import multiprocessing
from multiprocessing import Pool
def MyStrategy(SHORT):
LONG = 60
SYMBOL = "SHFE.cu1907"
acc = TqSim()
try:
api = TqApi(acc, backtest=TqBacktest(start_dt=date(2019, 5, 6), end_dt=date(2019, 5, 10)), auth=TqAuth("信易账户", "账户密码"))
data_length = LONG + 2
klines = api.get_kline_serial(SYMBOL, duration_seconds=60, data_length=data_length)
target_pos = TargetPosTask(api, SYMBOL)
while True:
api.wait_update()
if api.is_changing(klines.iloc[-1], "datetime"):
short_avg = ma(klines.close, SHORT)
long_avg = ma(klines.close, LONG)
if long_avg.iloc[-2] < short_avg.iloc[-2] and long_avg.iloc[-1] > short_avg.iloc[-1]:
target_pos.set_target_volume(-3)
if short_avg.iloc[-2] < long_avg.iloc[-2] and short_avg.iloc[-1] > long_avg.iloc[-1]:
target_pos.set_target_volume(3)
except BacktestFinished:
api.close()
print("SHORT=", SHORT, "最终权益=", acc.