backtrader回测框架实例

backtrader是基于Python的量化回测框架,功能丰富,操作方便。其优点是运行速度快,支持pandas的矢量运算;内置多种技术指标计算,还支持股票分析技术指标库talib;支持参数自动寻优运算;支持多品种(股票、期货、期权、外汇和数字货币)、多策略、多周期(Ticks、秒、分、日、周、月和年)的回测和交易;支持PyFlio、empyrica分析模块库、alphalens多因子分析模块库等;扩展灵活,可以集成TensorFlow、PyTorch和Keras等机器学习、神经网络分析模块。

一、安装

pip install backtrader

官网:Backtrader

安装需求:

Python 2.7

Python 3.2 / 3.3/ 3.4 / 3.5

pypy/pypy3

绘图需要用到Matplotlib,最新版本建议>=1.4.1。

包引用:import backtrader as bt

二、backtrader功能

backtrader包主要包括:框架(Cerebro)、数据加载(Data Feed)、交易策略(Strategies)、技术指标(Indicators)、订单(Orders)、观察者(Observers)、测量评估(Analyzers)、订单(Orders)、经纪人(Broker)、实盘交易(Live Trading)、结果可视化(Plotting)等部分

整体结构如下图所示:

下面对其中的关键部分进行详细说明。

1.数据类型

backtrader回测的数据类型是由一系列的点组成的Lines,通常包括以下类别的数据:Open(开盘价)、High(最高价)、Low(最低价)、 Close(收盘价)、Volume(成交量)、OpenInterest(未平仓权益)。Data Feeds(数据加载)、Indicators(技术指标)和Strategies(策略)都会生成 Lines。价格数据中的所有的开盘价按时间组成一条 Line,因此一组含有以上6个类别的价格数据,共有6条 Lines。如果算上DateTime(时间,可以看作是一组数据的主键),一共有7条 Lines。

一条Line数据的下标为0表示访问当前时刻数据,-1表示访问最后一个数据。因此,在回测过程中,无需知道已经处理了多少条/分钟/天/月,”0”一直指向当前值,下标 -1 来访问最后一个值。

2.交易策略(Strategy)

该模块是回测系统最核心的部分,需要设计交易决策,得出买入/卖出信号。策略类代码包含重要的参数和用于执行策略的功能,要定义的参数或函数名如下:

(1)params:全局参数,可选,用于更改交易策略中变量/参数的值,可用于参数调优。

(2)log:日志,可选,用于记录策略的执行日志,可以打印出该函数提供的日期时间和txt变量。

(3)__init__:用于初始化交易策略,在其中声明的任何指标都会在next()方法调用之前进行计算。部分python操作符不支持,需要使用bt内置函数来处理,例如bt.And, bt.Or, bt.All, bt.Any等。

(4)notify_order,可选,用于跟踪交易订单(order)的状态。order具有提交,接受,买入/卖出执行和价格,已取消/拒绝等状态。

(5)notify_trade,可选,用于跟踪交易的状态,任何已平仓的交易都将报告毛利和净利润。

(6)next,必选,用于制定交易策略的函数,策略模块最核心的部分。

3.技术指标(Indicators)

实际上,策略的构建都需要指标来实现,该部分包括很多已经实现的指标,下面是一些比较常用的指标:

Average:指定周期的算术平均值。

SimpleMovingAverage(SMA):简单移动平均

WeightedAverage:加权移动平均

MACD:滑动平均

ExponentialMovingAverage(EMA):指数移动平均

RSI_SMA/RSI_EMA:

BollingerBands(BOLL):布林线

DoubleExponentialMovingAverage(DEMA):双指数移动平均

WilliamsR(WR):威廉指标

在指标中也集成了TA-Lib库,实际使用中发现无法正常调用,不知道是不是跟我没有安装该库有关。另外,还可以通过继承Indicator或其它已存在的指标类来实现自定义指标,方便用户加入自己开发的指标。

# 继承自bt.Indicator或其他已存在的指标类
class DummyInd(bt.Indicator):
    # 定义持有的lines,至少需要1个line
    lines = ('dummyline',)
    # params参数可选
    params = (('value', 5),)
    # plotinfo可选,用来控制绘图行为
    plotinfo = dict(subplot=False)
    # __init__方法或next方法必选
    def __init__(self):
        self.lines.dummyline = bt.Max(0.0, self.params.value)

