【手把手教你】用backtrader量化回测海龟交易策略

01

引言

海龟交易策略是比较经典的趋势交易系统之一,涵盖了从入场交易(品种选择)、仓位管理(基于ATR加减仓)、离场(触发条件)的整个过程。机械套用海龟交易法则在A股上进行交易可能效果不佳,但其交易系统的思维和动态仓位管理仍然值得挖掘和学习借鉴。公众号推文《【手把手教你】用Python量化海龟交易法则》简要介绍了海龟交易法则的基本原理,使用Python对其买卖信号进行了可视化分析,并利用Pandas对相关指数和个股运用简化版的海龟交易规则进行了历史回测。本文在此基础上,利用backtrader框架对海龟交易法则进行完整的量化回测。关于backtrader的入门和使用见公众号系列推文。

backtrader系列推文:

(1)【手把手教你】入门量化回测最强神器backtrader(一)

(2)【手把手教你】入门量化回测最强神器backtrader(二)

(3)【手把手教你】入门量化回测最强神器backtrader(三)

(4)backtrader如何加载股票因子数据?以换手率、市盈率为例进行回测【附Python代码】

(5)如何用backtrader对股票组合进行量化回测?

02

策略代码

以下代码使用Jupyter notebook进行编译。

from __future__ import (absolute_import, division, print_function,unicode_literals)
import backtrader as bt
import pandas as pd
import tushare as ts
import matplotlib.pyplot as plt
%matplotlib inline
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False

使用tushare获取数据,根据backtrader的数据格式对数据进行处理。

def get_data(code,start='2010-01-01',end='2020-07-16'):df=ts.get_k_data(code,autype='qfq',start=start,end=end)df.index=pd.to_datetime(df.date)df['openinterest']=0df=df[['open','high','low','close','volume','openinterest']]return df

交易策略

回顾一下海龟交易法则的策略思路:

入场条件:当收盘价突破20日价格高点时,买入一单元股票;

加仓条件:当价格大于上一次买入价格的0.5个ATR(平均波幅),买入一单元股票,加仓次数不超过3次;

止损条件:当价格小于上一次买入价格的2个ATR时清仓;

离场条件:当价格跌破10日价格低点时清仓。

这里的20日价格高点和10日价格低点构成唐奇安通道,所以海龟交易法则也可以理解成通道突破的趋势跟踪。

大家可能有一个疑问,唐奇安通道为啥是以20日、10线为上下轨线,不能是30日、5日或其他吗?当然,这里的20日和10日参数其实是根据经验而定的。不同标的参数选取可能存在一定差异,下面不妨将时间周期作为可变参数,通过参数优化选取合适的时间周期进行回测

