🚀 AI 一键生成 joinquant 策略代码

JoinQuant 实战:如何构建多品种商品期货动量轮动策略?

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

问题描述

请帮我构建一个多品种商品期货的动量轮动策略

解决方案

这是一个基于聚宽(JoinQuant)平台的多品种商品期货动量轮动策略

策略逻辑说明

  1. 标的池:选取市场上流动性较好的商品期货品种(如螺纹钢、铁矿石、铜、化工品等)。
  2. 主力合约映射:自动获取各品种当前的主力合约。
  3. 动量因子:使用过去 N 天(例如20天)的收益率作为动量指标。
  4. 轮动规则
    • 计算所有备选品种的动量。
    • 做多:选择动量最大且为正的 K 个品种(例如前3名)。
    • 风控/过滤:如果排名前列的品种动量为负,则不开仓(宁缺毋滥)。
  5. 调仓频率:按周调仓(每周第一个交易日)。
  6. 资金管理:等权重分配资金。

策略代码

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

def initialize(context):
    """
    初始化函数,设定基准、手续费、滑点、标的池等
    """
    # 1. 设定基准(这里随意设定一个,期货策略主要看绝对收益)
    set_benchmark('000300.XSHG')
    
    # 2. 开启真实价格(动态复权)
    set_option('use_real_price', True)
    
    # 3. 设定账户类型为期货账户
    # 初始资金设为 1,000,000 元
    set_subportfolios([SubPortfolioConfig(cash=1000000, type='futures')])
    
    # 4. 设定手续费(这里设置得相对保守,模拟真实环境)
    # 开平仓万分之2
    set_order_cost(OrderCost(open_commission=0.0002, close_commission=0.0002, close_today_commission=0.0002), type='futures')
    
    # 5. 设定滑点
    set_slippage(PriceRelatedSlippage(0.002), type='futures')
    
    # 6. 定义全局变量
    # 待选品种列表(涵盖黑色、有色、化工、农产品等主要板块)
    # 注意:这里使用的是品种代码,不是具体合约代码
    g.instruments = [
        'RB', # 螺纹钢
        'HC', # 热卷
        'I',  # 铁矿石
        'J',  # 焦炭
        'CU', # 沪铜
        'AL', # 沪铝
        'ZN', # 沪锌
        'AU', # 黄金
        'TA', # PTA
        'MA', # 甲醇
        'PP', # 聚丙烯
        'L',  # 塑料
        'RU', # 橡胶
        'M',  # 豆粕
        'Y',  # 豆油
        'SR', # 白糖
    ]
    
    # 动量回看窗口(天)
    g.lookback_days = 20
    
    # 持仓数量(持有动量最强的前N个)
    g.top_k = 3
    
    # 7. 设定定时运行:每周第一个交易日开盘后运行
    run_weekly(weekly_adjust, weekday=1, time='09:30')


