xiaohe wrote:
- 你的日线数据是几点开始的呢?
我在github上提交了issue,具体重现方式可以查看issue上的描述。很奇怪的就是日数据的时间都是00:00的,只要把时间的类型从datetime.date
变为datetime.datetime
就正常了。
加载数据周期:日K线
数据库:sqlite
今天偶然发现的,自己写的脚本回测和官方图形化引擎的回测结果有出入,加载的是从2015年1月5日至今的数据。细查发现官方回测引擎加载的数据少了不少天数,例如2015年2月4日的日K线数据就没有加载。最后定位到下方的源码:
# vnpy/app/cta_backtester/ui/widget.py BacktesterManager start_backtesting()
start = self.start_date_edit.date().toPyDate()
end = self.end_date_edit.date().toPyDate()
官方回测引擎的load_data()
方法是按30天为周期加载数据的,而2015年2月4日刚好是2015年1月5日开始的第30天,在边界值上。这里是将图形化界面了日期编辑栏转换为Python的datetime.date
类型。不知道为什么导致了漏数据。我将其修改为
# vnpy/app/cta_backtester/ui/widget.py BacktesterManager start_backtesting()
start = self.start_date_edit.dateTime().toPyDateTime()
end = self.end_date_edit.dateTime().toPyDateTime()
转换为datetime.datetime
类型,官方图形化回测引擎加载的数据量就和自己的脚本一致了,2015年2月4日的K线也能看到了。不清楚其它数据周期会不会存在这个问题。
字面意思,这是说你的IP不是教育网的IP吧,学术账号必须通过教育网来访问RQData的接口
官方图形化回测界面是vnpy/app/cta_backtester/ui/widget.py
中的BacktesterManager
。__init__()
方法最后调用逻辑是这样的:
# vnpy/app/cta_backtester/ui/widget.py BacktesterManager __init__()
self.init_ui() # 初始化界面
self.register_event()
self.backtester_engine.init_engine()
self.init_strategy_settings() # 初始化策略配置
在init_ui()
方法里有一段代码似乎是从cta_backtester_setting.json
中读取上一次回测时使用的策略,配置为默认策略:
# vnpy/app/cta_backtester/ui/widget.py BacktesterManager init_ui()
# Load setting
setting = load_json(self.setting_filename)
if not setting:
return
self.class_combo.setCurrentIndex(
self.class_combo.findText(setting["class_name"])
)
但self.class_combo
此时还没有添加任何策略选项,所以self.class_combo.findText(setting["class_name"])
始终返回的是-1
,没有实现设置当前策略为上一次回测策略的效果。
self.class_combo
是在init_strategy_settings()
方法里才添加了策略选项:
# vnpy/app/cta_backtester/ui/widget.py BacktesterManager
def init_strategy_settings(self):
""""""
self.class_names = self.backtester_engine.get_strategy_class_names()
for class_name in self.class_names:
setting = self.backtester_engine.get_default_setting(class_name)
self.settings[class_name] = setting
self.class_combo.addItems(self.class_names) # 给`self.class_combo`添加策略选项
所以可能应该将设置当前策略为上一次回测策略的代码改到init_strategy_settings()
方法里:
def init_strategy_settings(self):
""""""
self.class_names = self.backtester_engine.get_strategy_class_names()
for class_name in self.class_names:
setting = self.backtester_engine.get_default_setting(class_name)
self.settings[class_name] = setting
self.class_combo.addItems(self.class_names) # 给`self.class_combo`添加策略选项
# Load setting
setting = load_json(self.setting_filename)
# 设置当前策略为上一次回测策略
self.class_combo.setCurrentIndex(
self.class_combo.findText(setting["class_name"])
)
今天回测的策略需要用到日K线,仿照默认BarGenerator
里update_bar()
方法的小时单位K线合成的逻辑,再下面添加了合成日K线的代码:
# vnpy/trader/utility.py BarGenerator update_bar()
elif self.interval == Interval.HOUR:
if self.last_bar and bar.datetime.hour != self.last_bar.datetime.hour:
# 1-hour bar
if self.window == 1:
finished = True
# x-hour bar
else:
self.interval_count += 1
if not self.interval_count % self.window:
finished = True
self.interval_count = 0
elif self.interval == Interval.DAILY:
# 以下是我添加的
if self.last_bar and bar.datetime.day != self.last_bar.datetime.day:
if self.window == 1:
finished = True
else:
self.interval_count += 1
if not self.interval_count % self.window:
finished = True
self.interval_count = 0
回测发现合成的日K线和从其它平台获取的有出入,特别是开盘价和收盘价都完全不同。仔细看了一下update_bar()
方法逻辑,发现在判断是否合成完毕前,函数会将最新的bar
的数据添加到self.window_bar
里。这里貌似存在以下问题,例如我要合成1小时K线,那么9点开始的这个1小时K线的时间范围应该(按我理解)是从9:00到9:59。在发现新的bar
的bar.datetime.hour
值不是9
而是10
时,9点的K线合成完毕,下面是合成10点这1小时的K线。
但是update_bar()
方法首先将最新的bar
的数据添加到self.window_bar
里,再进行判断,会导致10点的第1个bar
被计算到了9点的1小时K线里。这也是为什么我直接在下面添加合成日线的逻辑,出来的日线数据会和实际数据的开盘、收盘价完全不同的原因,因为每次的开盘bar
都被算到了前1个合成K线的收盘bar
里,而收盘bar
都算进了下一个合成K线的开盘bar
里。
我想可能将判断是否合成完毕的代码放到update_bar()
方法开头会比较合理,但我不知道自己的想法有没有错,纯作交流:
# vnpy/trader/utility.py BarGenerator
def update_bar(self, bar: BarData) -> None:
"""
Update 1 minute bar into generator
"""
if self.window_bar is not None:
finished = False
if self.interval == Interval.MINUTE:
# 对于分钟,只能合成60分钟以内的K线
# 不知道这么写对不对
if not (bar.datetime.minute) % self.window:
finished = True
elif self.interval == Interval.HOUR:
if self.last_bar and bar.datetime.hour != self.last_bar.datetime.hour:
# 1-hour bar
if self.window == 1:
finished = True
# x-hour bar
else:
self.interval_count += 1
if not self.interval_count % self.window:
finished = True
self.interval_count = 0
elif self.interval == Interval.DAILY:
if self.last_bar and bar.datetime.day != self.last_bar.datetime.day:
if self.window == 1:
finished = True
else:
self.interval_count += 1
if not self.interval_count % self.window:
finished = True
self.interval_count = 0
if finished:
self.on_window_bar(self.window_bar)
self.window_bar = None
# If not inited, creaate window bar object
if not self.window_bar:
# Generate timestamp for bar data
if self.interval == Interval.MINUTE:
dt = bar.datetime.replace(second=0, microsecond=0)
elif self.interval == Interval.HOUR:
dt = bar.datetime.replace(minute=0, second=0, microsecond=0)
elif self.interval == Interval.DAILY:
dt = bar.datetime.replace(hour=0, minute=0, second=0, microsecond=0)
self.window_bar = BarData(
symbol=bar.symbol,
exchange=bar.exchange,
datetime=dt,
gateway_name=bar.gateway_name,
open_price=bar.open_price,
high_price=bar.high_price,
low_price=bar.low_price
)
# Otherwise, update high/low price into window bar
else:
self.window_bar.high_price = max(
self.window_bar.high_price, bar.high_price)
self.window_bar.low_price = min(
self.window_bar.low_price, bar.low_price)
# Update close price/volume into window bar
self.window_bar.close_price = bar.close_price
self.window_bar.volume += int(bar.volume)
self.window_bar.open_interest = bar.open_interest
# Cache last bar object
self.last_bar = bar
另外按照回测引擎的逻辑,会导致最后1天的日K线无法合成出来,比如我要回测4月20日到4月27日,由于回测时4月27日没有bar
数据进入,就会导致4月26日的日K线生成条件bar.datetime.day != self.last_bar.datetime.day
不会满足,即使4月26日的数据跑完了,也不会出来4月26日的日K线数据(小时K线也是同理的),不知道这算不算bug。
手动把open_interest
这个字段去掉貌似可以正常跑回测。。。
只能多调用write_log
来排查了
今天想试一下股票回测,但报open_interest
字段非法的错误。想提问的是vnpy自带的回测平台只支持期货回测吗?是否可以支持股票回测?
过程是这样的:回测图形界面里填写的本地代码是688166.SSE
。
然后点下载数据,就报错了:
意思是rqdata返回说open_interest
字段非法。
查看了一下回测引擎从rqdata获取数据的代码。发现vnpy.trader.rqdata.RqdataClient
的query_history()
方法里,从rqdata请求的字段是写死的:
# vnpy.trader.rqdata.RqdataClient query_history()
# ...
df = rqdata_get_price(
rq_symbol,
frequency=rq_interval,
fields=["open", "high", "low", "close", "volume", "open_interest"],
start_date=start,
end_date=end,
adjust_type="none"
)
固定请求["open", "high", "low", "close", "volume", "open_interest"]
。
查看rqdata的官方文档,open_interest
字段是一个期货专用的字段:
单独测试rqdata的代码,不请求open_interest
字段,数据是能正常返回不报错的,但带上就会报同样的错误:
import rqdatac as rq
from rqdatac import *
rq.init()
# 下面这句代码不报错
rq.get_price('688166.XSHG', frequency='1m', fields=["open", "high", "low", "close", "volume"],start_date='2020-01-01', end_date='2020-02-25', adjust_type='none')
# 下面这句代码报错
rq.get_price('688166.XSHG', frequency='1m', fields=["open", "high", "low", "close", "volume", "open_interest"], start_date='2020-01-01', end_date='2020-02-25', adjust_type='none')
所以想提问的是vnpy自带的回测平台只支持期货回测吗?是否可以支持股票回测?