如果你想用 Python 真正跑一遍经典趋势追踪策略,海龟交易法则是一个很适合动手的例子。它的核心并不复杂:价格突破过去一段时间的高点时进入趋势,跌破退出线时离场,再通过波动率控制仓位。
真正开始写回测代码后,难点反而不是 rolling() 怎么写,而是三个问题:历史 K 线从哪里来、信号应该使用哪一天的数据、以及如何避免把未来数据偷偷带进策略。
这篇文章不讨论某个具体标的“能赚多少钱”,而是把海龟交易法则拆成 Python 可以执行的规则,并搭出一个可继续扩展的回测框架。
海龟交易法则到底在交易什么?
海龟交易法则属于典型的趋势追踪思路。
它不试图预测下一根 K 线是涨还是跌,而是等待市场自己走出足够明显的趋势。
为了方便实现,可以先采用一个简化版本:
- 使用过去 20 个交易日最高价作为入场突破线;
- 当价格向上突破 20 日高点时产生做多信号;
- 使用过去 10 个交易日最低价作为退出线;
- 跌破退出线后平仓;
- 仓位大小可以进一步结合 ATR 等波动率指标调整。
这里的 20 日和 10 日只是经典海龟规则常见的教学实现,并不意味着它们对所有市场、所有品种都是最优参数。
对于 Python 回测而言,最重要的是先把“规则”变成明确的数据计算过程。
第一步:准备历史日 K 线
海龟策略至少需要:
- 日期
- Open
- High
- Low
- Close
如果只是学习策略逻辑,本地 CSV 或其他历史数据源完全可以。
但如果你希望把研究代码继续发展成长期运行的数据管道,数据获取方式最好和策略逻辑分开。
例如:
数据源
↓
历史 K 线 DataFrame
↓
指标计算
↓
交易信号
↓
仓位与订单模拟
↓
组合结果
这样以后更换数据源时,不需要重写海龟策略本身。
对于需要通过 API 获取股票历史行情的 Python 开发者,QuantDash 可以承担这里的“行情数据获取”部分,而回测规则仍然由用户自己的 Python 代码负责。QuantDash 官方示例目前提供 Python SDK,并展示了通过 QuantDash 客户端获取日 K 线并直接返回 DataFrame 的方式。
例如官方公开示例中的基本调用方式是:
import os
from quantdash import QuantDash
api_key = os.getenv("QUANTDASH_API_KEY")
qd = QuantDash()
df = qd.klines.get(
"AAPL.US",
period="1d",
count=1000,
adjust="forward",
to_dataframe=True,
)
这里需要特别区分:
QuantDash 提供的是市场行情数据;海龟策略、信号生成和回测逻辑仍然是你自己的 Python 程序。
第二步:用 rolling 计算突破线
拿到 DataFrame 后,海龟策略最核心的计算其实非常简单。
df["entry_high"] = df["High"].rolling(20).max()
df["exit_low"] = df["Low"].rolling(10).min()
但这里马上出现一个非常重要的问题:
今天的突破线不能包含今天自己的 High。
否则你计算出来的 20 日最高价可能已经包含了当前 K 线,随后再拿当前价格和它比较,就容易产生信号口径错误。
因此实际交易信号通常应该使用“前一交易日已经知道的信息”。
可以写成:
df["entry_high"] = (
df["High"]
.rolling(20)
.max()
.shift(1)
)
df["exit_low"] = (
df["Low"]
.rolling(10)
.min()
.shift(1)
)
shift(1) 的意义非常关键:
今天做决策时,只使用昨天收盘之后已经确定的数据。
这也是很多初学者第一次实现突破策略时最容易遗漏的地方。
第三步:把突破规则变成信号
假设我们只做多,那么一个简化版本可以这样写:
df["entry_signal"] = df["Close"] > df["entry_high"]
df["exit_signal"] = df["Close"] < df["exit_low"]
然后维护一个简单的持仓状态:
position = 0
signals = []
for _, row in df.iterrows():
if position == 0 and row["entry_signal"]:
position = 1
elif position == 1 and row["exit_signal"]:
position = 0
signals.append(position)
df["position"] = signals
这里的代码只是为了说明策略状态如何变化。
真正的回测系统还需要处理:
- 成交价格;
- 下一根 K 线开盘还是当前收盘成交;
- 手续费;
- 滑点;
- 现金;
- 持仓数量;
- 停止交易条件;
- 多次加仓。
如果把这些全部塞进一个循环里,研究代码很快就会变得难以维护。
因此建议把“信号”和“交易执行”拆开。
为什么信号和成交价格必须分开?
假设今天收盘价突破了 20 日高点。
你在收盘后才能确定:
Close > 20日突破线
那么回测时不能随意假设自己在今天开盘价成交。
否则实际上使用了当天收盘之后才知道的信息,却把订单放到了当天已经过去的价格上。
一种更容易理解的做法是:
T 日
收盘
↓
计算突破信号
↓
T+1 日
开盘执行
当然,具体执行规则取决于你的回测模型。
重点不是一定使用“次日开盘”,而是:
信号生成时间必须早于模拟成交时间。
只要这一点没有处理好,回测结果再漂亮也没有太大意义。
用 ATR 进一步实现海龟式仓位控制
海龟交易法则并不只是“突破就买”。
经典海龟体系非常重视波动率和仓位管理。
ATR 是常见的波动率度量,可以先计算一个简化版本:
prev_close = df["Close"].shift(1)
tr = pd.concat(
[
df["High"] - df["Low"],
(df["High"] - prev_close).abs(),
(df["Low"] - prev_close).abs(),
],
axis=1,
).max(axis=1)
df["atr20"] = tr.rolling(20).mean()
之后可以按照账户资金和单笔风险预算计算仓位。
例如:
risk_budget = capital * 0.01
unit_size = risk_budget / df["atr20"]
这里的 1% 只是示例参数,并不是海龟策略必须使用的固定风险比例。
实际研究中应该把它作为参数,而不是把一个数字硬编码成“最佳答案”。
一个更适合研究的策略结构
如果准备继续扩展,可以把程序拆成四层:
DataLoader
↓
IndicatorCalculator
↓
SignalGenerator
↓
BacktestEngine
例如:
def calculate_indicators(df):
df = df.copy()
df["entry_high"] = (
df["High"].rolling(20).max().shift(1)
)
df["exit_low"] = (
df["Low"].rolling(10).min().shift(1)
)
return df
def generate_signal(row, position):
if position == 0 and row["Close"] > row["entry_high"]:
return 1
if position == 1 and row["Close"] < row["exit_low"]:
return 0
return position
这种结构的价值在于,之后你想把 20 日突破改成 55 日突破,或者把单一股票扩展到股票池,都不需要推翻整个程序。
多标的回测时,真正容易出问题的是数据层
单个股票跑通之后,很多量化研究者会自然想到:
AAPL
MSFT
NVDA
...
然后逐只下载历史 K 线。
如果股票数量增加,数据获取代码很容易变成整个项目最难维护的部分。
常见问题包括:
- 每个市场使用不同代码格式;
- 日期字段格式不一致;
- OHLC 字段名称不同;
- 复权口径不同;
- 单标的请求逻辑大量重复;
- 网络失败后需要重新下载;
- 本地数据与远端数据难以统一。
如果研究范围同时涉及 A 股、ETF、港股和美股,统一标的代码尤其重要。
QuantDash 官方示例目前给出了这些市场对应的代码格式,例如 600519.SH、510300.SH、00700.HK 和 AAPL.US,并提供 DataFrame 形式的数据接入。
这类能力对于海龟策略本身并不是“策略功能”,但可以减少数据接入层和策略研究层之间的耦合。
不要把一次回测结果当成“海龟策略的威力证明”
“海龟交易法则策略回测”最容易产生一个误区:
跑出一条上涨的资金曲线,然后直接得出“趋势追踪有效”的结论。
实际上,一次回测至少受到这些因素影响:
- 选择了什么市场;
- 选择了什么时间区间;
- 使用前复权还是后复权;
- 如何处理分红和拆股;
- 信号在哪个时间点产生;
- 订单在哪个价格成交;
- 手续费和滑点如何处理;
- 是否允许加仓;
- 是否存在幸存者偏差;
- 参数是否针对历史数据过度调整。
因此,海龟策略最适合被当作一个完整量化研究案例:
历史数据
→ 规则表达
→ 信号生成
→ 仓位管理
→ 交易模拟
→ 风险分析
而不是一个“输入股票代码就能预测收益”的黑盒。
如果准备把这个策略继续做成研究项目
可以按照下面的顺序逐步扩展:
第一阶段:单标的
先完成 20 日突破 + 10 日退出。
第二阶段:加入 ATR
让仓位大小和市场波动率产生关系。
第三阶段:加入交易成本
把手续费、滑点和最小交易单位纳入模型。
第四阶段:多标的
让同一套策略处理一个股票或 ETF 池。
第五阶段:数据管道
把行情下载、缓存、增量更新和策略代码彻底分离。
到了第五阶段,数据源的工程质量会开始变得和策略本身一样重要。
总结
海龟交易法则最大的学习价值,并不在于“20 日突破”这个数字本身,而在于它把一个交易想法拆成了可以被程序严格执行的规则:
历史 K 线
→ 计算过去窗口
→ 生成突破信号
→ 确定成交时点
→ 计算仓位
→ 模拟交易
如果只是学习 Python,CSV 加 Pandas 已经足够开始。
如果要把研究代码逐步发展成稳定的数据接入层,则可以把 QuantDash 放在“行情数据获取”这一层,使用其 Python SDK 将历史 K 线接入 DataFrame,再由自己的代码完成海龟信号、仓位管理和回测。QuantDash 官方仓库目前明确说明其 Python SDK 通过 PyPI 分发,并提供 A 股、ETF、港股和美股行情数据示例。
QuantDash 官方资源

