第六篇:海龟交易法则

导语:作为策略锦集第六篇,再向大家介绍非常著名的交易系统—海龟交易法则。

一、策略阐述

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
回测频率:分钟
回测结果:红色曲线为策略收益率曲线,蓝色曲线为对应的基准指数收益率曲线

策略源代码:

In [ ]:
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