4.订单(Orders)

将策略中逻辑做出的决策转换为适合经纪人执行操作的消息,通常在交易策略中调用。

5.经纪人(Broker)

通过设置回测的初始自己、佣金费率、税收费率、滑点率等交易条件,模拟不同的订单类型,并根据现金检查订单,计算每次交易的现金和权益,保存交易数据。

6.策略评估(Analyzers)

用于分析交易策略的利润和风险,分析交易系统的绩效。

7.实盘交易(Live Trading)

从1.5.0开始支持实盘数据和实时交易。具体请参考:Live Trading

8.观察者(Observers)

用于记录交易过程,包括现金、权益、费用以及交易动作、买卖订单等数据。

9.结果可视化(Plotting)

通过图形的方式显示交易测量回测的结果,绘图显示的结果包括三部分类型:现金及权益、交易损益、买卖动作。

绘图设置通过plotinfo来设置,其参数主要有:plot(是否绘图,默认为True),subplot(是否单独窗口绘图,默认为True,MA类指标该参数为False),plotname(指标图名,默认为指标类名),plotabove(绘图位置在数据上方,默认为False),plotlinelabels, plotymargin, plotyticks,plothlines, plotyhlines, plotforce。

注意:由于技术指标的不同,可视化结果中所包含的数据可能不同,因此需要根据技术指标来选择其所支持的参数进行设置。

三、系统流程

在backtrader内部通过对数据和指标的处理执行策略,并记录所有的交易过程和数据输出给用户。

backtrader的入口为Cerebro类,该类将所有输入(Data Feeds)、执行 (Stratgegies)、观察者(Observers)、评价(Analyzers) 和文档 (Writers)整合起来,实现回测以及交易,并返回结果和图表。

(1)创建实例

cerebro = bt.Cerebro()  # 创建回测系统实例

(2)关联输入数据

cerebro.adddata(data)  # 将数据加载至回测系统

(3)关联交易策略

cerebro.addstrategy(BollStrategy,nk=14,m=2,printlog=True)

(4)关联其他可选功能

addwriter():指定交易过程和数据保存的方式。

addanalyzer():增加策略评估。

addobserver():增加观察者。

addobservermulti()

(5)指定经纪人

broker = MyBroker()
cerebro.broker = broker

可以根据不同类型的交易对象设置经纪人模型。

(6)接收消息

接收回测消息有三种方式:在Cerebro类的实例中通过addnotifycallback(callback)增加回调函数、在Strategy子类中重载notify_store()方法、继承Cerebro类并重载notify_store()方法

(7)执行回测并返回结果

result=cerebro.run(maxcpus=1)

(8)记录交易信息

这个功能是通过观察者(Observers)实现,有三种类型:

经纪人观察者(Broker)保存现金和权益信息。交易观察者(Trades)记录交易的有效性。买卖观察者(Buy/Sell)记录所有交易执行的操。

(9)绘图显示

cerebro.plot()

绘图显示底层实际上是调用Matplotlib包实现的。

四、使用示例

下面以BOLL线指标为例,展示backtrader中策略的编写以及整个回测的流程。BOLL线指标的详细介绍可参考文章《BOLL指标计算》,本次回测采用的简易BOLL策略如下:

(1)当收盘价下跌跌破BOLL线通道下轨卖出;

(2)当收盘价上涨突破BOLL线通道上轨卖出。

本次将测试中采用招商银行的前复权日K线数据,将BOLL线的周期N设为13,倍数因子为默认值,并在交易回测中加入了交易过程打印功能。

示例代码如下:

from datetime import datetime
import akshare as ak
import pandas as pd
import backtrader as bt

