
通常我们在回测时使用的是单一频率的数据,比如:日线、分钟线等。有些策略是需要结合多个周期的数据进行决策的。这个时候应该怎么办呢。
其实backtrader本身就支持多周期回测,在通常情况下,最简单的方式就是同时提供多个维度的数据源,backtrader会选择最先提供给它的数据源的时间周期作为策略执行的时间周期。
比如我有1分钟线和日线,想实现这样的一个日内择时的策略:若当前分钟线的收盘价小于前一天的日线最低价则买入,若当前分钟线的收盘价大于前一天的最高价则卖出,怎么做呢?
可以这样,把1分钟线和日线都添加到cerebro中,然后策略启动后,每次执行next方法时self.datas都会调用下一根分钟线和分钟线所在日期的日线,这样在next方法中只用考虑当前分钟线和前一天日线就可以了。
下面是具体的代码,用的恒生科技ETF2024年3月份的数据:
main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import backtrader as bt
import strategy
cerebro = bt.Cerebro()
cerebro.addstrategy(strategy.MyStrategy)
data0=bt.feeds.BacktraderCSVData(dataname='SHSE.513180_1min.csv',timeframe=bt.TimeFrame.Minutes) data1=bt.feeds.BacktraderCSVData(dataname='SHSE.513180_1day.csv',timeframe=bt.TimeFrame.Days)
cerebro.adddata(data0) cerebro.adddata(data1)
cerebro.broker.setcash(100000)
print('初始时资金持仓:%.2f' % cerebro.broker.getvalue()) cerebro.run() print('结束时资金持仓:%.2f' % cerebro.broker.getvalue()) cerebro.plot(style='candle',barup='red',bardown='green',volup='red',voldown='green',voloverlay=False,valuetags=False)
|
strategy.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import backtrader as bt class MyStrategy(bt.Strategy): def log(self, txt, dt=None): dt = dt or self.datas[0].datetime.date(0) bartim=bt.utils.num2date(self.datas[0].datetime[0]) print('%s, %s' % (bartim, txt))
def __init__(self): self.order = None self.buyprice = None self.buycomm = None
def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: return
if order.status in [order.Completed]: if order.isbuy(): self.log( '买入已执行,价格为:%.3f,花费:%.2f,佣金:%.2f' % (order.executed.price, order.executed.value, order.executed.comm)) self.buyprice = order.executed.price self.buycomm = order.executed.comm else: self.log( '卖出已执行,价格为:%.3f,花费:%.2f,佣金:%.2f' % (order.executed.price, order.executed.value, order.executed.comm))
self.bar_executed = len(self) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade): if not trade.isclosed: return
self.log('当前盈亏 %.2f ,去除佣金后的盈亏 %.2f' % (trade.pnl, trade.pnlcomm))
def next(self): if self.order: return if self.data0.close[0] < self.data1.low[0]: if not self.position: vol = self.broker.getvalue()*0.9 / self.data0.close[0] // 100 * 100 self.log('创建买单,%.3f,量为:%d' % (self.data0.close[0] , vol)) self.order=self.buy(size=vol) elif self.data0.close[0]>self.data1.high[0]: if self.position: self.log('创建卖单,%.3f,量为:%d' % (self.data0.close[0] , self.position.size)) self.order=self.sell(size=self.position.size)
|
分钟线数据:

日线数据:

回测结果:


可以看到上方密集的图是分钟线,下方细条的是日线。为什么日线会变得这么细长呢?在多周期回测时,日线也会用分钟线的宽度表示,不知道这算不算bug。放大看是这样子的:

在多周期回测时,长周期的k线只会在其所在的周期结束后才可用,也就是如果今天是3月5日的9点30分,则1分钟线self.data0用的是9点29分开始,9点30分结束的k线,而日线self.data1用的是3月4日的日线,因为3月5日还没过完。
这一篇就到这里啦。欢迎大家点赞、转发、私信。还没有关注我的朋友可以关注 江达小记
