backtrader程序介绍-策略回测用法

backtrader的策略回测初尝

前言

backtrader作为能够在自己的python环境运行的回测程序之一,不得不说很好用。今天进行了初步的学习,稍微进行分享。

一、回测基础步骤

应用backtrader进行回测,首先需要了解backtrader的基础步骤

# 基础步骤
from __future__ import print_function, absolute_import
import backtrader as bt

if __name__== '__main__':
    cerebro=bt.Cerebro()  #实例化了Cerebro引擎,Cerebro引擎在后台创建了一个Broker (代理)实例
    cerebro.broker.setcash(100000.0) #设置broker的金额
    print(f'组合开始时的价值:%.2f'%cerebro.broker.getvalue())  

    cerebro.run()  #调用 了cerebro 实例的 run 方法(里面会遍历数据)
    print(f'组合运行结束后的价值:%.2f'%cerebro.broker.getvalue())
组合开始时的价值:100000.00
组合运行结束后的价值:100000.00

回测的基础步骤比较简单

二、加载交易数据

backtrader默认使用的雅虎财经的数据,但是国内现在好像也用不了雅虎财经数据库,但是不要紧,可以采用自己喜欢的数据集进行数据导入,步骤如下:

# 打印出每天的”收盘价”
# 新建一个策略
class TestStrategy(bt.Strategy):
    def log(self,txt,dt=None): #此策略的日志记录功能
        dt=dt or self.datas[0].datetime.date(0)
        print('%s,%s'%(dt.isoformat(),txt))
        
    def __init__(self):  #保留对 data[0] 数据系列中“close”行的引用
        self.dataclose= self.datas[0].close
        
    def next(self): #只需从参考记录该系列的收盘价
        self.log('Close,%.2f'% self.dataclose[0])
if __name__== '__main__':
    cerebro=bt.Cerebro()
    
    # Add a strategy
    cerebro.addstrategy(TestStrategy)

#     引入数据
    modpath=os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath=os.path.join(modpath,'C:/Users/gws/Desktop/OPTIONS_DATA/600519.csv')
#     将数据放入Data Feeds,默认的数据集是yahoo的股票数据
# 载入自己的数据也是可以的,只不过你需要设定每个列的含义
    data=bt.feeds.GenericCSVData(
    dataname=datapath,
    datetime=0,
    open=1,
    high=3,
    low=4,
    close=2,
    volume=5,
    dtformat=('%Y%m%d'),
#         使用datetime来过滤价格数据 的起止范围
    fromdate=datetime.datetime(2010,1,1), #不传递此日期之前的数值
    todate=datetime.datetime(2022,11,1)  #不传递此日期之后的数值
    )
    # 将数据 Feed到Cerebro
    cerebro.adddata(data)
#     设置投资金额
    cerebro.broker.setcash(100000.0)
    print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
#     运行broker
    cerebro.run()
    print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())

导出的数据如下所示(部分):

组合初始价值:100000.00
2010-01-04,Close,104.59
2010-01-05,Close,104.29
2010-01-06,Close,102.64
2010-01-07,Close,100.77
2010-01-08,Close,99.71
2010-11-01,Close,99.27
2010-11-02,Close,101.95
2010-11-03,Close,99.36
2010-11-04,Close,103.73
2010-11-05,Close,104.55
2010-11-08,Close,105.23
2010-11-09,Close,104.49
2010-01-20,Close,102.12
2010-12-01,Close,102.62
2010-12-02,Close,101.02
.......
2021-10-27,Close,1819.00
2021-10-28,Close,1825.49
2021-10-29,Close,1826.08
2021-11-01,Close,1803.00
组合期末价值:100000.00

三、进行交易策略的回测

进行策略回测前,需要自定义自己的交易策略:

# 一个新的策略,如果价格三连跌的话,买入
class TestStrategy(bt.Strategy):
    def log(self,txt,dt=None): #此策略的日志记录功能
        dt=dt or self.datas[0].datetime.date(0)
        print('%s,%s'%(dt.isoformat(),txt))
        
    def __init__(self):  #保留对 data[0] 数据系列中“close”行的引用
        self.dataclose= self.datas[0].close
        
    def next(self): #只需从参考记录该系列的收盘价
        self.log('Close,%.2f'% self.dataclose[0])
        
        if self.dataclose[0]<self.dataclose[-1]: #当前收盘价低于上一个收盘价
            if self.dataclose[-1]<self.dataclose[-2]: #当前收盘价低于上一个收盘价
                self.log('BUY CREATE,%.2f'%self.dataclose[0]) #创建买单
                self.buy()  

