好友提出要验证连续下跌买入止盈止损卖出策略,本文对该策略回测和实现做分析记录。
买入条件中,连续下跌定义为收盘价连续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方法遵循以下规则:
buy_bracket方法中除了设置买入价、止损价、止盈价外,还设置了订单有效时间,如果订单在有效时间内未执行,则会过期失效。一般我们将买入有效期设置为3天以内,卖出有效期设置为较长时间,以保证股票可以卖出。
回测000001后的最终资产为102781.90元,这里交易大小仅为1000股,所用资金仅为不到20000元,提高交易量,收益还是相当可观。
回测000002后的最终资产为96476.14元,亏损。
回测601318后的最终资产为75044.50元,亏得一塌糊涂。
观察几张图标可以发现,我们经常可以找到很好的买点,但是会出现本来盈利的订单,最后变成亏损的情况。在挖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() # 绘图
为了便于相互交流学习,新建了微信群,感兴趣的读者请加微信。