🚀 AI 一键生成 ptrade 策略代码

PTrade 实盘必读:如何处理委托失败、废单等交易异常情况?

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

问题描述

在实盘交易中,如果我的下单委托失败或变成了废单,应该如何在代码里处理这种情况?

解决方案

在PTrade实盘交易中,处理委托失败或废单最及时、有效的方法是利用 on_order_response(委托主推回调) 函数。

当订单状态发生变化(如变为“废单”)时,交易柜台会立即推送消息给策略,触发该函数。相比于在 handle_data 中轮询查询订单状态,这种方式延迟更低,能更快速地做出补救措施(如重发订单或停止交易)。

核心处理逻辑

  1. 监听回调:在 on_order_response 中遍历推送的订单列表。
  2. 判断状态:检查订单的 status 字段。根据PTrade数据字典,状态 '9' 代表废单
  3. 获取原因:读取 error_info 字段,获取具体的失败原因(如“资金不足”、“超过涨跌停价格”等)。
  4. 执行对策:根据错误原因记录日志、发送报警或尝试重新下单(需注意防止死循环)。

策略代码示例

以下是一个完整的策略示例,展示了如何捕获废单并进行简单的重试逻辑(为了防止无限重试,增加了重试次数限制)。

def initialize(context):
    # 设置股票池
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 定义一个全局字典,用于记录订单的重试次数,防止无限重试
    # key: 股票代码, value: 重试次数
    g.retry_counts = {} 
    # 最大重试次数
    g.max_retries = 3

def handle_data(context, data):
    # 示例:每个交易日尝试买入一次
    # 实际策略中这里会有具体的开仓逻辑
    if g.security not in context.portfolio.positions:
        # 重置重试计数
        g.retry_counts[g.security] = 0
        
        # 下单买入,这里故意使用一个可能导致废单的价格(例如跌停价买入,如果当前价很高可能无法成交或被拒)
        # 或者在实盘中,如果资金不足,这里也会触发废单
        log.info("尝试发起买入委托: %s" % g.security)
        order(g.security, 100)

def on_order_response(context, order_list):
    """
    委托主推回调函数
    当委托状态发生变化时(如变为已报、部成、已成、废单等),系统自动调用此函数
    """
    for order_info in order_list:
        # 获取订单状态
        status = order_info['status']
        # 获取股票代码
        stock_code = order_info['stock_code']
        # 获取委托编号
        entrust_no = order_info['entrust_no']
        
        # 状态 '9' 代表废单
        if status == '9':
            error_msg = order_info['error_info']
            log.error("【警告】出现废单!股票: %s, 委托编号: %s, 原因: %s" % (stock_code, entrust_no, error_msg))
            
            # --- 废单处理逻辑 ---
            
            # 1. 简单的重试逻辑 (仅作示例,实盘请谨慎使用)
            # 检查是否超过最大重试次数
            current_retry = g.retry_counts.get(stock_code, 0)
            
            if current_retry < g.max_retries:
                g.retry_counts[stock_code] = current_retry + 1
                log.info("正在尝试第 %d 次重试下单: %s" % (g.retry_counts[stock_code], stock_code))
                
                # 重新下单,这里可以根据 error_msg 调整策略
                # 例如:如果是价格超出笼子,可以尝试用最新价下单
                # order(stock_code, 100) # 慎用:直接重试可能再次废单
                
                # 示例:改用市价单尝试(需确保该标的支持市价单)
                # order_market(stock_code, 100, 1) 
            else:
                log.warning("股票 %s 重试次数已达上限,停止下单。" % stock_code)
                
            # 2. 发送通知 (如果券商支持邮件或企业微信)
            # send_email(context, "废单报警", "股票 %s 下单失败: %s" % (stock_code, error_msg))

        elif status == '8':
            log.info("订单已完全成交: %s" % stock_code)

关键点解析

  1. status == '9': 这是判断废单的唯一标准。虽然 status == '6' (已撤) 也意味着订单未成交,但那是用户主动或系统盘后撤单,而 '9' 通常是因为参数错误、验资验券失败或合规拦截。
  2. error_info: 务必打印这个字段。常见的废单原因包括:
    • 可用资金/持仓不足。
    • 委托价格超过涨跌停限制或价格笼子限制。
    • 非交易时间报单。
    • 证券代码错误或处于停牌状态。
    • 合规风控限制(如黑名单)。
  3. 防止死循环: 在 on_order_response 中重新下单是非常危险的操作。如果废单是因为“资金不足”,你立即重试会再次废单,导致瞬间产生大量废单记录,甚至可能导致账户被锁或程序崩溃。必须设置重试计数器或时间间隔。
  4. order_list 数据结构: 回调函数中的 order_list 是一个字典列表,包含 entrust_no, error_info, status, stock_code 等关键字段。

替代方案:轮询法

如果你不想使用回调函数,也可以在 handle_datarun_interval 中轮询检查。这种方法实时性稍差,但逻辑更直观。

def handle_data(context, data):
    # 获取当日所有订单
    all_orders = get_all_orders()
    
    for order_obj in all_orders:
        if order_obj['status'] == '9':
            # 避免重复处理,可以记录已处理的订单号
            if order_obj['entrust_no'] not in g.processed_orders:
                log.error("发现废单: %s, 原因: %s" % (order_obj['symbol'], order_obj.get('error_info', '未知')))
                g.processed_orders.append(order_obj['entrust_no'])
                # 执行处理逻辑...

Q&A

Q1: 为什么我的 on_order_response 没有被触发?
A1: 请检查是否在 initialize 中正确设置了相关参数。通常策略内发出的委托会自动触发,但如果是外部(如手机端)下单产生的废单,需要在 initialize 中设置 set_parameters(receive_other_response="1") 才能接收到推送。

Q2: 废单会占用资金吗?
A2: 废单意味着委托未被交易所或柜台接受,因此不会占用资金或持仓。但是,如果订单状态是“已报”(status '2'),此时资金是冻结的,直到成交或撤单。

Q3: 如何区分“废单”和“撤单”?
A3: 查看 status 字段。'9' 是废单(通常是系统拒绝),'6' 是已撤(通常是策略或人工主动撤销)。处理逻辑通常不同:废单需要检查参数或风控,撤单通常是策略逻辑的一部分(如超时撤单)。