问题描述
解决方案
在PTrade实盘交易中,处理委托失败或废单最及时、有效的方法是利用 on_order_response(委托主推回调) 函数。
当订单状态发生变化(如变为“废单”)时,交易柜台会立即推送消息给策略,触发该函数。相比于在 handle_data 中轮询查询订单状态,这种方式延迟更低,能更快速地做出补救措施(如重发订单或停止交易)。
核心处理逻辑
- 监听回调:在
on_order_response中遍历推送的订单列表。 - 判断状态:检查订单的
status字段。根据PTrade数据字典,状态'9'代表废单。 - 获取原因:读取
error_info字段,获取具体的失败原因(如“资金不足”、“超过涨跌停价格”等)。 - 执行对策:根据错误原因记录日志、发送报警或尝试重新下单(需注意防止死循环)。
策略代码示例
以下是一个完整的策略示例,展示了如何捕获废单并进行简单的重试逻辑(为了防止无限重试,增加了重试次数限制)。
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)
关键点解析
status == '9': 这是判断废单的唯一标准。虽然status == '6'(已撤) 也意味着订单未成交,但那是用户主动或系统盘后撤单,而'9'通常是因为参数错误、验资验券失败或合规拦截。error_info: 务必打印这个字段。常见的废单原因包括:- 可用资金/持仓不足。
- 委托价格超过涨跌停限制或价格笼子限制。
- 非交易时间报单。
- 证券代码错误或处于停牌状态。
- 合规风控限制(如黑名单)。
- 防止死循环: 在
on_order_response中重新下单是非常危险的操作。如果废单是因为“资金不足”,你立即重试会再次废单,导致瞬间产生大量废单记录,甚至可能导致账户被锁或程序崩溃。必须设置重试计数器或时间间隔。 order_list数据结构: 回调函数中的order_list是一个字典列表,包含entrust_no,error_info,status,stock_code等关键字段。
替代方案:轮询法
如果你不想使用回调函数,也可以在 handle_data 或 run_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' 是已撤(通常是策略或人工主动撤销)。处理逻辑通常不同:废单需要检查参数或风控,撤单通常是策略逻辑的一部分(如超时撤单)。