问题描述
解决方案
在PTrade中,委托单变成“废单”(状态码为 '9'),通常意味着该委托未能通过柜台的风控检查或交易所的撮合规则,直接被拒绝执行。
以下是导致废单的常见原因及相应的代码处理方案。
一、 废单的常见原因
- 价格超出涨跌停限制:
- 委托买入价高于涨停价,或卖出价低于跌停价。
- 科创板/创业板价格笼子:委托价格超过了基准价格(如卖一价)的 $\pm 2%$(具体规则视板块而定),导致直接废单。
- 标的状态异常:
- 股票处于停牌(HALT)、退市(DELISTED)或未上市状态。
- 资金或持仓不足:
- 买入时可用资金不足(需考虑手续费)。
- 卖出时可用持仓不足(注意T+1规则,今日买入的股票今日不可卖出)。
- 委托数量错误:
- 股票买入必须是100的整数倍(零股卖出除外)。
- 科创板/创业板单笔申报数量限制(如最低200股)。
- 非交易时间报单:
- 在非交易时间段(如午休或收盘后)发送了不支持挂单的委托类型。
二、 代码处理方案
处理废单的最佳策略是 “事前预防” 和 “事后补救” 相结合。
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说明
-
get_snapshot(security):- 获取实时的行情快照。
- 关键字段:
up_px(涨停价),down_px(跌停价),trade_status(交易状态)。 - 作用:下单前确保价格在合法区间内,且股票未停牌。
-
on_order_response(context, order_list):- 这是处理废单最高效的方法。
order_list中的字典包含error_info字段,通常会直接告诉你废单的具体原因(如“可用资金不足”、“价格超出限制”等)。
-
order对象状态码:'9': 废单 (重点关注)。'8': 已成。'2': 已报 (等待成交)。
Q&A
Q: 为什么我在回测里很少遇到废单,实盘却很多?
A: 回测系统通常会做很多“理想化”处理,例如自动撮合、忽略部分微小的资金误差。而实盘(包括模拟盘)直接对接柜台,校验非常严格。例如,实盘中如果你的委托价格偏离现价太远(超过价格笼子),或者资金缺几分钱,都会直接废单。
Q: 如何处理“价格笼子”导致的废单?
A: 在科创板和创业板,建议使用 order_market (市价单) 并配合保护限价,或者在下单前获取买一/卖一价,确保限价单价格在 基准价 * 1.02 (买入) 或 基准价 * 0.98 (卖出) 范围内。
Q: 废单会扣除手续费吗?
A: 不会。废单意味着委托未被交易所接受或未成交,因此不会产生交易费用。