class TurtleStrategy(bt.Strategy):
#默认参数params = (('long_period',20),('short_period',10),  ('printlog', False), )   def __init__(self):        self.order = None      self.buyprice = 0      self.buycomm = 0      self.buy_size = 0      self.buy_count = 0       # 海龟交易法则中的唐奇安通道和平均波幅ATR        self.H_line = bt.indicators.Highest(self.data.high(-1), period=self.p.long_period)        self.L_line = bt.indicators.Lowest(self.data.low(-1), period=self.p.short_period)       self.TR = bt.indicators.Max((self.data.high(0)- self.data.low(0)),\abs(self.data.close(-1)-self.data.high(0)), \abs(self.data.close(-1)  - self.data.low(0)))        self.ATR = bt.indicators.SimpleMovingAverage(self.TR, period=14)       # 价格与上下轨线的交叉      self.buy_signal = bt.ind.CrossOver(self.data.close(0), self.H_line)        self.sell_signal = bt.ind.CrossOver(self.data.close(0), self.L_line)    def next(self): if self.order:return        #入场:价格突破上轨线且空仓时        if self.buy_signal > 0 and self.buy_count == 0:                                 self.buy_size = self.broker.getvalue() * 0.01 / self.ATR            self.buy_size  = int(self.buy_size  / 100) * 100                             self.sizer.p.stake = self.buy_size             self.buy_count = 1            self.order = self.buy()        #加仓:价格上涨了买入价的0.5的ATR且加仓次数少于3次(含)        elif self.data.close >self.buyprice+0.5*self.ATR[0] and self.buy_count > 0 and self.buy_count <=4:           self.buy_size  = self.broker.getvalue() * 0.01 / self.ATR            self.buy_size  = int(self.buy_size  / 100) * 100            self.sizer.p.stake = self.buy_size             self.order = self.buy()           self.buy_count += 1        #离场:价格跌破下轨线且持仓时        elif self.sell_signal < 0 and self.buy_count > 0:            self.order = self.sell()            self.buy_count = 0        #止损:价格跌破买入价的2个ATR且持仓时        elif self.data.close < (self.buyprice - 2*self.ATR[0]) and self.buy_count > 0:           self.order = self.sell()self.buy_count = 0   #交易记录日志(默认不打印结果)def log(self, txt, dt=None,doprint=False):if self.params.printlog or doprint:dt = dt or self.datas[0].datetime.date(0)print(f'{dt.isoformat()},{txt}')#记录交易执行情况(默认不输出结果)def notify_order(self, order):# 如果order为submitted/accepted,返回空if order.status in [order.Submitted, order.Accepted]:return# 如果order为buy/sell executed,报告价格结果if order.status in [order.Completed]: if order.isbuy():self.log(f'买入:\n价格:{order.executed.price},\成本:{order.executed.value},\手续费:{order.executed.comm}')self.buyprice = order.executed.priceself.buycomm = order.executed.commelse:self.log(f'卖出:\n价格:{order.executed.price},\成本: {order.executed.value},\手续费{order.executed.comm}')self.bar_executed = len(self) # 如果指令取消/交易失败, 报告结果elif order.status in [order.Canceled, order.Margin, order.Rejected]:self.log('交易失败')self.order = None#记录交易收益情况(可省略,默认不输出结果)def notify_trade(self,trade):if not trade.isclosed:returnself.log(f'策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}')def stop(self):self.log(f'(组合线:{self.p.long_period},{self.p.short_period}); \期末总资金: {self.broker.getvalue():.2f}', doprint=True)

由于交易过程中需要对仓位进行动态调整,每次交易一单元股票(不是固定的一股或100股,根据ATR而定),因此交易头寸需要重新设定。

class TradeSizer(bt.Sizer):params = (('stake', 1),)    def _getsizing(self, comminfo, cash, data, isbuy):        if isbuy:          return self.p.stake        position = self.broker.getposition(data)        if not position.size:            return 0        else:            return position.size        return self.p.stake

为了与backtrader回测结果进行对比,下面对选取的标的在区间的价格走势和累计涨幅进行可视化。

def plot_stock(code,title,start,end):dd=ts.get_k_data(code,autype='qfq',start=start,end=end)dd.index=pd.to_datetime(dd.date)dd.close.plot(figsize=(14,6),color='r')plt.title(title+'价格走势\n'+start+':'+end,size=15)plt.annotate(f'期间累计涨幅:{(dd.close[-1]/dd.close[0]-1)*100:.2f}%', xy=(dd.index[-150],dd.close.mean()), xytext=(dd.index[-500],dd.close.min()), bbox = dict(boxstyle = 'round,pad=0.5',fc = 'yellow', alpha = 0.5),arrowprops=dict(facecolor='green', shrink=0.05),fontsize=12)plt.show()

将backtrader回测设置封装成函数,减少代码的复用。

def main(code,long_list,short_list,start,end='',startcash=1000000,com=0.001):#创建主控制器cerebro = bt.Cerebro()      #导入策略参数寻优cerebro.optstrategy(TurtleStrategy,long_period=long_list,short_period=short_list)    #获取数据df=ts.get_k_data(code,autype='qfq',start=start,end=end)df.index=pd.to_datetime(df.date)df=df[['open','high','low','close','volume']]#将数据加载至回测系统data = bt.feeds.PandasData(dataname=df)    cerebro.adddata(data)#broker设置资金、手续费cerebro.broker.setcash(startcash)           cerebro.broker.setcommission(commission=com)    #设置买入设置,策略,数量cerebro.addsizer(TradeSizer)    print('期初总资金: %.2f' % cerebro.broker.getvalue())    cerebro.run(maxcpus=1)    

