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

聚宽平台如何编写行业 ETF 轮动策略?(附 Python 源码)

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

问题描述

聚宽行业 ETF 轮动策略:如何实现行业强弱排序与定期调仓?
请用聚宽策略代码实现行业 ETF 轮动,包含行情数据获取、动量排名、目标 ETF 选择、定期调仓和持仓数量控制。

解决方案

聚宽行业 ETF 轮动策略实现指南

行业 ETF 轮动策略是一种经典的动量交易策略。其核心逻辑是:在每一个调仓周期,计算候选行业 ETF 列表的过去一段时间的涨幅(动量),对它们进行强弱排序,买入并持有最强势的若干只 ETF,同时卖出不再强势的 ETF。

以下是在聚宽(JoinQuant)平台上实现该策略的完整 Python 代码及详细解析。


1. 策略核心代码

请在聚宽策略编辑框中粘贴以下代码进行回测:

# 导入聚宽函数库
import pandas as pd
import numpy as np
from jqdata import *

def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 过滤掉比 error 级别低的 log
    log.set_level('order', 'error')
    
    # 策略参数设置
    g.etf_pool = [
        '510300.XSHG',  # 沪深300ETF
        '510500.XSHG',  # 中证500ETF
        '518880.XSHG',  # 黄金ETF
        '512880.XSHG',  # 证券ETF
        '512010.XSHG',  # 医药ETF
        '512660.XSHG',  # 军工ETF
        '515000.XSHG',  # 科技ETF
        '159915.XSHE',  # 创业板ETF
    ]
    
    g.momentum_window = 20  # 动量计算窗口(过去20个交易日)
    g.hold_num = 3          # 同时持有的最大 ETF 数量
    
    # 设定交易税费:买入万三,卖出万三(ETF免印花税)
    set_order_cost(OrderCost(close_tax=0, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='fund')
    
    # 设定调仓频率:每月第一个交易日调仓
    run_monthly(handle_rotation, monthday=1, time='9:30', reference_security='000300.XSHG')

def handle_rotation(context):
    # 1. 获取历史行情数据并计算动量(过去N日涨幅)
    momentum_scores = {}
    for etf in g.etf_pool:
        # 获取过去 N+1 天的收盘价
        close_data = attribute_history(etf, g.momentum_window + 1, '1d', ['close'])
        if not close_data.empty and len(close_data) >= g.momentum_window + 1:
            # 计算涨幅: (当前价格 - N日前价格) / N日前价格
            start_price = close_data['close'].iloc[0]
            end_price = close_data['close'].iloc[-1]
            if start_price > 0:
                momentum_scores[etf] = (end_price - start_price) / start_price
    
    # 2. 行业强弱排序
    # 按动量得分从大到小排序
    sorted_etfs = sorted(momentum_scores.items(), key=lambda x: x[1], reverse=True)
    
    # 3. 选择目标 ETF(只选择动量为正且排名前 hold_num 的 ETF)
    target_list = [etf for etf, score in sorted_etfs if score > 0][:g.hold_num]
    
    log.info("当前调仓日:%s" % context.current_dt.date())
    log.info("动量排序结果:%s" % str(sorted_etfs))
    log.info("目标持仓列表:%s" % str(target_list))
    
    # 4. 定期调仓与持仓数量控制
    # 获取当前持仓
    current_holdings = list(context.portfolio.positions.keys())
    
    # 卖出不在目标列表中的 ETF
    for etf in current_holdings:
        if etf not in target_list:
            order_target(etf, 0)
            log.info("卖出不在目标列表的 ETF: %s" % etf)
            
    # 重新计算可用资金并等额分配买入目标 ETF
    if len(target_list) > 0:
        # 计算每个 ETF 分配的资金
        available_cash = context.portfolio.available_cash
        # 考虑已经持有的目标 ETF 价值,进行等额再平衡
        total_value = context.portfolio.total_value
        target_value_per_etf = total_value / len(target_list)
        
        for etf in target_list:
            order_target_value(etf, target_value_per_etf)
            log.info("调整目标 ETF 仓位: %s 至价值 %.2f" % (etf, target_value_per_etf))

2. 策略关键步骤解析

  • 行情数据获取:使用 attribute_history 函数获取每个 ETF 过去 momentum_window + 1 天的收盘价,避免未来函数。
  • 动量排名:通过计算 (当前价格 - N日前价格) / N日前价格 得到动量得分,并使用 Python 的 sorted 函数进行降序排列。
  • 目标选择与过滤:策略仅选择动量得分大于 0(即处于上升通道)的 ETF,若所有 ETF 都在下跌,则选择空仓避险。
  • 定期调仓:通过 run_monthly 注册每月第一个交易日开盘时运行 handle_rotation,降低交易频次与摩擦成本。
  • 持仓控制与再平衡:使用 order_target 卖出淘汰品种,再通过 order_target_value 对目标 ETF 进行等额本金再平衡,确保风险分散。