🚀 AI 一键生成 joinquant 策略代码
立即体验

如何在聚宽中将年化回归收益、R²、短期动量和波动率惩罚合成为 ETF 轮动评分?

joinquant | 发布时间: 2026/8/13
以下内容由 EasyQuant 生成。

问题描述

聚宽多重动量 ETF 轮动:如何结合回归、趋势与波动率评分?
如何在聚宽中将年化回归收益、R²、短期动量和波动率惩罚合成为 ETF 轮动评分?

解决方案

聚宽多重动量 ETF 轮动评分系统构建指南

在量化投资中,传统的单一动量(如单纯看过去N天的涨幅)容易在市场拐点处失效,且无法识别趋势的平滑度与波动风险。为了构建一个更稳健的 ETF 轮动策略,我们可以将以下四个维度合成为一个多重动量评分(Multi-Momentum Score)

  1. 年化回归收益(Annualized Regression Return):通过对数价格进行线性回归,计算趋势的斜率,代表长期趋势的速度。
  2. R² 拟合优度(R-squared):衡量价格走势与回归线的贴合程度,R² 越高说明趋势越平滑、越稳定。
  3. 短期动量(Short-term Momentum):捕捉近期的爆发力,避免长期回归对近期拐点的钝化。
  4. 波动率惩罚(Volatility Penalty):对高波动、高风险的 ETF 进行评分扣减,实现风险调整后收益的最大化。

一、 核心算法与评分合成公式

1. 长期趋势与平滑度(回归斜率 & $R^2$)

对过去 $N$ 天的复权收盘价取对数 $\ln(P_t)$,对时间 $t$ 进行一元线性回归:
$$\ln(P_t) = \alpha + \beta \cdot t + \epsilon_t$$

  • 年化回归收益率:$Slope_{annual} = (e^{\beta \cdot 250} - 1) \times 100%$
  • 趋势平滑度:$R^2 = 1 - \frac{\sum \epsilon_t^2}{\sum (\ln(P_t) - \overline{\ln(P)})^2}$
  • 长期趋势得分:$Score_{long} = Slope_{annual} \times R^2$

2. 短期动量

计算过去 $M$ 天(如 5 天或 10 天)的简单收益率:
$$Score_{short} = \frac{P_{today} - P_{t-M}}{P_{t-M}}$$

3. 波动率惩罚

计算过去 $N$ 天日收益率的标准差并年化:
$$\sigma_{annual} = Std(Return_{daily}) \times \sqrt{250}$$

4. 综合评分合成

将各项指标进行标准化(Z-Score)或直接加权合成:
$$Final_Score = w_1 \cdot Z(Score_{long}) + w_2 \cdot Z(Score_{short}) - w_3 \cdot Z(\sigma_{annual})$$
(通常权重可设为:$w_1 = 0.5, w_2 = 0.3, w_3 = 0.2$)


二、 聚宽(JoinQuant)策略完整源码

以下代码展示了如何在聚宽平台中实现该多重评分系统,并在每月初进行 ETF 轮动调仓:

import jqdata
import numpy as np
import pandas as pd
from scipy.stats import linregress

def initialize(context):
    # 开启真实价格模式
    set_option('use_real_price', True)
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    
    # 策略参数设置
    g.etf_pool = [
        '510300.XSHG', # 沪深300ETF
        '510500.XSHG', # 中证500ETF
        '159915.XSHE', # 创业板ETF
        '518880.XSHG', # 黄金ETF
        '513100.XSHG', # 纳指ETF
        '511010.XSHG'  # 国债ETF
    ]
    
    g.long_window = 60   # 长期回归窗口
    g.short_window = 10  # 短期动量窗口
    g.hold_num = 2       # 每次持有的ETF数量
    
    # 每月第一个交易日开盘运行
    run_monthly(handle_rotation, monthday=1, time='9:30')

def get_rotation_score(security, end_date):
    # 获取历史收盘价数据
    hist = attribute_history(security, g.long_window, '1d', ['close'], skip_paused=True)
    if len(hist) < g.long_window:
        return -999
    
    closes = hist['close'].values
    
    # 1. 计算长期回归斜率和 R2
    log_closes = np.log(closes)
    x = np.arange(len(log_closes))
    slope, intercept, r_value, p_value, std_err = linregress(x, log_closes)
    
    annual_slope = (np.exp(slope * 250) - 1) * 100
    r_squared = r_value ** 2
    long_score = annual_slope * r_squared
    
    # 2. 计算短期动量
    short_return = (closes[-1] - closes[-g.short_window]) / closes[-g.short_window]
    
    # 3. 计算年化波动率
    daily_returns = np.diff(closes) / closes[:-1]
    ann_vol = np.std(daily_returns) * np.sqrt(250)
    
    return {
        'long_score': long_score,
        'short_score': short_return,
        'volatility': ann_vol
    }

def handle_rotation(context):
    score_list = []
    
    # 计算每个 ETF 的各项指标
    for etf in g.etf_pool:
        metrics = get_rotation_score(etf, context.previous_date)
        if metrics != -999:
            metrics['code'] = etf
            score_list.append(metrics)
            
    df = pd.DataFrame(score_list)
    if df.empty:
        return
        
    # 对各项指标进行 Z-Score 标准化
    df['z_long'] = (df['long_score'] - df['long_score'].mean()) / (df['long_score'].std() + 1e-6)
    df['z_short'] = (df['short_score'] - df['short_score'].mean()) / (df['short_score'].std() + 1e-6)
    df['z_vol'] = (df['volatility'] - df['volatility'].mean()) / (df['volatility'].std() + 1e-6)
    
    # 合成最终评分 (长期趋势 50% + 短期动量 30% - 波动率惩罚 20%)
    df['final_score'] = 0.5 * df['z_long'] + 0.3 * df['z_short'] - 0.2 * df['z_vol']
    
    # 按评分降序排列,选择前 N 个
    df = df.sort_values(by='final_score', ascending=False)
    target_list = df['code'].head(g.hold_num).tolist()
    
    # 调仓执行
    # 1. 卖出不在目标列表中的持仓
    for etf in list(context.portfolio.positions.keys()):
        if etf not in target_list:
            order_target(etf, 0)
            log.info(f"卖出 {etf}")
            
    # 2. 等权重买入目标持仓
    if len(target_list) > 0:
        target_value = context.portfolio.total_value / len(target_list)
        for etf in target_list:
            order_target_value(etf, target_value)
            log.info(f"调仓/买入 {etf} 至目标价值 {target_value:.2f}")

三、 策略优势与优化建议

  1. 趋势平滑度过滤噪音:引入 $R^2$ 乘数后,能够有效过滤掉那些依靠单日暴涨拉高均线、但实际走势极其不稳定的“伪强势”ETF。
  2. 动态风险控制:波动率惩罚项(-0.2 * z_vol)在市场剧烈震荡时,会自动将仓位导向国债 ETF、黄金 ETF 等低波动避险资产,起到天然的防守作用。
  3. 参数敏感性调优:建议在聚宽研究模块中,对 long_window(长期窗口)和权重系数进行网格搜索,以适应不同宏观周期下的市场特征。