进行回测:

if __name__== '__main__':
    cerebro=bt.Cerebro()
    
    # Add a strategy
    cerebro.addstrategy(TestStrategy)

#     引入数据
    modpath=os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath=os.path.join(modpath,'C:/Users/gws/Desktop/OPTIONS_DATA/600519.csv')
#     将数据放入Data Feeds,默认的数据集是yahoo的股票数据
# 载入自己的数据也是可以的,只不过你需要设定每个列的含义
    data=bt.feeds.GenericCSVData(
    dataname=datapath,
    datetime=0,
    open=1,
    high=3,
    low=4,
    close=2,
    volume=5,
    dtformat=('%Y%m%d'),
#         使用datetime来过滤价格数据 的起止范围
    fromdate=datetime.datetime(2010,1,1), #不传递此日期之前的数值
    todate=datetime.datetime(2021,12,1)  #不传递此日期之后的数值
    )
    # 将数据 Feed到Cerebro
    cerebro.adddata(data)
#     设置投资金额
    cerebro.broker.setcash(100000.0)
    print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
#     运行broker
    cerebro.run()
    print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())
    cerebro.plot()

回测文字结果:

组合初始价值:100000.00
2010-01-04,Close,104.59
2010-01-05,Close,104.29
2010-01-05,BUY CREATE,104.29
2010-01-06,Close,102.64
2010-01-06,BUY CREATE,102.64
2010-01-07,Close,100.77
2010-01-07,BUY CREATE,100.77
2010-01-08,Close,99.71
2010-01-08,BUY CREATE,99.71
2010-11-01,Close,99.27
2010-11-01,BUY CREATE,99.27
2010-11-02,Close,101.95
2010-11-03,Close,99.36
......
2021-11-05,BUY CREATE,2062.58
2021-11-08,Close,2043.76
2021-11-08,BUY CREATE,2043.76
2021-11-09,Close,1990.67
2021-11-09,BUY CREATE,1990.67
2021-01-20,Close,2021.60
组合期末价值:948494.55

回测结果图
在这里插入图片描述
可以看出,这个策略是盈利客观的,但是策略过于简单,在后几年的大涨行情中并没有触发。

继续进行策略的回测

# 不止买入,还要卖出
# Strategy 类有一个变量 position 保存当前持有的资产数量
# buy() 和 sell() 会返回 被创建的订单 (还未被执行的)
# 订单状态改变后将会通知 Strategy 实例的 notify() 方法
# 5个柱之后(在第6个时候执行)不管涨跌都卖
# 请注意,这里没有指定具体时间,而是指定的柱的数量。一个柱可能代表1分钟、1小时、1天、1星期等等,这取决于你价格数据文件里一条数据代表的周期。
# 创建一个新策略
class TestStrategy(bt.Strategy):
    
    def log(self,txt,dt=None):
        dt=dt or self.datas[0].datetime.date(0)
        print('%s,%s'%(dt.isoformat(),txt))
        
    def __init__(self):
        self.dataclose=self.datas[0].close
        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(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
                         
            else:
                 self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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 next(self):
        self.log('Close,%.2f'%self.dataclose[0])
        if self.order: ## 检查订单是否处于待处理状态...如果是,我们不能发送第二个
            return
        if not self.position: #检查我们是否在市场上
            if self.dataclose[0]<self.dataclose[-1]:
                if self.dataclose[-1]<self.dataclose[-2]:
                    self.log('BUY CREATE,%.2f'%self.dataclose[0])
                    self.order=self.buy() #跟踪创建的订单以避免第二个订单
        else:
            if len(self)>=(self.bar_executed+5):
                self.log('SELL CREATE,%.2f'%self.dataclose[0])
                self.order=self.sell()
if __name__== '__main__':
    cerebro=bt.Cerebro()
    
    # Add a strategy
    cerebro.addstrategy(TestStrategy)

#     引入数据
    modpath=os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath=os.path.join(modpath,'C:/Users/gws/Desktop/OPTIONS_DATA/600519.csv')
#     将数据放入Data Feeds,默认的数据集是yahoo的股票数据
# 载入自己的数据也是可以的,只不过你需要设定每个列的含义
    data=bt.feeds.GenericCSVData(
    dataname=datapath,
    datetime=0,
    open=1,
    high=3,
    low=4,
    close=2,
    volume=5,
    dtformat=('%Y%m%d'),
#         使用datetime来过滤价格数据 的起止范围
    fromdate=datetime.datetime(2010,1,1), #不传递此日期之前的数值
    todate=datetime.datetime(2021,12,1)  #不传递此日期之后的数值
    )
    # 将数据 Feed到Cerebro
    cerebro.adddata(data)
#     设置投资金额
    cerebro.broker.setcash(100000.0)
    
#     加入手续费
    cerebro.broker.setcommission(commission=0.001) # 0.001 即是 0.1%
    print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
#     运行broker
    cerebro.run()
    print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())
    cerebro.plot()

