海龟交易法则 (Turtle Trading Strategy) 深入解析
海龟交易法则并非一个单一的技术“指标”,而是一个完整的、机械化的趋势跟踪交易系统。它由传奇交易员理查德·丹尼斯(Richard Dennis)和威廉·埃克哈特(William Eckhardt)在1980年代初提出并用于培训一组被称为“海龟”的新手交易员,并取得了巨大成功。
该策略的核心思想是:市场的大部分利润来自于捕捉并持有大的趋势,通过严格的规则来入场、加仓、止损和出场,消除人为情绪的干扰。
1. 核心组成部分
海龟系统主要包含以下几个关键要素:
- 市场与时间周期: 最初应用于流动性好的期货市场(商品、货币、利率等),主要基于日线数据。
- 入场信号 (Breakouts):
- 系统一 (S1 - 短周期): 当价格突破过去 20天 的最高价时,买入一个单位(Unit);当价格跌破过去 20天 的最低价时,卖出一个单位。
- 系统二 (S2 - 长周期): 当价格突破过去 55天 的最高价时,买入一个单位;当价格跌破过去 55天 的最低价时,卖出一个单位。
- 过滤规则: 如果上一次的突破(无论是S1还是S2触发的)是盈利的,则忽略当前的S1入场信号(避免在趋势末期或盘整初期反复入场)。S2信号始终有效。 (注:这是原始规则之一,后续实践中可能被修改或简化)
- 仓位规模 (Position Sizing): 这是海龟法则的精髓,基于波动性调整仓位大小。
- 波动性衡量 (N): 使用真实波幅均值 (ATR - Average True Range) 来衡量市场的日均波动。N通常基于20天的ATR计算。
TR = Max(High - Low, abs(High - PrevClose), abs(Low - PrevClose))
N = ATR(20) = SMA(TR, 20)
(原始海龟使用一种特殊的指数加权平均,但SMA或EMA(20)常用作近似)
- 单位 (Unit): 一个交易单位的大小,使得当价格向不利方向移动 1N 时,账户损失约为账户净值的1% (或预设的其他风险百分比,如0.5%或2%)。
Dollar Volatility per point = N * PointValue
(PointValue 是每点价格变动代表的美元价值,如原油期货1点=1000美元)Unit Size = (AccountSize * Risk%) / Dollar Volatility per point
- 计算出的
Unit Size
通常向下取整为合约张数。
- 意义: 市场波动大时,每个单位的合约数量减少;波动小时,合约数量增加。确保单笔交易的初始风险相对恒定。
- 波动性衡量 (N): 使用真实波幅均值 (ATR - Average True Range) 来衡量市场的日均波动。N通常基于20天的ATR计算。
- 加仓 (Pyramiding): 在初始入场后,如果价格朝着有利方向移动 0.5N,则加仓一个单位。继续按0.5N的间隔加仓,但有总仓位上限(通常是4或5个单位)。
- 止损 (Stop Loss):
- 初始止损: 对于任何一个单位,其止损位设置在距离入场价 2N 的反向位置。例如,多头入场价为P,止损设在 P - 2N;空头入场价P,止损设在 P + 2N。
- 动态调整: 当加仓时,所有单位的止损位可能会被向上(多头)或向下(空头)移动,通常以最新加仓单位的止损位为准,或保持一定距离(如最后入场价 - 2N)。这有助于保护已有利潤。 (注:具体止损调整规则有不同版本)
- 出场信号 (Exits):
- 系统一 (S1): 多头头寸在价格跌破过去 10天 最低价时退出;空头头寸在价格突破过去 10天 最高价时退出。
- 系统二 (S2): 多头头寸在价格跌破过去 20天 最低价时退出;空头头寸在价格突破过去 20天 最高价时退出。
- 止损退出: 如果价格触及任何单位的止损位,则立即退出该单位(在某些版本中是退出所有头寸)。
2. 海龟策略的优点与局限性
- 优点:
- 系统性、纪律性强: 完全基于规则,排除情绪干扰。
- 能抓住大趋势: 核心是趋势跟踪,一旦抓住趋势,利润可能非常可观。
- 风险管理明确: 基于波动性的仓位管理和明确的止损规则是其核心优势。
- 普适性: 理论上可应用于任何有趋势性的市场。
- 局限性:
- 在盘整市表现糟糕: 趋势跟踪策略在没有明显趋势的市场中会频繁产生“Whipsaw”(拉锯)信号,导致连续小额亏损。
- 胜率通常不高: 依赖少数几次大的盈利来弥补多次小的亏损。这在心理上难以承受。
- 回撤可能很大: 在抓住大趋势之前,可能经历较长时间的亏损或盘整期。
- 滞后性: 基于历史价格突破,入场点可能已经远离趋势的起点,出场点也可能滞后于趋势的转折点。
将海龟逻辑与其他指标结合构建CTA策略
原始海龟系统是自成体系的,但其核心思想(趋势突破、波动性管理)可以被借鉴,并尝试与其他指标结合以期改善其表现,尤其是在过滤假突破或适应不同市场环境方面。
结合思路:
-
使用趋势过滤指标 (如 MA, MACD, ADX) 确认趋势:
- 目的: 减少在盘整市中的交易次数,只在指标确认存在趋势时才启用海龟的突破信号。
- 策略示例:
- MA过滤: 仅当价格位于长期移动平均线(如100日或200日EMA)之上时,才接受海龟系统的做多突破信号;仅当价格位于长期MA之下时,才接受做空突破信号。
- ADX过滤: 仅当ADX(平均动向指数)高于某个阈值(如20或25),表明市场处于趋势状态时,才启用海龟的突破信号。ADX低于阈值时,暂停交易或切换到其他策略(如均值回归)。
- 优点: 可能有效过滤掉大量盘整市中的假突破信号,提高胜率。
- 缺点: 可能错过趋势的早期启动阶段(因为需要等待MA或ADX确认),增加滞后性。
-
使用动量指标 (如 RSI, Stochastics) 确认突破强度:
- 目的: 在突破发生时,利用动量指标确认背后是否有足够的力量支撑趋势继续。
- 策略示例:
- RSI确认: 当出现海龟做多突破信号时,要求RSI也大于50(甚至更高,如60),表明市场处于多头动能;做空突破时,要求RSI小于50(甚至更低,如40)。
- 优点: 可能过滤掉一些动能不足的弱突破。
- 缺点: 动量指标有时会与趋势突破产生背离,可能过滤掉有效的早期突破。
-
使用波动率指标 (如 Bollinger Bands) 优化入场或识别机会:
- 目的: 结合布林带的收窄(Squeeze)来寻找潜在的突破机会,或者在突破后利用布林带中轨进行回调入场。
- 策略示例:
- Squeeze后突破: 重点关注布林带收窄(低波动)后的海龟突破信号,认为这种突破更可能引发大行情。
- 回调入场: 在海龟信号触发后(例如突破55日高点),不立即入场,而是等待价格回调至布林带中轨(如20期SMA)附近时再入场,寻求更好的入场价格。
- 优点: Squeeze可以提示高概率突破时机;回调入场可能改善风险回报比。
- 缺点: 回调入场可能错过强势的直接突破行情。
-
结合成交量 (Volume) 确认突破:
- 目的: 验证突破是否伴随着资金的积极参与。
- 策略示例: 海龟突破信号发生时,要求当天的成交量显著高于近期平均成交量(如过去20天的均量)。
- 优点: 量价结合是经典的技术分析方法,可以增加突破信号的可靠性。
- 缺点: 成交量数据在某些市场(如外汇现货)不准确或不可用。
结合的注意事项:
- 复杂性与过拟合: 增加指标会使系统更复杂,更容易产生过拟合历史数据的风险。必须进行严格的样本外测试和前向测试。
- 保持核心思想: 结合的目的是优化而非颠覆。海龟的核心是捕捉趋势和风险管理,增加的指标不应与其根本冲突。
- 参数选择: 新增指标的参数与海龟系统自身的参数(突破周期、ATR周期、风险比例等)需要协同优化。
Python 示例:简化版海龟策略 + MA趋势过滤
这个示例将演示一个简化版的海龟系统(使用20日突破入场,10日反向突破出场,不包含完整的加仓和基于N的精确单位计算),并加入一个长期移动平均线(EMA)作为趋势过滤器。
注意: 这仍然是一个教学性质的简化示例,省略了许多实盘细节(如资金管理、滑点、手续费、完整的单位和风险计算)。
import pandas as pd
import numpy as np
import talib # 需要先安装 TA-Lib 库: pip install TA-Lib
import matplotlib.pyplot as plt # 用于可视化
# 1. 准备数据 (使用模拟数据)
dates = pd.date_range(start='2022-01-01', periods=500, freq='D')
# 模拟具有一定趋势性的价格数据
price_noise = np.random.randn(500) * 2
price_trend = np.linspace(0, 30, 500) + np.sin(np.linspace(0, 20, 500)) * 10
close_prices = 100 + price_trend + price_noise
price_data = pd.DataFrame({
'Close': close_prices,
}, index=dates)
# 基于收盘价生成 OHLC (简化)
price_data['Open'] = price_data['Close'].shift(1).fillna(method='bfill')
price_data['High'] = price_data['Close'] + np.random.rand(500) * 3
price_data['Low'] = price_data['Close'] - np.random.rand(500) * 3
# 确保 High >= Close and Low <= Close
price_data['High'] = price_data[['High', 'Close']].max(axis=1)
price_data['Low'] = price_data[['Low', 'Close']].min(axis=1)
# 2. 计算所需指标
entry_breakout_period = 20 # 入场突破周期 (海龟S1)
exit_breakout_period = 10 # 出场反向突破周期 (海龟S1 Exit)
ma_filter_period = 100 # 移动平均线过滤周期
# 计算唐奇安通道 (Donchian Channel) - 用于判断突破
# talib 没有直接的 Donchian Channel, 我们手动计算 N日高点和低点
price_data['EntryHigh'] = price_data['High'].rolling(entry_breakout_period).max().shift(1) # 前N日的最高价
price_data['EntryLow'] = price_data['Low'].rolling(entry_breakout_period).min().shift(1) # 前N日的最低价
price_data['ExitHigh'] = price_data['High'].rolling(exit_breakout_period).max().shift(1) # 前N日的最高价 (用于空头出场)
price_data['ExitLow'] = price_data['Low'].rolling(exit_breakout_period).min().shift(1) # 前N日的最低价 (用于多头出场)
# 计算移动平均线过滤器
price_data['MA_Filter'] = talib.EMA(price_data['Close'], timeperiod=ma_filter_period)
# (可选) 计算ATR (N) - 虽然本例简化了仓位管理,但可以计算出来看看
# atr_period = 20
# price_data['ATR'] = talib.ATR(price_data['High'], price_data['Low'], price_data['Close'], timeperiod=atr_period)
# 丢弃NaN值
price_data.dropna(inplace=True)
# 3. 生成交易信号 (状态机方式模拟持仓)
price_data['Position'] = 0 # 0: 空仓, 1: 持有多头, -1: 持有空头
position = 0 # 当前持仓状态
for i in range(1, len(price_data)):
current_close = price_data['Close'].iloc[i]
ma_filter = price_data['MA_Filter'].iloc[i]
entry_high = price_data['EntryHigh'].iloc[i]
entry_low = price_data['EntryLow'].iloc[i]
exit_high = price_data['ExitHigh'].iloc[i]
exit_low = price_data['ExitLow'].iloc[i]
# 当前空仓
if position == 0:
# 检查做多信号: 价格突破N日高点 且 价格在MA之上
if current_close > entry_high and current_close > ma_filter:
position = 1 # 进入多头
price_data.loc[price_data.index[i], 'Signal'] = 1 # 标记开仓点
# 检查做空信号: 价格跌破N日低点 且 价格在MA之下
elif current_close < entry_low and current_close < ma_filter:
position = -1 # 进入空头
price_data.loc[price_data.index[i], 'Signal'] = -1 # 标记开仓点
# 当前持有多头
elif position == 1:
# 检查多头出场信号: 价格跌破退出周期低点
if current_close < exit_low:
position = 0 # 平多仓
price_data.loc[price_data.index[i], 'Signal'] = -2 # 标记平多仓点
# (如果需要,可以在这里加入止损逻辑,例如 P < EntryPrice - 2*N)
# 当前持有空头
elif position == -1:
# 检查空头出场信号: 价格突破退出周期高点
if current_close > exit_high:
position = 0 # 平空仓
price_data.loc[price_data.index[i], 'Signal'] = 2 # 标记平空仓点
# (如果需要,可以在这里加入止损逻辑,例如 P > EntryPrice + 2*N)
price_data['Position'].iloc[i] = position
# 填充信号列的NaN值为0
price_data['Signal'] = price_data['Signal'].fillna(0)
# 4. 可视化
plt.figure(figsize=(16, 10))
# 主图:价格、MA、突破线和信号
ax1 = plt.subplot(2, 1, 1)
ax1.plot(price_data.index, price_data['Close'], label='Close Price', color='black', linewidth=0.8)
ax1.plot(price_data.index, price_data['MA_Filter'], label=f'EMA({ma_filter_period}) Filter', color='orange', linestyle='--', linewidth=1)
ax1.plot(price_data.index, price_data['EntryHigh'], label=f'{entry_breakout_period}-day High', color='red', linestyle=':', linewidth=0.7)
ax1.plot(price_data.index, price_data['EntryLow'], label=f'{entry_breakout_period}-day Low', color='green', linestyle=':', linewidth=0.7)
# ax1.plot(price_data.index, price_data['ExitLow'], label=f'{exit_breakout_period}-day Low (Long Exit)', color='lime', linestyle='-.', linewidth=0.7)
# ax1.plot(price_data.index, price_data['ExitHigh'], label=f'{exit_breakout_period}-day High (Short Exit)', color='magenta', linestyle='-.', linewidth=0.7)
# 标记开仓点
ax1.plot(price_data[price_data['Signal'] == 1].index,
price_data['Close'][price_data['Signal'] == 1],
'^', markersize=10, color='lime', label='Enter Long')
ax1.plot(price_data[price_data['Signal'] == -1].index,
price_data['Close'][price_data['Signal'] == -1],
'v', markersize=10, color='red', label='Enter Short')
# 标记平仓点 (用不同形状或颜色)
ax1.plot(price_data[price_data['Signal'] == -2].index,
price_data['Close'][price_data['Signal'] == -2],
'x', markersize=8, color='blue', label='Exit Long')
ax1.plot(price_data[price_data['Signal'] == 2].index,
price_data['Close'][price_data['Signal'] == 2],
'x', markersize=8, color='purple', label='Exit Short')
ax1.set_title('Simplified Turtle Strategy with MA Trend Filter')
ax1.set_ylabel('Price')
ax1.legend(fontsize='small', loc='upper left')
ax1.grid(True)
# 副图:持仓状态
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
ax2.plot(price_data.index, price_data['Position'], label='Position (1=Long, -1=Short, 0=Flat)', color='blue', drawstyle='steps-post')
ax2.set_title('Position Status')
ax2.set_ylabel('Position')
ax2.set_xlabel('Date')
ax2.set_yticks([-1, 0, 1])
ax2.grid(True)
plt.tight_layout()
plt.show()
# 输出最后几行数据看看信号和持仓
print(price_data[['Close', 'EntryHigh', 'EntryLow', 'MA_Filter', 'Signal', 'Position']].tail(20))
代码解释:
- 数据准备: 生成模拟OHLC数据,使其包含一些趋势性变化。
- 指标计算:
- 使用
rolling().max().shift(1)
和rolling().min().shift(1)
计算过去N日的最高价和最低价(即唐奇安通道的上下轨)。.shift(1)
很重要,确保我们用前N日的数据来判断当天是否突破。 - 使用
talib.EMA
计算长期指数移动平均线作为趋势过滤器。 - (可选)计算ATR,虽然本例中未用于仓位管理。
- 使用
- 信号生成:
- 采用一个简单的状态机 (
position
变量) 来模拟持仓状态(空仓0, 多头1, 空头-1)。 - 遍历数据,根据当前持仓状态和满足的条件(价格突破、MA过滤、退出信号)来更新持仓状态,并在
Signal
列中标记交易动作(1:开多, -1:开空, -2:平多, 2:平空)。 - 关键的过滤逻辑: 只有当价格在MA之上时才考虑做多突破 (
current_close > entry_high and current_close > ma_filter
);只有当价格在MA之下时才考虑做空突破 (current_close < entry_low and current_close < ma_filter
)。 - 出场逻辑基于简化的S1规则(10日反向突破)。
- 采用一个简单的状态机 (
- 可视化:
- 主图展示价格、MA过滤器、入场突破线,并用不同标记标出开仓和平仓点。
- 副图展示持仓状态随时间的变化。
总结:
海龟交易法则是量化交易史上的一个里程碑,其核心在于纪律性地跟踪趋势并进行严格的风险管理。虽然原始系统设计为独立运行,但其核心突破逻辑可以与现代技术分析中的其他指标(如MA、ADX、RSI、成交量等)相结合,尝试过滤噪音、适应不同市场环境或优化入场/出场。关键在于理解结合的目的,并通过严格的回测来验证效果,同时警惕增加复杂性可能带来的过拟合风险。Python及其生态(Pandas, TA-Lib, Matplotlib等)为实现、测试和优化这些复杂的CTA策略提供了强大的支持。记住,没有完美的策略,持续的测试、调整和风险控制才是长期成功的关键。