QMT 委托已提交但没有成交怎么办?
委托已报出但长时间未成交,通常是委托价格或数量与市场不匹配。最常见的原因包括:限价单价格偏离市场价、报价类型选择不当、委托触及涨跌停限制、委托数量不符合最小交易单位。通过检查委托价格与当前行情的匹配度,可以快速定位问题。
问题现象
策略成功提交了委托(委托记录中可以看到),但委托长时间处于"已报"或"部分成交"状态,没有全部成交。账户持仓未按预期变化。
直接判断
委托已报出说明 passorder 调用成功且被交易服务器接受。未成交的原因在于委托条件(价格、数量)与当前市场状况不匹配,或品种处于特殊状态(涨跌停、停牌)。
委托成交需要满足"价格匹配"和"数量匹配"两个条件。限价单只有在市场价格达到或优于委托价格时才会成交。如果委托价格远离市场价,委托会一直挂在订单簿上等待。市价单通常会立即成交,但在涨跌停或流动性不足时也可能无法成交。
小建议 按以下 5 个步骤排查。重点是步骤 1(委托价格与行情对比)和步骤 2(报价类型),这两个是未成交的最常见原因。
排查步骤
步骤 1:检查委托价格与当前行情
限价单只有在市场价格达到委托价格时才会成交。买入委托的价格高于或等于当前卖一价时可以成交,卖出委托的价格低于或等于当前买一价时可以成交。
最常见的未成交原因是限价单价格设置不合理。例如,当前卖一价为 10.05 元,但买入限价设为 10.00 元,委托会挂在订单簿上等待价格回落到 10.00 元才成交。如果价格持续上涨,委托永远不会成交。同样,卖出限价设得过高也会导致无法成交。
操作方法: 在 QMT 客户端中查看该品种的当前买卖盘价格,与委托价格对比。在策略中获取实时行情数据进行比较。
正确结果: 买入委托价格不低于当前卖一价,或卖出委托价格不高于当前买一价,委托能够成交。
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
stock = '000001.SZ'
# 获取当前行情
last_price = ContextInfo.get_last_price(stock)
bid_price = ContextInfo.get_bid_price(stock) # 买价列表
ask_price = ContextInfo.get_ask_price(stock) # 卖价列表
print(f'{stock} 行情:')
print(f' 最新价: {last_price}')
if bid_price:
print(f' 买一价: {bid_price[0]}')
if ask_price:
print(f' 卖一价: {ask_price[0]}')
# 买入委托价格应接近卖一价才能快速成交
if ask_price:
buy_price = ask_price[0] # 使用卖一价作为买入价
print(f' 建议买入价: {buy_price}')
步骤 2:检查报价类型
passorder 的 priceType 参数决定报价方式。常用的价格类型包括:11(限价单)、12(最新价)、14(市价)、5(最优价)、0(最优五档即时成交剩撤销)。不同价格类型的成交逻辑不同。
限价单(priceType=11)需要指定精确价格,只有市场价格达到该价格才成交。市价单(priceType=14)以当前市场最优价格成交,通常能立即成交但价格不确定。如果希望快速成交,应使用市价单或以对手价作为限价单价格。使用限价单但价格设为 0 会导致委托无效。
操作方法: 检查 passorder 中的 priceType 参数。如果需要快速成交,考虑使用 priceType=14(市价)或 priceType=12(最新价)。如果使用限价单,确保 price 参数为合理的市场价格。
正确结果: 报价类型与策略需求匹配,委托能够按预期成交。
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
close = ContextInfo.get_close_price()
# 方式1:市价单(快速成交,价格不确定)
ContextInfo.passorder(
0, 1101, ContextInfo.accountid,
'000001.SZ', 14, 0, 100, # priceType=14, price=0
1, ContextInfo
)
# 方式2:限价单(精确控制价格,需合理定价)
# ContextInfo.passorder(
# 0, 1101, ContextInfo.accountid,
# '000001.SZ', 11, close, 100, # priceType=11, price=close
# 1, ContextInfo
# )
# 方式3:最新价(跟随最新价成交)
# ContextInfo.passorder(
# 0, 1101, ContextInfo.accountid,
# '000001.SZ', 12, 0, 100, # priceType=12
# 1, ContextInfo
# )
步骤 3:检查涨跌停限制
当品种触及涨停价或跌停价时,无法以超过涨跌停价的价格成交。涨停时买入委托可能无法成交(没有卖盘),跌停时卖出委托可能无法成交(没有买盘)。
涨跌停是交易所对品种价格波动的限制。涨停价 = 前收盘价 * (1 + 涨跌幅限制),跌停价 = 前收盘价 * (1 - 涨跌幅限制)。A股普通股票的涨跌幅限制为 10%(ST 股票为 5%)。当品种涨停时,所有买入委托都排队等待,只有有人卖出才能成交;跌停时则相反。如果你的委托价格超出了涨跌停范围,委托会被直接拒绝。
操作方法: 查看品种当前是否处于涨停或跌停状态。在策略中计算涨跌停价并与委托价格比较。
正确结果: 委托价格在涨跌停范围内,品种不在涨跌停状态(或方向正确)。
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
stock = '000001.SZ'
last_price = ContextInfo.get_last_price(stock)
closes = ContextInfo.get_history_data('close', 2, stock)
if closes and len(closes) >= 2:
prev_close = closes[-2] # 昨日收盘价
limit_up = round(prev_close * 1.1, 2) # 涨停价
limit_down = round(prev_close * 0.9, 2) # 跌停价
print(f'{stock} 涨跌停检查:')
print(f' 昨收: {prev_close}, 涨停: {limit_up}, 跌停: {limit_down}')
print(f' 最新价: {last_price}')
if last_price >= limit_up:
print(' 状态: 已涨停,买入委托可能无法成交')
elif last_price <= limit_down:
print(' 状态: 已跌停,卖出委托可能无法成交')
else:
print(' 状态: 正常交易')
步骤 4:检查委托数量
A 股股票的最小交易单位为 100 股(1 手),买入委托数量必须是 100 的整数倍。卖出委托数量不足 100 股的可以零股卖出。不同品种的最小交易单位可能不同(如可转债为 10 张)。
如果委托数量不是 100 的整数倍(如 150 股),委托可能被交易所拒绝或只成交 100 股。此外,如果委托数量超过市场当前的挂单量,委托可能只能部分成交,剩余部分继续挂单等待。
操作方法: 检查 passorder 中的 volume 参数是否为正确的交易单位。在策略中添加数量校验逻辑。
正确结果: 委托数量符合最小交易单位要求,委托正常成交。
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
close = ContextInfo.get_close_price()
# 确保数量为 100 的整数倍
desired_value = 10000 # 目标金额
volume = int(desired_value / close / 100) * 100 # 向下取整到100的倍数
if volume < 100:
volume = 100 # 最少 100 股
print(f'计算委托数量: {volume} 股 (金额: {volume * close})')
ContextInfo.passorder(
0, 1101, ContextInfo.accountid,
'000001.SZ', 11, close, volume,
1, ContextInfo
)
步骤 5:查看委托状态
通过 order_callback 回调或 get_orders 方法可以查看委托的实时状态。委托状态码包括:50(已提交)、55(已确认)、56(部分成交)、57(全部成交)、58(已撤)、59(部分撤)。
委托长时间停留在 50 或 55 状态说明没有成交。56 状态说明部分成交但还有剩余。通过监控委托状态变化,可以判断委托是否需要撤单后重新下单。
操作方法: 在策略中定义 order_callback 回调监控委托状态。使用 get_orders 查询当日所有委托的状态。
正确结果: 委托状态最终变为 57(全部成交),或在超时后执行撤单操作。
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
# 查询当日所有委托状态
orders = ContextInfo.get_orders()
for order in orders:
status_text = {
50: '已提交', 55: '已确认', 56: '部分成交',
57: '全部成交', 58: '已撤', 59: '部分撤'
}.get(order.m_nOrderStatus, '未知')
print(f'委托: {order.m_strStockCode} {order.m_nVolume}股 状态:{status_text} 已成交:{order.m_nTradedVolume}')
def order_callback(ContextInfo, order_info):
status = order_info.m_nOrderStatus
print(f'委托回报: {order_info.m_strStockCode} 状态码:{status} 已成交:{order_info.m_nTradedVolume}/{order_info.m_nVolume}')
if status == 56:
print(' 部分成交,剩余委托继续等待')
elif status == 57:
print(' 全部成交')
QMT 撤单方法
未成交的委托可以通过 cancel 方法撤销。可以撤销指定委托号的委托、撤销某品种的所有未成交委托、或撤销所有未成交委托。
撤单是将已提交但未成交的委托从交易所订单簿中移除。撤单后,委托状态变为 58(已撤)或 59(部分撤,如果有部分成交)。撤单是处理未成交委托的常用手段——撤单后可以重新以更合理的价格下单。
小建议 在策略中设置超时撤单逻辑:委托提交后如果一定时间内未成交,自动撤单并重新定价下单。避免委托长时间挂在订单簿上占用资金或持仓。
def init(ContextInfo):
ContextInfo.pending_orders = {} # 记录待成交委托
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
# 检查超时未成交委托
from datetime import datetime, timedelta
now = datetime.now()
for order_id, info in list(ContextInfo.pending_orders.items()):
submit_time = info.get('submit_time')
if submit_time and (now - submit_time) > timedelta(minutes=5):
# 超过 5 分钟未成交,撤单
ContextInfo.cancel(order_id, ContextInfo)
print(f'超时撤单: {order_id}')
del ContextInfo.pending_orders[order_id]
def order_callback(ContextInfo, order_info):
from datetime import datetime
if order_info.m_nOrderStatus in [50, 55, 56]:
# 委托未完成,记录到待成交列表
ContextInfo.pending_orders[order_info.m_strOrderSysID] = {
'submit_time': datetime.now(),
'stock': order_info.m_strStockCode,
}
elif order_info.m_nOrderStatus in [57, 58, 59]:
# 委托结束,从待成交列表移除
order_id = order_info.m_strOrderSysID
if order_id in ContextInfo.pending_orders:
del ContextInfo.pending_orders[order_id]
最小验证代码
使用以下代码验证委托成交流程。此代码以市价单发送委托,应该能够立即成交:
def init(ContextInfo):
ContextInfo.set_universe(['000001.SZ'])
ContextInfo.order_sent = False
print('策略初始化完成')
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return
if ContextInfo.order_sent:
return
last_price = ContextInfo.get_last_price('000001.SZ')
if last_price and last_price > 0:
print(f'当前价: {last_price},发送市价买入委托')
# 使用市价单,应该立即成交
ContextInfo.passorder(
0, 1101, ContextInfo.accountid,
'000001.SZ', 14, 0, 100,
1, ContextInfo
)
ContextInfo.order_sent = True
def deal_callback(ContextInfo, deal_info):
print(f'成交回报: {deal_info.m_strStockCode}')
print(f' 成交数量: {deal_info.m_nVolume}')
print(f' 成交价格: {deal_info.m_dPrice}')
def order_callback(ContextInfo, order_info):
print(f'委托回报: 状态码={order_info.m_nOrderStatus} 已成交={order_info.m_nTradedVolume}')
小建议 如果市价单也无法成交,说明品种可能处于停牌、涨跌停或非交易时段。确认品种状态正常后再切换回限价单进行交易。对于限价单策略,建议添加超时撤单和重新定价逻辑,避免委托长时间挂单无效。