🚀 AI 一键生成 ptrade 策略代码

PTrade 交易失败?一文读懂“废单”产生原因及处理方法

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

问题描述

我下的单子变成了“废单”,这是为什么?应该怎么在代码里处理这种情况?

解决方案

在PTrade中,委托单变成“废单”(状态码为 '9'),通常意味着该委托未能通过柜台的风控检查或交易所的撮合规则,直接被拒绝执行。

以下是导致废单的常见原因及相应的代码处理方案。

一、 废单的常见原因

  1. 价格超出涨跌停限制
    • 委托买入价高于涨停价,或卖出价低于跌停价。
    • 科创板/创业板价格笼子:委托价格超过了基准价格(如卖一价)的 $\pm 2%$(具体规则视板块而定),导致直接废单。
  2. 标的状态异常
    • 股票处于停牌(HALT)、退市(DELISTED)或未上市状态。
  3. 资金或持仓不足
    • 买入时可用资金不足(需考虑手续费)。
    • 卖出时可用持仓不足(注意T+1规则,今日买入的股票今日不可卖出)。
  4. 委托数量错误
    • 股票买入必须是100的整数倍(零股卖出除外)。
    • 科创板/创业板单笔申报数量限制(如最低200股)。
  5. 非交易时间报单
    • 在非交易时间段(如午休或收盘后)发送了不支持挂单的委托类型。

二、 代码处理方案

处理废单的最佳策略是 “事前预防”“事后补救” 相结合。

1. 事前预防:下单前检查

在调用 order 函数前,检查股票状态、涨跌停价格和资金情况。

2. 事后补救:监控委托状态

通过 get_orders 轮询或 on_order_response 回调函数捕捉废单,并打印错误信息或尝试重新下单。

三、 完整策略代码示例

以下代码展示了如何编写一个健壮的下单逻辑,包含下单前检查(防废单)和废单监控

def initialize(context):
    # 设置股票池
    g.security = '600570.SS'
    set_universe(g.security)
    # 设置交易参数,开启主推以便快速接收废单信息
    set_parameters(receive_other_response="1", receive_cancel_response="1")

def handle_data(context, data):
    stock = g.security
    
    # --- 1. 事前预防:下单前检查 ---
    
    # 获取快照数据(包含涨跌停价、停牌状态)
    # 注意:回测模式下 get_snapshot 可能受限,实盘/模拟盘推荐使用
    snapshot = get_snapshot(stock)
    
    if snapshot:
        stock_info = snapshot.get(stock, {})
        
        # A. 检查是否停牌
        # 交易状态: TRADE交易, HALT暂停, SUSP停盘
        trade_status = stock_info.get('trade_status', '')
        if trade_status != 'TRADE':
            log.info("股票 %s 处于非交易状态 (%s),跳过下单" % (stock, trade_status))
            return

        # B. 检查涨跌停价格
        current_price = stock_info.get('last_px', 0)
        up_limit = stock_info.get('up_px', 0)
        down_limit = stock_info.get('down_px', 0)
        
        # 假设我们要以最新价买入
        order_price = current_price
        
        # 防止价格超过涨停价(导致废单)
        if order_price >= up_limit:
            log.info("价格触及涨停,调整为涨停价下单或放弃")
            order_price = up_limit
        
        # C. 检查资金是否足够 (简单估算)
        cash = context.portfolio.cash
        amount = 100
        cost = order_price * amount * 1.0003 # 预估包含佣金
        
        if cash < cost:
            log.info("资金不足,无法买入 %s" % stock)
            return

        # --- 正式下单 ---
        log.info("尝试下单: %s, 价格: %f, 数量: %d" % (stock, order_price, amount))
        order(stock, amount, limit_price=order_price)
        
    else:
        # 如果获取不到快照,可以使用 get_stock_status 辅助检查
        if get_stock_status([stock], 'HALT')[stock]:
            log.info("%s 停牌,不交易" % stock)
            return
        # 盲下
        order(stock, 100)

    # --- 2. 事后补救:轮询检查废单 (适用于回测和低频交易) ---
    check_rejected_orders(context)

def check_rejected_orders(context):
    """
    轮询检查当日所有订单,寻找废单
    """
    orders = get_orders()
    for o in orders:
        # 状态 '9' 代表废单
        if o.status == '9':
            log.error("发现废单! 订单号: %s, 股票: %s, 原因: 可能触碰限制或资金不足" % (o.order_id, o.symbol))
            # 在此处可以添加重试逻辑,例如调整价格后重新下单
            # retry_order(o) 

# --- 3. 实时补救:使用委托主推回调 (仅交易/模拟盘有效,推荐) ---
def on_order_response(context, order_list):
    """
    当委托状态发生变化时,会触发此函数。
    比 handle_data 里的轮询反应更快。
    """
    for order_dict in order_list:
        # 检查状态是否为废单 ('9')
        if order_dict['status'] == '9':
            stock = order_dict['stock_code']
            error_info = order_dict.get('error_info', '未知原因')
            log.error("【实时报警】收到废单回报: 股票 %s, 说明: %s" % (stock, error_info))
            
            # 策略逻辑:如果是废单,可以尝试修正价格重发
            # 注意:要防止死循环下单,建议设置计数器
            # if g.retry_count < 3:
            #     order(stock, 100)
            #     g.retry_count += 1

四、 核心API说明

  1. get_snapshot(security):

    • 获取实时的行情快照。
    • 关键字段:up_px (涨停价), down_px (跌停价), trade_status (交易状态)。
    • 作用:下单前确保价格在合法区间内,且股票未停牌。
  2. on_order_response(context, order_list):

    • 这是处理废单最高效的方法。
    • order_list 中的字典包含 error_info 字段,通常会直接告诉你废单的具体原因(如“可用资金不足”、“价格超出限制”等)。
  3. order 对象状态码:

    • '9': 废单 (重点关注)。
    • '8': 已成。
    • '2': 已报 (等待成交)。

Q&A

Q: 为什么我在回测里很少遇到废单,实盘却很多?
A: 回测系统通常会做很多“理想化”处理,例如自动撮合、忽略部分微小的资金误差。而实盘(包括模拟盘)直接对接柜台,校验非常严格。例如,实盘中如果你的委托价格偏离现价太远(超过价格笼子),或者资金缺几分钱,都会直接废单。

Q: 如何处理“价格笼子”导致的废单?
A: 在科创板和创业板,建议使用 order_market (市价单) 并配合保护限价,或者在下单前获取买一/卖一价,确保限价单价格在 基准价 * 1.02 (买入) 或 基准价 * 0.98 (卖出) 范围内。

Q: 废单会扣除手续费吗?
A: 不会。废单意味着委托未被交易所接受或未成交,因此不会产生交易费用。