可能存在问题的代码块如下
if self.last_dt and self.last_dt.minute != tick.datetime.minute:
for bar in self.bars.values():
bar.datetime = bar.datetime.replace(second=0, microsecond=0)
self.on_bars(self.bars)
self.bars = {}
bar = self.bars.get(tick.vt_symbol, None)
if not bar:
bar = BarData(
symbol=tick.symbol,
exchange=tick.exchange,
interval=Interval.MINUTE,
datetime=tick.datetime,
gateway_name=tick.gateway_name,
open_price=tick.last_price,
high_price=tick.last_price,
low_price=tick.last_price,
close_price=tick.last_price,
open_interest=tick.open_interest
)
self.bars[bar.vt_symbol] = bar
else:
bar.high_price = max(bar.high_price, tick.last_price)
bar.low_price = min(bar.low_price, tick.last_price)
bar.close_price = tick.last_price
bar.open_interest = tick.open_interest
bar.datetime = tick.datetime
last_tick = self.last_ticks.get(tick.vt_symbol, None)
if last_tick:
bar.volume += max(tick.volume - last_tick.volume, 0)
bar.turnover += max(tick.turnover - last_tick.turnover, 0)
self.last_ticks[tick.vt_symbol] = tick
self.last_dt = tick.datetime
其中的基本逻辑为当tick的datetime标识中的minute改变时,就将已经缓存好的self.bars推出(不包含当前tick),并将self.bars清空,当前tick进入self.bars。
在这种逻辑下导致了如下问题,以下截图为实盘打印,使用品种SA209.CZCE、FG209.CZCE、MA209.CZCE
截图中开头为@NormTick的是在on_tick首行进行打印,开头为@minbar是在on_bars首行进行打印,可梳理出运行逻辑如下:
- 14:47:00的甲醇tick进入到on_tick之后被PortfolioBarGenerator.update_tick认为满足分钟已发生切换的条件(self.last_dt.minute != tick.datetime.minute,46变为47), 所以触发strategy.on_bars将缓存好所有品种的bars全部推出,所以看到了三条开头为@minbar的输出,此时PortfolioBarGenerator缓存的bars被清空并且放入了这条14:47:00的甲醇tick
- 下一时刻,14:46:59的纯碱tick进入到on_tick之后,update_tick再次认为满足分钟已发生切换的条件(self.last_dt.minute != tick.datetime.minute,47变为46),所以再次触发strategy.on_bars将缓存的bars(有且仅有14:47:00的甲醇tick)推出,所以看到了1条开头为@minbar的输出
- 下一时刻,14:47:00的玻璃tick进入on_tick,update_tick再次认为满足分钟已发生切换的条件(self.last_dt.minute != tick.datetime.minute,46变为47),所以再次触发strategy.on_bars将缓存的bars(有且仅有14:56:59的纯碱tick)推出,所以看到了1条开头为@minbar的输出
在论坛中找到解决类似问题的帖子:https://www.vnpy.com/forum/topic/7028-portfolio-strategy-zhong-on-tick-han-shu-zhong-geng-xin-bars-de-luo-ji-shi-cuo-wu-de-xiu-zheng-ban-de-luo-ji-ru-xia
基本思想为将 self.last_dt.minute != tick.datetime.minute 修改为 self.last_dt.minute < tick.datetime.minute,这样的确可以避免on_bars反复运行,但是在我的例子中会将14:46:59的纯碱tick包裹在14:47的min bar中,虽说数据仍然不准确,但好在没有很离谱(一个min bar实则只包含了一个tick)
目前正在思考更完善的解决方案