今天回测的策略需要用到日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。