3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
行业 ETF 轮动是量化投资中非常经典的策略。单一的动量因子容易在市场风格切换时遭遇大幅回撤,因此,构建一个综合相对强弱(动量)、盈利趋势(基本面)、估值、上涨宽度、资金流和拥挤度的多维度打分模型,能显著提升策略的鲁棒性。
在聚宽(JoinQuant)平台上,我们可以利用 get_price 获取行情、get_fundamentals 获取行业基本面、get_money_flow 获取资金流,并最终通过聚宽强大的投资组合优化器 portfolio_optimizer 实现科学的权重分配与调仓。
为了对行业 ETF 进行综合评分,我们对每个候选 ETF 对应的行业或标的进行以下六个维度的打分(分值归一化在 0~1 之间):
get_history_fundamentals 跟踪行业成分股的净利润增长率(net_profit_growth_rate)或营业收入增长率。get_money_flow 获取主力资金净流入占比(net_pct_main)。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))
get_index_stocks 获取 ETF 跟踪指数的成分股,再通过 get_fundamentals 汇总计算成分股的整体估值与盈利趋势,从而实现真正的基本面驱动轮动。BarraConstraint 进行风险控制:在 portfolio_optimizer 中,可以加入 BarraConstraint 限制行业在特定风格(如大盘/小盘、高贝塔/低贝塔)上的暴露,使轮动策略在获取行业超额收益的同时,保持整体组合的风格中性。