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']}")

运行方式

  1. 将上述代码保存为 rsi_strategy.py。
  2. 若使用在线数据,直接运行 python rsi_strategy.py;若使用本地数据,将510300的日线CSV(需含date和close列)放在同目录。
  3. 输出将显示策略与基准的净值、最大回撤及回测区间。

验证方法

运行后检查以下条件以判定策略有效:

  • 程序无异常退出,输出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)将权重滞后一日,确保信号当日收盘后次日执行,无前视偏差。