量化投资之工具篇一:Backtrader从入门到精通(5)-Strategy类源代码解读

本文详细解析了Backtrader库中Strategy类的工作原理,从实例化、初始化、数据处理到策略操作,阐述了Strategy如何与Cerebro协同完成交易逻辑。Strategy作为策略核心,通过重写`__init__`和`next`方法实现自定义交易策略,如均线策略。在数据未达到最小周期时,会调用`prenext`,在数据有效后调用`next`进行实际交易操作。此外,还介绍了SignalStrategy,一种简化策略实现的类,通过信号触发买卖操作。

上一篇文章详细介绍数据相关类,本文开始 介绍和Cerebro一样重要的Strategy。如果Cerebro是大脑,那Strategy就是心脏,所有血液(数据)流经心脏(Strategy)处理。

老规矩,先上Strategies的家族图谱。

Strategy家族图谱

在这里插入图片描述

同样的,牢牢记住这个图,这是咱们的家谱。

Note1:图中类名后面加标号直接和未加标号同名类定义完全相同,比如LineRoot1和Lineroot是相同的。主要是为了图形清爽,不然太多交叉,看不清楚。

Note2:由于Strategy类通常要自定义,所以增加了一个自定义类继承自Strategy。

一个简单的均线Strategy

为了更好地进行代码解读,我们提供了一个简单的定制MyCustomStrategy类,继承Strategy,完成均线策略。

class MyCustomStrategy(bt.Strategy):
    params = (
        ('maperiod', 5),
    )

    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

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buycomm = None

        # Add a MovingAverageSimple indicator
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.maperiod)

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        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:  # Sell
                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):
        # Simply log the closing price of the series from the reference
        self.log('Close:%.3f' % self.data.close[0])
        self.log('turnover, %.8f' % self.data.turnover[0])
        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        #Check if we are in the market
        if not self.position:

            
            if self.dataclose[0] > self.sma[0]:

                # 大于均线就买
                self.log('BUY CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()

        else:

            if self.dataclose[0] < self.sma[0]:
                # 小于均线卖卖卖!
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()

此段代码具体解释,请参见系列文章1.

下面以人的一生来描述Strategy的发展过程。

孕育阶段

在Cerebro代码详解一文中,我们说明了如何使用strategy:

  • 首先通过Cerebro中添加Strategy类(本文以添加定制类MyCustomStrategy为例),这个只是添加类,还没有实例化,和Strategy实现没啥关系。
  • 然后Cerebro在runstrategies函数中实例化和初始化,代码如下:
        for stratcls, sargs, skwargs in iterstrat:
            sargs = self.datas + list(sargs)
            try:
                strat = stratcls(*sargs, **skwargs)
            except bt.errors.StrategySkipError:
                continue  # do not add strategy to the mix    

下面我们看看Strategy的实例化和初始化。

Strategy的实例化

从家谱图中我们可以看出,MyCustomStrategy的父类中有元类,所以实例化会受MetaBase元类的控制。首先到MetaBase的__call__走一圈:

  • 首先是doprenew,没人重写,啥也没做。

  • 然后donew,顺着家谱找,MetaStrategy重写了donew,但是其中第一句话,就先调用父类的donew,父类是MetaLineIterator,它的donew又调用到自己父类的donew,继续MetaLineSeries–>MetaLineRoot->MetaParams->MetaBase.太复杂了,俄罗斯套娃啊。熟悉的味道,又到MetaParams,这个之前介绍过,就是调用MetaBase的donew实例化MyCustomStrategy,并且完成参数到属性的映射。

  • MyCustomStrategy实例化完成之后,就到MetaLineRoot的donew,这个系列文章4也讲过,主要是查找自己的owner是谁?MyCustomStrategy是初始化的发起者,所以它没有owner。

  • 继续到MetaLineSeries的donew,这个系列文章4也讲过(为啥都讲过?这也是面向对象的好处啊,代码大量复用)。 首先初始化一个AutoInfoClass保存plotinfo,并根据参数设置属性,画图使用,暂时忽略,后续专题再讲。然后就是最重要的也实例化了和初始化了lines以及LineBuffer(这一块在文章4中有详细描述),这里注意下,LineBuffer的owner就是MyCustomStrategy。这样MyCustomStrategy和数据源一样也拥有lines了,请参见家谱图。

  • 下一步就是MetaLineIterator,这个就是strategy特有的,看代码:

    def donew(cls, *args, **kwargs):
            _obj, args, kwargs = \
                super(MetaLineIterator, cls).donew(*args, **kwargs)
    
            # Prepare to hold children that need to be calculated and
            # influence minperiod - Moved here to support 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值