03

回测实例

指数回测

上面对整个海龟交易策略在backtrader上的回测进行了函数封装,下面以上证综指为例(假设可以直接交易),对2010-01-01至2020-07-17期间进行回测。从下图可看出,如果按照“买入持有”进行交易,期间累计涨幅是-0.91%,这对于广大A股股民来说已经是见怪不怪了。两会期间已经有专家指出上证综指的失真,并提出对上证综指进行修订以反映实体经济的发展趋势。

plot_stock('sh','上证综指','2010-01-01','2020-07-17')

通过对参数进行优化发现,如果以上证综指为交易标的,最优的参数并非是(20、10)日组合,而是(50,5)日,即指数突破50日线新高时才入场,跌破5日最低时离场。

long_list=range(20,70,5)
short_list=range(5,20,5)
main('sh',long_list,short_list,'2010-01-01','2020-07-17')


将回测设置和策略评价指标可视化进行封装,由于篇幅有限,策略评价指标可视化代码此处省略,完整代码见知识星球分享。

def performance(code,long,short,start,end,startcash,com):代码略,完整代码见知识星球#回测实例
df00,df0,df1,df2,df3,df4=performance('sh',50,5,'2010-01-01','2020-07-17',1000000,0.001)

#输出结果:

2020-07-17,(组合线:50,5); 期末总资金: 1541612.17

量化回测绩效评价指标:

df00

对交易标的回测结果进行可视化,包含账户价值、持仓价值和每年的年化收益率。

def plot_result_py(data,v,title,plot_type='line',zoom=False):代码略,完整代码见知识星球
plot_result_py(df0,'total_value','账户价值')

plot_result_py(df4,'total_position_value','持仓市值')

plot_result_py(df3,'year_rate','年化收益%',plot_type='bar')

个股回测

由于指数不可直接交易,下面以中国平安(601318)股票为例进行回测。下图显示,如果以“买入持有”策略交易中国平安,十年来累计收益率达到232.62%,远远跑赢上证综指。

plot_stock('601318','中国平安','2010-01-01','2020-07-17')

参数优化显示,中国平安最佳参数刚好是海龟交易法则提出的(20,10)日组合,看来先贤总结的经验还是值得推敲的。

long_list=range(20,70,5)
short_list=range(5,20,5)
main('601318',long_list,short_list,'2010-01-01','2020-07-16',startcash=100000)

中国平安回测绩效指标:

中国平安回测期间账户价值:

中国平安回测期间持仓市值:

中国平安回测期间年化收益:

04

结语

本文基于backtrader,构建了海龟交易法则的完整回测框架,并分别以上证综指和中国平安为例进行了回测。从回测实例看,海龟交易法则优于“买入持有”策略,(20,10)日线的参数选择可能只适合某些标的。当然这里只是简单举了两个例子,所以还无法将结论进一步推广。本文的目的主要在于为大家深入研究海龟交易法则的量化提供思路和分析框架,而不在于验证该策略是否适用于A股市场。上述回测过程给人的直观感受是回归交易法则更像是一个择时策略,即在价格触发某条件时买入\卖出。实际上择时和选股有时候是一个硬币的两面,没有本质上的区别。如果以所有A股为股票池(当然可以先剔除ST、业绩亏损、超大盘等),加入遍历循环,当某只股票满足突破上轨(如20日最高价)时买入构建投资组合,当跌破下轨(如10日最低价)或满足止损条件时从组合中剔除,这时候海龟交易法则兼有选股和择时,感兴趣的读者可以进一步去挖掘和分析。

参考资料:

 1. backtrader官方文档和安装包原生代码

    https://www.backtrader.com/docu/

 2.知乎:量化投资2:基于backtrader实现完整海龟法则量化回测

   https://zhuanlan.zhihu.com/p/114782214

关于Python金融量化

专注于分享Python在金融量化领域的应用。加入知识星球,可以免费获取量化投资视频资料、量化金融相关PDF资料、公众号文章Python完整源码、量化投资前沿分析框架,与博主直接交流、结识圈内朋友等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/52950.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[061量化交易]python使用baostock下载全部行情数据

