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

如何在JoinQuant平台构建期货近远期差价(期限结构)因子?

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

问题描述

期货近远期差价因子具体如何构建?

最近学期货策略,看到一个因子,用近月合约周收益率和远月收益率直接做差,我想获取近5年所有的合约的因子值,但是所有合约的时间间隔都不同,所以具体应该如何选什么样的合约作为远月,什么作为近月?

解决方案

构建期货的近远期差价因子(通常被称为期限结构因子展期收益率因子)是商品期货量化策略中非常经典且有效的方法。这个因子的核心逻辑在于捕捉现货溢价(Backwardation)或期货溢价(Contango)的状态。

针对你提到的“合约时间间隔不同,如何选择近月和远月合约”的问题,在实际量化投研中,通常有以下几种主流的处理方案:

一、 如何选择近月与远月合约?

由于不同品种的活跃合约月份分布不同(例如螺纹钢通常是1、5、10月活跃,而农产品可能是1、5、9月),直接固定月份做差是不可行的。动态选择合约的常见方案有:

方案1:主力合约 vs 次主力合约(推荐)

  • 近月合约:选择当前的主力合约(持仓量最大的合约)。因为主力合约流动性最好,价格最能反映当前市场真实预期。
  • 远月合约:选择交割月份在主力合约之后的次主力合约(或者直接取按交割月排序在主力合约之后的下一个活跃合约)。
  • 优点:完美避开了非活跃合约流动性差、价格失真的问题。

方案2:首月合约 vs 次月合约(按交割月严格排序)

  • 近月合约:在当前所有可交易合约中,交割月份最近的合约。
  • 远月合约:交割月份排在第二的合约。
  • 缺点:临近交割月时,首月合约流动性会急剧萎缩,容易产生异常波动。

二、 JoinQuant 平台实现逻辑与源码

在 JoinQuant 平台上,我们可以利用 get_dominant_future 获取主力合约,利用 get_future_contracts 获取所有合约列表,从而动态定位近月和远月合约。

以下是基于**“主力合约(近月)与主力后第一个合约(远月)”**构建周收益率差价因子的完整 Python 代码示例,你可以将其放在 JoinQuant 的研究环境中运行:

from jqdata import *
import pandas as pd
import numpy as np
import datetime

def get_term_structure_factor(underlying_symbol, date):
    """
    计算指定日期某期货品种的近远期差价因子
    :param underlying_symbol: 期货品种代码,如 'RB' (螺纹钢)
    :param date: 计算日期
    :return: 因子值 (近月周收益率 - 远月周收益率)
    """
    # 1. 获取当天该品种的所有可交易合约
    contracts = get_future_contracts(underlying_symbol, date)
    if len(contracts) < 2:
        return np.nan
        
    # 2. 获取当天的主力合约作为“近月合约”
    dominant = get_dominant_future(underlying_symbol, date)
    if not dominant:
        return np.nan
        
    # 3. 对合约按交割月份进行排序
    # JoinQuant的合约代码格式如 'RB1905.XSGE',直接字符串排序即可按时间先后排列
    contracts = sorted(contracts)
    
    try:
        dom_idx = contracts.index(dominant)
        # 4. 寻找远月合约:取主力合约之后的下一个合约
        if dom_idx + 1 < len(contracts):
            far_contract = contracts[dom_idx + 1]
        else:
            return np.nan # 主力已经是最后一个合约,无法计算
    except ValueError:
        return np.nan
        
    # 5. 获取这两个合约过去一周(5个交易日)的收盘价
    # count=6 是为了获取 5天前的价格 和 当天的价格
    prices = get_price([dominant, far_contract], end_date=date, count=6, fields=['close'], panel=False)
    
    if prices.empty:
        return np.nan
        
    try:
        # 提取近月和远月的价格序列
        dom_prices = prices[prices['code'] == dominant]['close'].values
        far_prices = prices[prices['code'] == far_contract]['close'].values
        
        # 确保数据长度足够
        if len(dom_prices) < 6 or len(far_prices) < 6:
            return np.nan
            
        # 6. 计算周收益率 (当天价格 / 5天前价格 - 1)
        dom_return = dom_prices[-1] / dom_prices[0] - 1
        far_return = far_prices[-1] / far_prices[0] - 1
        
        # 7. 计算因子值:近月收益率 - 远月收益率
        factor_value = dom_return - far_return
        return factor_value
        
    except Exception as e:
        return np.nan

# ================= 测试代码 =================
# 获取螺纹钢(RB)在过去一段时间的因子值
test_dates = get_trade_days(start_date='2023-01-01', end_date='2023-01-31')
factor_results = []

for d in test_dates:
    val = get_term_structure_factor('RB', d)
    factor_results.append({'date': d, 'factor_value': val})

df_factor = pd.DataFrame(factor_results)
print(df_factor.head())

三、 细节优化与注意事项

  1. 年化处理(可选):由于不同品种的主力与次主力之间的时间间隔不同(有的是1个月,有的是4个月),更严谨的做法是将差价年化。即:(近月价格/远月价格 - 1) * (365 / 两个合约交割日期的天数差)。这被称为年化展期收益率,在横截面多品种比较时更为科学。
  2. 换月跳空问题:当主力合约发生切换时,get_dominant_future 返回的合约会突变。如果你计算的是“过去5天的收益率”,在换月当天,你获取的“当前主力合约”在5天前可能流动性极差,导致5天前的价格失真。解决方案:在计算收益率时,确保使用的价格序列在过去5天内都有充足的成交量,或者在换月期间平滑过渡。
  3. 数据对齐:使用 get_price 获取多个合约数据时,注意 panel=False 返回的是 DataFrame,需要按 code 过滤出各自的价格序列,确保近远月价格在时间上是一一对应的。