问题描述
解决方案
在 PTrade 的 on_order_response(委托主推)回调函数中下单是一个非常敏感的操作。因为每次调用 order 函数都会产生新的委托状态变化,从而再次触发 on_order_response。如果不加限制,确实极易导致“无限递归”或“死循环”下单,瞬间耗尽资金或产生大量废单。
要防止这种情况,核心思路是打破因果链条。必须引入状态判断、计数器或特定条件过滤。
以下是几种防止死循环的有效策略及具体的代码实现:
防止死循环的核心策略
-
判断委托状态(Status Filtering):
- 不要对所有的状态变化都响应。通常只在订单完全成交(Status '8')或废单/撤单时才触发后续操作。
- 切忌在“已报”(Status '2')或“待报”状态下继续下单,否则会瞬间指数级爆发。
-
使用全局计数器/标志位(Global Counters/Flags):
- 在
initialize中定义全局变量(如g.trade_count),限制当天的触发次数。 - 每次在回调里下单前,检查计数器是否超限。
- 在
-
识别订单来源(Order Identification):
- 记录下你发出的特定订单的
order_id。在回调中,只有当收到的回报属于这个特定的 ID 时,才执行后续逻辑。
- 记录下你发出的特定订单的
-
持仓与资金检查:
- 在下单前严格检查
context.portfolio.positions或context.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("出现废单,请检查资金或持仓,停止追单逻辑。")
# 可以在这里重置逻辑或发送报警
代码逻辑解析
g.max_chain_orders:这是最重要的安全网。无论逻辑多么复杂,必须有一个硬性的数字限制(例如每天最多触发 5 次或 10 次),防止程序失控。if status == '8'::PTrade 的on_order_response会在订单的每一个生命周期节点触发(待报->已报->部成->已成)。必须明确指定只在已成('8')时触发。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_response 和 on_trade_response 有什么区别?
A: on_order_response 是委托状态变化的回报(包括已报、撤单、废单、成交等所有状态)。on_trade_response 仅在有成交发生时触发。如果你只想在成交后追单,使用 on_trade_response 会更简洁,因为它天然过滤掉了“已报”等中间状态,但依然需要配合计数器防止逻辑闭环。
Q: 如果我想实现“买入成交后立即卖出”的 T+0 逻辑,怎么写?
A: 在 on_trade_response 或 on_order_response (status='8') 中,判断 entrust_bs(买卖方向)。如果收到的是买入成交(entrust_bs 为 '1'),则执行 order(security, -amount) 进行卖出。同时务必检查可用持仓(T+1 制度下需有底仓)。
Q: 如何重置计数器?
A: 可以在 before_trading_start 函数中将 g.current_chain_count 重置为 0,这样每天开盘时策略状态会刷新。