问题描述
我是想实现对超过 N 秒的没成交的委托自动撤单功能:
先是用 get_trade_detail_data(A.acct,'STOCK','order')获取当天的委托明细
再循环每笔委托,用 can_cancel_order 判断该委托是否可撤。
但是发现当天的委托数量很大时(超过 1000 条),这个循环很耗时。
策略早上运行很快,下午会越来越慢。完成一次全循环需要十几秒。
请问 get_trade_detail_data 能否只获取最近的 100 条委托,获都只获取最近 5 分钟的委托数据?
解决方案
在 QMT 平台中,get_trade_detail_data 接口默认会返回指定账号当天的所有委托明细。API 底层并没有直接提供 limit(限制条数)或 start_time/end_time(限制时间)的参数。
你遇到的“下午越来越慢”的问题,根本原因通常不是 get_trade_detail_data 获取数据慢,而是对 1000 多条历史委托(包括已成、已撤、废单)进行了无意义的遍历,并且频繁调用 can_cancel_order 导致了极大的性能开销。
为了实现高效的“超时 N 秒自动撤单”,你可以采用以下几种优化方案:
优化方案一:利用 Python 列表切片(最简单)
既然接口返回的是一个列表,且通常是按时间顺序追加的,你可以直接在 Python 层面截取最后 100 条数据进行遍历,避免遍历早上的历史订单。
order_list = get_trade_detail_data(ContextInfo.accid, 'STOCK', 'order')
# 只取最后 100 条委托
recent_orders = order_list[-100:]
优化方案二:过滤委托状态(强烈推荐)
绝大多数历史委托已经是“已成(56)”、“已撤(54)”或“废单(57)”,对它们调用 can_cancel_order 是毫无意义且耗时的。你应该在循环的第一步,直接过滤掉这些状态,只处理“未报(48)”、“待报(49)”、“已报(50)”和“部成(55)”的活跃订单。
优化方案三:在策略中自行维护“活跃订单字典”(最高效)
对于高频/大量订单的策略,最专业的做法是不依赖全量查询,而是在下单时将订单号和时间记录在全局字典中,成交或撤单后将其剔除。这样你每次只需要遍历几个真正未成交的订单。
综合优化代码示例(超时自动撤单)
下面结合了状态过滤和时间比对的优化代码,能将十几秒的耗时降低到毫秒级:
import time
import datetime
def init(ContextInfo):
ContextInfo.accid = '你的资金账号'
ContextInfo.timeout_seconds = 60 # 设置超时时间 N = 60秒
def handlebar(ContextInfo):
# 1. 获取当天所有委托
order_list = get_trade_detail_data(ContextInfo.accid, 'STOCK', 'order')
# 2. 优化:如果订单量极大,可先切片取最近 200 条
if len(order_list) > 200:
order_list = order_list[-200:]
current_time = datetime.datetime.now()
for order in order_list:
# 3. 核心优化:只处理活跃状态的订单
# 48:未报, 49:待报, 50:已报, 51:已报待撤, 52:部成待撤, 53:部撤, 55:部成
# 如果是 54(已撤), 56(已成), 57(废单),直接跳过,极大地节省时间
if order.m_nOrderStatus in [54, 56, 57, 86, 255]:
continue
# 4. 解析订单时间 (m_strInsertTime 格式通常为 'HH:MM:SS')
try:
# 拼接当天的日期和订单时间
today_str = current_time.strftime('%Y-%m-%d')
order_time_str = f"{today_str} {order.m_strInsertTime}"
order_time = datetime.datetime.strptime(order_time_str, '%Y-%m-%d %H:%M:%S')
# 计算时间差
time_diff = (current_time - order_time).total_seconds()
# 5. 判断是否超时 N 秒
if time_diff > ContextInfo.timeout_seconds:
# 6. 确认可撤后执行撤单
if can_cancel_order(order.m_strOrderSysID, ContextInfo.accid, 'STOCK'):
cancel(order.m_strOrderSysID, ContextInfo.accid, 'STOCK', ContextInfo)
print(f"订单 {order.m_strOrderSysID} 超时 {time_diff} 秒,已发送撤单指令")
except Exception as e:
print(f"解析订单时间出错: {e}")
总结
- 不要依赖 API 去限制条数,直接用 Python 的
[-100:]切片。 - 拦截无效状态:通过
order.m_nOrderStatus过滤掉已成、已撤的单子,这是解决你循环耗时十几秒的最关键一步。 - 减少 API 调用:
can_cancel_order内部有通信开销,只有在状态和时间都满足条件时,才去调用它。