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()
运行方式
- 安装依赖:
pip install pandas numpy matplotlib yfinance - 将上述代码保存为
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)使信号次日开盘执行,避免未来函数
策略要点
- 布林带突破策略本质是趋势跟踪:价格突破上轨视为强势信号入场,跌破下轨视为弱势信号离场
- 仓位状态使用
ffill()保持,直到出现反向突破信号才改变,避免频繁交易 - 回测中未计入交易成本,实盘需考虑手续费与滑点对收益的侵蚀