故事引入:海龟们的传奇
1983年,两位华尔街传奇人物——理查德·丹尼斯和威廉·埃克哈特——打了一个赌。
丹尼斯认为,交易是可以教会的,成功的交易者不是天生的,而是培养出来的。埃克哈特则认为,交易天赋是与生俱来的,无法通过教学获得。
为了验证谁对谁错,丹尼斯在报纸上刊登广告,招募了一群完全没有交易经验的普通人——包括一个游戏设计师、一个会计师、一个服务员。他给这些人两周的密集培训,教给他们一套简单的趋势跟踪规则,然后每人发了一笔钱去实盘交易。
这套规则后来被称为"海龟交易法则"。核心就三条:
- 入场:当价格突破过去20日高点时买入
- 仓位:根据ATR计算,每笔交易风险不超过总资金的2%
- 出场:当价格跌破过去10日低点时卖出
结果如何?这群"海龟"在随后的四年里,创造了年均80%以上的复利回报。其中一些人后来成立了自己的对冲基金,管理数十亿美元。
海龟实验证明了什么?交易成功不取决于智商、学历或内幕消息,而取决于是否有一套纪律严明的规则,以及能否坚持执行它。这正是量化交易的核心精神。
核心知识:趋势跟踪的量化密码
2.1 趋势跟踪的底层哲学
趋势跟踪的理论基础是价格动量的持续性。也就是说,涨了一段时间的股票,短期内继续上涨的概率略高于下跌的概率。这听起来像"追涨杀跌",但在统计学上确有支撑。
为什么会有趋势?行为金融学给出了几个解释:
- 锚定效应:投资者倾向于以近期价格为"锚",价格突破后产生新的心理预期
- herd behavior(羊群效应):看到价格上涨,更多人跟风买入,自我强化
- 信息扩散:好消息不会瞬间被所有人知道,而是逐步传播,推动价格持续上涨
- 机构调仓:大资金的建仓过程需要时间,持续买入推动趋势形成
趋势跟踪者不关心趋势形成的原因,只关心趋势是否存在。就像冲浪者不关心浪从哪里来,只关心浪在哪里。
2.2 唐奇安通道:捕捉趋势的网
唐奇安通道(Donchian Channel)是海龟策略的核心工具。它非常简单:
上轨 = 过去N日的最高价
下轨 = 过去N日的最低价
中轨 = (上轨 + 下轨) / 2
海龟策略用20日通道突破作为入场信号,10日通道突破作为出场信号。这种"长周期入场、短周期出场"的设计非常精妙:它让策略能够捕捉到大的趋势行情,同时在趋势反转时快速离场。
flowchart LR
A[计算20日最高价] --> B{价格突破?}
B -->|是| C[买入开仓]
B -->|否| D[继续等待]
C --> E[计算10日最低价]
E --> F{价格跌破?}
F -->|是| G[卖出平仓]
F -->|否| H[持有让利润奔跑]
H --> E
style C fill:#10b981,color:#fff
style G fill:#e74c3c,color:#fff
style B fill:#f97316,color:#fff
2.3 ATR:衡量市场波动的尺子
平均真实波幅(Average True Range, ATR)是海龟策略中用于仓位管理的关键指标。它回答的问题是:这只股票平均每天波动多少?
真实波幅(TR) = max(当日最高-最低, |当日最高-昨日收盘|, |当日最低-昨日收盘|)
ATR = 过去N日TR的移动平均(通常N=20)
ATR的核心用途:
- 仓位计算:高ATR股票分配小仓位,低ATR股票分配大仓位
- 止损设置:止损距离 = 入场价 - 2倍ATR(或3倍ATR)
- 趋势过滤:ATR突然放大可能预示趋势开始或结束
2.4 海龟策略的仓位管理:N值系统
海龟策略的仓位管理是其最精妙的部分。它把资金分成若干"单位",每个单位的风险严格控制在总资金的2%。
"""
海龟策略核心逻辑 - 简化版
"""
import backtrader as bt
class TurtleStrategy(bt.Strategy):
params = (
('entry_period', 20), # 入场通道周期
('exit_period', 10), # 出场通道周期
('atr_period', 20), # ATR计算周期
('risk_pct', 0.02), # 每笔风险2%
('atr_multiplier', 2), # 止损 = 入场价 - N*ATR
)
def __init__(self):
# 唐奇安通道
self.donchian_high = bt.indicators.Highest(
self.data.high, period=self.p.entry_period
)
self.donchian_low = bt.indicators.Lowest(
self.data.low, period=self.p.exit_period
)
# ATR
self.atr = bt.indicators.ATR(period=self.p.atr_period)
self.order = None
self.entry_price = None
self.position_size = 0
def calculate_unit_size(self):
"""
计算1个交易单位的股数
公式: 单位股数 = (账户资金 * 风险比例) / (ATR * ATR倍数)
"""
cash = self.broker.getvalue()
risk_amount = cash * self.p.risk_pct
stop_distance = self.atr[0] * self.p.atr_multiplier
if stop_distance == 0:
return 0
unit_size = int(risk_amount / stop_distance)
return unit_size
def next(self):
if self.order:
return
# 出场逻辑(优先)
if self.position:
if self.data.close[0] < self.donchian_low[0]:
self.order = self.sell(size=self.position.size)
return
# 入场逻辑
if not self.position:
if self.data.close[0] > self.donchian_high[-1]:
self.position_size = self.calculate_unit_size()
if self.position_size > 0:
self.order = self.buy(size=self.position_size)
def notify_order(self, order):
if order.status in [order.Completed]:
if order.isbuy():
self.entry_price = order.executed.price
print(f'海龟买入: {self.position_size}股 @ {order.executed.price:.2f}')
else:
pnl = (order.executed.price - self.entry_price) * order.executed.size
print(f'海龟卖出: {order.executed.price:.2f}, 盈亏: {pnl:.2f}')
self.entry_price = None
self.order = None
2.5 趋势跟踪者的"痛苦":低胜率与高盈亏比
趋势跟踪策略有一个反直觉的特征:胜率通常很低,只有30%-40%,但盈亏比很高,平均盈利是平均亏损的2-3倍。
这意味着趋势跟踪者的大部分交易都是亏损的。他们依靠少数几次大趋势交易来覆盖所有小额亏损并实现盈利。就像钓鱼:你抛竿十次,可能九次空竿,但钓到的那一条可能是大鱼。
这种特征对交易者的心理是巨大的考验。连续七八次亏损后,你还敢执行第九次信号吗?大多数人不敢。但正是这种心理门槛,让趋势跟踪策略的Alpha能够持续存在——因为它太反人性了。
科学争议:趋势跟踪已经失效了吗?
正方:从2009年到2020年的美股长牛中,趋势跟踪策略确实表现不佳,因为它频繁在小幅回调中被震出。但这只是十一年间的表现。把时间拉长到五十年,趋势跟踪在应对大熊市(如2000年、2008年)时的保护作用无可替代。趋势跟踪不是每天都赚钱,而是在关键时刻救命。
反方:央行干预改变了市场结构。量化宽松让资产价格单向上涨,波动率被人为压低,传统的趋势信号大量失效。在"永远涨"的市场中,买入持有比趋势跟踪更优。
量化交易者的务实态度:不预测市场结构会不会变,而是把趋势跟踪作为多策略组合的一部分,分配合理的权重。同时,用波动率过滤(如只在VIX>20时启用趋势跟踪)来提高策略的适应性。
2.6 趋势强度过滤:只在强趋势中交易
一个改进思路是:不是所有突破都值得跟,只跟"强趋势"的突破。衡量趋势强度的常用指标是ADX(平均趋向指数):
- ADX < 20:趋势很弱,市场可能处于震荡,不宜交易
- ADX 20-40:趋势中等,可以交易
- ADX > 40:趋势很强,但可能过热,需警惕反转
把ADX作为过滤条件加入海龟策略,可以显著减少震荡市中的假信号。代价是会错过一些趋势初期的机会。
2.7 多品种分散:趋势跟踪的免费午餐
海龟策略的另一个精髓是同时在多个不相关的市场中交易。股票、债券、商品、外汇、期货——当某个市场没有趋势时,另一个市场可能正在爆发。
对于A股个人投资者,虽然无法直接投资期货和外汇,但可以通过ETF实现跨资产分散:沪深300ETF(股票)、国债ETF(债券)、黄金ETF(商品)。当股票市场震荡时,黄金可能正在走出趋势。
动手实践:海龟策略模拟器
下面这个交互式模拟器可以帮助你理解海龟策略的仓位计算逻辑。调整参数,观察仓位和风险的变化:
海龟仓位计算器
单位股数: 2000股
止损价位: 90.00元
单笔风险: 2.00万元
建议最大持仓单位: 4个
下面的代码是一个完整的海龟策略回测模板,你可以直接复制运行:
"""
海龟策略完整版 - 含ADX过滤和ATR仓位管理
"""
import backtrader as bt
import akshare as ak
import pandas as pd
class TurtleStrategy(bt.Strategy):
params = (
('entry_period', 20),
('exit_period', 10),
('atr_period', 20),
('risk_pct', 0.02),
('atr_mult', 2),
('adx_period', 14),
('adx_filter', 25),
)
def __init__(self):
self.donchian_high = bt.ind.Highest(self.data.high, period=self.p.entry_period)
self.donchian_low = bt.ind.Lowest(self.data.low, period=self.p.exit_period)
self.atr = bt.ind.ATR(period=self.p.atr_period)
self.adx = bt.ind.AverageDirectionalMovementIndex(period=self.p.adx_period)
self.order = None
def calculate_size(self):
cash = self.broker.getvalue()
risk_amount = cash * self.p.risk_pct
stop_dist = self.atr[0] * self.p.atr_mult
return int(risk_amount / stop_dist) if stop_dist > 0 else 0
def next(self):
if self.order: return
if self.position:
if self.data.close[0] < self.donchian_low[0]:
self.order = self.sell(size=self.position.size)
return
if (self.data.close[0] > self.donchian_high[-1] and
self.adx[0] > self.p.adx_filter):
size = self.calculate_size()
if size > 0:
self.order = self.buy(size=size)
def notify_order(self, order):
if order.status in [order.Completed]:
self.order = None
if __name__ == "__main__":
cerebro = bt.Cerebro()
cerebro.addstrategy(TurtleStrategy)
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.0003)
# 请替换为实际数据加载代码
# df = ak.stock_zh_a_hist(...)
# data = bt.feeds.PandasData(dataname=df, ...)
# cerebro.adddata(data)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd')
results = cerebro.run()
strat = results[0]
print(f'最终资金: {cerebro.broker.getvalue():.2f}')
print(f"夏普: {strat.analyzers.sharpe.get_analysis().get('sharperatio','N/A')}")
print(f"最大回撤: {strat.analyzers.dd.get_analysis().max.drawdown:.2f}%")