Skip to content

Latest commit

 

History

History
185 lines (138 loc) · 14 KB

File metadata and controls

185 lines (138 loc) · 14 KB

7、backtrader 的一些基本概念---Strategy 讲解

原文:https://yunjinqi.blog.csdn.net/article/details/108569865

backtrader 的一些基本概念—Strategy 讲解

​ 在上一讲中,我们尝试了最简单的双均线策略,如果您已经阅读过前两讲的代码,对 backtrader 应该有一定的了解了,本讲将会深入分析 backtrader 一些常用的模块,使得大家能够有一个全局的了解。

backtrader 的核心模块

  1. bt.Strategy

    在这个模块中,我们根据我们的交易思想和交易逻辑,去一步步的实现策略,是重中之重。从这个模块中,我们可以调取到我们加载的数据,可以获取当前的持仓量和持仓状态,可以获取到账户资产、账户可以使用的金额等等信息,中间计算的各种变量,也都可以自定义 log 出来或者保存到 excel 中(很少用原生的 Writer)。

    首先,我们编写一个策略的时候,需要继承 backtrader 里面的 Strategy 模块,即 class MyStrategy(bt.Strategy),其中,class 是声明类,MyStrategy 是我们自己编写的策略的名字,bt.Strategy 是我们继承的 backtrader 的 Strategy 的类。

    下面详细讲解下,Strategy 里面的主要函数,内容都在函数里面进行注释了,请详细看这个类中的主要函数。

    class MyStrategy(bt.Strategy):
    
        def __init__(self):
            '''添加一些指标或者一些 self 属性值'''
            pass 
    
        def start(self):
            '''cerebro 告诉策略开始运行,一般默认是空,可以忽略。有时候在实盘的时候,需要调试,这个函数也是需要用上的'''
            pass
    
        def prenext(self):
            '''大部分指标的计算都需要一定的 bar 之后才会产生值,在 backtrader 的机制中,会根据各个指标的参数值,设定一个需要满足的最小周期,当 bar 的个数没有达到这个最小周期的时候,就会在 prenext 中运行,这也是一个相当有用的函数,在实际中,很有可能会用到'''
            pass
    
        def nextstart(self):
            '''只调用一次,当 bar 的个数满足之后,开始从 prenext 转移到 next 中。这个函数默认是空的,也可以不要'''
            # 相应的调用 next 的代码
    
        def next(self):
            '''在 next 中,调用相应的数据,指标值,买卖平仓,输出数据等,策略的主要部分'''
    
        def stop(self):
            '''策略结束的时候会调用一次这个函数,如果想要输出到 excel 一些策略运行的信息,可以在这个函数里面进行,一般情况下,我会用 df.to_csv()输出到 excel 中相应的策略运行信息,如每个 bar 的 value 是多少。默认是空的,可以要可以不要'''
            pass
    
        def notify_cashvalue(cash, value):
            '''使用这个函数获取 cash 和 value 变动的信息,说来惭愧,使用了好几年了,我没有用过这个函数。这也说明了,backtrader 实在是大而全,很多功能都提供了'''
            pass
    
        def notify_fund(cash, value, fundvalue, shares):
            '''使用这个函数获取 cash,value,fundvalue,shares 的变动信息,同样没有使用过'''
            pass
    
        def notify_order(order):
            '''使用这个函数,可以获取订单每次变动的信息,一般来说,使用固定的代码就行,下面是我自己在回测的时候,常用的固定代码'''
            if order.status in [order.Submitted, order.Accepted]:
                # order 被提交和接受,这个一般不用打印出来,实盘的时候,可能少数情况下,需要测试这两个
             return
            if order.status == order.Rejected:
                self.log(f"order is rejected : order_ref:{order.ref} order_info:{order.info}")
            if order.status == order.Margin:
                self.log(f"order need more margin : order_ref:{order.ref} order_info:{order.info}")
            if order.status == order.Cancelled:
                self.log(f"order is concelled : order_ref:{order.ref} order_info:{order.info}")
            if order.status == order.Partial:
                # 这个其实一般也只有在实盘中才会有,回测中,一般情况下,都是全部成交
                self.log(f"order is partial : order_ref:{order.ref} order_info:{order.info}")
            # 如果订单完成了,就打印如下信息
            if order.status == order.Completed:
                # 如果是做多或者平空
                if order.isbuy():
                    self.log("buy result : buy_price : {} , buy_cost : {} , commission : {}".format(
                                order.executed.price,order.executed.value,order.executed.comm))
                # 如果是平多或者做空 
                else:  
                    self.log("sell result : sell_price : {} , sell_cost : {} , commission : {}".format(
                                order.executed.price,order.executed.value,order.executed.comm))
    
        def notify_trade(trade):
            '''这个函数可以获取每次交易的变动信息'''
            # 一个 trade 结束的时候输出信息
            if trade.isclosed:
                self.log('closed symbol is : {} , total_profit : {} , net_profit : {}' .format(
                                trade.getdataname(),trade.pnl, trade.pnlcomm))
            # trade 开始的时候输入的信息
            if trade.isopen:
                self.log('open symbol is : {} , price : {} ' .format(
                                trade.getdataname(),trade.price))
            # 如果想要把每笔交易都输出出来,可以在 init 中,设置 self.trade_result=[],这样,把每笔交易的开和平,都保存在这里面,在 stop 中输出到本地进行校对策略。
      		# self.trade_result.append([self.current_date,trade.getdataname(),
            #                            trade.size,self.getdatabyname(trade.getdataname()).open[0],
            #                            trade.data.open[-1*trade.barlen],trade.pnl,trade.pnlcomm]) 

    扯一段对策略的感悟,不感兴趣的可以忽略,对编写策略影响不大。

    backtrader 的官方文档中,作者把 strategy 按照人的生命周期划分了几个阶段,怀孕(init),出生(start),童年(prenext),成年礼(nextstart,作者没有提这个函数,我自己加的),成年(next),死亡(stop),当看到作者的这几个阶段的不同划分,我特别震惊,越想,越有韵味。原来,人生,就是一个大的策略,原来,我们可以站在策略的角度上,设计我们的人生,用不同的算法,我们的人生经历和结果完全不同。

    在怀孕阶段,从策略层面上来说,我们要设置好初始变量,加载上需要调用的指标,大部分策略的初始变量和调用的指标可能差不多,但是,每个人的初始变量和加载上的指标,各有不同,有的人多一些,有的人少一些。这和我们每一个人的投胎,不是异曲同工之妙吗?有的人投胎比较好,加载的初始变量和指标比较多,在以后的成长阶段中,父母和家庭提供的帮助比较多(更多的变量和指标,往往需要计算机使用更多的内存和 cpu),在长大成人之后,素质一般相对比别人更好(可以很方便的调用指标值,如果,加载的指标是有用的话,如果加载的指标是错的,可能反而危害会更大)。

    出生,童年,成年礼之前,从主要靠父母家庭,慢慢到自己完全主宰自己的生活,我们一步步成长,长大成人。

    当我们长大成人之后,从策略层面上看,我们就可以设计我们的交易策略,决定什么情况下买卖,什么情况下,加仓减仓,什么情况下,止盈止损,交易哪些品种,交易多少。从人生的阶段,我们就要开始独立自主,独立做判断:我们如何认识世界,如何和世界相处,做什么,不做什么,和谁交往,把时间和经历花在什么地方,这就是我们成年后的生活,在某种程度上,是可以做成一个策略的。

    总有一天,我们也会死亡。当我们的人生 stop 的时候,你能给这个世界留下来什么呢?


    在代码部分,分享完了一个策略的各个部分,在接下来,主要分享一下,在 next 中,也就是在写策略的过程中,主要使用的一些函数,从功能实现的角度讲起

    # 1、如何买卖和取消订单
    """可以做多,平多,做空,平空,取消下的未成交的限价单,是一个完善的量化平台的基本功能。
    
    在 backtrader 的 strategy 中,主要使用 self.buy()实现做多或者减少一个空头仓位,self.sell()实现做空或者减少一个多头仓位,self.close()实现平仓,self.cancel()实现取消订单。
    
    self.buy(),self.sell()的参数有很多,大家在使用的时候,主要记住两个就可以了,其他的用到的时候可以去查询。这两个关键的参数就是 data 和 size,
    
    data 就是选择哪个股票或者期货品种进行买卖,如果 data 没有设置,那么默认的值就是 None,在交易的时候会默认加载到 cerebro 中的第一个数据,所以,如果回测的是多个股票或者品种(即多个数据),记得要设置好数据哦。
    
    size 就是交易的数量,都是正数,做空也用正数。
    
    还有个参数 exectype 需要提一下,当我们忽略这个参数的时候,我们是使用的 Market order,即市价单,也就是说,我们不限定价格,也不知道我们会在什么价格成交,我们下单的下一个瞬间,就尽可能去成交,如果我们是在收盘的时候下单,就是在第二天的开盘成交;如果你的交易量比较小,一般可以默认是对手价加 0 到 2 个滑点成交,如果你的交易量比较大,你就要考虑使用算法交易,进行拆单,降低摩擦成本。
    
    self.close,传入参数是 data,size 一般默认是 None,这样会自动获取当前这个 data 的持仓,然后全部平掉,即 self.close(data)就会把这个 data 上的仓位全部平掉。
    
    self.cancel(),传入的参数是 order,self.cancel(order)会把这个 order 给平掉,如果这个 order 还没有成交的话。
    
    这四个是基本的功能,也比较推荐大家使用这几个进行交易买卖。
    
    """
    # 2、如何维持一定量的交易量
    """使用 order_target_size 函数来实现,这个函数主要有两个函数,
    data 和 target,data 就是选择哪个数据来维持 target 量的持仓量,target 就是一个数字,是要维持的持仓量,
    当现有的数据的持仓量大于 target,就会平仓一部分,
    当现有的数据的持仓量小于 target,就会加仓一部分,
    如果持仓量和 target 相等,就不操作。
    当你使用这个函数的时候,结果就是,最终 data 上的持仓量就是 target"""
    
    # 3、如何调整仓位,使得现在的持仓等于一个固定价值?
    
    """使用 order_target_value,同样也是两个参数
    data 和 target,data 就是选择哪个数据来维持 target 的价值,target 就是一个数字,是要维持的价值,
    当现有的数据的价值大于 target,就会平仓一部分,
    当现有的数据的价值小于 target,就会加仓一部分,
    如果价值和 target 相等,就不操作。
    当你使用这个函数的时候,结果就是,最终 data 上的当前 bar 结束的时候的 value 就是 target
    
    """
    # 4、如何使得当前的仓位的价值等于账户当前 value 的一定比例?
    """使用 order_target_percent
    data 和 target,data 就是选择哪个数据来维持 target 的价值,target 就是一个数字,是要维持的当前账户总的 value 的比例,一般用一个小数表示,
    实际上,需要实现的是,data 的持仓价值等于 target*self.broker.get_value(),用 data_value 代表现有的持仓的价值,用 value 代表要实现的目标价值,
    当 value>data_value 的时候,就会加仓,原先做多的时候,继续加多;原先做空的时候,继续加空仓;
    当 value<data_value 的时候,就会减仓,原先做多的时候,会平掉一部分多单;原先做空的时候,会平掉一部分空单。
    
    """
    # 5、如何下一个组合单(成交之后,实现一个止损单和止盈单)?
    """backtrader 上增加的在 buy 和 sell 的基础上的组合功能。
    def buy_bracket(self, data=None, size=None, price=None, plimit=None,
                        exectype=bt.Order.Limit, valid=None, tradeid=0,
                        trailamount=None, trailpercent=None, oargs={},
                        stopprice=None, stopexec=bt.Order.Stop, stopargs={},
                        limitprice=None, limitexec=bt.Order.Limit, limitargs={},
                        **kwargs):
     在使用这个功能的时候,主要有五个参数需要注意:
     1.data 在哪个数据上去实现
     2.size 交易量的大小是多少
     3.price 限价单的价格
     4.stopprice 止损价的价格,做多时,应该小于 price
     5.limitprice 止盈价的价格,做多时,应该大于 price
    
    """# 6、如何获取一个 data 的持仓量?
    """有好几个函数可以实现,如 self.getposition(data).size 或者用 self.getpositionbyname(data._name).size"""
    
    # 7、如何获取当前账户的 value?
    
    """获取账户整体价值,使用 self.broker.get_value();获取可用资金,使用 self.broker.get_cash()"""
    
    #以上是实现一个策略的时候,经常用到的一些函数。写到这里,不知不觉,已经 11 点了,时间过得真快。
    # 人生有时候就是一个策略,在什么地方分配我们的时间和精力,我们的策略不同,对我们的人生,也许会产生很大的影响。即将到来新的一周,祝大家一周快乐。