回测结果

扫描二维码关注公众号,回复: 14628069 查看本文章
2021-01-08,Close,2070.51
2021-11-01,Close,2080.14
2021-11-02,Close,2140.74
2021-11-03,Close,2143.82
2021-11-04,Close,2114.09
2021-11-05,Close,2062.58
2021-11-05,BUY CREATE,2062.58
2021-11-08,BUY EXECUTED, Price: 2041.84, Cost: 2041.84, Comm 2.04
2021-11-08,Close,2043.76
2021-11-09,Close,1990.67
2021-01-20,Close,2021.60
组合期末价值:100912.05

在这里插入图片描述
可以看出,策略盈利不多,而且没有第一种简单策略的收益高。

加入技术指标进行回测

# 添加技术指标
# 收盘价高于平均价的时候,以市价买入

# 持有仓位的时候,如果收盘价低于平均价,卖出

# 只有一个待执行的订单
# Create a Stratey
class TestStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
    )
    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
        
    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close
        self.order = None
        self.buyprice = None
        self.buycomm = None
 # 添加技术指标
        self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
    # 绘图显示的指标
        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
        bt.indicators.WeightedMovingAverage(self.datas[0], period=25,
                                            subplot=True)
        bt.indicators.StochasticSlow(self.datas[0])
        bt.indicators.MACDHisto(self.datas[0])
        rsi = bt.indicators.RSI(self.datas[0])
        bt.indicators.SmoothedMovingAverage(rsi, period=10)
        bt.indicators.ATR(self.datas[0], plot=False)
    
    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(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
                         
            else:
                 self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                 (trade.pnl, trade.pnlcomm))

    
        
    def next(self):
        self.log('Close,%.2f'%self.dataclose[0])
        if self.order: ## 检查订单是否处于待处理状态...如果是,我们不能发送第二个
            return
        if not self.position: #检查我们是否在市场上
            if self.dataclose[0] > self.sma[0]:
            
                self.log('BUY CREATE,%.2f'%self.dataclose[0])
                self.order=self.buy() #跟踪创建的订单以避免第二个订单
        else:
            if self.dataclose[0] < self.sma[0]:
                self.log('SELL CREATE,%.2f'%self.dataclose[0])
                self.order=self.sell()

if __name__== '__main__':
    cerebro=bt.Cerebro()
    
    # Add a strategy
    cerebro.addstrategy(TestStrategy)

#     引入数据
    modpath=os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath=os.path.join(modpath,'C:/Users/gws/Desktop/OPTIONS_DATA/600519.csv')
#     将数据放入Data Feeds,默认的数据集是yahoo的股票数据
# 载入自己的数据也是可以的,只不过你需要设定每个列的含义
    data=bt.feeds.GenericCSVData(
    dataname=datapath,
    datetime=0,
    open=1,
    high=3,
    low=4,
    close=2,
    volume=5,
    dtformat=('%Y%m%d'),
#         使用datetime来过滤价格数据 的起止范围
    fromdate=datetime.datetime(2010,1,1), #不传递此日期之前的数值
    todate=datetime.datetime(2021,12,1)  #不传递此日期之后的数值
    )
    # 将数据 Feed到Cerebro
    cerebro.adddata(data)
#     设置投资金额
    cerebro.broker.setcash(100000.0)
    
#     加入手续费
    cerebro.broker.setcommission(commission=0.001) # 0.001 即是 0.1%
    print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
#     运行broker
    cerebro.run()
    print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())
    
    
    cerebro.plot()

回测结果

2021-11-05, Close,2062.58
2021-11-08, Close,2043.76
2021-11-09, Close,1990.67
2021-11-09, SELL CREATE,1990.67
2021-01-20, Close,2021.60
组合期末价值:100998.60

在这里插入图片描述
回测后的收益不高。

加入手续费后,后两者策略的表现都不好,从2020年到2021年的收益率还不如余额宝收益率,看来后两者策略在该标的上的表现不如意。

总结

本文简单介绍了backtrader的回测用法。有兴趣的可以在官网进行深入的学习。
Backtrader 官网:https://www.backtrader.com/docu/plotting/plotting/

猜你喜欢

转载自blog.csdn.net/xiaowu1997/article/details/122629350