🚀 AI 一键生成 joinquant 策略代码

如何利用宏观经济数据(PMI)在 JoinQuant 中构建择时策略?

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

问题描述

如何编写一个策略,根据每月公布的 PMI 数据来决定是买入还是卖出指数基金?

解决方案

这是一个基于宏观经济数据(PMI)进行择时的策略设计。

策略逻辑

  1. 数据源:使用国家统计局公布的制造业采购经理指数 (PMI)
  2. 核心逻辑:PMI 是衡量制造业荣枯的重要指标。
    • PMI > 50:代表经济处于扩张期,股市通常表现较好 -> 全仓买入/持有
    • PMI < 50:代表经济处于收缩期,股市风险较大 -> 清仓/空仓
  3. 交易标的:沪深300 ETF(510300.XSHG)作为指数基金代表。
  4. 调仓频率:按月调仓。通常官方 PMI 数据在每月的最后一天或次月1号公布,因此我们在每月的第一个交易日运行策略。

代码实现

# -*- coding: utf-8 -*-
from jqdata import *

def initialize(context):
    """
    初始化函数
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 设置交易标的:沪深300ETF
    g.security = '510300.XSHG'
    
    # 设置手续费:股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 设置滑点
    set_slippage(PriceRelatedSlippage(0.002), type='stock')
    
    # 设定按月运行,在每月第一个交易日的开盘时运行
    run_monthly(trade_func, 1, time='open')

def trade_func(context):
    """
    月度调仓函数
    """
    # 获取当前的日期
    current_date = context.current_dt.date()
    
    # 查询宏观数据:制造业PMI (MAC_MANUFACTURING_PMI)
    # 按照统计月份降序排列,取最近的一条数据
    # 注意:macro.run_query 会自动根据回测时间 context.current_dt 过滤掉未来数据
    q = query(
        macro.MAC_MANUFACTURING_PMI
    ).order_by(
        macro.MAC_MANUFACTURING_PMI.stat_month.desc()
    ).limit(1)
    
    df = macro.run_query(q)
    
    # 如果没有获取到数据,则不进行操作
    if df.empty:
        log.warn("未获取到PMI数据,跳过本月操作")
        return
    
    # 获取PMI数值
    current_pmi = df['pmi'][0]
    stat_month = df['stat_month'][0]
    
    log.info("当前时间: %s, 获取到的最新PMI数据月份: %s, PMI数值: %s" % (current_date, stat_month, current_pmi))
    
    # 获取当前持仓
    position = context.portfolio.positions[g.security]
    
    # 策略逻辑:荣枯线 50
    if current_pmi > 50:
        # 扩张区间,买入
        if position.total_amount == 0:
            log.info("PMI > 50 (扩张区间),全仓买入")
            order_target_value(g.security, context.portfolio.available_cash)
        else:
            log.info("PMI > 50 (扩张区间),继续持有")
            
    elif current_pmi <= 50:
        # 收缩区间,卖出
        if position.total_amount > 0:
            log.info("PMI <= 50 (收缩区间),清仓卖出")
            order_target(g.security, 0)
        else:
            log.info("PMI <= 50 (收缩区间),继续空仓")

关键点说明

  1. 宏观数据获取 (macro.run_query)

    • 使用了 macro.MAC_MANUFACTURING_PMI 表。
    • 聚宽的回测引擎会自动处理“未来函数”。当你调用 macro.run_query 时,系统只会返回在 context.current_dt 之前已经公布的数据。
    • 官方 PMI 通常在当月最后一天发布,所以每月1号调仓时通常能获取到上个月的数据。
  2. 交易逻辑

    • 使用了最经典的荣枯线(50)作为阈值。
    • 使用了 order_target_value 进行全仓买入,使用 order_target 进行清仓。
  3. 标的选择

    • 代码中使用了 510300.XSHG (沪深300ETF) 作为交易对象,这比直接交易指数(如 000300.XSHG,不可交易)更符合实盘逻辑。

常见问题 (Q&A)

Q: 为什么回测时有时候会发现 PMI 数据获取不到?
A: 宏观数据的发布有时间滞后。虽然 PMI 发布较快,但如果回测时间设置得过早(例如2005年以前),可能数据库中缺乏当时的数据。代码中加入了 if df.empty 判断来防止程序报错。

Q: 可以使用财新 PMI 吗?
A: 可以。财新 PMI 通常更能反映中小企业的状况。在聚宽数据库中,可以通过查询 macro.MAC_CAIXIN_PMI 表来获取财新 PMI 数据,逻辑与上述代码完全一致,只需更改查询的表名即可。

Q: 如何避免频繁买卖?
A: 单纯以 50 为界限可能会在 50 附近频繁震荡导致磨损。可以优化策略,例如:

  • 加入观察期:连续 2 个月 PMI > 50 才买入。
  • 加入趋势判断:PMI > 50 且 PMI > 上月 PMI 才买入。
  • 设置缓冲区:PMI > 50.5 买入,PMI < 49.5 卖出。