Python量化交易学习笔记(20)——保护点卖出策略

本文主要记录保护点卖出策略,给买入的股票设立保护点,随着股票收盘价的提升,保护点不断提高,股价一旦跌破保护点,即卖出股票。

示例的买入条件为,5日线金叉60日线,且股价进行小幅回踩(较金叉日收盘价下跌1%)。卖出条件为,股价跌破保护点。保护点首先设置为买入当天收盘价减去一个资金回撤值(率),示例把回撤率设置为5%。后续如果股票的收盘价上升,则用新的收盘价更新保护点,如果股票的收盘价下跌,则保留原有保护点。回测初始资金100000元,单笔操作单位1000股,佣金千分之一,回测时间自2018年1月1日至2020年3月20日。

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

    def next(self):
        # 无场内资产
        if not self.position:
            # 未提交买单
            if None == self.order:
                # 金叉到达了买点
                if self.buy_con:
                    # 计算订单有效期时间,如果超过有效期,股价仍未回踩,则放弃下买入订单
                    valid = self.data.datetime.date(0)
                    if self.p.buy_valid_date:
                        valid = valid + datetime.timedelta(days=self.p.buy_valid_date)
                    # 计算回踩后的买入价格
                    price = self.datas[0].close[0] * (1.0 - self.p.buy_limit_percent)
                    print('Buy order created: {}: close: {} / limit price: {} / valid: {}'.format(
                        self.datetime.date(), self.datas[0].close[0], price, valid) )
                    # 用有效时间及回踩买点提交买入订单
                    self.order = self.buy(exectype = bt.Order.Limit, price = price, valid = valid)
                    #o = self.buy()
                    print('*' * 50)
        elif self.order is None:
            # 提交stoptrail订单
            self.order = self.sell(exectype=self.p.stoptype,
                                   trailamount=self.p.trailamount,
                                   trailpercent=self.p.trailpercent)

在买入时,程序中使用了backtrader的Limit类型订单,在Limit订单创建时,会设置一个price及有效日期valid,如果到达日期valid后,股价还没有匹配上price,订单就会被取消。匹配price包括两种情况:

  • 如果开盘价低于price,那么订单就会用开盘价被立即执行
  • 如果开盘价高于price,但是当日最低价低于price,那么订单将会以price的价格被执行
    从Limit订单的描述可以看出,正好符合我们所说的股价回踩买入规则。

在卖出时,程序使用了backtrader的StopTrail订单,回撤可以用回撤值(trailamount)或者回撤率(trailpercent)来表示,这里我们选择用5%的回撤率来做回测。StopTrail将按下面的逻辑进行工作:

  • 使用当日收盘价作为price值
  • price按照回撤率回撤计算后的价格,被作为触发价格(trigger price),即我们所说的保护点。比如price=100,trailpercent=5%,那么trigger price=100*(1-5%)=95
  • 代理在下一根K线迭代计算时,会计算是否达到trigger price
  • 如果达到trigger price,卖单将被执行
  • 如果没有达到trigger price,那么trigger price将用当前的收盘价按照之前选定的回撤率进行重新计算
  • 如果股价上涨,那么trigger price将会被更新为新的trigger price
  • 如果估计不变或者下跌,那么trigger price不更新

从StopTrail订单的描述可以看出,保护点会随着股价的上升不断更新,不断提高,一旦股价跌破保护点,就会进行卖出,这样可以有效的保护我们的利润。同时可以看到,即使股价一直下跌,StopTrail订单也可以有效止损。

回测000001后的最终资产为102017.97元。
在这里插入图片描述

回测000002后的最终资产为103236.59元。

在这里插入图片描述
回测603999后的最终资产为99510.06元。

在这里插入图片描述
友情提示:本系列学习笔记只做数据分析,记录个人学习过程,不作为交易依据,盈亏自负。

保护点卖出策略代码:

# 创建策略
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(
        buy_limit_percent = 0.01,
        buy_valid_date = 5,
        stoptype=bt.Order.StopTrail,
        trailamount=0.0,
        trailpercent=0.05,
        p_high_period = 5,
        p_fast = 5,
        p_slow = 60,
    )
    def __init__(self):
        slowSMA = bt.ind.SMA(period = self.p.p_slow)
        self.buy_con = bt.And(
            bt.ind.CrossUp(
            bt.ind.SMA(period = self.p.p_fast), slowSMA),
            #slowSMA == bt.ind.Highest(slowSMA, period = self.p.p_high_period, plot = False)
        )
        self.order = None
    def notify_order(self, order):
        if order.status in [order.Completed]:
            print('Completed order: {}: Order ref: {} / Type {} / Status {} '.format(
                self.data.datetime.date(0),
                order.ref, 'Buy' * order.isbuy() or 'Sell',
                order.getstatusname()))
            self.order = None
        if order.status in [order.Expired]:
            self.order = None
        print('{}: Order ref: {} / Type {} / Status {}'.format(
            self.data.datetime.date(0),
            order.ref, 'Buy' * order.isbuy() or 'Sell',
            order.getstatusname()))
    def next(self):
        # 无场内资产
        if not self.position:
            # 未提交买单
            if None == self.order:
                # 金叉到达了买点
                if self.buy_con:
                    # 计算订单有效期时间,如果超过有效期,股价仍未回踩,则放弃下买入订单
                    valid = self.data.datetime.date(0)
                    if self.p.buy_valid_date:
                        valid = valid + datetime.timedelta(days=self.p.buy_valid_date)
                    # 计算回踩后的买入价格
                    price = self.datas[0].close[0] * (1.0 - self.p.buy_limit_percent)
                    print('Buy order created: {}: close: {} / limit price: {} / valid: {}'.format(
                        self.datetime.date(), self.datas[0].close[0], price, valid) )
                    # 用有效时间及回踩买点提交买入订单
                    self.order = self.buy(exectype = bt.Order.Limit, price = price, valid = valid)
                    #o = self.buy()
                    print('*' * 50)

        elif self.order is None:
            # 提交stoptrail订单
            self.order = self.sell(exectype=self.p.stoptype,
                                   trailamount=self.p.trailamount,
                                   trailpercent=self.p.trailpercent)
            if self.p.trailamount:
                tcheck = self.data.close - self.p.trailamount
            else:
                tcheck = self.data.close * (1.0 - self.p.trailpercent)
            print('Sell stoptrail order created: {}: \
                close: {} /  \
                Limit price: {} / check price {}'.format(
                self.datetime.date(), self.data.close[0],
                self.order.created.price, tcheck
            ))
            print('-' * 10)
        else:
            if self.p.trailamount:
                tcheck = self.data.close - self.p.trailamount
            else:
                tcheck = self.data.close * (1.0 - self.p.trailpercent)
            print('update limit price: {}: \
                close: {} /  \
                Limit price: {} / check price {}'.format(
                self.datetime.date(), self.data.close[0],
                self.order.created.price, tcheck
            ))

cerebro = bt.Cerebro()  # 创建cerebro
# 先找到脚本的位置,然后根据脚本与数据的相对路径关系找到数据位置
# 这样脚本从任意地方被调用,都可以正确地访问到数据
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../TQDat/day/stk/603999.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(style = 'candlestick')  # 绘图

为了便于相互交流学习,新建了微信群,感兴趣的读者请加微信。
在这里插入图片描述

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值