天天看点

Python量化交易学习笔记(19)——连续下跌买入止盈止损卖出策略

好友提出要验证连续下跌买入止盈止损卖出策略,本文对该策略回测和实现做分析记录。

买入条件中,连续下跌定义为收盘价连续4日低于前1日的收盘价。卖出条件中,止盈率设置为10%,止损率设置为5%。回测初始资金100000元,单笔操作单位1000股,佣金千分之一,回测时间自2018年1月1日至2020年3月20日。

策略核心代码位于策略类的next方法中:

def next(self):
        if self.orefs:  # order列表,用于存储尚未执行完成的订单
            return      # 有尚未执行的订单
        # 尚未进场
        if not self.position:
            # 获取近几日收盘价用于判断是否连续下跌
            lastcloses = list()
            for i in range(self.p.p_downdays + 1):
                lastcloses.append(self.dataclose[-i])
            # 连续N日下跌
            if lastcloses == sorted(lastcloses):
                # 计算买入报价跑p1,止损价p2,止盈价p3
                close = self.dataclose[0]
                p1 = close * (1.0 - self.p.limit)
                p2 = p1 - self.p.p_stoploss * close
                p3 = p1 + self.p.p_takeprofit * close
                # 计算订单有效期
                valid1 = datetime.timedelta(self.p.limdays)
                valid2 = valid3 = datetime.timedelta(self.p.limdays2)
                # 使用bracket orders设置买入卖出
                os = self.buy_bracket(
                    price=p1, valid=valid1,
                    stopprice=p2, stopargs=dict(valid=valid2),
                    limitprice=p3, limitargs=dict(valid=valid3),)
                # 保存激活的的订单
                self.orefs = [o.ref for o in os]      

这里主要应用了backtrader的bracket order,它其实并不是1个订单,而是有3个订单构成:1个买单,1个止损卖单,1个止盈卖单。2个卖单就像是把买单用括号括起来一样,因此合称为bracket order(括号订单)。我们所使用的buy_bracket方法遵循以下规则:

  • 为了避免被分开执行,3个订单要被一起提交
  • 2个卖单要作为买单的子订单
  • 在买单执行前,2个卖单处于非激活状态
  • 买单如果被取消,2个卖单也会随之被取消
  • 买单被执行后,2个卖单均被激活
  • 一旦两个卖单被激活,其中一个被执行或者被取消,另一个都将被自动取消。

buy_bracket方法中除了设置买入价、止损价、止盈价外,还设置了订单有效时间,如果订单在有效时间内未执行,则会过期失效。一般我们将买入有效期设置为3天以内,卖出有效期设置为较长时间,以保证股票可以卖出。

回测000001后的最终资产为102781.90元,这里交易大小仅为1000股,所用资金仅为不到20000元,提高交易量,收益还是相当可观。

Python量化交易学习笔记(19)——连续下跌买入止盈止损卖出策略

回测000002后的最终资产为96476.14元,亏损。

Python量化交易学习笔记(19)——连续下跌买入止盈止损卖出策略

回测601318后的最终资产为75044.50元,亏得一塌糊涂。

Python量化交易学习笔记(19)——连续下跌买入止盈止损卖出策略

观察几张图标可以发现,我们经常可以找到很好的买点,但是会出现本来盈利的订单,最后变成亏损的情况。在挖backtrader文档的时候,发现了一个比较好的利润保护方法,将在下一篇文章中进行实现及分析,敬请期待。

友情提示:本系列学习笔记只做数据分析,记录个人学习过程,不作为交易依据,盈亏自负。

连续下跌买入止盈止损卖出策略代码:

from __future__ import (absolute_import, division, print_function, unicode_literals)
import datetime  # 用于datetime对象操作
import os.path  # 用于管理路径
import sys  # 用于在argvTo[0]中找到脚本名称
import backtrader as bt # 引入backtrader框架

# 创建策略
class St(bt.Strategy):
    params = dict(
        p_downdays = 4,     # 连续下跌天数
        p_stoploss = 0.05,  # 止损比例
        p_takeprofit = 0.1, # 止盈比例
        limit=0.005,
        limdays=3,
        limdays2=1000,
        hold=10,
        usebracket=False,  # use order_target_size
        switchp1p2=False,  # switch prices of order1 and order2
    )
    def notify_order(self, order):
        print('{}: Order ref: {} / Type {} / Status {}'.format(
            self.data.datetime.date(0),
            order.ref, 'Buy' * order.isbuy() or 'Sell',
            order.getstatusname()))

        if order.status == order.Completed:
            self.holdstart = len(self)

        if not order.alive() and order.ref in self.orefs:
            self.orefs.remove(order.ref)
    def __init__(self):
        # 引用data[0]数据的收盘价数据
        self.dataclose = self.datas[0].close
        sma = bt.ind.SMA(period = self.p.p_downdays + 1, plot = False)
        self.orefs = list()
    def next(self):
        if self.orefs:  # order列表,用于存储尚未执行完成的订单
            return      # 有尚未执行的订单
        # 尚未进场
        if not self.position:
            # 获取近几日收盘价用于判断是否连续下跌
            lastcloses = list()
            for i in range(self.p.p_downdays + 1):
                lastcloses.append(self.dataclose[-i])
            # 连续N日下跌
            if lastcloses == sorted(lastcloses):
                # 计算买入报价跑p1,止损价p2,止盈价p3
                close = self.dataclose[0]
                p1 = close * (1.0 - self.p.limit)
                p2 = p1 - self.p.p_stoploss * close
                p3 = p1 + self.p.p_takeprofit * close
                # 计算订单有效期
                valid1 = datetime.timedelta(self.p.limdays)
                valid2 = valid3 = datetime.timedelta(self.p.limdays2)
                # 使用bracket orders设置买入卖出
                os = self.buy_bracket(
                    price=p1, valid=valid1,
                    stopprice=p2, stopargs=dict(valid=valid2),
                    limitprice=p3, limitargs=dict(valid=valid3),)
                # 保存激活的的订单
                self.orefs = [o.ref for o in os]

cerebro = bt.Cerebro()  # 创建cerebro
# 先找到脚本的位置,然后根据脚本与数据的相对路径关系找到数据位置
# 这样脚本从任意地方被调用,都可以正确地访问到数据
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../TQDat/day/stk/000001.csv')
# 创建价格数据
data = bt.feeds.GenericCSVData(
        dataname = datapath,
        fromdate = datetime.datetime(2018, 1, 1),
        todate = datetime.datetime(2020, 3, 31),
        nullvalue = 0.0,
        dtformat = ('%Y-%m-%d'),
        datetime = 0,
        open = 1,
        high = 2,
        low = 3,
        close = 4,
        volume = 5,
        openinterest = -1
        )
# 在Cerebro中添加价格数据
cerebro.adddata(data)
# 设置启动资金
cerebro.broker.setcash(100000.0)
# 设置交易单位大小
cerebro.addsizer(bt.sizers.FixedSize, stake = 1000)
# 设置佣金为千分之一
cerebro.broker.setcommission(commission=0.001)
cerebro.addstrategy(St)  # 添加策略
cerebro.run()  # 遍历所有数据
# 打印最后结果
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.plot()  # 绘图