🚀 AI 一键生成 ptrade 策略代码

PTrade 期货主力合约自动换月(移仓换月)逻辑实现

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

问题描述

期货策略如何实现自动换月移仓?

解决方案

在PTrade中实现期货自动换月移仓(Rollover),核心逻辑通常包含以下三个步骤:

  1. 判断换月时机:通常根据“距离交割日的天数”或“主力合约切换(持仓量/成交量变化)”来判断。最简单且常用的是基于时间的换月(例如:在交割日前N天进行移仓)。
  2. 获取下月合约:根据当前合约代码解析出下一个主力合约的代码。
  3. 执行移仓操作:平掉当前合约的持仓,并在新合约上开出同方向、同数量的仓位。

下面是一个完整的PTrade策略示例,实现了基于距离交割日天数的自动换月功能。

策略代码实现

import datetime

def initialize(context):
    # 设定初始主力合约(示例为沪深300股指期货)
    g.current_contract = 'IF2309.CCFX'
    
    # 设定换月阈值:距离交割日剩余多少天时进行移仓
    g.days_before_delivery = 5 
    
    # 设置股票池
    set_universe([g.current_contract])
    
    # 开启日志打印,方便观察
    log.info("策略初始化完成,当前主力合约: %s" % g.current_contract)

def handle_data(context, data):
    # 1. 获取当前时间
    current_date = context.blotter.current_dt.date()
    
    # 2. 获取当前合约的交割日期
    # get_instruments 返回 FutureParams 对象
    instr_info = get_instruments(g.current_contract)
    
    if instr_info is None:
        log.error("无法获取合约信息: %s" % g.current_contract)
        return

    # delivery_date 格式通常为 '20230915' 字符串
    delivery_date_str = instr_info.delivery_date
    delivery_date = datetime.datetime.strptime(delivery_date_str, '%Y%m%d').date()
    
    # 3. 计算距离交割日的天数
    days_delta = (delivery_date - current_date).days
    
    # 4. 检查是否满足换月条件
    # 如果剩余天数小于等于设定的阈值,且当前合约还在股票池中(说明还没换)
    if days_delta <= g.days_before_delivery:
        log.info("当前日期: %s, 合约 %s 交割日: %s, 剩余天数: %d, 触发换月逻辑。" % (
            current_date, g.current_contract, delivery_date, days_delta))
        
        perform_rollover(context)

    # --- 以下为简单的开仓逻辑示例,仅用于演示有持仓的情况 ---
    # 如果当前无持仓,且不是换月日,随机开一手多单用于测试
    pos = get_position(g.current_contract)
    if pos.amount == 0 and days_delta > g.days_before_delivery:
        buy_open(g.current_contract, 1)
        log.info("测试开仓: 买入 %s" % g.current_contract)

def perform_rollover(context):
    """
    执行移仓操作
    """
    # 1. 获取下月合约代码
    next_contract = get_next_month_contract(g.current_contract)
    log.info("准备从 %s 移仓至 %s" % (g.current_contract, next_contract))
    
    # 2. 获取当前合约的持仓信息
    position = get_position(g.current_contract)
    
    # 多头持仓量
    long_amt = position.long_amount
    # 空头持仓量
    short_amt = position.short_amount
    
    # 3. 平掉旧合约持仓
    if long_amt > 0:
        # 平多 (注意:上期所品种可能需要区分平今/平昨,此处使用默认False优先平昨)
        sell_close(g.current_contract, long_amt, close_today=False)
        log.info("平旧合约多单: %s, 数量: %s" % (g.current_contract, long_amt))
        
    if short_amt > 0:
        # 平空
        buy_close(g.current_contract, short_amt, close_today=False)
        log.info("平旧合约空单: %s, 数量: %s" % (g.current_contract, short_amt))
        
    # 4. 在新合约开出同等仓位
    if long_amt > 0:
        buy_open(next_contract, long_amt)
        log.info("开新合约多单: %s, 数量: %s" % (next_contract, long_amt))
        
    if short_amt > 0:
        sell_open(next_contract, short_amt)
        log.info("开新合约空单: %s, 数量: %s" % (next_contract, short_amt))
        
    # 5. 更新全局变量和股票池
    g.current_contract = next_contract
    set_universe([g.current_contract])
    log.info("换月完成,当前主力合约更新为: %s" % g.current_contract)

