backtrader量化回测框架入门与参数调优

文摘   其他   2023-05-16 21:43   上海  

1 介绍

backtrader 是一个使用 python 进行策略研发的开源框架,策略研研究员可以使用它进行策略的设计、模拟评估和实盘交易。

这个框架不仅支持多种数据源,内置了许多技术指标,同时支持 talib、pyfolio、empyrical、alphalens 等多个量化工具包,并且可以对诸如 tensorflow、pytorch 和 keras 等机器学习模块进行集成,具有其余回测框架没有的轻便和灵活。

2 基本组件

  1. Cerebro:核心引擎,它可以同时实例化Broker、Strategy、Sizer,并添加到自身,完成策略执行的一系列操作。

  2. Data Feed:数据传递,可以用于读取历史数据文件,实时获取行情数据、从数据库中获取数据等。

  3. Broker:经纪人,用于管理账户资金、执行订单、交易成本的设置与记录等操作。

  4. Strategy:策略类,研究员在该组件内完成自身的交易逻辑,包括入市、出市、止损、止盈等操作。

  5. Sizer:调整仓位大小,根据不同的策略和市场情况,以适当的比例建立仓位大小,比如FixedSize,根据固定的资金额份来进行建仓;PercentSizer 根据账户资产的百分比来进行建仓等。

  6. Observer:观察者,用于监听和记录交易活动、观察和记录市场信息等。

  7. Analyzer:分析器,可以用来对交易策略的结果进行分析,比如计算策略的年化收益率、夏普比率、最大回撤等指标。

  8. Indicator:指标模块,用于计算出趋势和周期,除了内置了很多常用的指标和绘制工具,还可以自行开发自定义的指标。

3 最简单的例子

在这里,我们使用长短期均线策略来得到市场买卖的时机,当短期均线上穿长期均线时,称为“金叉”,表示市场上涨,这是买入的信号;反之,当短期均线下穿长期均线时,称为“死叉”,表示市场下跌,这是卖出的信号。

在编写好策略后,我们将它和数据一同喂入Cerebro量化引擎实例中,来得到最简单的回测代码,并通过它的运行得到最后的策略表现图,它一共包含三个子图:

  • 资金变动图。从曲线上可以看到在实施交易策略的期间,所持资产的价值与现金的变动情况。
  • 交易收益/亏损。圆点表示每次交易的获利情况以及获利多少,其中蓝色表示盈利,红色表示亏损。
  • 价格图表。图上一共有三条曲线相互交织,它们分别是标的的收盘价走势、长期均线曲线和短期均线曲线。其中,绿色和红色箭头分别表示交易策略的进入点和退出点,最下面的条形图则表示每个条形图期间资金的交易量。
class MyStrategy(bt.Strategy):
    params = (
        ("fast_window", 20),
        ("slow_window", 50),
    )

    def __init__(self):
        self.fast_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.fast_window
        )
        self.slow_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.slow_window
        )

    def next(self):
        if self.fast_ma[0] > self.slow_ma[0] and self.fast_ma[-1] <= self.slow_ma[-1]:
            self.buy()
        elif self.fast_ma[0] < self.slow_ma[0] and self.fast_ma[-1] >= self.slow_ma[-1]:
            self.sell()
if __name__ == '__main__':
    cerebro = bt.Cerebro()
    
    data = pd.read_csv('../data/stock/600036SH.csv', index_col=0, parse_dates=True)
    data = bt.feeds.PandasData(dataname=data, datetime=None, open=1, high=2,
                               low=3, close=4, volume=8, openinterest=-1)
    cerebro.adddata(data)
    
    cerebro.addstrategy(MyStrategy)
    
    cerebro.run()
    
    cerebro.plot(style='candlestick')

4 更进阶的例子

如上图所示,一个比较完整的回测代码主要包括以下的流程:

  1. 建立Cerebro实例。
  2. 使用Data Feed加载数据源,并被Cerebro调度。
  3. 创建自己的策略,在初始化函数中使用内置的指标进行初始化,并在next函数完成策略逻辑的填充,再实现notify_order、notify_trade函数来分别跟踪每次交易的订单状态和交易状态。如果想要进一步优化,也可以实现stop函数,它在策略结束时被调用,常用于结合optstrategy函数来对策略参数进行调优。
  4. 添加Analyzer或者Observers实例,跟踪策略交易的表现并对回测结果进行评估,如最大回撤的计算。
  5. 设置Broker初始资金。
  6. 设置交易佣金,模拟真实场景所需的交易成本。
  7. 设置Sizer来执行买卖,如交易头寸大小。
  8. 运行Cerebro实例进行回测的正式启动,并打印出所有已执行的交易。
  9. 绘制策略表现图。
