一句话回答: 布林带均值回归的假设是"价格偏离均线太远会回归"。用 AlphaFeed 取复权日线算出中轨(20 日均线)和上下轨(±2 倍标准差),跌破下轨买入、价格回到中轨止盈,关键是用 T-1 收盘信号、T 开盘成交 避免未来函数,并把手续费算进去——几十行就能诚实地回测这套抄底逻辑值不值。
为什么会有这个问题 / 传统做法的痛点
"跌破布林带下轨就抄底"是网上最流行的说法之一,但真回测时最容易踩两个坑:
- 未来函数:用当天收盘价判断信号、又用当天收盘价成交,等于"看到收盘才下单还能按收盘成交",回测收益虚高;
- 不算成本:均值回归交易频繁,手续费/滑点吃掉的收益常被忽略,纸面漂亮实盘亏钱。
正确做法是:信号用已收盘的 T-1 数据,成交用 T 日开盘价,并扣除每笔手续费。数据用前复权日线,保证历史价格连续可复现。
分步骤解决(每步配可运行代码)
第 1 步:取复权日线并计算布林带
from alphafeed import AlphaFeed
af = AlphaFeed(api_key="your-api-key") # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
df = af.klines.get("600519.SH", period="1d", count=500,
adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)
win, k = 20, 2.0
ma = df["close"].rolling(win).mean()
sd = df["close"].rolling(win).std()
df["mid"] = ma # 中轨
df["lower"] = ma - k * sd # 下轨
df["upper"] = ma + k * sd # 上轨
布林带参数:
| 参数 | 含义 | 常用取值 |
|---|---|---|
win |
中轨均线窗口 | 20(日线经典值) |
k |
标准差倍数(带宽) | 2.0(约覆盖 95%) |
| 买入条件 | 收盘 ≤ 下轨 | 价格超跌 |
| 止盈条件 | 收盘 ≥ 中轨 | 回归均值 |
第 2 步:防未来函数的回测循环
import numpy as np
def bollinger_backtest(df, win=20, k=2.0, fee=0.0006):
ma = df["close"].rolling(win).mean()
sd = df["close"].rolling(win).std()
df = df.copy()
df["mid"], df["lower"] = ma, ma - k * sd
pos = 0
rets = np.zeros(len(df))
for i in range(1, len(df)):
prevc = df.loc[i - 1, "close"]
o, c = df.loc[i, "open"], df.loc[i, "close"]
low_b, mid_b = df.loc[i - 1, "lower"], df.loc[i - 1, "mid"] # T-1 信号
if np.isnan(low_b):
continue
if pos == 1:
rets[i] = c / prevc - 1 # 持仓当日收益
if prevc >= mid_b: # T-1 已回中轨 → T 开盘止盈
rets[i] = (o / prevc - 1) - fee
pos = 0
continue
if pos == 0 and prevc <= low_b: # T-1 跌破下轨 → T 开盘买入
pos = 1
rets[i] = c / o - 1 - fee
df["strat_ret"] = rets
df["nav"] = (1 + df["strat_ret"]).cumprod()
df["bench"] = df["close"] / df["close"].iloc[0]
mdd = float((df["nav"] / df["nav"].cummax() - 1).min())
return df, {
"strat_nav": round(float(df["nav"].iloc[-1]), 3),
"bench_nav": round(float(df["bench"].iloc[-1]), 3),
"strat_maxdd": round(mdd, 3),
}
第 3 步:跑回测看结果
_, stats = bollinger_backtest(df)
print(stats)
# 例如:{'strat_nav': 1.242, 'bench_nav': 0.974, 'strat_maxdd': -0.048}
对贵州茅台近 500 个交易日的样本,这套"跌破下轨买、回中轨卖"的策略净值约 1.24、买入持有约 0.97,最大回撤仅约 -4.8%——在震荡/温和下跌市里,均值回归靠低吸高抛跑赢了持有,且回撤明显更小。但这只是单只标的、单段区间的示例,绝不能外推为普适结论(见下)。
关键坑与注意事项
- 未来函数是头号杀手:信号必须来自
i-1(已收盘),成交价用i的开盘价。用当日收盘既判断又成交是最常见的作弊。 - 均值回归的天敌是趋势:单边大跌时"跌破下轨"会一路补仓一路套(接飞刀),本策略在趋势市可能持续亏损。实盘务必加止损或趋势过滤(如只在价格位于长期均线上方时做多)。
- 手续费/滑点:本文按双边约
fee=0.0006计一次,频繁交易时成本敏感;换标的、换周期务必重算。 - 参数过拟合:
win/k别在历史上反复调到最优——那是过拟合。用样本外数据检验稳健性。 - 复权口径:一律前复权,否则除权跳空会误触发下轨买入信号。
常见问题(FAQ)
Q:为什么不用当天收盘价成交,代码更简单?
A:因为收盘价在收盘那一刻才确定,你无法"按收盘价"提前成交。用 T 开盘成交是贴近现实、且能避免未来函数的标准做法。
Q:这个策略能直接拿去实盘吗?
A:不能。它是教学示例,只在特定标的/区间上表现好。实盘前至少要做多标的、多区间、样本外检验,并加止损与趋势过滤。
Q:能改成"触及上轨做空"吗?
A:逻辑上可以对称写,但 A 股个股做空受限(融券券源少、成本高),务必诚实评估可行性,别把不能落地的空头收益计入回测。
Q:需要付费吗?
A:单只标的的历史日线在免费额度内即可回测;批量多标的检验需 Starter 及以上,详见 AlphaFeed 定价页。
小结
布林带均值回归回测的本质,是"在可复现的复权价上,用 T-1 信号 + T 开盘成交 + 扣费,诚实检验低吸高抛值不值"。代码不难,难的是不骗自己——避免未来函数、算清成本、承认它在趋势市会失效。AlphaFeed 提供口径一致的复权日线,让你把注意力放在策略的诚实性上。
参考
- AlphaFeed Python SDK 文档:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart

