海龟交易法
1. 原理
1.1 起源
海龟交易思想起源于上世纪八十年代的美国。理查德丹尼斯与好友比尔打赌,主题是一个成功的交易员是天生的还是后天的。理查德用十年时间证明了通过日常系统培训,交易员可以通过后天培训成为一名优秀的交易者。这套培训系统就是海龟交易系统。
海龟交易系统是一个完整的、机械的交易思想,可以系统地完成整个交易过程。它包括了买卖什么、头寸规模、何时买卖、何时退出等一系列交易策略,是一个趋势交易策略。它最显著的特点是捕捉中长期趋势,力求在短期内获得最大的收益。
1.2 建仓资金
海龟交易法将建仓资金按照一定比例划分为若干个小部分,每次建仓头寸和加仓规模都与波动量N(又称平均真实波动振幅average true range ATR)有关。ATR是日内指数最大波动的平均振幅,由当日最高、最低价和上一交易日的收盘价决定。
ATR
其中PDC是前一交易日的收盘价,ATR就是TR在N天内的均值。
价值波动量
利用N值来体现价值波动量DV:
其中每点代表的价值量是指每一个指数点数所代表的价格。
每一次开仓交易合约数unit的确定是将总资产的1%除以DV得到。
1.3 入市信号
海龟交易法使用的是以一个理查德唐奇安的通道突破系统为基础的入市系统。唐奇安通道分为系统一和系统二,对应短期突破和中长期突破。其中,短期突破系统是以20日(最高价或最低价)突破为基础,当价格突破20日价格即为入市信号;中长期系统是当盘中价格突破过去55日价格为入市信号。
1.4 加仓和止损
海龟交易法的加仓规则是当捕捉到入市信号后建立第一个交易单位的头寸,市价继续向盈利方向突破1/2N时加仓。
止损位为2N,同加仓一样采用平均真实振幅N值为止损单位。每加仓一次,止损位就提高1/2N。
1.5 止盈
短期:多头头寸在突破过去10日最低价处止盈离市,空头头寸在突破过去10日最高价处止盈离市。
中长期:多头头寸在突破过去20日最低价处止盈离市,空头头寸在突破过去20日最高价处止盈离市。
2. 策略思路
第一步:获取历史数据,计算唐奇安通道和ATR
第二步:当突破唐奇安通道时,开仓。
第三步:计算加仓和止损信号。
回测标的:DCE.i2012
回测时间:2020-02-15 至 2020-09-01
回测初始资金:100万
Tips:
ATR值是不断变化的,这就会导致在对期货平仓时,可能出现平仓数量 > 持仓数量的现象。比如前一交易日的持仓为10,今日的ATR值为22.假设当前价格触发平仓条件,平仓1/2ATR。1/2ATR=11 > 10, 这样就会导致委托失败报错。所以要加入一个变量volume_hold用来记录当前持仓量,与1/2*ATR作比较。
注意:若修改回测期,需要修改对应的回测标的。
3. 策略代码
# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
import numpy as np
import pandas as pd
from gm.api import *
'''
以短期为例:20日线
第一步:获取历史数据,计算唐奇安通道和ATR
第二步:当突破唐奇安通道时,开仓。
第三步:计算加仓和止损信号。
'''
def init(context):
# 设置计算唐奇安通道的参数
context.n = 20
# 设置合约标的
context.symbol = 'DCE.i2012'
# 设置交易最大资金比率
context.ratio = 0.8
# 订阅数据
subscribe(symbols=context.symbol, frequency='60s', count=2)
# 获取当前时间
time = context.now.strftime('%H:%M:%S')
# 如果策略执行时间点是交易时间段,则直接执行algo定义atr等参数,以防直接进入on_bar()导致atr等未定义
if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00':
algo(context)
# 如果是交易时间段,等到开盘时间确保进入algo()
schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')
schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')
def algo(context):
# 计算通道的数据:当日最低、最高、上一交易日收盘
# 注:由于talib库计算ATR的结果与公式求得的结果不符,所以这里利用公式计算ATR
# 如果是回测模式,当天的数据直接用history取到
if context.mode == 2:
data = history_n(symbol=context.symbol, frequency='1d', count=context.n+1, end_time=context.now, fields='close,high,low,bob', df=True) # 计算ATR
tr_list = []
for i in range(0, len(data)-1):
tr = max((data['high'].iloc[i] - data['low'].iloc[i]), data['close'].shift(-1).iloc[i] - data['high'].iloc[i],
data['close'].shift(-1).iloc[i] - data['low'].iloc[i])
tr_list.append(tr)
context.atr = int(np.floor(np.mean(tr_list)))
context.atr_half = int(np.floor(0.5 * context.atr))
# 计算唐奇安通道
context.don_open = np.max(data['high'].values[-context.n:])
context.don_close = np.min(data['low'].values[-context.n:])
# 如果是实时模式,当天的数据需要用current取到
if context.mode == 1:
data = history_n(symbol=context.symbol, frequency='1d', count=context.n, end_time=context.now, fields='close,high,low',
df=True) # 计算ATR
current_data = current(symbols=context.symbol) # 最新一个交易日的最高、最低
tr_list = []
for i in range(1, len(data)):
tr = max((data['high'].iloc[i] - data['low'].iloc[i]),
data['close'].shift(-1).iloc[i] - data['high'].iloc[i],
data['close'].shift(-1).iloc[i] - data['low'].iloc[i])
tr_list.append(tr)
# 把最新一期tr加入列表中
tr_new = max((current_data[0]['high'] - current_data[0]['low']),
data['close'].iloc[-1] - current_data[0]['high'],
data['close'].iloc[-1] - current_data[0]['low'])
tr_list.append(tr_new)
context.atr = int(np.floor(np.mean(tr_list)))
context.atr_half = int(np.floor(0.5 * context.atr))
# 计算唐奇安通道
context.don_open = np.max(data['high'].values[-context.n:])
context.don_close = np.min(data['low'].values[-context.n:])
# 计算加仓点和止损点
context.long_add_point = context.don_open + context.atr_half
context.long_stop_loss = context.don_open - context.atr_half
context.short_add_point = context.don_close - context.atr_half
context.short_stop_loss = context.don_close + context.atr_half
def on_bar(context, bars):
# 提取数据
symbol = bars[0]['symbol']
recent_data = context.data(symbol=context.symbol, frequency='60s', count=2, fields='close,high,low')
close = recent_data['close'].values[-1]
# 账户仓位情况
position_long = context.account().position(symbol=symbol, side=PositionSide_Long)
position_short = context.account().position(symbol=symbol, side=PositionSide_Short)
# 当无持仓时
if not position_long and not position_short:
# 如果向上突破唐奇安通道,则开多
if close > context.don_open:
order_volume(symbol=symbol, side=OrderSide_Buy, volume=context.atr, order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('开多仓atr')
# 如果向下突破唐奇安通道,则开空
if close < context.don_close:
order_volume(symbol=symbol, side=OrderSide_Sell, volume=context.atr, order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('开空仓atr')
# 有持仓时
# 持多仓,继续突破(加仓)
if position_long:
# 当突破1/2atr时加仓
if close > context.long_add_point:
order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy, order_type=OrderType_Market,position_effect=PositionEffect_Open)
print('继续加仓0.5atr')
context.long_add_point += context.atr_half
context.long_stop_loss += context.atr_half
# 持多仓,止损位计算
if close < context.long_stop_loss:
volume_hold = position_long['volume']
if volume_hold >= context.atr_half:
order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close)
else:
order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Sell, order_type=OrderType_Market,position_effect=PositionEffect_Close)
print('平多仓0.5atr')
context.long_add_point -= context.atr_half
context.long_stop_loss -= context.atr_half
# 持空仓,继续突破(加仓)
if position_short:
# 当跌破加仓点时加仓
if close < context.short_add_point:
order_volume(symbol = symbol, volume=context.atr_half, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('继续加仓0.5atr')
context.short_add_point -= context.atr_half
context.short_stop_loss -= context.atr_half
# 持多仓,止损位计算
if close > context.short_stop_loss:
volume_hold = position_short['volume']
if volume_hold >= context.atr_half:
order_volume(symbol=symbol, volume=context.atr_half, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Close)
else:
order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Buy, order_type=OrderType_Market,position_effect=PositionEffect_Close)
print('平空仓0.5atr')
context.short_add_point += context.atr_half
context.short_stop_loss += context.atr_half
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
'''
run(strategy_id='strategy_id',
filename='main.py',
mode=MODE_BACKTEST,
token='token',
backtest_start_time='2020-02-15 09:15:00',
backtest_end_time='2020-09-01 15:00:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=1000000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001)
4. 回测结果与稳健性分析
设定初始资金100万,手续费率为0.01%,滑点比率为0.01%。回测结果如下图所示。
回测期累计收益率为18.75%,年化收益率为6.42%,沪深300收益率为18.75%,策略跑输沪深300指数。最大回撤为4.20%,胜率为48.15%。
为了检验策略的稳健性,改变策略标的和计算唐奇安通道的参数n,回测结果如下。
标的 | 唐奇安通道参数 | 年化收益率 | 最大回撤 |
---|---|---|---|
DCE.i2012 | 20 | 6.42% | 4.20% |
DCE.i2012 | 25 | 3.45% | 4.99% |
DCE.i2012 | 30 | -0.35% | 4.23% |
DCE.m2012 | 20 | -0.28% | 1.50% |
DCE.m2012 | 25 | 1.19% | 0.52% |
DCE.m2012 | 30 | 1.23% | 0.47% |
SHFE.rb2012 | 20 | -4.48% | 2.61% |
SHFE.rb2012 | 25 | -2.80% | 2.84% |
SHFE.rb2012 | 30 | -3.01% | 2.39% |
由上表可知,不同标的收益结果呈现差异。其中大商所的铁矿石收益情况最好,其他两个品种收益较差,整体收益情况较差。说明该策略在使用上存在一定风险。
注:此策略只用于学习、交流、演示,不构成任何投资建议。