4. 布林带突破调仓策略

任务目标

实现一个基于布林带突破的调仓策略:当收盘价突破布林带上轨时加仓至满仓,跌破下轨时清仓,介于两者之间时维持原仓位。使用A股历史日线数据回测,输出策略净值曲线并与基准(买入持有)对比,计算年化收益率、最大回撤和夏普比率。

环境与依赖

需要 pandas、numpy、matplotlib。数据使用 yfinance 获取,若网络受限可替换为本地CSV(列名需为 Date, Open, High, Low, Close, Volume)。

策略逻辑

  • 布林带参数:窗口20日,标准差倍数2.0
  • 调仓规则(以次日开盘价执行):
    • 当日收盘价 > 上轨 → 次日仓位 = 1.0(满仓)
    • 当日收盘价 < 下轨 → 次日仓位 = 0.0(空仓)
    • 否则 → 次日仓位 = 当日仓位(保持不变)
  • 初始资金100万,不考虑手续费和滑点

完整实现代码

import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt

# ---------- 1. 获取数据 ----------
df = yf.download("510300.SS", start="2020-01-01", end="2024-12-31", auto_adjust=True)
df = df[["Open", "High", "Low", "Close"]].dropna()

# ---------- 2. 计算布林带 ----------
df["MA20"] = df["Close"].rolling(20).mean()
df["STD20"] = df["Close"].rolling(20).std()
df["Upper"] = df["MA20"] + 2 * df["STD20"]
df["Lower"] = df["MA20"] - 2 * df["STD20"]

# ---------- 3. 生成调仓信号(次日执行) ----------
df["Signal"] = 0
df.loc[df["Close"] > df["Upper"], "Signal"] = 1   # 突破上轨 → 满仓
df.loc[df["Close"] < df["Lower"], "Signal"] = -1  # 跌破下轨 → 空仓
df["Position"] = df["Signal"].replace(0, np.nan).ffill().fillna(0).shift(1)
df["Position"] = df["Position"].fillna(0)

# ---------- 4. 回测计算 ----------
df["MarketReturn"] = df["Close"].pct_change()
df["StrategyReturn"] = df["Position"] * df["MarketReturn"]
df["StrategyEquity"] = (1 + df["StrategyReturn"]).cumprod() * 1_000_000
df["BenchmarkEquity"] = (1 + df["MarketReturn"]).cumprod() * 1_000_000

# ---------- 5. 绩效指标 ----------
def max_drawdown(equity_series):
    peak = equity_series.cummax()
    return ((equity_series - peak) / peak).min()

total_days = len(df)
annual_return = (df["StrategyEquity"].iloc[-1] / 1_000_000) ** (252 / total_days) - 1
benchmark_return = (df["BenchmarkEquity"].iloc[-1] / 1_000_000) ** (252 / total_days) - 1
strategy_dd = max_drawdown(df["StrategyEquity"])
benchmark_dd = max_drawdown(df["BenchmarkEquity"])
daily_std = df["StrategyReturn"].std() * np.sqrt(252)
sharpe = (annual_return - 0.03) / daily_std if daily_std > 0 else 0

print(f"策略年化收益: {annual_return:.2%} | 基准年化收益: {benchmark_return:.2%}")
print(f"策略最大回撤: {strategy_dd:.2%} | 基准最大回撤: {benchmark_dd:.2%}")
print(f"策略夏普比率: {sharpe:.2f}")

# ---------- 6. 可视化 ----------
plt.figure(figsize=(12, 6))
plt.plot(df.index, df["StrategyEquity"], label="布林带策略", linewidth=2)
plt.plot(df.index, df["BenchmarkEquity"], label="买入持有", alpha=0.7)
plt.title("布林带突破调仓策略净值对比")
plt.xlabel("日期")
plt.ylabel("账户净值(元)")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

运行方式

  1. 安装依赖:pip install pandas numpy matplotlib yfinance
  2. 将上述代码保存为 bollinger_strategy.py,在终端执行 python bollinger_strategy.py

验证方法与通过条件

  • 程序正常输出三行绩效指标,无报错
  • 净值曲线图显示策略净值与基准净值两条曲线,且策略在2020-2024年间存在明显空仓区间(净值走平段),证明调仓逻辑生效
  • 手动抽查:打印 df[["Close","Upper","Lower","Position"]].tail(20),确认当Close>Upper时Position为1.0,Close<Lower时Position为0.0,其余情况Position延续前值

常见问题

  • 数据获取失败:yfinance需联网,若失败可下载510300日线CSV,用 pd.read_csv() 读取并确保列名一致
  • 首日无信号:前20日因滚动窗口无布林带值,Position默认为0(空仓),属正常现象
  • 信号延迟:代码使用 shift(1) 使信号次日开盘执行,避免未来函数
布林带突破调仓策略逻辑示意 价格与布林带 上轨 (MA+2σ) 中轨 (MA20) 下轨 (MA-2σ) 收盘价 突破上轨 突破上轨 跌破下轨 仓位状态(次日执行) 满仓 (100%) 空仓 (0%) 维持原仓位 调仓规则 • 收盘价 > 上轨 → 次日满仓 • 收盘价 < 下轨 → 次日空仓 • 上轨 ≥ 收盘价 ≥ 下轨 → 仓位不变 参数 窗口: 20日 标准差倍数: 2.0 执行价: 次日开盘 初始资金: 100万元

策略要点

  • 布林带突破策略本质是趋势跟踪:价格突破上轨视为强势信号入场,跌破下轨视为弱势信号离场
  • 仓位状态使用 ffill() 保持,直到出现反向突破信号才改变,避免频繁交易
  • 回测中未计入交易成本,实盘需考虑手续费与滑点对收益的侵蚀