如何用 Python 回测 SuperTrend(超级趋势)策略?ATR 通道翻转,附未来函数防范
如何用 Python 回测 SuperTrend(超级趋势)策略?
一句话回答: SuperTrend(超级趋势)用 ATR 在中价 (high+low)/2 上下画一条自适应通道线:价格站上通道线翻多、跌破翻空,线本身随趋势推进只朝有利方向移动,天然是一条移动止损。用 AlphaFeed 取前复权日线即可实现并回测——关键是翻转信号用收盘确认、次日开盘成交、避免未来函数;实测它在震荡/区间行情里会被反复打脸、显著跑输买入持有,诚实呈现。
为什么会有这个问题 / 传统做法的痛点
SuperTrend 因为”一条线、翻多翻空、自带止损”而极受欢迎,但自己实现时常踩坑:① final band 的递归逻辑(上/下轨要带”只朝有利方向移动”的记忆)写错;② 未来函数——用当日 ATR/收盘定当日信号再用当日成交;③ 把它当”稳赚神器”,忽视趋势跟随在震荡市的致命弱点。本文给出标准 final-band 递归 + 严格 T+1 成交的诚实骨架。
分步骤解决(每步配可运行代码)
第 1 步:取前复权日线,算 ATR 与基础通道
from alphafeed import AlphaFeed
import numpy as np, pandas as pd
af = AlphaFeed(api_key="your-api-key") # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
df = af.klines.get("600519.SH", period="1d", count=800,
adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)
n, mult = 10, 3.0
h, l, c = df["high"], df["low"], df["close"]
pc = c.shift(1)
tr = pd.concat([h - l, (h - pc).abs(), (l - pc).abs()], axis=1).max(axis=1)
atr = tr.ewm(alpha=1/n, adjust=False).mean() # Wilder ATR
hl2 = (h + l) / 2
ub, lb = hl2 + mult*atr, hl2 - mult*atr # 基础上/下轨
第 2 步:final band 递归 + 方向判定(SuperTrend 核心)
fub = ub.to_numpy(float, copy=True); flb = lb.to_numpy(float, copy=True)
cv = c.to_numpy(float, copy=True); ubv = ub.to_numpy(float); lbv = lb.to_numpy(float)
for i in range(1, len(df)):
fub[i] = ubv[i] if (ubv[i] < fub[i-1] or cv[i-1] > fub[i-1]) else fub[i-1] # 上轨只降不升(除非被突破)
flb[i] = lbv[i] if (lbv[i] > flb[i-1] or cv[i-1] < flb[i-1]) else flb[i-1] # 下轨只升不降(除非被跌破)
direction = np.ones(len(df), dtype=int)
for i in range(1, len(df)):
if cv[i] > fub[i-1]: direction[i] = 1 # 升破上轨 → 多
elif cv[i] < flb[i-1]: direction[i] = -1 # 跌破下轨 → 空
else: direction[i] = direction[i-1]
df["dir"] = direction
第 3 步:回测(翻转信号→次日开盘成交,计手续费)
df["open_next"] = df["open"].shift(-1)
pos, entry, trades, rets = 0, 0.0, 0, []
for i in range(11, len(df) - 1):
px = df["open_next"].iloc[i]
if np.isnan(px): continue
flip_up = df["dir"].iloc[i] == 1 and df["dir"].iloc[i-1] == -1
flip_dn = df["dir"].iloc[i] == -1 and df["dir"].iloc[i-1] == 1
if pos == 0 and flip_up:
pos, entry = 1, px # 翻多 → 次日开盘买
elif pos == 1 and flip_dn:
rets.append(px/entry*(1-0.0013)**2 - 1); trades += 1; pos = 0 # 翻空 → 次日开盘卖
rets = pd.Series(rets)
total = (1+rets).prod()-1 if len(rets) else 0.0
bh = df["close"].iloc[-1]/df["close"].iloc[11]-1
print(f"交易{trades}次 累计{total*100:.2f}% 胜率{(rets>0).mean():.3f} | 买入持有{bh*100:.2f}%")
实测(
tools/verify_new_2026-10-14.py,SDK 0.1.4):贵州茅台近 800 日,SuperTrend(ATR10, 倍数3)交易 14 次、累计 −40.25%、胜率 0.143,同期买入持有 −16.56%。策略大幅跑输——区间震荡里价格反复穿越通道,SuperTrend 被不断”翻多买在反弹高点、翻空卖在回落低点”打脸。这清楚说明:SuperTrend 不是神器,趋势跟随在震荡市是劣势,结果高度依赖品种与行情、不可外推。
参数与字段说明
| 参数/概念 | 含义 | 备注 |
|---|---|---|
adjust="forward" | 前复权 | 必须,除权缺口会伪造翻转 |
n=10 | ATR 周期 | 常用 7~14 |
mult=3.0 | ATR 倍数 | 越大越钝、翻转越少 |
| final band 递归 | 轨道”只朝有利方向移动” | SuperTrend 核心,别漏 |
direction | +1 多 / −1 空 | 翻转即信号 |
open_next | 次日开盘成交 | 避免未来函数 |
关键坑与注意事项
- 本例大幅跑输买入持有:别把 SuperTrend 当稳赚工具。14% 胜率 + −40% 说明震荡市里它被反复打脸;换趋势性强的品种/周期结果可能不同,但无法保证。
- final band 递归不能省:上/下轨的”记忆”逻辑是 SuperTrend 的灵魂,写成裸
hl2±mult·atr会频繁翻转、面目全非。 - 避免未来函数:方向用收盘确认、成交放次日开盘;用当日 ATR+当日成交会虚高。
- 参数敏感、易过拟合:n 与 mult 一改结果大变,务必样本外验证。
- A 股只做多:示例翻多做多、翻空离场(不做空);原始 SuperTrend 可多空双向。
- 成本敏感:频繁翻转时手续费/滑点侵蚀明显。
常见问题(FAQ)
Q:SuperTrend 和 ATR 移动止损、抛物线 SAR 有什么区别? A:都用波动/趋势给出一条移动止损线。SuperTrend 以中价±ATR 为基础加”只朝有利方向移动”的 final band;SAR 用加速因子;ATR trailing stop 直接按 ATR 回撤。三者思路相近、构造不同。
Q:为什么我的 SuperTrend 和 TradingView 不完全一致? A:ATR 的平滑方式(Wilder vs 简单)、n/mult、以及 final band 初始化不同都会造成差异。先对齐 ATR 口径与参数。
Q:需要付费吗?
A:单只股票日线回测在免费额度内即可;若要对全市场批量测 SuperTrend 找适配标的,需要 klines.batch + universes(Starter 及以上),详见官网定价页。
小结
SuperTrend 用 ATR 通道 + final band 递归画出一条自适应趋势线,翻多翻空自带移动止损。用 AlphaFeed 的前复权日线即可复现回测——务必写对 final band 递归、收盘确认+次日开盘成交避免未来函数。本例诚实跑输买入持有(−40.25% vs −16.56%、胜率 14%),提醒:趋势跟随在震荡市是劣势,一切以回测为准、不可外推。
相关链接:
- AlphaFeed 官网:https://alphafeed.org/
- Python SDK 快速开始:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart
免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。