def opt_strategy_params(cerebro, **kwargs):
    cerebro_cp = copy.deepcopy(cerebro)

    cerebro_cp.optstrategy(
        MyStrategy,
        **kwargs
    )

    cerebro_cp.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio')
    cerebro_cp.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown')

    results = cerebro_cp.run()

    best_param = None
    best_score = -100
    for strat in results:

        param = strat[0].params._getkwargs()
        sr = strat[0].analyzers._SharpeRatio.get_analysis()
        dd = strat[0].analyzers._DrawDown.get_analysis()

        if best_score < sr['sharperatio']:
            best_score = sr['sharperatio']
            best_param = dict(param)

        print('策略参数:', param)
        print('夏普比率:', sr)
        print('回撤指标:', dd)

    return best_param
class MyStrategy(bt.Strategy):

    params = (
        ("fast_window", 20),
        ("slow_window", 50),
    )

    def log(self, txt):
        datetime = self.data.datetime[0]
        dt = bt.num2date(datetime)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        self.fast_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.fast_window
        )
        self.slow_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.slow_window
        )

        self.order = 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))

            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        # Write down: no pending order
        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):
        if self.order:
            self.log("==is order: %s" % self.order)
            return

        if not self.position:
            if self.fast_ma[0] > self.slow_ma[0] and self.fast_ma[-1] <= self.slow_ma[-1]:
                self.order = self.buy()
                self.log('BUY CREATE, %.2f' % self.data.close[0])
        else:
            if self.fast_ma[0] < self.slow_ma[0] and self.fast_ma[-1] >= self.slow_ma[-1]:
                self.order = self.sell()
                self.log('SELL CREATE, %.2f' % self.data.close[0])

    def stop(self):
        self.log('(均线周期 %2d %2d)期末资金 %.2f' %
                 (self.params.fast_window, self.params.slow_window, self.broker.getvalue()))

if __name__ == '__main__':

    cerebro = bt.Cerebro()

    data = pd.read_csv('../data/stock/600036SH.csv', index_col=0, parse_dates=True)
    data = bt.feeds.PandasData(dataname=data, datetime=None, open=1, high=2,
                               low=3, close=4, volume=8, openinterest=-1)
    cerebro.adddata(data)

    cerebro.addsizer(bt.sizers.FixedSize, stake=10)

    cerebro.broker.setcash(10000.0)
    cerebro.broker.setcommission(commission=0.002)  # 手续费

    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio')
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown')

    best_params = opt_strategy_params(cerebro,
                                      fast_window=range(15, 25),
                                      slow_window=range(45, 60))

    cerebro.addstrategy(MyStrategy, **best_params)

    print('初始资金: %.2f' % cerebro.broker.getvalue())
    results = cerebro.run()
    print('最终资金: %.2f' % cerebro.broker.getvalue())

    strat = results[0]
    print('夏普比率:', strat.analyzers._SharpeRatio.get_analysis())
    print('回撤指标:', strat.analyzers._DrawDown.get_analysis())

    cerebro.plot(style='candlestick')

程序使用自定义调参函数来对策略参数所需要的短期窗口和长期窗口大小进行优化,它使用暴力搜索的方法计算每一种参数组合,并返回使得策略夏普率最大的策略参数。最后使用返回的最佳参数,完成一次回测,并绘制策略表现图。

5 参考文献

  1. 《买入-持有策略backtrader回测》:https://mp.weixin.qq.com/s/pSU9F0g-8sOHia1GewViBw

  2. 《backtrader入门》:https://mp.weixin.qq.com/s/r65nbkQJxT-kHNzIY24o_Q

  3. 《【python量化】基于backtrader的深度学习模型量化回测框架》:https://mp.weixin.qq.com/s/FV4uB_r2ov-jOj3_OzGaqg

  4. 《量化框架backtrader中文翻译之可视化与参数优化》:https://zhuanlan.zhihu.com/p/149205772?utm_id=0

  5. 《量化交易开源平台Backtrade使用示例》:https://zhuanlan.zhihu.com/p/603482181?utm_id=0

  6. 《手把手教你】入门量化回测最强神器backtrader(二)》:https://mp.weixin.qq.com/s/KUjL3TNxXld-4uAY_yUYrA

  7. 《初识Backtrader|了解基本概念|实现最简单的回测》:https://mp.weixin.qq.com/s/eqGW0zfqCiOp69nUCB37JQ

  8. 《backtrader官网文档》https://www.backtrader.com/docu/quickstart/quickstart/


知守溪的收纳屋
存放觉得有用的文章。关键词:金融量化、因子选择、因果推断、可解释性、人工智能
 最新文章