🚀 AI 一键生成 joinquant 策略代码

如何在 JoinQuant 中构建一个简单的指数增强策略?

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

问题描述

请帮我写一个指数增强策略,需要用到指数的成分股权重

解决方案

这是一个基于 沪深300指数 的增强策略示例。

策略逻辑说明

  1. 基准选择:以沪深300指数(000300.XSHG)为基准。
  2. 获取权重:使用 get_index_weights 获取指数成分股及其原始权重。
  3. 增强因子:使用 市盈率(PE) 作为增强因子。
    • 逻辑:价值投资思路,认为低PE股票未来表现更好。
  4. 增强方式(负向剔除法)
    • 按PE从小到大排序。
    • 剔除 PE最高的后20%的股票(认为是高估值或业绩差的股票)。
    • 保留剩余80%的股票,并按其在指数中的原始权重比例进行归一化,重新分配仓位。
  5. 调仓频率:按月调仓。

策略代码

# -*- coding: utf-8 -*-
from jqdata import *
import pandas as pd

def initialize(context):
    """
    初始化函数
    """
    # 1. 设定基准:沪深300
    g.index_id = '000300.XSHG'
    set_benchmark(g.index_id)
    
    # 2. 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 3. 设置手续费(模拟股票交易佣金)
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 4. 设定调仓周期:每月第一个交易日运行
    run_monthly(rebalance, monthday=1, time='09:30')
    
    # 打印日志
    log.info("策略初始化完成,基准:{}".format(g.index_id))

def rebalance(context):
    """
    调仓核心逻辑
    """
    log.info("开始进行调仓...")
    
    # --- 第一步:获取指数成分股及原始权重 ---
    # 注意:使用上一交易日日期,避免未来函数
    date = context.previous_date
    
    # get_index_weights 返回 DataFrame,索引为股票代码,包含 'weight', 'display_name', 'date'
    index_weights = get_index_weights(g.index_id, date)
    
    if index_weights is None or index_weights.empty:
        log.warn("未获取到指数权重数据,跳过本次调仓")
        return
        
    # 提取股票列表
    stock_list = list(index_weights.index)
    
    # --- 第二步:获取增强因子数据(此处以PE为例) ---
    q = query(
        valuation.code,
        valuation.pe_ratio
    ).filter(
        valuation.code.in_(stock_list),
        valuation.pe_ratio > 0  # 过滤掉亏损股票(PE为负)
    )
    
    df_fund = get_fundamentals(q, date=date)
    
    if df_fund.empty:
        log.warn("未获取到财务数据")
        return
        
    # 将财务数据索引设为股票代码,方便合并
    df_fund.set_index('code', inplace=True)
    
    # --- 第三步:数据合并与增强逻辑 ---
    # 合并 权重数据 和 因子数据
    # inner join 确保股票既在指数中,又有财务数据
    df_merged = pd.concat([index_weights['weight'], df_fund['pe_ratio']], axis=1, join='inner')
    
    # 增强逻辑:剔除PE最高的20%的股票(负向剔除)
    # 1. 按PE升序排列 (小的在前)
    df_merged = df_merged.sort_values(by='pe_ratio', ascending=True)
    
    # 2. 计算保留的数量 (保留前80%)
    keep_count = int(len(df_merged) * 0.8)
    df_target = df_merged.iloc[:keep_count].copy()
    
    # 3. 权重归一化
    # 因为剔除了部分股票,剩余股票的权重加起来小于100%,需要重新调整让它们加起来等于100%
    total_weight = df_target['weight'].sum()
    df_target['final_weight'] = df_target['weight'] / total_weight
    
    log.info("选股完成,原始成分股数量: {}, 剔除后数量: {}".format(len(index_weights), len(df_target)))
    
    # --- 第四步:执行交易 ---
    do_trade(context, df_target)

def do_trade(context, df_target):
    """
    执行下单交易
    """
    # 获取当前账户总资产
    total_value = context.portfolio.total_value
    
    # 1. 卖出不在目标持仓中的股票
    current_positions = context.portfolio.positions
    for stock in current_positions:
        if stock not in df_target.index:
            order_target_value(stock, 0)
    
    # 2. 买入/调整目标股票
    # 遍历目标股票,按计算出的权重下单
    # 注意:为了防止资金不足导致无法买入最后几只,通常可以预留少量现金或按比例下单
    
    for stock in df_target.index:
        target_weight = df_target.loc[stock, 'final_weight']
        target_value = total_value * target_weight
        
        # 下单调整到目标金额
        order_target_value(stock, target_value)

代码关键点解析

  1. get_index_weights(index_id, date):

    • 这是本策略的核心API。它返回指定日期指数成分股的权重信息。
    • 返回的 DataFrame 中,index 是股票代码,weight 列是权重(通常是百分比形式,如 0.5 代表 0.5%)。
    • 注意:我们在计算最终权重时进行了归一化(df_target['weight'] / total_weight),所以无论原始权重是百分比还是小数都不影响最终比例。
  2. 增强逻辑 (Negative Screening):

    • 代码中使用了 pd.concat 将指数权重和 PE 数据对齐。
    • 通过 sort_values 和切片 iloc[:keep_count] 实现了剔除高估值股票的操作。
    • 这是一种最基础且稳健的增强方法:保留了指数的大部分特征(Beta),同时试图通过剔除垃圾股来获取超额收益(Alpha)。
  3. 权重归一化:

    • 剔除股票后,剩余股票的原始权重之和肯定小于 1。
    • 必须执行 df_target['weight'] / total_weight,将资金分配填满,否则会导致仓位长期不满,跑输基准。
  4. 交易执行:

    • 使用 order_target_value 根据总资产和目标权重计算每只股票应持有的金额,自动进行买卖调整。

常见问题 Q&A

Q: 为什么要使用 context.previous_date 获取权重?
A: 为了避免未来函数。在回测中,如果在开盘前(09:30)获取“今天”的指数权重,这在现实中是不可能的(因为今天的权重可能受今日行情影响)。使用前一交易日的数据更符合实盘逻辑。

Q: 这个策略可以换成其他指数吗?
A: 可以。只需将 g.index_id 修改为其他指数代码,例如中证500(000905.XSHG)或上证50(000016.XSHG)。

Q: 如何更换增强因子?
A: 修改 get_fundamentals 中的查询语句。例如,如果你想用 ROE(净资产收益率) 增强:

  1. 查询 indicator.roe
  2. 排序时改为 ascending=False(因为ROE越高越好,我们要保留头部股票)。