提前建立下载目录文件夹 import baostock as bs import pandas as pd import datetime# 是否删除停盘数据 DROP_SUSPENSION Truedef load_stk_list():df pd.read_csv(D:/stk_list.csv)return df[code].tolist()def convert_time(t):H t[8:10]M t[10:12]S t[12:14]return …

文心一言释义|科技创新

文心一言 前言1、什么是文心一言&#xff1f;2、有什么作用&#xff1f;3、从哪里来&#xff0c;发展前景&#xff1f; 一、文心一言优势1、文学创作、商业文案创作、数理逻辑推算、中文理解、多模态生成1、中文理解2、多模态生成一大特点&#xff1a;自动文本摘要 总结 前言 …

一文详说怎么打开和使用caj文件,以及caj是什么格式的文件

文件目录 1. caj是什么格式的文件2. 打开和使用caj文件3. 文末总结 1. caj是什么格式的文件 曾记得做毕业设计时&#xff0c;从中国知网下载的文档&#xff0c;就是caj格式的。 曾经试着将caj文件转为pdf文件&#xff0c;比如&#xff0c;我将软件开发.caj转化为软件开发.pdf&…

怎么注销计算机的用户,如何注销/取消绑定在临时电脑上administrator用户上登陆过的微软账号教程...

相比大家遇到这个问题很久了&#xff0c;这个其实上是windows10 的一个bug&#xff1b;大致的问题有如下几个 问题描述&#xff1a; 问题1&#xff1a;在非个人电脑上登录过微软账户&#xff0c;改用本地账户登录后&#xff0c;微软自带的邮件应用、todo、OneNote、OneDrive、e…

注销账号功能(账号保留一个月)

数据库实现 -- 1、打开调度&#xff08;这样才会自动调用事件&#xff09; -- 查询方法&#xff08;注意: -- 和查询之间有个空格,否则不能被识别为注释&#xff09; select event_scheduler -- 设置方法(开启) SET global.event_scheduler ON; -- 设置方法(关闭) -- SET gl…

微信怎么注销账号?10%的人已注销

微信怎么注销账号&#xff1f;使用微信的各位一定知道一个人是可以同时拥有很多个微信号的&#xff0c;基本上你有多少个手机号&#xff0c;就等于拥有等量的微信号。怎么注销微信&#xff1f;当你已经拥有多个微信号的时候&#xff0c;有时候某个多余的微信号根本不常用到&…

ChatGPT 背后的技术重点:RLHF、IFT、CoT、红蓝对抗

近段时间&#xff0c;ChatGPT 横空出世并获得巨大成功&#xff0c;使得 RLHF、SFT、IFT、CoT 等这些晦涩的缩写开始出现在普罗大众的讨论中。这些晦涩的首字母缩略词究竟是什么意思&#xff1f;为什么它们如此重要&#xff1f;我们调查了相关的所有重要论文&#xff0c;以对这些…

新程序员大会(NPCon):从大模型到AGI,ChatGPT的大航海时代!

AI激荡70载&#xff0c;身处操作系统演变的中心位置&#xff0c;在ChatGPT、文心一言等AIGC产品&#xff0c;GPT-4、Stable Diffusion、Midjourney等AI大模型的加持下&#xff0c;程序员现有的编程范式将会遭到怎样的冲击&#xff1f;面向的全新AI应用时代&#xff0c;开发者的…

AI大模型时代下运维开发探索第一篇:ReAct工程初探

引子 人工智能大模型的出现&#xff0c;已渐渐地影响了我们的日常生活和工作方式。生活中无处不在的AI&#xff0c;使我们的生活变得更加智能和便捷。工作中&#xff0c;AI大模型的高效和精准&#xff0c;极大地提升了我们解决问题的效率。 是的&#xff0c;我们不能忽视AI大…

ITK和VTK读取DICOM图像文件

ITK和VTK读取DICOM图像文件 ITK读取DICOM图像 相比于VTK类库中vtkDICOMImageReader类读取DICOM序列图像&#xff0c;借助ITK类库实现对DICOM序列图像的读取要复杂许多。但是&#xff0c;使用ITK类库读取图像不像使用VTK类库那么功能局限&#xff0c;VTK类库对每种图像格式都有…

