本文主要记录保护点卖出策略,给买入的股票设立保护点,随着股票收盘价的提升,保护点不断提高,股价一旦跌破保护点,即卖出股票。
示例的买入条件为,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包括两种情况:
在卖出时,程序使用了backtrader的StopTrail订单,回撤可以用回撤值(trailamount)或者回撤率(trailpercent)来表示,这里我们选择用5%的回撤率来做回测。StopTrail将按下面的逻辑进行工作:
从StopTrail订单的描述可以看出,保护点会随着股价的上升不断更新,不断提高,一旦股价跌破保护点,就会进行卖出,这样可以有效的保护我们的利润。同时可以看到,即使股价一直下跌,StopTrail订单也可以有效止损。
回测000002后的最终资产为103236.59元。
友情提示:本系列学习笔记只做数据分析,记录个人学习过程,不作为交易依据,盈亏自负。
保护点卖出策略代码:
# 创建策略
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') # 绘图
为了便于相互交流学习,新建了微信群,感兴趣的读者请加微信。