海龟交易法介绍
海龟交易法(Turtle Trading System)是金融交易领域最著名的趋势跟踪策略之一,起源于1983年理查德·丹尼斯(Richard Dennis)与威廉·埃克哈特(William Eckhardt)的一场著名赌约。丹尼斯认为优秀的交易员可以通过系统化训练培养,而埃克哈特则认为交易能力更多依赖天赋。为验证这一观点,丹尼斯招募了13名毫无交易经验的普通人(来自服务员、音乐家等不同行业),对他们进行了为期两周的培训,并提供了真实资金进行交易。这些学员被称为"海龟",该实验在随后的四年中创造了年均复利超过80%的惊人收益,证明了系统化交易策略的可复制性和有效性。
核心交易哲学与原则
海龟交易法的核心建立在四大原则之上:
-
掌握优势:找到一个期望值为正的交易策略,长期坚持以获得正回报。
-
管理风险:严格控制每笔交易的风险,确保在不利行情中能够生存下来。
-
坚定不移:完全机械地执行交易系统的每一个信号,杜绝情绪干扰。
-
简单明了:简单的系统比复杂的系统更具生命力和可执行性。
完整的交易系统框架
海龟交易法是一个完整的机械交易系统,涵盖了"交易什么、交易多少、何时买卖、何时退出"等所有环节。
|------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| 系统组成部分 | 具体规则与说明 |
| 市场选择 | 主要交易高流动性、具有趋势性的市场,如商品期货、外汇、股指期货等。 |
| 头寸规模管理(核心) | 使用平均真实波幅(ATR) 动态衡量市场波动性,确保在不同波动性的市场中承担相同的风险。计算公式为:头寸单位 = (账户总资金 × 风险比例) / (ATR × 合约乘数) 。通常将单笔交易风险控制在账户总资金的1%-2% |
| 入市信号 | 采用唐奇安通道突破 系统,分为两套: • 短期系统(S1) :价格突破过去20日 的最高价时做多,跌破过去20日 的最低价时做空。 • 长期系统(S2) :价格突破过去55日 的最高价时做多,跌破过去55日的最低价时做空。 |
| 加仓策略 | 初始头寸建立后,若价格向有利方向移动0.5个ATR ,则加仓一个单位。最多允许加仓3次,即总持仓可达初始仓位的4倍。 |
| 止损规则 | 任何头寸的初始止损点都设置为2倍ATR。例如,做多入场价为100,ATR为5,则止损价为90(100 - 2×5)。 |
| 退出(离市)规则 | 不预设盈利目标,采用移动止损跟踪趋势: • 短期系统 :多头头寸在价格跌破过去10日 最低价时平仓;空头头寸在价格涨破过去10日 最高价时平仓。 • 长期系统 :多头头寸在价格跌破过去20日 最低价时平仓;空头头寸在价格涨破过去20日最高价时平仓。 |
策略的优势与局限性
优势:
-
系统化与纪律性:完全规则化,消除了交易中情绪和主观判断的干扰。
-
捕捉大趋势:在单边趋势行情中,能够牢牢抓住主要波段,实现丰厚利润。
-
风险控制严谨:通过ATR和固定百分比风险模型,严格限制了单笔和整体账户的最大亏损。
局限性与挑战:
-
震荡市表现不佳:在无趋势的盘整行情中,会因频繁的假突破信号导致连续多次的小额亏损,对交易者心理是巨大考验。
-
资金回撤较大:策略盈利依赖于少数几次大趋势,在抓住趋势前可能经历较长时间的资金回撤(可能高达30%-50%)。
-
对执行力要求极高:必须像机器一样无条件执行所有信号,在连续亏损时仍能坚持,这违背人性,绝大多数人难以做到。
海龟交易法的策略实现
在聚宽这个量化交易网站上,按照以上介绍的海龟交易法的原则,编写一个海龟交易的策略,并且进行测试,代码如下:
python
# -*- coding: utf-8 -*-
# 海龟交易法量化策略
from jqdata import *
import numpy as np
import pandas as pd
def initialize(context):
"""初始化海龟策略"""
# 设置基准
set_benchmark('000300.XSHG')
set_option('use_real_price', True)
# 海龟参数
g.entry_sys1_days = 20 # 系统1入场周期
g.entry_sys2_days = 55 # 系统2入场周期
g.exit_sys1_days = 10 # 系统1离场周期
g.exit_sys2_days = 20 # 系统2离场周期
g.atr_days = 20 # ATR计算周期
g.stop_loss_atr = 2.0 # 止损ATR倍数
g.pyramid_gap = 0.5 # 加仓间隔(ATR倍数)
g.max_units = 4 # 最大加仓单位数
# 股票池(示例:沪深300成分股)
g.stock_pool = get_index_stocks('000300.XSHG')
# 记录每个股票的状态
g.positions_data = {} # 结构:{股票代码: {'units': 数量, 'entry_price': [], 'last_pyramid': 价格}}
# 风险参数
g.risk_per_trade = 0.01 # 单笔交易风险1%
# 设置手续费
set_order_cost(OrderCost(
close_tax=0.001,
open_commission=0.0003,
close_commission=0.0003,
min_commission=5
), type='stock')
# 每日运行
run_daily(turtle_trading, time='open', reference_security='000300.XSHG')
def turtle_trading(context):
"""海龟交易主逻辑"""
# 获取当前时间
current_date = context.current_dt
for stock in g.stock_pool:
# 跳过停牌和ST
#if is_suspended(stock) or is_st_stock(stock):
if check_stock_status(context, stock):
continue
# 获取历史数据
prices = get_price(stock,
end_date=current_date,
count=max(g.entry_sys2_days, g.atr_days)+20,
frequency='daily',
fields=['high', 'low', 'close'])
if len(prices) < max(g.entry_sys2_days, g.atr_days)+10:
continue
# 计算关键指标
high_prices = prices['high'].values
low_prices = prices['low'].values
close_prices = prices['close'].values
# 计算ATR
atr_value = calculate_atr(high_prices, low_prices, close_prices, g.atr_days)
# 计算突破价位
sys1_entry = np.max(high_prices[-g.entry_sys1_days:-1]) # 20日最高
sys2_entry = np.max(high_prices[-g.entry_sys2_days:-1]) # 55日最高
sys1_exit = np.min(low_prices[-g.exit_sys1_days:-1]) # 10日最低
sys2_exit = np.min(low_prices[-g.exit_sys2_days:-1]) # 20日最低
# 当前价格
current_price = close_prices[-1]
# 获取当前持仓
if stock in context.portfolio.positions:
current_position = context.portfolio.positions[stock].total_amount
else:
current_position = 0
# 检查是否已有该股票记录
if stock not in g.positions_data:
g.positions_data[stock] = {
'units': 0,
'entry_price': [],
'last_pyramid': None,
'system': None
}
pos_data = g.positions_data[stock]
# ========== 离场逻辑 ==========
if current_position > 0:
# 系统1离场条件
if current_price < sys1_exit and pos_data['system'] == 1:
if place_order(stock, 0):
log.info(f"系统1离场 {stock},价格{current_price:.2f} < 10日最低{sys1_exit:.2f}")
pos_data['units'] = 0
pos_data['entry_price'] = []
pos_data['last_pyramid'] = None
continue
'''
# 系统2离场条件
if current_price < sys2_exit and pos_data['system'] == 2:
if place_order(stock, 0):
log.info(f"系统2离场 {stock},价格{current_price:.2f} < 20日最低{sys2_exit:.2f}")
pos_data['units'] = 0
pos_data['entry_price'] = []
pos_data['last_pyramid'] = None
continue
'''
# 止损检查
if len(pos_data['entry_price']) > 0:
# 使用平均入场价计算止损
avg_entry = np.mean(pos_data['entry_price'])
stop_loss_price = avg_entry - g.stop_loss_atr * atr_value
if current_price < stop_loss_price:
if place_order(stock, 0):
log.info(f"止损离场 {stock},价格{current_price:.2f} < 止损价{stop_loss_price:.2f}")
pos_data['units'] = 0
pos_data['entry_price'] = []
pos_data['last_pyramid'] = None
continue
# ========== 金字塔加仓逻辑 ==========
if pos_data['units'] < g.max_units:
# 检查加仓条件
if pos_data['last_pyramid'] is None:
log.info(f"stock: {stock}, date: {context.current_dt}, pos_data: {pos_data}")
last_pyramid_price = pos_data['entry_price'][0]
else:
last_pyramid_price = pos_data['last_pyramid']
# 价格比上次加仓上涨了0.5ATR
if current_price >= last_pyramid_price + g.pyramid_gap * atr_value:
# 计算加仓数量
unit_size = calculate_unit_size(context, stock, atr_value)
if unit_size > 0:
if place_order(stock, unit_size):
pos_data['units'] += 1
pos_data['entry_price'].append(current_price)
pos_data['last_pyramid'] = current_price
log.info(f"金字塔加仓 {stock},第{pos_data['units']}单位,价格{current_price:.2f}")
# ========== 入场逻辑 ==========
elif current_position == 0:
# 系统1入场(20日突破)
if current_price > sys1_entry:
unit_size = calculate_unit_size(context, stock, atr_value)
if unit_size > 0:
if place_order(stock, unit_size):
pos_data['units'] = 1
pos_data['entry_price'] = [current_price]
pos_data['last_pyramid'] = current_price
pos_data['system'] = 1
log.info(f"系统1入场 {stock},价格{current_price:.2f} > 20日最高{sys1_entry:.2f}")
'''
# 系统2入场(55日突破)- 更保守
if current_price > sys2_entry:
unit_size = calculate_unit_size(context, stock, atr_value)
if unit_size > 0:
if place_order(stock, unit_size):
pos_data['units'] = 1
pos_data['entry_price'] = [current_price]
pos_data['last_pyramid'] = current_price
pos_data['system'] = 2
log.info(f"系统2入场 {stock},价格{current_price:.2f} > 55日最高{sys2_entry:.2f}")
'''
def calculate_atr(high, low, close, period=20):
"""计算平均真实波幅(ATR)"""
tr = np.zeros(len(high))
for i in range(1, len(high)):
hl = high[i] - low[i]
hc = abs(high[i] - close[i-1])
lc = abs(low[i] - close[i-1])
tr[i] = max(hl, hc, lc)
# 计算ATR
atr = np.zeros(len(tr))
atr[period] = np.mean(tr[1:period+1])
for i in range(period+1, len(tr)):
atr[i] = (atr[i-1] * (period-1) + tr[i]) / period
return atr[-1] if len(atr) > 0 else 0
def calculate_unit_size(context, stock, atr_value):
"""计算头寸规模单位(海龟法核心)"""
if atr_value <= 0:
return 0
# 获取股票基本信息
stock_info = get_security_info(stock)
# 计算每点价值(A股通常1点=1元)
point_value = 1.0
# 计算可承受的亏损金额
total_capital = context.portfolio.total_value
risk_amount = total_capital * g.risk_per_trade # 单笔风险1%
# 计算头寸规模
# 公式:头寸单位 = 风险金额 / (ATR × 每点价值)
unit_size = risk_amount / (atr_value * point_value)
# 转换为整手(A股100股一手)
unit_size = int(unit_size / 100) * 100
# 确保不超过可用资金
available_cash = context.portfolio.available_cash
price = get_price(stock, count=1, fields='close')['close'].iloc[-1]
if math.isnan(price):
return 0
max_by_cash = available_cash / price
max_by_cash = int(max_by_cash / 100) * 100
unit_size = min(unit_size, max_by_cash)
return unit_size
def handle_data(context, data):
"""备用数据处理器"""
pass
def check_stock_status(context, stock):
"""检查股票状态:停牌和ST"""
current_data = get_current_data()
stock_info = current_data[stock]
# 检查是否停牌
is_paused = stock_info.paused # True表示停牌,False表示正常
# 检查是否为ST股票
is_st = stock_info.is_st # True表示ST/*ST,False表示正常
return is_paused or is_st
def place_order(stock, unit_size):
order_result = order(stock, unit_size)
if order_result:
if order_result.filled > 0:
return True
return False
以上代码中分别包括了系统1和系统2的实现,可以对这两个系统分别进行测试。
以系统1为例,对沪深300成分股应用海龟交易法进行回测,测试区间为2019-01-01到2025-12-31,回测结果如下:

从以上结果可见,海龟交易法不太适合股票震荡市,在2019-01到2023-03这个时间段其收益是跑输沪深300的基准收益的。但是在牛市,其收益是大幅跑赢基准收益。
另外如果我们只选取沪深300成分股的其中前100个股票测试,其回测结果如下:

从以上结果可见,选取不同的股票,其收益基本都跑赢基准收益,可见选择不同的股票,对于结果影响也是很大的。这启发了我们可以对股票进行筛选,以提高海龟交易法的收益。