def weekly_adjust(context):
    """
    周度调仓逻辑
    """
    # 获取当前日期
    current_date = context.current_dt.date()
    
    # 1. 获取主力合约并计算动量
    momentum_scores = []
    
    for code in g.instruments:
        # 获取该品种当前的主力合约代码
        dom_contract = get_dominant_future(code)
        
        if dom_contract is None:
            continue
            
        # 获取历史价格数据
        # 取 lookback_days + 1 天的数据,以便计算收益率
        # fields=['close'] 获取收盘价
        hist_data = attribute_history(dom_contract, g.lookback_days + 1, '1d', ['close'])
        
        # 如果数据长度不足,跳过
        if len(hist_data) < g.lookback_days + 1:
            continue
            
        # 计算动量:(当前价格 / N天前价格) - 1
        p_now = hist_data['close'][-1]
        p_prev = hist_data['close'][0]
        
        if p_prev == 0:
            continue
            
        momentum = (p_now / p_prev) - 1.0
        
        # 记录数据:(合约代码, 动量值)
        momentum_scores.append((dom_contract, momentum))
    
    # 2. 筛选目标合约
    # 按动量从大到小排序
    momentum_scores.sort(key=lambda x: x[1], reverse=True)
    
    # 筛选出动量为正的前 top_k 个合约
    # 逻辑:只做多头,且要求动量大于0(趋势向上)
    target_list = []
    for contract, score in momentum_scores:
        if score > 0:
            target_list.append(contract)
            if len(target_list) >= g.top_k:
                break
    
    log.info("当前日期: %s, 目标持仓: %s" % (current_date, target_list))
    
    # 3. 执行交易
    
    # 3.1 平仓逻辑
    # 遍历当前持有的所有多单
    for contract in list(context.portfolio.long_positions.keys()):
        # 如果当前持仓不在目标列表中,或者主力合约发生了切换(旧合约不在目标列表),则平仓
        if contract not in target_list:
            order_target_value(contract, 0, side='long')
            log.info("平仓: %s" % contract)
            
    # 3.2 开仓/调仓逻辑
    if len(target_list) > 0:
        # 计算每个标的分配的资金
        # 使用总权益的 80% 进行投资,保留 20% 作为保证金缓冲,防止波动爆仓
        total_value = context.portfolio.total_value
        cash_per_asset = (total_value * 0.8) / len(target_list)
        
        for contract in target_list:
            # 调整仓位到目标价值
            # order_target_value 会自动处理买入或卖出以达到目标金额
            # side='long' 表示做多
            order_target_value(contract, cash_per_asset, side='long')
            log.info("调仓: %s, 目标市值: %.2f" % (contract, cash_per_asset))

def handle_data(context, data):
    """
    盘中运行函数(本策略为周频,此处留空或用于风控)
    """
    pass

策略关键点解析

  1. get_dominant_future(code):

    • 这是聚宽特有的API,用于获取某个品种(如 'RB')在当前时间点的主力合约(如 'RB2105.XSGE')。这是期货策略回测中最关键的一步,因为它解决了合约换月的问题。
  2. 动量计算:

    • 代码中使用 (当前收盘价 / N天前收盘价) - 1 来衡量动量。
    • g.lookback_days = 20 代表使用一个月的趋势作为信号。你可以根据需要修改为 60(季度趋势)或 5(周趋势)。
  3. 资金管理:

    • total_value * 0.8: 期货自带杠杆,满仓交易风险极大。代码中预留了 20% 的资金作为保证金缓冲,避免因盘中波动导致强平。
    • order_target_value: 这个函数非常方便,它会自动计算需要买卖的手数,使该标的的持仓市值等于目标金额。
  4. 主力合约切换:

    • 策略逻辑中隐含了主力合约切换的处理。每周调仓时,get_dominant_future 会获取最新的主力合约。如果上周持有的合约不再是主力(或者动量掉出前三),它会在“平仓逻辑”中被卖出,并在“开仓逻辑”中买入新的主力合约。

常见问题 Q&A

Q: 为什么只做多(Long Only)?
A: 商品期货虽然可以做空,但对于初学者或简单的动量策略,做多通常比做空容错率更高。商品价格往往具有“长期通胀、短期波动”的特性,且做空面临无限亏损的理论风险。如果你想加入做空逻辑,可以在筛选时选取动量为负且排名倒数的品种进行 side='short' 的开仓。

Q: 如何增加更多的品种?
A: 只需在 g.instruments 列表中添加对应的品种代码即可(例如添加 'AG' 白银, 'CF' 棉花等)。

Q: 回测时发现收益很高但回撤也很大,怎么办?
A: 动量策略本质上是趋势跟踪,在震荡市中会频繁止损(来回打脸)。优化方法包括:

  1. 增加波动率调整:根据品种的波动率分配仓位(波动率大的少买,小的多买)。
  2. 加入趋势过滤器:例如只有当价格在 60日均线之上时才做多。
  3. 分散化:加入更多相关性低的品种。

Q: 为什么使用 run_weekly 而不是每天判断?
A: 动量是一个中长期信号。如果每天调仓,不仅手续费高昂,而且容易被日内的市场噪音干扰。周频调仓在捕捉趋势和控制成本之间取得了较好的平衡。