检查回测数据的时候发现以上不正常的交易记录,从管理员 @west 和 @Ringo 那里得知是一个以前就发现的bug有待修复,是因为交易所18点的时候还会传递数据过来,就把这个tick当成了行情,所以交易的时候用这个18点的tick来做了成交。当时发觉哪里不对,策略是按照日k线
if api.is_changing(klines.iloc[-1], "datetime"):
判断后做的交易,再怎么也应该是交易到9点或21点以后的时间的tick上,通过反复试验发现这个问题可能不仅仅是交易所盘后传数据的问题,可能是tqsdk底层代码对日k线处理逻辑存在bug,因为通过日k线调用is_changing datetime就会有这个问题,显示不正常的tick数据时间为:14:59:59或18:00:00,而1分钟,60分钟k线同样是用is_changing datetime做判断当到9点的时候就不会有这个问题并且显示的tick数据时间是正常的 08:59:00.***。这里的原因可能是日k线推进的时候,当tick走到头一天14:59:59的时候就计入到第二天日k线的开盘tick就合成了有问题的日k线,实际上是应该当tick走到08:59:00.***的时候才产生新的一个日k,所以日k线处理开盘bar或is_changing datetime的逻辑按照处理1分钟,60分钟的类似逻辑处理就可以修复了。
并且还发现一个可能也是这个原因导致的问题,就是大周期的日k线用is_changing不能同时和1分钟、60分钟k线用is_changing同时为真,单独1分钟和60分钟是可以的,照理说用is_changing datetime做判断大的粒度应该覆盖小粒度的(小级别k线时间的能在秒单位上被大级别k线整除),应该同一时刻为真的推进。
日k bug复现如下图:
1分钟,60分钟k线正常打印如下图:
以下是图1的代码:
from datetime import date import sys import numpy as np from tqsdk import TqApi, TqAccount, TargetPosTask, BacktestFinished, TqBacktest, TqSim, tafunc def test3(): api = None ticks = None quote = None klines = None order = None trade = None SYMBOL = "DCE.jd2005" target_pos = None try: api = TqApi(backtest=TqBacktest(start_dt=date(2019, 11, 5), end_dt=date(2019, 11, 29)), web_gui="http://127.0.0.1:11111/") ticks = api.get_tick_serial(symbol=SYMBOL) quote = api.get_quote(symbol=SYMBOL) klines = api.get_kline_serial(symbol=SYMBOL, duration_seconds=24 * 3600) klines_1min = api.get_kline_serial(symbol=SYMBOL, duration_seconds=60) klines_60min = api.get_kline_serial(symbol=SYMBOL, duration_seconds=60*60) trade = api.get_trade() target_pos = TargetPosTask(api, SYMBOL) pass except Exception as e: print("进入Exception") pass try: while True: sys.stdout.flush() api.wait_update() if api.is_changing(klines.iloc[-1], "datetime"): #if api.is_changing(klines_1min.iloc[-1], "datetime"): #if api.is_changing(klines_60min.iloc[-1], "datetime") and api.is_changing(klines_1min.iloc[-1], "datetime"): print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-1]["datetime"]), ticks.iloc[-1]["last_price"])) print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-2]["datetime"]), ticks.iloc[-2]["last_price"])) #print(type(ticks.iloc[-2]["last_price"])) #print(str(ticks.iloc[-2]["last_price"])) #print(ticks.iloc[-2]["last_price"]) print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-3]["datetime"]), ticks.iloc[-3]["last_price"])) print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-4]["datetime"]), ticks.iloc[-4]["last_price"])) print("klines_1min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-1]["datetime"])) #print("klines_1min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-2]["datetime"])) print("klines_60min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-1]["datetime"])) #print("klines_60min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-2]["datetime"])) print("klines datetime:", tafunc.time_to_datetime(klines.iloc[-1]["datetime"])) #print("klines datetime:", tafunc.time_to_datetime(klines.iloc[-2]["datetime"])) sys.stdout.flush() buy_open_price = ticks.iloc[-1]["last_price"] + 1 order = api.insert_order(symbol=SYMBOL, direction="BUY", offset="OPEN", volume=1, limit_price=buy_open_price) sys.stdout.flush() #target_pos.set_target_volume(1) #sys.stdout.flush() except BacktestFinished as e: api.close() print("进入BacktestFinished") pass return 123 for _ in range(5): ret = 0 try: test3() except Exception as e: print("Exception!!!!!!!!!!") print(test3())
以下是图2的代码:
from datetime import date import sys import numpy as np from tqsdk import TqApi, TqAccount, TargetPosTask, BacktestFinished, TqBacktest, TqSim, tafunc def test3(): api = None ticks = None quote = None klines = None order = None trade = None SYMBOL = "DCE.jd2005" target_pos = None try: api = TqApi(backtest=TqBacktest(start_dt=date(2019, 11, 5), end_dt=date(2019, 11, 29)), web_gui="http://127.0.0.1:11111/") ticks = api.get_tick_serial(symbol=SYMBOL) quote = api.get_quote(symbol=SYMBOL) klines = api.get_kline_serial(symbol=SYMBOL, duration_seconds=24 * 3600) klines_1min = api.get_kline_serial(symbol=SYMBOL, duration_seconds=60) klines_60min = api.get_kline_serial(symbol=SYMBOL, duration_seconds=60*60) trade = api.get_trade() target_pos = TargetPosTask(api, SYMBOL) pass except Exception as e: print("进入Exception") pass try: while True: sys.stdout.flush() api.wait_update() #if api.is_changing(klines.iloc[-1], "datetime"): #if api.is_changing(klines_1min.iloc[-1], "datetime"): if api.is_changing(klines_60min.iloc[-1], "datetime") and api.is_changing(klines_1min.iloc[-1], "datetime"): print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-1]["datetime"]), ticks.iloc[-1]["last_price"])) print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-2]["datetime"]), ticks.iloc[-2]["last_price"])) #print(type(ticks.iloc[-2]["last_price"])) #print(str(ticks.iloc[-2]["last_price"])) #print(ticks.iloc[-2]["last_price"]) print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-3]["datetime"]), ticks.iloc[-3]["last_price"])) print("ticks datetime:%s tick_last_price:%f" % (tafunc.time_to_datetime(ticks.iloc[-4]["datetime"]), ticks.iloc[-4]["last_price"])) print("klines_1min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-1]["datetime"])) #print("klines_1min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-2]["datetime"])) print("klines_60min datetime:", tafunc.time_to_datetime(klines_60min.iloc[-1]["datetime"])) #print("klines_60min datetime:", tafunc.time_to_datetime(klines_1min.iloc[-2]["datetime"])) print("klines datetime:", tafunc.time_to_datetime(klines.iloc[-1]["datetime"])) #print("klines datetime:", tafunc.time_to_datetime(klines.iloc[-2]["datetime"])) sys.stdout.flush() buy_open_price = ticks.iloc[-1]["last_price"] + 1 order = api.insert_order(symbol=SYMBOL, direction="BUY", offset="OPEN", volume=1, limit_price=buy_open_price) sys.stdout.flush() #target_pos.set_target_volume(1) #sys.stdout.flush() except BacktestFinished as e: api.close() print("进入BacktestFinished") pass return 123 for _ in range(5): ret = 0 try: test3() except Exception as e: print("Exception!!!!!!!!!!") print(test3())
这个函数的实现就很迷…..因为打印所有周期的datetime你可以发现他们是严格按时间顺序推送的,但是大粒度和小粒度的is_changing()就是不能同时判断。所以回测的时候我是人工保存多周期k的datetime自己判断是否更新。
你看下我的例子py代码,1分钟和60分钟k线时可以同时用is_changing判断到时间变化的,这也是符合逻辑的,应该就是日k线的bug。其他周期的也可以测试下。大力度周期要能被小周期的整除才行。