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

聚宽平台如何通过精简因子提升ETF轮动策略的稳健性?

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

问题描述

聚宽ETF轮动策略如何通过精简因子提升稳健性?
在聚宽中实现ETF轮动策略时,如何从多个辅助过滤模块中筛选出真正有效的核心组件,并避免过拟合?

解决方案

在聚宽(JoinQuant)平台上实现ETF轮动策略时,许多宽客容易陷入“因子堆砌”和“过度拟合”的陷阱。为了提升策略在实盘中的稳健性,必须通过科学的方法精简因子,并剔除冗余的辅助过滤模块。以下是系统性的优化方案:

一、 为什么“多因子+多过滤”会导致过拟合?

  1. 幸存者偏差与历史拟合:在历史回测中,增加一个“仅在周二买入”或“过滤掉某特定行业”的规则可能会让回测曲线非常完美,但这种规则往往缺乏金融逻辑支撑,实盘极易失效。
  2. 多重共线性:许多动量指标(如ROC、Momentum、均线偏离度)本质上高度相关。引入多个同质因子不仅不会增加信息量,反而会放大噪声。
  3. 自由度丧失:策略的参数和过滤条件越多,自由度越低,对未来市场的适应能力就越差。
  4. 交易成本吞噬:过于复杂的过滤模块会导致频繁调仓,产生的滑点和手续费会迅速吞噬利润。

二、 如何精简因子与筛选核心组件?

1. 坚持“奥卡姆剃刀”原则(金融逻辑优先)

每一个保留的因子或过滤模块,必须有坚实的经济学或行为金融学逻辑。例如:

  • 核心动量因子:ETF轮动的核心是“强者恒强”。建议仅保留一个中周期动量因子(如 20日或60日收益率)。
  • 核心过滤模块:仅保留大盘风控(如沪深300指数跌破20日均线则空仓),避免加入针对单一ETF的微观过滤。

2. 使用聚宽因子分析工具(IC/IR分析)

利用聚宽的 analyze_factor 工具对候选因子进行单因子分析:

  • IC(信息系数)均值:评估因子的预测能力,绝对值应稳定大于 0.02。
  • IC标准差与IR(信息比率):IR 越高,说明因子表现越稳定。
  • 换手率分析:通过 quantile_turnover 评估因子的交易成本。如果一个辅助因子略微提升了收益,但大幅增加了换手率,应果断剔除。

3. 因子正交化与去相关性

如果必须使用多个因子,应在计算前进行正交化处理(如聚宽提供的 neutralize 中性化方法),消除彼此的线性相关部分,只保留纯净的超额收益贡献。


三、 聚宽精简版ETF轮动策略源码示例

以下是一个精简、稳健的ETF轮动策略示例。该策略仅保留核心动量因子(21日收益率)大盘均线风控,不包含任何冗余的微观过滤模块,具有极强的抗过拟合能力。

import pandas as pd
from jqdata import *

def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 核心ETF候选池(覆盖宽基、行业及黄金,避免同质化)
    g.etf_pool = [
        '510300.XSHG', # 沪深300 ETF
        '510500.XSHG', # 中证500 ETF
        '159915.XSHE', # 创业板 ETF
        '518880.XSHG', # 黄金 ETF
        '513100.XSHG'  # 纳指 ETF
    ]
    
    # 核心风控参考标的
    g.market_index = '000300.XSHG'
    
    # 调仓周期:每周第一个交易日
    run_weekly(market_open, weekday=1, time='09:30')

def market_open(context):
    # 1. 大盘风控模块(极简风控:若沪深300跌破20日均线,则全仓避险)
    index_close = attribute_history(g.market_index, 20, '1d', ['close'])['close']
    ma20 = index_close.mean()
    current_index_price = index_close[-1]
    
    if current_index_price < ma20:
        log.info("大盘处于弱势,清仓避险")
        for asset in context.portfolio.positions:
            order_target(asset, 0)
        return

    # 2. 核心动量计算(仅使用单一核心因子:21日收益率)
    scores = {}
    for etf in g.etf_pool:
        hist = attribute_history(etf, 21, '1d', ['close'])['close']
        # 计算21日动量(当前价格 / 21日前价格 - 1)
        momentum = (hist[-1] / hist[0]) - 1
        scores[etf] = momentum
        
    # 3. 轮动决策:选择动量最强的1只ETF
    best_etf = max(scores, key=scores.get)
    
    # 如果最强ETF的动量为负,则不持仓
    if scores[best_etf] <= 0:
        log.info("所有候选ETF动量均为负,空仓观望")
        for asset in context.portfolio.positions:
            order_target(asset, 0)
        return
        
    # 4. 执行调仓(卖出非目标,买入目标)
    for asset in list(context.portfolio.positions.keys()):
        if asset != best_etf:
            order_target(asset, 0)
            
    # 买入最强ETF
    cash = context.portfolio.available_cash
    if cash > 0:
        order_value(best_etf, cash)
        log.info("调仓买入最强动量ETF: %s" % best_etf)

四、 总结:如何保持策略的长期生命力?

  1. 减少参数调优(Parameter Tuning):不要为了追求回测曲线的完美去微调参数(如把20日均线改成18.5日)。参数应取整数或市场常用默认值(如20日、60日)。
  2. 样本外测试(Out-of-Sample Test):在2015-2020年样本内研发策略,必须在2021-2024年样本外进行验证。如果样本外表现断崖式下跌,说明策略存在严重过拟合。
  3. 控制标的数量:ETF轮动池不宜过大,选择5-8只具有代表性、相关性低的底层资产即可,过多的标的会引入不必要的噪声。