class BollStrategy(bt.Strategy): #BOLL策略程序
    params = (("nk", 13), #求均值的天数
              ('printlog', False),)  #打印log

    def __init__(self): #初始化
        self.data_close = self.datas[0].close  # 指定价格序列        
        # 初始化交易指令、买卖价格和手续费
        self.order = None
        self.buy_price = None
        self.buy_comm = None
        #Boll指标计算
        self.top=bt.indicators.BollingerBands(self.datas[0],period=self.params.nk).top
        self.bot=bt.indicators.BollingerBands(self.datas[0],period=self.params.nk).bot
        # 添加移动均线指标
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.nk )

    def next(self): #买卖策略
        if self.order:  # 检查是否有指令等待执行
            return
        # 检查是否持仓
        """
        if not self.position:  # 没有持仓
            if self.data_close[0] > self.sma[0]:  # 执行买入条件判断:收盘价格上涨突破20日均线
                self.order = self.buy(size=100)  # 执行买入
        else:
            if self.data_close[0] < self.sma[0]:  # 执行卖出条件判断:收盘价格跌破20日均线
                self.order = self.sell(size=100)  # 执行卖出
        """
        if not self.position:  # 没有持仓
            if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨
                self.log("BUY CREATE, %.2f" % self.data_close[0])
                self.order = self.buy() #执行买入
        else:
            if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨
                self.log("SELL CREATE, %.2f" % self.data_close[0])
                self.order = self.sell() #执行卖出
        
    def log(self, txt, dt=None, do_print=False): #日志函数
        if self.params.printlog or do_print:
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))

    def notify_order(self, order): #记录交易执行情况
        # 如果order为submitted/accepted,返回空
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 指令为buy/sell,报告价格结果
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    f"买入:\n价格:{order.executed.price},\
                成本:{order.executed.value},\
                手续费:{order.executed.comm}"
                )
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:
                self.log(
                    f"卖出:\n价格:{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("交易失败") #指令取消/交易失败, 报告结果
        self.order = None

    def notify_trade(self, trade): #记录交易收益情况
        if not trade.isclosed:
            return
        self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}")

    def stop(self): #回测结束后输出结果
        self.log("(BOLL线: %2d日) 期末总资金 %.2f" % (self.params.nk, self.broker.getvalue()), do_print=True)
 
code="600036" #股票代码
start_cash=1000000 #初始自己为1000000
stake=100 #单次交易数量为1手
commfee=0.0005 #佣金为万5
sdate='20210101' #回测时间段
edate='20220930'
cerebro = bt.Cerebro()  # 创建回测系统实例
# 利用AKShare获取股票的前复权数据的前6列
df_qfq = ak.stock_zh_a_hist(symbol=code, adjust="qfq", start_date=sdate, end_date=edate).iloc[:, :6]
# 处理字段命名,以符合Backtrader的要求
df_qfq.columns = ['date','open','close','high','low','volume',]
# 把date作为日期索引,以符合Backtrader的要求
df_qfq.index = pd.to_datetime(df_qfq['date'])
start_date = datetime.strptime(sdate,"%Y%m%d")  #转换日期格式
end_date = datetime.strptime(edate,"%Y%m%d")
#start_date=datetime(2022,1,4)
#end_date=datetime(2022,9,16)
data = bt.feeds.PandasData(dataname=df_qfq, fromdate=start_date, todate=end_date)  #规范化数据格式
cerebro.adddata(data)  #加载数据
cerebro.addstrategy(BollStrategy,nk=13,printlog=True) #加载交易策略
cerebro.broker.setcash(start_cash)  # broker设置资金
cerebro.broker.setcommission(commission=commfee)  # broker手续费
cerebro.addsizer(bt.sizers.FixedSize, stake=stake)  # 设置买入数量
print("期初总资金: %.2f" % start_cash)
cerebro.run() #运行回测
end_value=cerebro.broker.getvalue() # 获取回测结束后的总资金
print("期末总资金: %.2f" % end_value)
cerebro.plot()

注意:

1.backtrader的输入数据需要遵循其标准。对于pandas的标准就是这些列的名字是open、close、high、low、volume。

2.backtrader要求pandas下的DataFrame的index是时间,所以设置要将date设置为索引。

3.backtrader的回测日期参数类型为datetime。

结果和交易记录如下图所示:

以上只是对的一个简单BOLL策略进行了回测演示,只是回测中万里长征的第一步。实际使用中还需要做大量的测试工作,例如不同交易费率、不同时间段、不同头寸大小等进行测试,加入止损和止盈等等,最后还要根据结果来修改交易策略。

-----------------------------------

原创不易,请多支持

  • 14
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值