def get_next_month_contract(code):
    """
    根据当前合约代码计算下月合约代码
    假设代码格式为 'IF2309.CCFX' (品种+年份后2位+月份.后缀)
    """
    # 分离代码和后缀,例如 'IF2309' 和 'CCFX'
    symbol_part, suffix = code.split('.')
    
    # 提取品种字符(非数字部分)和日期数字
    import re
    match = re.match(r"([A-Z]+)(\d+)", symbol_part)
    if not match:
        log.error("合约代码格式解析失败: %s" % code)
        return code
        
    product_code = match.group(1) # 例如 IF
    date_str = match.group(2)     # 例如 2309
    
    year = int(date_str[:2])
    month = int(date_str[2:])
    
    # 计算下个月
    next_month = month + 1
    next_year = year
    
    if next_month > 12:
        next_month = 1
        next_year = year + 1
        
    # 格式化新的日期字符串,保持2位年份和2位月份
    new_date_str = "%02d%02d" % (next_year, next_month)
    
    # 拼接新代码
    new_code = product_code + new_date_str + "." + suffix
    return new_code

代码逻辑详解

  1. get_instruments(contract):

    • 这是PTrade获取合约基础信息的关键API。
    • 我们从中提取 delivery_date(交割日),这是判断是否需要换月的基准。
  2. 换月判断逻辑:

    • handle_data 中,每天检查 (交割日 - 当前日期) 的天数。
    • 如果天数小于 g.days_before_delivery(例如5天),则触发移仓。
  3. get_next_month_contract 函数:

    • 这是一个自定义的辅助函数。它利用正则表达式解析当前的合约代码(如 IF2309),提取年份和月份。
    • 通过简单的数学运算计算下一个月,并处理跨年逻辑(12月变1月,年份+1)。
    • 最后重新拼接成PTrade标准代码格式。
  4. perform_rollover 函数:

    • 平旧仓:使用 get_position 获取当前合约的多空持仓量,然后调用 sell_close(平多)和 buy_close(平空)。
    • 开新仓:使用 buy_open(开多)和 sell_open(开空)在新合约上建立相同的头寸。
    • 更新全局变量:非常重要的一步是更新 g.current_contract 并调用 set_universe,确保后续的交易逻辑(如止盈止损)是针对新合约执行的。

注意事项

  1. 上期所平今仓限制
    • 如果你交易的是上期所(XSGE)的品种(如螺纹钢 RB),在平仓时要注意 close_today 参数。PTrade中 close_today=True 仅平今仓,False 优先平昨。通常换月涉及的是老仓,使用 False 即可。
  2. 主力合约规则
    • 上述代码假设下个月就是主力合约(适用于股指期货)。
    • 对于商品期货(如螺纹钢、铁矿石),主力合约通常是1月、5月、9月。如果是这种情况,你需要修改 get_next_month_contract 的逻辑,使其寻找下一个主力月份(例如当前是01,下一个是05,而不是02)。
  3. 滑点与流动性
    • 换月操作通常涉及两笔交易(一平一开),可能会产生滑点。建议在流动性较好的时间段(如开盘后半小时)执行,避免在集合竞价或尾盘剧烈波动时换月。

Q&A

Q: 如何将换月逻辑改为“基于持仓量”判断?
A: 你可以使用 get_snapshotget_history 获取当前合约和潜在下月合约的 amount (持仓量)。在 handle_data 中对比两者的持仓量,当远月合约持仓量超过近月合约时,触发 perform_rollover

Q: 为什么代码中不使用 import os
A: PTrade 策略环境出于安全考虑,禁止使用 ossys 等系统级库。所有文件读写和路径获取必须使用 PTrade 提供的专用 API(如 get_research_path)。

Q: 移仓时如果资金不足怎么办?
A: 这是一个常见风险。通常先平仓会释放保证金,然后再开新仓是可行的。但如果行情波动剧烈导致可用资金紧缺,或者新合约保证金比例更高,可能会开仓失败。建议在 perform_rollover 中加入 context.portfolio.cash 的检查逻辑。