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

如何在聚宽中构建行业 ETF 轮动模型?综合相对强弱、盈利趋势与拥挤度打分

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

问题描述

聚宽行业 ETF 轮动策略:如何结合产业趋势、盈利与拥挤度评分?
如何在聚宽中构建行业 ETF 轮动模型,综合相对强弱、盈利趋势、估值、上涨宽度、资金流和拥挤度进行打分与调仓?

解决方案

聚宽行业 ETF 轮动策略构建指南

行业 ETF 轮动是量化投资中非常经典的策略。单一的动量因子容易在市场风格切换时遭遇大幅回撤,因此,构建一个综合相对强弱(动量)、盈利趋势(基本面)、估值、上涨宽度、资金流和拥挤度的多维度打分模型,能显著提升策略的鲁棒性。

在聚宽(JoinQuant)平台上,我们可以利用 get_price 获取行情、get_fundamentals 获取行业基本面、get_money_flow 获取资金流,并最终通过聚宽强大的投资组合优化器 portfolio_optimizer 实现科学的权重分配与调仓。


一、 多维度打分系统设计

为了对行业 ETF 进行综合评分,我们对每个候选 ETF 对应的行业或标的进行以下六个维度的打分(分值归一化在 0~1 之间):

  1. 相对强弱 (Relative Strength):计算过去 20 日和 60 日的超额收益率,捕捉中短期动量。
  2. 盈利趋势 (Earnings Trend):通过 get_history_fundamentals 跟踪行业成分股的净利润增长率(net_profit_growth_rate)或营业收入增长率。
  3. 估值水平 (Valuation):计算行业整体的 PE/PB 百分位,估值越低得分越高(逆向安全边际)。
  4. 上涨宽度 (Breadth):计算行业成分股中站上 20 日均线的股票比例,宽度越宽代表上涨越健康。
  5. 资金流向 (Money Flow):利用 get_money_flow 获取主力资金净流入占比(net_pct_main)。
  6. 拥挤度评分 (Crowdedness):计算行业近期成交额占全市场比重或换手率标准差(turnover_volatility)。拥挤度过高时进行扣分,防止追高触顶。

二、 聚宽策略核心源码实现

以下是一个简化但结构完整的行业 ETF 轮动策略模板,展示了如何获取数据、计算综合评分并调用 portfolio_optimizer 进行调仓:

import pandas as pd
import numpy as np
from jqdata import *
from jqlib.optimizer import *

def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 行业 ETF 候选池(示例:半导体、医药、消费、新能源、军工)
    g.etf_pool = [
        '512480.XSHG', # 半导体ETF
        '512010.XSHG', # 医药ETF
        '510810.XSHG', # 上海红利ETF
        '510150.XSHG', # 消费ETF
        '510210.XSHG', # 指数ETF
    ]
    
    # 交易税费设置
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 每月第一个交易日调仓
    run_monthly(handle_rotation, monthday=1, time='09:30')

def get_etf_scores(context):
    scores = {}
    for etf in g.etf_pool:
        # 1. 获取相对强弱 (动量)
        hist = attribute_history(etf, 60, '1d', ['close'])
        momentum_20 = (hist['close'][-1] / hist['close'][-20]) - 1
        momentum_60 = (hist['close'][-1] / hist['close'][-60]) - 1
        momentum_score = (momentum_20 * 0.6) + (momentum_60 * 0.4)
        
        # 2. 获取资金流向
        flow = get_money_flow(etf, count=5)
        flow_score = flow['net_pct_main'].mean() if not flow.empty else 0
        
        # 3. 获取拥挤度 (换手率标准差,越低代表越不拥挤,得分越高)
        vol = attribute_history(etf, 20, '1d', ['volume'])
        crowd_score = -vol['volume'].std() # 负相关处理
        
        # 综合评分 (简单加权,实际研究中可做标准化处理)
        total_score = (momentum_score * 0.5) + (flow_score * 0.3) + (crowd_score * 0.2)
        scores[etf] = total_score
        
    return pd.Series(scores)

def handle_rotation(context):
    log.info("开始执行行业 ETF 轮动调仓...")
    
    # 计算所有 ETF 的综合得分
    scores = get_etf_scores(context)
    log.info("各行业 ETF 综合得分:\n%s" % scores)
    
    # 过滤掉得分过低的标的,选择前 3 只进行配置
    top_etfs = list(scores.nlargest(3).index)
    
    # 获取当前持仓,不在买入名单中的卖出
    for stock in list(context.portfolio.positions.keys()):
        if stock not in top_etfs:
            order_target(stock, 0)
            log.info("卖出不在名单中的 ETF: %s" % stock)
            
    # 使用聚宽投资组合优化器进行权重分配
    # 目标:在满足权重约束的前提下,最大化组合标的打分 (MaxScore)
    try:
        optimized_weight = portfolio_optimizer(
            date=context.previous_date,
            securities=top_etfs,
            target=MaxScore(scores=scores[top_etfs]),
            constraints=[WeightConstraint(low=0.9, high=1.0)], # 保持高仓位
            bounds=[Bound(0.1, 0.5)], # 单只 ETF 仓位限制在 10% ~ 50%
            default_port_weight_range=[0., 1.0],
            return_none_if_fail=True
        )
        
        if optimized_weight is not None:
            log.info("优化器输出权重:\n%s" % optimized_weight)
            total_value = context.portfolio.total_value
            for etf, weight in optimized_weight.items():
                target_val = total_value * weight
                order_target_value(etf, target_val)
                log.info("调仓 %s 至权重 %.2f%%" % (etf, weight * 100))
        else:
            # 优化失败则等权重配置
            log.warn("组合优化失败,采用等权重备用方案")
            for etf in top_etfs:
                order_target_value(etf, context.portfolio.total_value / len(top_etfs))
    except Exception as e:
        log.error("调仓异常: %s" % str(e))

三、 策略优化建议(进阶)

  1. 穿透底层成分股计算基本面:ETF 本身无法直接获取 PE/PB 财报数据。建议使用 get_index_stocks 获取 ETF 跟踪指数的成分股,再通过 get_fundamentals 汇总计算成分股的整体估值与盈利趋势,从而实现真正的基本面驱动轮动。
  2. 动态拥挤度阈值:拥挤度(如成交额占比)具有明显的均值回归特性。当某行业 ETF 的成交额占比达到历史 90% 分位数以上时,应触发一票否决机制(直接剔除或权重降为 0),以规避短期过热回撤。
  3. 结合 BarraConstraint 进行风险控制:在 portfolio_optimizer 中,可以加入 BarraConstraint 限制行业在特定风格(如大盘/小盘、高贝塔/低贝塔)上的暴露,使轮动策略在获取行业超额收益的同时,保持整体组合的风格中性。