海龟交易法
# 1. 海龟交易法则简介
# 1.1 历史背景
海龟交易法则起源于1983年,由著名商品投机家理查德·丹尼斯(Richard Dennis) 和 威廉·艾克哈特(William Eckhardt) 共同创立。
两人曾就"交易能力是天生的还是可以培养的"展开辩论,最终决定通过实验来验证。他们招募了一批没有交易经验的普通人,称之为"海龟",并传授给他们一套完整的交易系统。
实验结果证明,这套系统在短短几年内创造了惊人的收益,许多"海龟"成为了成功的交易员。
# 1.2 核心理念
- 趋势跟踪:顺势而为,不预测市场方向
- 严格纪律:机械化执行交易规则
- 风险控制:通过头寸管理控制每笔交易的风险
- 一致性:所有交易决策基于相同的规则
# 1.3 CTA策略分类
海龟交易法是典型的 CTA(Commodity Trading Advisor,商品交易顾问)策略,属于趋势跟踪类型。
| CTA策略类型 | 特点 | 代表策略 |
|---|---|---|
| 趋势跟踪 | 顺势而为,捕捉中长期趋势 | 海龟交易法、双均线策略 |
| 均值回归 | 逆势操作,价格偏离后回归 | 布林带策略、配对交易 |
| 套利策略 | 利用价差获利,风险较低 | 跨期套利、跨品种套利 |
| 高频策略 | 短线交易,依赖速度优势 | 做市策略、统计套利 |
💡 CTA的核心优势:与股票市场的低相关性,在股市下跌时往往能提供正收益,是资产配置中重要的对冲工具。
# 2. 核心概念
# 2.1 真实波动幅度(TR)与 ATR
真实波动幅度(True Range, TR) 是衡量市场波动性的关键指标,取以下三个值的最大值:
| 计算方式 | 公式 |
|---|---|
| 当日振幅 | 最高价 - 最低价 |
| 跳空高开 | 最高价 - 前收盘价 |
| 跳空低开 | 最低价 - 前收盘价 |
真实波动幅度均值(Average True Range, ATR) 是过去N日TR的移动平均值:
其中:
= 周期天数(通常为20天) = 前一日的ATR值 = 当日的真实波动幅度
💡 ATR的意义:ATR反映了市场的波动性,波动越大ATR越高。海龟用它来标准化不同品种的风险。
# 2.2 头寸单位(Unit)
海龟将资金分成若干"头寸单位",核心原则是:价格波动1个ATR,总仓位资金变化1%。
头寸计算公式(股票):
其中:
头寸计算公式(期货):
计算示例(股票):
假设:总资金10万元,平安银行价格16.20元,ATR=0.464元
即每次交易约34914元。
# 2.3 上轨与下轨
在海龟交易系统中,上轨和下轨是价格通道的边界:
| 术语 | 含义 | 系统1 | 系统2 |
|---|---|---|---|
| 入场上轨 | 突破买入信号线 | 20日最高价 | 55日最高价 |
| 入场下轨 | 跌破做空信号线 | 20日最低价 | 55日最低价 |
| 出场上轨 | 空头平仓信号线 | 10日最高价 | 20日最高价 |
| 出场下轨 | 多头平仓信号线 | 10日最低价 | 20日最低价 |
示意图:
价格 ↑
──────── 55日最高价(系统2入场上轨)
──────── 20日最高价(系统1入场上轨 / 系统2出场上轨)
──────── 10日最高价(系统1出场上轨)
~~~~ 价格波动区间
──────── 10日最低价(系统1出场下轨)
──────── 20日最低价(系统1入场下轨 / 系统2出场下轨)
──────── 55日最低价(系统2入场下轨)
价格 ↓
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 3. 交易规则详解
# 3.1 入市规则
海龟使用两套入市系统:
# 系统1(短期系统)
- 入市信号:价格突破过去20日的最高价(做多)或最低价(做空)
- 过滤规则:若上次突破为盈利性突破,则忽略本次信号
- 盈利性突破:上次入场后,价格未触及止损就再次突破
- 亏损性突破:上次入场后,触发了止损
- 执行:信号出现立即行动,不等收盘
# 系统2(长期系统)
- 入市信号:价格突破过去55日的最高价(做多)或最低价(做空)
- 无过滤:所有突破信号都执行
- 执行:信号出现立即行动
# 3.2 加仓规则
入市后可逐步建仓:
- 初始建仓:入市时买入 1个头寸单位
- 加仓条件:价格每向盈利方向移动 0.5个ATR,加仓1个头寸单位
- 上限:单一品种最多持有 4个头寸单位
加仓示例(做多):
| 加仓次数 | 触发条件 | 累计头寸 |
|---|---|---|
| 第1次(入场) | 突破20日高点 | 1单位 |
| 第2次 | 入场价 + 0.5ATR | 2单位 |
| 第3次 | 入场价 + 1.0ATR | 3单位 |
| 第4次 | 入场价 + 1.5ATR | 4单位(满仓) |
# 3.3 止损规则
严格的止损是海龟系统的核心:
- 止损原则:单笔交易导致总资金亏损不超过 2%
- 止损距离:入场价格 ± 2个ATR(做多减,做空加)
- 动态调整:每次加仓后,止损位调整为最新加仓价 ± 2ATR
止损示例:
- 入场价格:100元
- ATR:2元
- 止损价格:100 - 2×2 = 96元
⚠️ 重要:一旦触发止损,所有头寸全部退出!
# 3.4 止盈/退出规则
| 系统 | 多头退出 | 空头退出 |
|---|---|---|
| 系统1 | 价格跌破10日最低价 | 价格突破10日最高价 |
| 系统2 | 价格跌破20日最低价 | 价格突破20日最高价 |
# 4. 完整交易流程图
开始
│
▼
计算ATR和头寸单位
│
▼
监控价格 ─────────────────┐
│ │
▼ │
是否突破入场上/下轨? │
│ │
├─ 否 ─────────────────┘
│
▼ 是
检查过滤条件(系统1)
│
▼
买入1个头寸单位
│
▼
设置止损(入场价±2ATR)
│
▼
持仓监控 ◄────────────────┐
│ │
├─ 触发止损? │
│ └─ 是 → 全部平仓 → 结束
│ │
├─ 触发止盈? │
│ └─ 是 → 全部平仓 → 结束
│ │
├─ 价格上涨0.5ATR且未满仓?
│ └─ 是 → 加仓1单位 → 更新止损 ─┘
│
└─ 否 ─────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 5. Python代码实现
# 5.1 核心指标计算
import pandas as pd
import numpy as np
class TurtleTrader:
"""海龟交易系统"""
def __init__(self, capital=100000, risk_percent=0.01, atr_period=20):
self.capital = capital # 总资金
self.risk_percent = risk_percent # 单次风险比例(1%)
self.atr_period = atr_period # ATR周期
# 系统参数
self.entry_period_s1 = 20 # 系统1入场周期
self.exit_period_s1 = 10 # 系统1出场周期
self.entry_period_s2 = 55 # 系统2入场周期
self.exit_period_s2 = 20 # 系统2出场周期
# 交易状态
self.position = 0 # 当前持仓单位数
self.entry_price = 0 # 入场价格
self.stop_loss = 0 # 止损价格
self.max_units = 4 # 最大头寸单位
def calculate_tr(self, high, low, prev_close):
"""计算真实波动幅度 TR"""
tr1 = high - low
tr2 = abs(high - prev_close)
tr3 = abs(low - prev_close)
return max(tr1, tr2, tr3)
def calculate_atr(self, df):
"""计算ATR指标"""
df = df.copy()
df['prev_close'] = df['close'].shift(1)
df['tr'] = df.apply(
lambda x: self.calculate_tr(x['high'], x['low'], x['prev_close']),
axis=1
)
df['atr'] = df['tr'].rolling(window=self.atr_period).mean()
return df
def calculate_channels(self, df):
"""计算唐奇安通道(上下轨)"""
df = df.copy()
# 系统1通道
df['entry_up_s1'] = df['high'].rolling(self.entry_period_s1).max()
df['entry_down_s1'] = df['low'].rolling(self.entry_period_s1).min()
df['exit_up_s1'] = df['high'].rolling(self.exit_period_s1).max()
df['exit_down_s1'] = df['low'].rolling(self.exit_period_s1).min()
# 系统2通道
df['entry_up_s2'] = df['high'].rolling(self.entry_period_s2).max()
df['entry_down_s2'] = df['low'].rolling(self.entry_period_s2).min()
df['exit_up_s2'] = df['high'].rolling(self.exit_period_s2).max()
df['exit_down_s2'] = df['low'].rolling(self.exit_period_s2).min()
return df
def calculate_unit_size(self, price, atr):
"""计算头寸单位(金额)"""
if atr == 0:
return 0
unit = (self.capital * self.risk_percent * price) / atr
return unit
def generate_signals(self, df, system=1):
"""生成交易信号"""
df = self.calculate_atr(df)
df = self.calculate_channels(df)
if system == 1:
entry_up = 'entry_up_s1'
entry_down = 'entry_down_s1'
exit_up = 'exit_up_s1'
exit_down = 'exit_down_s1'
else:
entry_up = 'entry_up_s2'
entry_down = 'entry_down_s2'
exit_up = 'exit_up_s2'
exit_down = 'exit_down_s2'
df['signal'] = 0
# 做多信号:突破入场上轨
df.loc[df['close'] > df[entry_up].shift(1), 'signal'] = 1
# 做空信号:跌破入场下轨
df.loc[df['close'] < df[entry_down].shift(1), 'signal'] = -1
# 多头出场:跌破出场下轨
df.loc[df['close'] < df[exit_down].shift(1), 'exit_long'] = True
# 空头出场:突破出场上轨
df.loc[df['close'] > df[exit_up].shift(1), 'exit_short'] = True
return df
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 5.2 回测框架
def backtest_turtle(df, trader, system=1):
"""海龟策略回测"""
df = trader.generate_signals(df, system)
position = 0
entry_price = 0
stop_loss = 0
units = 0
trades = []
equity = [trader.capital]
for i in range(1, len(df)):
row = df.iloc[i]
prev_row = df.iloc[i-1]
current_equity = equity[-1]
# 计算当前头寸单位大小
unit_size = trader.calculate_unit_size(row['close'], row['atr'])
if position == 0: # 无持仓
if row['signal'] == 1: # 做多信号
position = 1
entry_price = row['close']
stop_loss = entry_price - 2 * row['atr']
units = 1
trades.append({
'date': row.name,
'action': 'BUY',
'price': entry_price,
'units': units
})
elif position == 1: # 持有多头
# 检查止损
if row['low'] <= stop_loss:
pnl = (stop_loss - entry_price) * unit_size / row['close'] * units
current_equity += pnl
trades.append({
'date': row.name,
'action': 'STOP_LOSS',
'price': stop_loss,
'pnl': pnl
})
position = 0
units = 0
# 检查止盈出场
elif row.get('exit_long', False):
pnl = (row['close'] - entry_price) * unit_size / row['close'] * units
current_equity += pnl
trades.append({
'date': row.name,
'action': 'EXIT',
'price': row['close'],
'pnl': pnl
})
position = 0
units = 0
# 检查加仓条件
elif units < trader.max_units:
add_price = entry_price + units * 0.5 * row['atr']
if row['high'] >= add_price:
units += 1
stop_loss = add_price - 2 * row['atr']
trades.append({
'date': row.name,
'action': 'ADD',
'price': add_price,
'units': units
})
equity.append(current_equity)
return pd.DataFrame(trades), equity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 6. 注意事项与私货
# 6.1 适用场景
| 适用 | 不适用 |
|---|---|
| 期货市场(原设计场景) | 波动极小的品种 |
| 趋势明显的市场 | 震荡盘整行情 |
| 流动性好的品种 | 流动性差的小盘股 |
# 6.2 A股应用调整
- 头寸计算调整:A股无杠杆,头寸单位可能超过总仓位,需设置上限
- 最小交易单位:A股需100股整手交易,计算后需取整
- T+1限制:当日买入次日才能卖出,无法及时止损
- 涨跌停限制:可能导致无法按预期价格成交
# 6.3 作者评价(来自知乎文章)
- 当标的波动较小时,单位头寸会超过总仓位
- 头寸计算公式有点复杂,很多人简化为将资金等分为5-6份
- 稳定盈利的交易系统需要和交易者自身的财务状况、性格等匹配
- 与总结系统相比,可能更重要的是系统的执行,有术无道,无根之木,活不了
# 7. 参考资料
上次更新: 2026/01/05, 21:58:44