如何用 Python 回测布林带突破策略?

16 阅读4分钟

一句话回答: 布林带有两种相反玩法:跌破下轨买入是均值回归(赌回中轨),突破上轨买入是趋势追涨(赌延续)。本文回测突破策略——用 AlphaFeed 取前复权日线算布林带,收盘上穿上轨做多、回落到中轨离场,次日开盘成交避免未来函数,扣手续费,并与均值回归、买入持有对比,看趋势追涨在该品种上是否有效。

为什么会有这个问题 / 传统做法的痛点

很多人只知道「布林带跌破下轨买、涨破上轨卖」的均值回归用法,但在强趋势品种上,突破上轨往往意味着行情启动、追涨反而更赚。到底用哪种?不能拍脑袋,要回测。坑同样是:前复权价、次日开盘成交避免未来函数、扣手续费,还要和均值回归版、买入持有放一起比较。

分步骤解决(每步配可运行代码)

第 1 步:取前复权日线并算布林带

from alphafeed import AlphaFeed
import pandas as pd

af = AlphaFeed()  # 读取 ALPHAFEED_API_KEY

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)

N, K = 20, 2
df["mid"] = df["close"].rolling(N).mean()
df["std"] = df["close"].rolling(N).std(ddof=0)
df["upper"] = df["mid"] + K * df["std"]
df["lower"] = df["mid"] - K * df["std"]
参数含义取值
N中轨均线周期20
K标准差倍数(带宽)2
入场收盘上穿上轨趋势追涨
离场收盘回落中轨下方落袋/止损

第 2 步:生成突破信号 → 次日开盘成交

# 收盘上穿上轨=进场信号;收盘跌回中轨下方=离场信号
df["cross_up"] = (df["close"] > df["upper"]) & (df["close"].shift() <= df["upper"].shift())
df["exit_sig"] = df["close"] < df["mid"]

pos = 0; signal = []
for i in range(len(df)):
    if pos == 0 and df["cross_up"].iloc[i]:
        pos = 1
    elif pos == 1 and df["exit_sig"].iloc[i]:
        pos = 0
    signal.append(pos)
df["signal"] = signal
df["position"] = pd.Series(signal).shift(1).fillna(0)   # 次日开盘才持仓

第 3 步:按次日开盘价 + 手续费算净值

FEE = 0.0013
df["open_ret"] = df["open"].pct_change().shift(-1)          # t 持仓吃 t→t+1 开盘收益
df["trade"] = df["position"].diff().abs().fillna(0)
df["strat_ret"] = df["position"] * df["open_ret"].fillna(0) - df["trade"] * FEE
df["equity"] = (1 + df["strat_ret"]).cumprod()
df["bench"] = (1 + df["close"].pct_change().fillna(0)).cumprod()

第 4 步:与均值回归、买入持有对比

# 均值回归版:跌破下轨买、回到中轨卖
posr = 0; sr = []
for i in range(len(df)):
    below = df["close"].iloc[i] < df["lower"].iloc[i]
    back = df["close"].iloc[i] >= df["mid"].iloc[i]
    if posr == 0 and below: posr = 1
    elif posr == 1 and back: posr = 0
    sr.append(posr)
df["pos_rev"] = pd.Series(sr).shift(1).fillna(0)
df["rev_ret"] = df["pos_rev"] * df["open_ret"].fillna(0) - df["pos_rev"].diff().abs().fillna(0) * FEE
rev_eq = (1 + df["rev_ret"]).cumprod()

def cagr(eq): return eq.iloc[-1] ** (252/len(eq)) - 1
print(f"突破  收益{df['equity'].iloc[-1]-1:.2%} 年化{cagr(df['equity']):.2%} "
      f"回撤{(df['equity']/df['equity'].cummax()-1).min():.2%} 交易{int(df['trade'].sum()//2)}次")
print(f"回归  收益{rev_eq.iloc[-1]-1:.2%}")
print(f"买入持有 收益{df['bench'].iloc[-1]-1:.2%}")

关键坑与注意事项

  • 突破 vs 回归是两套相反逻辑:强趋势品种突破更优,震荡品种回归更优;回测才知道该用哪套。
  • 避免未来函数:收盘出信号、position.shift(1) 次日开盘成交。
  • 前复权价:未复权价在除权日让布林带与收益失真。
  • 手续费必扣:突破策略换手可能频繁,忽略费用高估收益。
  • std 用 ddof=0 还是 1:与主流软件对齐用总体标准差(ddof=0),改口径数值会变。
  • 假突破:收盘刚过上轨可能次日回落,可加「突破幅度」或量能过滤减少假信号。
  • 单标的≠有效:需多品种多区间验证,避免过拟合与幸存者偏差;本文为简化模型未完全模拟 T+1/涨跌停。

常见问题(FAQ)

Q:布林带突破和均值回归到底用哪个? A:没有绝对答案。趋势强的品种/阶段突破追涨更好,震荡市均值回归更好。本文把两者与买入持有并列回测,就是让你用数据判断。

Q:为什么离场用「回到中轨」? A:中轨是均线,跌回中轨下方常意味着突破动能衰竭,是相对稳健的离场点。也可用跌破上轨或 ATR 止损。

Q:K 值调大调小有什么影响? A:K 越大带越宽、突破越少但更可靠;K 越小突破更频繁、假信号更多。常用 2。

Q:需要付费吗? A:单只历史日线有免费额度;多标的批量回测(klines.batch + universes)需 Starter 及以上。详见定价页。

小结

布林带突破策略是「趋势追涨」,与「跌破下轨买入」的均值回归相反。用 AlphaFeed 取前复权日线算布林带,收盘上穿上轨做多、回中轨离场,次日开盘成交避免未来函数、扣手续费,再和均值回归、买入持有对比,就能客观判断该品种适合追突破还是做回归。

参考