2. RSI超买超卖调仓策略
任务目标
基于RSI(相对强弱指数)识别ETF的超买超卖状态,实现动态调仓策略:当RSI进入超买区(>70)时减仓,进入超卖区(<30)时加仓,并对比基准买入持有策略的收益表现。
实现方案
使用Python获取沪深300ETF(510300)历史日线数据,计算14日RSI,按以下规则生成每日持仓权重:
- RSI > 70:持仓降至40%(超买减仓)
- RSI < 30:持仓升至100%(超卖满仓)
- 其余情况:持仓保持80%(默认仓位)
策略每日按收盘价调仓,对比基准(始终100%持有)的累计收益与最大回撤。
环境依赖
需要 pandas、numpy、akshare(或yfinance)。若网络受限,可使用本地CSV数据(代码中已提供本地读取分支)。
完整代码
import pandas as pd
import numpy as np
# ---------- 1. 获取数据(优先本地CSV,否则在线拉取) ----------
def load_data():
try:
df = pd.read_csv('510300.csv', parse_dates=['date'], index_col='date')
except FileNotFoundError:
import akshare as ak
df = ak.fund_etf_hist_em(symbol='510300', period='daily',
start_date='20220101', end_date='20241231',
adjust='qfq')
df = df[['日期', '收盘']].rename(columns={'日期': 'date', '收盘': 'close'})
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
return df['close'].astype(float)
# ---------- 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
return 100 - (100 / (1 + rs))
# ---------- 3. 策略回测 ----------
def backtest(close, rsi):
df = pd.DataFrame({'close': close, 'rsi': rsi}).dropna()
# 生成目标仓位
def target_weight(r):
if r > 70:
return 0.4
elif r < 30:
return 1.0
else:
return 0.8
df['weight'] = df['rsi'].apply(target_weight)
df['weight'] = df['weight'].shift(1) # 次日生效,避免未来函数
df.dropna(inplace=True)
# 策略净值(每日按权重调仓)
df['strategy_ret'] = df['close'].pct_change() * df['weight']
df['strategy_nav'] = (1 + df['strategy_ret']).cumprod()
# 基准净值(始终满仓)
df['bench_ret'] = df['close'].pct_change()
df['bench_nav'] = (1 + df['bench_ret']).cumprod()
# 最大回撤
def max_drawdown(nav):
peak = nav.cummax()
return ((nav - peak) / peak).min()
return {
'strategy_nav': df['strategy_nav'].iloc[-1],
'bench_nav': df['bench_nav'].iloc[-1],
'strategy_mdd': max_drawdown(df['strategy_nav']),
'bench_mdd': max_drawdown(df['bench_nav']),
'trade_dates': len(df),
'df': df
}
# ---------- 4. 执行 ----------
if __name__ == '__main__':
close = load_data()
rsi = calc_rsi(close)
result = backtest(close, rsi)
print(f"回测区间: {result['df'].index[0].date()} 至 {result['df'].index[-1].date()}")
print(f"策略累计净值: {result['strategy_nav']:.4f}")
print(f"基准累计净值: {result['bench_nav']:.4f}")
print(f"策略最大回撤: {result['strategy_mdd']*100:.2f}%")
print(f"基准最大回撤: {result['bench_mdd']*100:.2f}%")
print(f"调仓交易日数: {result['trade_dates']}")
运行方式
- 将上述代码保存为
rsi_strategy.py。 - 若使用在线数据,直接运行
python rsi_strategy.py;若使用本地数据,将510300的日线CSV(需含date和close列)放在同目录。 - 输出将显示策略与基准的净值、最大回撤及回测区间。
验证方法
运行后检查以下条件以判定策略有效:
- 程序无异常退出,输出5行指标数据。
- 策略累计净值与基准净值均为正数(区间内ETF未归零)。
- 若策略净值 > 基准净值且策略最大回撤 < 基准最大回撤,说明RSI调仓在控制风险的同时提升了收益;若仅回撤降低而收益略低,也视为风险控制有效。
- 手动抽查:打印
result['df'].tail(20),确认RSI>70的日期次日权重为0.4,RSI<30的日期次日权重为1.0。
预期输出示例
回测区间: 2022-01-04 至 2024-12-31
策略累计净值: 1.1523
基准累计净值: 1.0876
策略最大回撤: -12.34%
基准最大回撤: -18.21%
调仓交易日数: 724
注:以上数值仅为格式示例,实际结果取决于数据源与区间,需以真实运行为准。
常见问题
- 数据获取失败:检查网络或改用本地CSV;akshare接口字段可能随版本变化,若报KeyError请打印
df.columns确认列名。 - RSI全为NaN:数据不足14个交易日,需扩大时间范围。
- 未来函数:代码已用
shift(1)将权重滞后一日,确保信号当日收盘后次日执行,无前视偏差。