VTK使用

入门参考的这篇&#xff08;修改了很多&#xff09;&#xff1a;QT加载显示DICOM文件浏览 但还是必须在main()中先读文件再show&#xff0c;先show了再打开文件是黑的显示不出来&#xff08;后续再查&#xff09;。 //.h中要加入 #include <vtkAutoInit.h> VTK_MODULE_I…

VTK教程3--------打开vtk文件

下面&#xff0c;本教程将介绍如何在MFC下打开vtk文件&#xff0c;最终的效果如图所示&#xff1a; 如果你看过一些三维重建相关的论文&#xff0c;那么一定对这个图像很熟悉。很多论文都会以这个图像作为例子讲解&#xff0c;好了&#xff0c;闲话少说&#xff0c;直接讲实现…

Android中读取电话本Contacts联系人的所有电话号信息

1.首先&#xff0c;要知道android 的contacts里的电话信息有多类&#xff1a;moblie,家庭&#xff0c;工作&#xff0c;传真等。如图&#xff1a; 2.android的Contacts是通过ContentProvider来提供的&#xff0c;其实android把contacts和SMS给组织成数据库文件了&#xff0c;你…

虚拟化 : VT-x VT-d VT-c的分别

虚拟化 &#xff1a; VT-x VT-d VT-c的分别: VT-x [运行ESXI上的64bit Guest OS基本指令] Intel运用Virtualization虚拟化技术中的一个指令集。VT-x有助于提高基于软件的虚拟化解决方案的灵活性与稳定性。通过按照纯软件虚拟化的要求消除虚拟机监视器(VMM&#xff09;代表…

VTK-vtkPolyDataNormals

前言&#xff1a;本博文主要记录vtkPolyDataNormals的用途&#xff0c;使用方法以及实现原理&#xff0c;帮助更多的小伙伴更好的理解和应用vtk。 目录 1. vtkPolyDataNormals 2. 使用示例 3. 实现原理 1. vtkPolyDataNormals vtkPolyDataNormals可以计算多边形网格数据的点…

手机联系人批量导入(字符编码、xlsx与vcf互转)

一、前言 单位同事每人都办理了一个192新手机号&#xff0c;一打过来不知道是谁&#xff0c;又懒的一个个保存姓名。一想是不是可以批量导入呢?电子表格号码名单我有啊&#xff0c;试试吧。 二、实践 1.先下载手机联系人文件看看吧。在手机联系人设置中有个导出选项&#x…

信创干部人事档案管理系统单机版 - 人力资源档案管理系统软件

信创干部人事档案管理系统单机版v2.0&#xff08;以下简称系统&#xff09;&#xff0c;是一套具有先进性、安全性、前瞻性的人力资源档案管理系统&#xff0c;是在总结近二十年为万余家单位档案信息化建设实践经验的基础上&#xff0c;遵循ISO15489、ISO23081、ISO14721等国际…

VTK-vtkFieldData

欢迎大家加入社区&#xff0c;雪易VTK社区-CSDN社区云 前言&#xff1a;为区分vtkPoints和vtkPointData的区别&#xff0c;了解vtkFieldData在VTK中的存在意义&#xff0c;从而系统的掌握vtk中关于数据的表达方式。 vtk中通过vtkDataArray进行数据的存储&#xff0c;通过vtkD…

虚拟内存统计----Vmstat命令

目录 一、Vmstat命令 概述 1.1 物理内存和虚拟内存区别 1.1.1 物理内存 1.1.2 虚拟内存 1.2 vmstat 命令语法 1.2.1 格式 1.2.2 命令参数 二、 vmstat 示例 2.1 显示虚拟内存使用情况 2.1.1 查看 2.1.2 表示在1秒时间内进行2次采样 2.1.3 指定的MB 单位输…

票据机器人-OCR自动识别助力智能财务

当前票据的数字化问题尚未完全解决&#xff0c;人工处理海量票据的工作量较大&#xff0c;把票据录入系统、校验真伪的成本较高。 票据机器人对财务报销环节涉及的增票、火车票、飞机行程单、出租车票、定额发票、汽车票等票据进行自动分类、识别录入、验真及归档。 票据机器人…