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

聚宽平台行业 ETF 轮动策略:如何结合止盈止损与动态再平衡?(附 Python 源码)

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

问题描述



Title: 聚宽行业 ETF 轮动:如何加入止盈止损与动态再平衡?

Question: 如何在聚宽行业及全品类 ETF 轮动策略中结合止盈止损机制,并实现稳定的动态调仓?

解决方案

聚宽行业 ETF 轮动与动态再平衡策略设计

在量化交易中,ETF 轮动策略因其能有效分散个股风险、捕捉行业板块轮动红利而广受欢迎。然而,传统的等权重或简单动量轮动在面对市场剧烈波动时,往往面临回撤过大和调仓损耗高的问题。本文将介绍如何在聚宽(JoinQuant)平台上,结合止盈止损机制投资组合优化器(Portfolio Optimizer),构建一个高回撤控制力的动态再平衡 ETF 轮动策略。


一、 核心设计思路

  1. 标的选择(ETF 资金池)
    精选代表性强、流动性好的行业 ETF(如消费、科技、医药、金融等)以及宽基/商品 ETF(如沪深300 ETF、黄金 ETF)构建轮动池。

  2. 动态再平衡(Portfolio Optimizer)
    利用聚宽内置的 jqlib.optimizer 库,采用**最大夏普比率(MaxSharpeRatio)最小方差(MinVariance)**模型,在满足权重约束(如单只 ETF 权重不超过 20%)的前提下,每月初动态计算最优配置权重,实现资产的动态再平衡。

  3. 多维度止盈止损机制

    • 个股/ETF 级别止损:利用 get_current_data() 监控盘中价格,若某只持仓 ETF 相比买入均价(position.avg_cost)跌幅超过设定的阈值(如 -8%),则立即触发市价单平仓。
    • 组合级别止损:若整体账户权益回撤达到预警线,可触发全局减仓。

二、 聚宽 Python 策略源码实现

以下是完整的策略代码,可直接复制到聚宽回测环境中运行:

# 导入聚宽函数库与优化器
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免印花税)
    set_order_cost(OrderCost(close_tax=0.0, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='fund')
    
    # 策略参数设置
    g.etf_pool = [
        '510300.XSHG', # 沪深300 ETF
        '510500.XSHG', # 中证500 ETF
        '512880.XSHG', # 证券ETF
        '512010.XSHG', # 医药ETF
        '512660.XSHG', # 军工ETF
        '510810.XSHG', # 上海红利 ETF
        '518880.XSHG', # 黄金ETF
    ]
    
    g.stop_loss_threshold = -0.08  # 单只 ETF 止损阈值:-8%
    g.stop_profit_threshold = 0.25 # 单只 ETF 止盈阈值:+25%
    
    # 定时运行:每月第一个交易日开盘进行动态再平衡调仓
    run_monthly(rebalance_portfolio, monthday=1, time='09:30', reference_security='000300.XSHG')
    # 每日盘中监控止盈止损
    run_daily(check_stop_limits, time='every_bar')

def rebalance_portfolio(context):
    """每月初执行投资组合动态再平衡"""
    log.info("====== 开始执行月度再平衡调仓 ======")
    
    # 1. 卖出不在当前候选池中的持仓
    current_positions = list(context.portfolio.positions.keys())
    for stock in current_positions:
        if stock not in g.etf_pool:
            order_target(stock, 0)
            log.info("非池内标的,清仓: %s" % stock)
            
    # 2. 调用聚宽组合优化器:最大化夏普比率
    # 约束条件:单只 ETF 权重在 0% 到 25% 之间
    try:
        optimized_weight = portfolio_optimizer(
            date=context.previous_date,
            securities=g.etf_pool,
            target=MaxSharpeRatio(rf=0.02, count=250), # 假设无风险利率 2%
            constraints=[WeightConstraint(low=0.0, high=1.0)],
            bounds=[Bound(0.0, 0.25)],
            default_port_weight_range=[0.0, 1.0],
            ftol=1e-09,
            return_none_if_fail=True
        )
    except Exception as e:
        log.error("优化器运行异常: %s" % str(e))
        optimized_weight = None
        
    if optimized_weight is None:
        log.warn("组合优化失败,采用等权重被动调仓")
        # 备用方案:等权重分配
        optimized_weight = pd.Series(1.0 / len(g.etf_pool), index=g.etf_pool)
        
    # 3. 执行调仓交易
    total_value = context.portfolio.total_value
    
    # 先卖出超配或需要减仓的标的,释放资金
    for stock in g.etf_pool:
        target_weight = optimized_weight.get(stock, 0.0)
        target_value = total_value * target_weight
        current_value = context.portfolio.positions[stock].value
        if current_value > target_value:
            order_target_value(stock, target_value)
            
    # 再买入增配的标的
    for stock in g.etf_pool:
        target_weight = optimized_weight.get(stock, 0.0)
        target_value = total_value * target_weight
        current_value = context.portfolio.positions[stock].value
        if current_value < target_value:
            order_target_value(stock, target_value)
            log.info("调整标的 %s 权重至 %.2f%%" % (stock, target_weight * 100))

def check_stop_limits(context):
    """每日盘中实时监控个股止盈止损"""
    current_data = get_current_data()
    
    for stock in list(context.portfolio.positions.keys()):
        position = context.portfolio.positions[stock]
        if position.closeable_amount > 0:
            # 计算当前收益率
            current_price = current_data[stock].last_price
            avg_cost = position.avg_cost
            
            if avg_cost > 0:
                return_rate = (current_price - avg_cost) / avg_cost
                
                # 触发止损
                if return_rate <= g.stop_loss_threshold:
                    order_target(stock, 0)
                    log.warn("【止损触发】标的 %s 跌幅达 %.2f%%,执行清仓!" % (stock, return_rate * 100))
                
                # 触发止盈
                elif return_rate >= g.stop_profit_threshold:
                    order_target(stock, 0)
                    log.info("【止盈触发】标的 %s 涨幅达 %.2f%%,锁定利润!" % (stock, return_rate * 100))

三、 策略要点解析

  1. 避免未来函数
    在调用 portfolio_optimizer 时,传入的日期参数为 context.previous_date(前一交易日),确保优化器计算协方差矩阵和历史收益率时仅使用历史数据,完全规避未来函数。

  2. 真实价格模式(use_real_price)
    策略开启了 set_option('use_real_price', True)。在真实价格模式下,分红送股会自动调整持仓数量,且止盈止损监控中获取的 position.avg_costget_current_data() 的最新价均对应真实市场价格,保证了回测与模拟盘的一致性。

  3. 双重调仓逻辑的协同
    月度再平衡负责从宏观资产配置角度优化夏普比率;每日盘中监控则作为微观风控防线,一旦个股触发止损,立即清仓,不干扰下一次月度再平衡的重新计算。