2. RSI超买超卖策略
任务目标
实现一个基于RSI(14)的均值回归策略:当RSI低于30时买入,高于70时卖出。使用A股历史日线数据回测,输出策略累计收益并与同期基准(买入持有)对比。
环境与依赖
需要 pandas、numpy、matplotlib。数据使用 akshare 拉取(若网络受限,可替换为本地CSV,字段需包含 date/open/high/low/close)。
完整实现代码
import pandas as pd
import numpy as np
import akshare as ak
# ---------- 1. 获取数据 ----------
def fetch_data(symbol="000001", start="2020-01-01", end="2023-12-31"):
df = ak.stock_zh_a_hist(symbol=symbol, period="daily",
start_date=start, end_date=end, adjust="qfq")
df = df.rename(columns={"日期": "date", "开盘": "open", "最高": "high",
"最低": "low", "收盘": "close", "成交量": "volume"})
df = df[["date", "open", "high", "low", "close"]].copy()
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date").sort_index()
return df
# ---------- 2. RSI 计算 ----------
def calc_rsi(close, period=14):
delta = close.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean()
avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
# ---------- 3. 策略信号 ----------
def generate_signals(df, rsi_period=14, buy_th=30, sell_th=70):
df = df.copy()
df["rsi"] = calc_rsi(df["close"], rsi_period)
df["signal"] = 0
df.loc[df["rsi"] < buy_th, "signal"] = 1 # 超卖买入
df.loc[df["rsi"] > sell_th, "signal"] = -1 # 超买卖出
df["position"] = df["signal"].replace(0, np.nan).ffill().fillna(0)
df["position"] = df["position"].shift(1).fillna(0) # 次日开盘执行
return df
# ---------- 4. 回测 ----------
def backtest(df, initial_capital=100000):
df = df.copy()
df["daily_ret"] = df["close"].pct_change().fillna(0)
df["strategy_ret"] = df["position"] * df["daily_ret"]
df["strategy_equity"] = initial_capital * (1 + df["strategy_ret"]).cumprod()
df["benchmark_equity"] = initial_capital * (1 + df["daily_ret"]).cumprod()
return df
# ---------- 5. 运行与输出 ----------
if __name__ == "__main__":
data = fetch_data()
sig_df = generate_signals(data)
result = backtest(sig_df)
# 打印关键指标
total_days = len(result)
in_market_days = int(result["position"].sum())
strategy_return = result["strategy_equity"].iloc[-1] / 100000 - 1
benchmark_return = result["benchmark_equity"].iloc[-1] / 100000 - 1
print(f"回测区间: {result.index[0].date()} ~ {result.index[-1].date()}")
print(f"总交易日: {total_days}, 持仓天数: {in_market_days}")
print(f"策略累计收益: {strategy_return:.2%}")
print(f"基准累计收益: {benchmark_return:.2%}")
# 绘制净值曲线
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
plt.figure(figsize=(10, 5))
plt.plot(result.index, result["strategy_equity"], label="RSI策略", linewidth=1.5)
plt.plot(result.index, result["benchmark_equity"], label="买入持有", linewidth=1.5, alpha=0.7)
plt.legend()
plt.title("RSI超买超卖策略回测")
plt.xlabel("日期")
plt.ylabel("账户净值")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
运行方式
pip install pandas numpy akshare matplotlib
python rsi_strategy.py
验证方法与通过条件
- 数据验证:打印的日期区间应为2020-01-01至2023-12-31,总交易日约970天(A股交易日历)。
- 信号验证:持仓天数应显著小于总交易日(均值回归策略大部分时间空仓),且持仓天数大于0。
- 逻辑验证:RSI低于30次日买入、高于70次日卖出的信号已通过
position.shift(1)实现,避免未来函数。 - 结果判定:程序正常输出两组收益百分比并弹出净值曲线图即视为运行成功。策略收益与基准收益的大小关系取决于行情,不预设优劣;若RSI长期钝化(如单边牛市),策略跑输基准属正常现象。
常见问题
- akshare数据拉取失败:检查网络或更换
symbol(如"600519")。也可将本地CSV的date/open/high/low/close列读入后替换fetch_data返回的DataFrame。 - RSI初始值为NaN:前14个交易日无RSI值,对应
signal=0,策略自动空仓,不影响回测正确性。 - 信号闪烁:本策略在RSI穿越阈值时立即反转仓位,未做平滑处理。若需减少交易频率,可引入持仓状态锁存(如连续N日低于阈值才买入),但当前最小实现已完整覆盖目标。