导语:作为策略锦集第六篇,再向大家介绍非常著名的交易系统—海龟交易法则。
1.海龟交易法则的由来
Riachard Dennis是七八十年代著名的期货投机商,是一位具有传奇色彩的人物,在多年的投机生涯中,Dennis出尽风头,给人的感觉是常常可以在最低点买进,然后在最高峰反手卖空。
他相信优秀的交易员是后天培养而非天生的。在1983年12月,他招聘了23名新人,昵称为海龟,并对这些交易员进行了一个趋势跟踪交易策略培训。随后给予每个新人100万美元的初始资金。经5年的运作,大部分“海龟”的业绩非常惊人,其中最好的业绩达到1.72亿美元。多年后,海龟交易法则公布于世,我们才有幸看到曾名噪一时的完整的海龟交易法则。
2.海龟交易法则介绍
A.市场与标的
海龟交易法则应用于流动性高的市场中,选择交易量较大的标的进行交易。本文以沪深300指数ETF为标的,构建海龟交易法则。
B、仓位:仓位是海龟交易系统最核心的部分,通过ATR真实波幅指标来管理仓位。
第一步:计算True Range,简称TR。
TR = Max ( H−L , H−P , P−L ) ,其中H为当日日内最高价,L为当日日内最低价,P为前一日收盘价。
第二步:计算ATR
ATR = mean ( TR , 20 ) , 即计算过去20天的TR的平均值,ATR是TR的移动平均值
第三步:计算unit(法则的交易单位)
Unit = (value x 1% ) / ATR ,其中value x 1%即为总资产的1%,考虑到国内最小变化量是0.01元,1手是100股,所以1ATR即为持1股股票的资产最大变动,那么买入1Unit单位的股票,使得总资产当天震幅不超过1% 。
C.开仓入市
海龟交易法则分两个交易系统,两者都为分钟回测,即盘中交易:
系统一:
I、若当前价格高于过去20日的最高价,则买入一个Unit
II、加仓:若股价在上一次买入的基础上上涨了0.5ATR,则加仓一个Unit。
系统二:
I、若当前价格高于过去55日的最高价,则买入一个Unit
II、加仓:若股价在上一次买入的基础上上涨了0.5N,则加仓一个Unit。
举例:若某只股票A的ATR为1,20日最高价为40。
则当股价突破40时买入一个Unit,当股价突破40+0.5×1=40.5时加仓一个Unit。
当股价突破40.5+0.5×1=41时加仓一个Unit。
D.止损:海龟交易法则规定,当价格比最后一次买入价格下跌2ATR时,则卖出全部头寸止损。
E.止盈:两个系统分别采用不同参数来止盈。
系统一:当股价跌破10日内最低价格时,清仓结束交易
系统二:当股价跌破20日内最低价格时,清仓结束交易
考虑到系统一与系统二的差异性集中在参数上,本篇内容实现系统二,来向大家展示海龟交易法则。
以下为策略实现的基本信息:
策略实现难度:2
实现过程中所需要用到的API函数,ps:通过SuperMind量化交易平台API文档快速掌握:
需要用到的API函数 | 功能 |
---|---|
context.portfolio.stock_account.total_value | 获取账户总资产 |
set_benchmark() | 设置基准指数 |
本策略的编写难点在于理解海龟交易法则的运行逻辑,以下是海龟法则运行逻辑的梳理:
编写海龟交易法则的时候,建议采用主干+枝干的思路。
策略回测区间:2021.01.01-2022.12.31
回测资金:1000000
回测频率:分钟
回测结果:红色曲线为策略收益率曲线,蓝色曲线为对应的基准指数收益率曲线
策略源代码:
import pandas as pd
import numpy as np
# 初始化函数,全局只运行一次
def init(context):
context.security = '159919.OF'#确定交易标的
set_benchmark(context.security)
context.ART = 0#储存ATR的值,每个交易日更新一次
context.unit = 0#买卖单位的储存变量
context.steam = False#交易系统
context.price = 0 #记录系统的买入价,以便加仓和离市
#每日开盘前9:00被调用一次,用于储存自定义参数、全局变量,执行盘前选股等
def before_trading(context):
#更新n值
ATR=get_ATR(context)
#获取账户总资产
value=context.portfolio.stock_account.total_value
# 依本策略,计算一个单位的unit为多少股,以便后续下单交易,注意一共两个系统,因此需要除以2
context.unit = (value*0.01)/context.ATR
if context.unit<100:
log.info('一个unit单位的股数不满1手,无法下单!')
## 开盘时运行函数
def handle_bar(context, bar_dict):
#====================系统1================================
#系统是否需要开启运作
if context.steam == False:
#获取开启系统的结果
context.steam = steam(context,bar_dict,55)
if context.steam == False:
pass
else:
order(context.security,context.unit)
#买入后记录当前价位,以便加仓和离市
log.info('系统开启')
nowclose = history(context.security, ['close'], 1, '1m', False, 'pre', is_panel=1)['close']
context.price = nowclose[0]
#系统已经开启运作
if context.steam == True:
#获取进行加仓结果
signal=addtrade(context,bar_dict)
#执行结果加仓
if signal=='buy':
log.info('系统加仓')
nowclose = history(context.security, ['close'], 1, '1m', False, 'pre', is_panel=1)['close']
order(context.security,context.unit)
#买入后记录当前价位,以便加仓和离市
context.price1 = nowclose[0]
else:
pass
#获取止盈结果
signal=down(context,bar_dict)
#执行止盈,关闭系统
if signal=='sell':
log.info('系统1止盈')
order_target(context.security,0)
#止盈后情况价位记录
context.price = 0
#关闭系统
context.steam = False
#离场结果判断
if context.steam==True:
#获取离场结果
signal=giveuptrade(context,bar_dict,20)
#执行离场,关闭系统
if signal=='sell':
log.info('系统1离场')
order_target(context.security,0)
#离场后情况价位记录
context.price=0
#关闭系统
context.steam=False
#=================判断是否离场函数============================
def giveuptrade(context,bar_dict,n):
#根据系统来获取相应数据长度
close = history(context.security, ['low'], n, '1d', False, 'pre', is_panel=1)['low']
close_min=min(close)
nowclose = history(context.security, ['close'], 1, '1m', False, 'pre', is_panel=1)['close']
#最新价格突破过去N日最大收盘价,即为突破,开启系统
if nowclose[0]<close_min:
return 'sell'
else:
return None
#==================判断是否止盈函数=================================
def down(context,bar_dict):
nowclose = history(context.security, ['close'], 1, '1m', False, 'pre', is_panel=1)['close']
n=context.ATR
TP=context.price-2*n
if nowclose[0]<TP:
return 'sell'
else:
return None
#==================判断是否加仓函数=================================
def addtrade(context,bar_dict):
nowclose = history(context.security, ['close'], 1, '1m', False, 'pre', is_panel=1)['close']
n=context.ATR
TP=context.price+n/2
if nowclose[0]>TP:
return 'buy'
else:
return None
#==================判断系统开启函数=================================
def steam(context,bar_dict,n):
close = history(context.security, ['close'], n, '1d', False, 'pre', is_panel=1)['close']
close_max=max(close)
nowclose = history(context.security, ['close'], 1, '1m', False, 'pre', is_panel=1)['close']
#最新价格突破过去N日最大收盘价,即为突破,开启系统
if nowclose[0]>close_max:
return True
else:
return False
#==================计算n值的函数=================================
def get_ATR(context):
#由于用到前20个交易日的n值,ATR计为过去20日的TR均值
price = history(context.security, ['close','high','low'], 21, '1d', False, 'pre', is_panel=1)
h = price['high'].iloc[1:] #最高价,获取21个需弃掉第一个
l= price['low'].iloc[1:]#最低价,获取21个需弃掉第一个
rc = price['close'].shift().iloc[1:]#昨日收盘价,获取21个需弃掉第一个
#shift()操作专门是用于获取前收盘价数据的
tr_list = []
for i in range(0,20,1):
h = price['high'].iloc[i]
l = price['low'].iloc[i]
rc = price['close'].iloc[i]
TR = max(h-l, h-rc, rc-l)
tr_list.append(TR)
ATR=np.mean(tr_list)
context.ATR=ATR
return context.ATR