问题描述
客户是使用量化策略进行委托的,检查量化后台sys日志,发现确实没有1000委托数量的部撤记录,检查PTRADE_SA的msgc插件日志,发现对应的主推和量化sys日志中打印的是一致的
解决方案
问题分析
在 ptrade 量化交易平台中,当您对某一笔订单进行撤单操作时,如果发现 on_trade_response(成交主推)返回的撤单数量(business_amount)与实际撤单数量不符(例如实际撤了1100股,但主推只显示100股),且底层日志(sys日志和msgc插件日志)也一致,通常是由以下几个原因导致的:
- 柜台推送机制差异:主推数据是由底层券商柜台直接推送的。在某些极端情况下(如行情剧烈波动、撮合引擎分批处理),柜台可能会将一笔撤单拆分成多次推送,或者在推送时发生了合并/遗漏。如果底层日志确实没有那1000股的推送记录,说明柜台本身就没有将这部分数据推送上来。
- 订单状态(Status)的理解偏差:在撤单时,需要特别关注主推字典中的
status字段。如果是5(部撤),说明订单部分成交、部分撤销;如果是6(已撤),说明订单完全撤销。有时主推中的business_amount可能只代表某一次撮合动作涉及的数量,而不是总撤单量。 - 分批成交与撤单的并发:如果在您发起撤单的瞬间,订单刚好发生了部分成交(例如成交了1000股),那么实际成功撤销的可能只有剩下的100股。此时主推返回撤单100股是正确的,您需要检查是否有对应的1000股的成交主推。
排查步骤与解决方案
针对这种主推数据可能存在遗漏或分批的情况,建议在编写量化策略时,不要完全依赖单次主推中的 business_amount 来计算最终的持仓或订单状态,而应采用“主推触发 + 主动查询”的防御性编程方式。
1. 检查历史成交记录
首先,确认这笔订单是否在撤单前或撤单瞬间发生了部分成交。您可以通过 get_trades() 或检查之前的 on_trade_response 记录,看看是否有1000股的成交记录。
2. 结合 get_order() 获取最终准确状态
由于主推可能存在延迟或分批,最稳妥的做法是在收到撤单主推后,使用 get_order(order_id) 主动查询该订单的最新全貌。
Order 对象中包含了:
amount: 初始委托总数量filled: 已成交数量status: 当前最新状态
实际撤单数量 = 初始委托数量 (amount) - 已成交数量 (filled)
3. 策略代码优化示例
以下是一个稳健处理撤单主推的代码示例:
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
g.target_order_id = None
def on_trade_response(context, trade_list):
for trade in trade_list:
# 检查是否是撤单相关的状态:'5' (部撤) 或 '6' (已撤)
if trade.get('status') in ['5', '6']:
order_id = trade.get('order_id')
if not order_id:
continue
# 1. 获取主推中的数量(可能不完整)
pushed_cancel_amount = trade.get('business_amount', 0)
log.info(f"收到撤单主推,订单号: {order_id}, 主推撤单数量: {pushed_cancel_amount}")
# 2. 主动查询订单最新状态,计算真实的撤单数量
order_info_list = get_order(order_id)
if order_info_list and len(order_info_list) > 0:
order_obj = order_info_list[0]
total_amount = abs(order_obj.amount) # 委托总数(取绝对值兼容买卖)
filled_amount = abs(order_obj.filled) # 已成交总数
# 真实撤单数量 = 委托总数 - 已成交总数
actual_canceled_amount = total_amount - filled_amount
log.info(f"订单 {order_id} 最终状态: {order_obj.status}, 委托总数: {total_amount}, 已成交: {filled_amount}, 实际撤单总数: {actual_canceled_amount}")
if pushed_cancel_amount != actual_canceled_amount:
log.warning(f"注意:主推撤单数量 ({pushed_cancel_amount}) 与 实际计算撤单总数 ({actual_canceled_amount}) 不一致!请以实际计算为准。")
def handle_data(context, data):
# 示例:下单并在条件满足时撤单
pass
总结
如果底层日志确认柜台只推送了100股的撤单记录,这属于柜台推送层面的现象(可能是分批推送丢失,或者是并发成交导致)。作为策略开发者,最佳实践是在 on_trade_response 中仅将主推作为“状态发生变化”的触发信号,随后调用 get_order() 获取订单的 amount 和 filled 字段,通过计算得出准确的撤单数量,从而保证策略逻辑的严密性。