🚀 AI 一键生成 ptrade 策略代码

PTrade 策略风控:防止 on_order_response 中的无限下单循环

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

问题描述

在 on_order_response 回调里继续下单,怎么防止程序死循环乱报单?

解决方案

在 PTrade 的 on_order_response(委托主推)回调函数中下单是一个非常敏感的操作。因为每次调用 order 函数都会产生新的委托状态变化,从而再次触发 on_order_response。如果不加限制,确实极易导致“无限递归”或“死循环”下单,瞬间耗尽资金或产生大量废单。

要防止这种情况,核心思路是打破因果链条。必须引入状态判断计数器特定条件过滤

以下是几种防止死循环的有效策略及具体的代码实现:

防止死循环的核心策略

  1. 判断委托状态(Status Filtering)

    • 不要对所有的状态变化都响应。通常只在订单完全成交(Status '8')废单/撤单时才触发后续操作。
    • 切忌在“已报”(Status '2')或“待报”状态下继续下单,否则会瞬间指数级爆发。
  2. 使用全局计数器/标志位(Global Counters/Flags)

    • initialize 中定义全局变量(如 g.trade_count),限制当天的触发次数。
    • 每次在回调里下单前,检查计数器是否超限。
  3. 识别订单来源(Order Identification)

    • 记录下你发出的特定订单的 order_id。在回调中,只有当收到的回报属于这个特定的 ID 时,才执行后续逻辑。
  4. 持仓与资金检查

    • 在下单前严格检查 context.portfolio.positionscontext.portfolio.cash,确保逻辑合理。

PTrade 策略代码示例

下面的代码展示了一个安全的实现方式:仅在上一笔订单完全成交后,且当天交易次数未超过限制时,才进行下一笔下单

def initialize(context):
    # 设置股票池
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 1. 定义最大连锁下单次数(防止死循环的硬闸)
    g.max_chain_orders = 5
    
    # 2. 当天已触发次数计数器
    g.current_chain_count = 0
    
    # 3. 初始触发开关(用于在 handle_data 启动第一笔)
    g.first_order_placed = False

def handle_data(context, data):
    # 在盘中触发第一笔交易,作为连锁反应的起点
    if not g.first_order_placed:
        log.info("【Handle Data】触发第一笔初始订单")
        order(g.security, 100)
        g.first_order_placed = True
        g.current_chain_count += 1

def on_order_response(context, order_list):
    """
    委托主推回调
    order_list: 发生变化的委托单列表
    """
    for order_info in order_list:
        # 获取关键信息
        status = order_info.get('status')
        stock_code = order_info.get('stock_code')
        
        # 过滤非本策略关注的股票
        if stock_code != g.security:
            continue

        # --- 核心防护逻辑 ---
        
        # 1. 状态过滤:只有当订单状态为 "8" (已成) 时才考虑追单
        # 如果在这里响应 "2" (已报),会立即引发死循环
        if status == '8':
            log.info("订单已成交,准备判断是否追单...")
            
            # 2. 计数器检查:检查是否超过最大允许次数
            if g.current_chain_count < g.max_chain_orders:
                log.info("当前次数 %s < 最大次数 %s,执行追单" % (g.current_chain_count, g.max_chain_orders))
                
                # 下单操作
                order(g.security, 100)
                
                # 更新计数器
                g.current_chain_count += 1
            else:
                log.info("已达到最大连锁下单次数,停止下单以防止死循环。")
        
        elif status == '9':
            log.warning("出现废单,请检查资金或持仓,停止追单逻辑。")
            # 可以在这里重置逻辑或发送报警

代码逻辑解析

  1. g.max_chain_orders:这是最重要的安全网。无论逻辑多么复杂,必须有一个硬性的数字限制(例如每天最多触发 5 次或 10 次),防止程序失控。
  2. if status == '8'::PTrade 的 on_order_response 会在订单的每一个生命周期节点触发(待报->已报->部成->已成)。必须明确指定只在已成('8')时触发。
  3. g.current_chain_count:每次下单后立即增加计数,确保条件判断是实时的。

常见错误陷阱

  • 错误 1:无状态判断
    # 错误示范:千万不要这样写!
    def on_order_response(context, order_list):
        order(g.security, 100) # 只要有回报就下单,瞬间爆炸
    
  • 错误 2:在“已报”状态下单
    如果在 status == '2' (已报) 时下单,因为下单函数调用后几乎立刻会收到“已报”回报,这也会形成极快的死循环。

总结

on_order_response 中下单属于高频/事件驱动策略的范畴,必须遵循 “先检查,后执行” 的原则。通过**状态过滤(仅限已成)计数器熔断(最大次数限制)**双重保险,可以有效防止程序死循环乱报单。


Q&A

Q: on_order_responseon_trade_response 有什么区别?
A: on_order_response委托状态变化的回报(包括已报、撤单、废单、成交等所有状态)。on_trade_response 仅在有成交发生时触发。如果你只想在成交后追单,使用 on_trade_response 会更简洁,因为它天然过滤掉了“已报”等中间状态,但依然需要配合计数器防止逻辑闭环。

Q: 如果我想实现“买入成交后立即卖出”的 T+0 逻辑,怎么写?
A: 在 on_trade_responseon_order_response (status='8') 中,判断 entrust_bs(买卖方向)。如果收到的是买入成交(entrust_bs 为 '1'),则执行 order(security, -amount) 进行卖出。同时务必检查可用持仓(T+1 制度下需有底仓)。

Q: 如何重置计数器?
A: 可以在 before_trading_start 函数中将 g.current_chain_count 重置为 0,这样每天开盘时策略状态会刷新。