Python 趋势跟踪策略

第 4 篇的双均线是趋势跟踪的最简形态。本篇升级到这一流派的巅峰之作——海龟交易法则的核心:唐奇安通道突破。

完成一个"20 日突破入场、10 日反向突破离场"的完整策略,并在茅台 2019-2024 数据上诚实评估它的表现。


海龟策略的来历

1983 年,交易大师理查德·丹尼斯与人打赌"交易能力可以后天培养",招募了一批学员称为"海龟",教授一套机械化规则。这套规则后来公开,成为量化入门的必修课。

其核心思想与双均线一致——追涨趋势,但入场离场更明确:

环节 规则 逻辑
入场 收盘价突破过去 20 日最高价 创阶段新高,趋势可能启动
离场 收盘价跌破过去 10 日最低价 趋势衰竭信号
仓位 按 ATR 波动率调整(原文用 2% 风险法则) 波动大的品种少买

说明:完整海龟法则还包含加仓、止损与多品种分散,篇幅所限本篇实现其核心框架;仓位管理(ATR 风险法则)在风险管理一篇展开。


唐奇安通道

唐奇安通道就是"过去 N 日的最高价/最低价"画出的两条线。实现时有一个关键细节:必须 shift(1) 前推一天。

理由与回测公平性有关:今天的收盘价要和"截至昨天"的 20 日最高比较,如果把今天也算进通道,价格永远突破自己,策略就废了。

实例

import yfinance as yf

close = yf.download("600519.SS", start="2019-01-01", end="2024-12-31",
                    progress=False, multi_level_index=False)["Close"]

# 通道用截至"昨天"的数据:rolling 之后必须 shift(1)
upper_ch = close.rolling(20).max().shift(1)   # 入场参考:20 日最高
lower_ch = close.rolling(10).min().shift(1)   # 离场参考:10 日最低

策略主循环

逐日遍历,空仓时看是否突破上轨,持仓时看是否跌破下轨:

实例

import pandas as pd

position = pd.Series(0, index=close.index)   # 每日持仓状态:1 持有,0 空仓
pos, trades = 0, 0

for i in range(1, len(close)):
    if pos == 0 and close.iloc[i] > upper_ch.iloc[i]:
        pos = 1        # 突破 20 日高点,入场
        trades += 1
    elif pos == 1 and close.iloc[i] < lower_ch.iloc[i]:
        pos = 0        # 跌破 10 日低点,离场
    position.iloc[i] = pos

# 策略日收益 = 昨日持仓 x 今日涨跌(信号次日执行,防未来函数)
daily_ret = close.pct_change()
strat_ret = position.shift(1).fillna(0) * daily_ret.fillna(0)

代码结构你在回测与绩效评估已经见过:持仓序列 shift(1) 后乘以日收益,这次只是把信号来源换成了通道突破。


回测与诚实评估

实例

import numpy as np

cum = (1 + strat_ret).cumprod()
days = len(strat_ret)
ann = cum.iloc[-1] ** (252 / days) - 1
vol = strat_ret.std() * np.sqrt(252)
mdd = (cum / cum.cummax() - 1).min()
sharpe = (ann - 0.03) / vol
bh = close.iloc[-1] / close.iloc[0] - 1

print(f"交易次数 {trades} | 策略累计 {cum.iloc[-1]-1:.2%} | 买入持有 {bh:.2%}")
print(f"年化 {ann:.2%} | 波动 {vol:.2%} | 最大回撤 {mdd:.2%} | 夏普 {sharpe:.2f}")

输出结果如下所示:

交易次数 33 | 策略累计 69.16% | 买入持有 187.58%
年化 9.55% | 波动 18.92% | 最大回撤 -28.53% | 夏普 0.35

买入持有同期对比(同一段数据计算):累计 187.58%、年化 20.12%、最大回撤 -47.48%、夏普 0.58。

结果值得认真解读,而不是只看收益率:

指标 海龟策略 买入持有 解读
累计收益 69.16% 187.58% 大幅跑输——趋势策略吃不到单边牛市全程
最大回撤 -28.53% -47.48% 回撤大幅收敛——通道离场在 2021 年后的下跌中起了保护作用
夏普比率 0.35 0.58 风险调整后仍有差距,策略并非免费午餐

把 2023-2024 段的通道与买卖点画出来,策略行为一目了然:

实例

import matplotlib.pyplot as plt

seg = close.loc["2023-01-01":"2024-12-31"]
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(seg.index, seg.values, color="#1f2933", linewidth=1.2, label="Close")
ax.plot(upper_ch.loc[seg.index], color="#14805e", linewidth=1.0,
        linestyle="--", label="20-day High Channel (Entry)")
ax.plot(lower_ch.loc[seg.index], color="#c0563f", linewidth=1.0,
        linestyle="--", label="10-day Low Channel (Exit)")

buy_pts = seg[(position.loc[seg.index].diff() == 1)]
sell_pts = seg[(position.loc[seg.index].diff() == -1)]
ax.scatter(buy_pts.index, buy_pts.values, marker="^",
           color="#14805e", s=110, zorder=3, label="Buy")
ax.scatter(sell_pts.index, sell_pts.values, marker="v",
           color="#c0563f", s=110, zorder=3, label="Sell")
ax.set_title("Maotai: Turtle Breakout (Donchian 20/10) 2023-2024")
ax.set_xlabel("Date")
ax.set_ylabel("Price (CNY)")
ax.legend()
ax.grid(alpha=0.25)
plt.show()

执行以上代码,输出的图表如下:

海龟通道突破买卖点

可以看到通道像两条随行的"护栏":价格突破上护栏时买入,跌穿下护栏时离场,全部交易在图上一目了然。


趋势策略的性格

结合第 9 篇双均线回测(同样跑输买入持有)与本篇结果,可以给趋势策略画出完整画像:

市场环境 趋势策略表现 原因
单边上涨 赚钱但跑输买入持有 突破才入场,永远慢半拍;离场规则又让它提前下车
单边下跌 大赚,或至少避开 空仓状态天然免疫,或做空获利
区间震荡 反复止损亏损 假突破不断触发假信号

这不是缺陷而是性格:趋势策略用"牛市少赚"换"熊市保命"。机构把趋势策略与均值回归策略搭配使用,正是为了互补——均值回归是下一篇的主角。