今天继续按更新计划更新教程内容,今天更新qmt常见的问题解答2,以前更新过1的内容量化教程--qmt量化常见问题解答1
1`ContextInfo`对象的逐K线保存机制具体是怎样的?
ContextInfo由底层维护并传递给系统函数。同一根K线(bar)内,ContextInfo本质上是同一个变量,对其修改只对本次handlebar调用的下文起作用。handlebar里对ContextInfo的修改在该bar结束后才会保存,在下一个bar体现出来。每次handlebar调用前会对ContextInfo进行深拷贝,如果新的分笔不是新K线的第一个分笔,则判断上一个分笔不是K线最后分笔,ContextInfo对象会被回退为之前深拷贝的那个。此机制目的是模拟K线效果,只在K线结束的分笔生效一次。

例子参考思路
#coding:gbk
def init(ContextInfo):
# 在初始化函数中,为ContextInfo添加一个计数器属性,初始值为0
ContextInfo.counter = 0
def handlebar(ContextInfo):
# 每进来一个tick,计数器加1
ContextInfo.counter += 1
# 打印当前计数器的值
# 在盘中,你会发现这个数字在同一根K线内会不断变化,但最终不会被保存
print(f"Bar Index: {ContextInfo.barpos}, Tick Counter: {ContextInfo.counter}, Is Last Bar: {ContextInfo.is_last_bar()}")
# 只在最后一根K线(最新K线)执行交易逻辑
if ContextInfo.is_last_bar():
print(f"------ 这是最新K线的tick,计数器的最终值(不一定会被保存):{ContextInfo.counter} ------")
2`passorder`函数中的`quickTrade`参数(快速交易参数)有哪几种取值?分别代表什么含义?
quickTrade参数默认为0。
- **传0**:只在K线结束分笔时调用passorder产生有效信号,其他情况调用不产生信号。适用于希望K线结束下单(信号不闪烁)的场景。
- **传1**:在当前K线为最新K线时(ContextInfo.is_last_bar()为True)调用passorder产生有效信号,历史K线调用不产生信号。适用于希望盘中出现信号立即下单,但接受信号闪烁风险的场景。
- **传2**:任何情况下调用passorder都产生有效信号,不会丢弃任何一次调用的信号。**注意**:在定时器回调、行情回调函数、after_init函数中调用下单函数时,必须传2,确保不会漏单。

例子参考
#coding:gbk
def init(ContextInfo):
# 在init函数中下单,必须设置quickTrade=2
passorder(23, 1101, account, '01.SZ', 5, 0, 100, 'init下单', 2, '教学示例', ContextInfo)
print("init函数中立即下单(quickTrade=2)")
def after_init(ContextInfo):
# 在after_init函数中下单,必须设置quickTrade=2
passorder(23, 1101, account, '01.SZ', 5, 0, 200, 'after_init下单', 2, '教学示例', ContextInfo)
print("after_init函数中立即下单(quickTrade=2)")
def handlebar(ContextInfo):
if not ContextInfo.is_last_bar():
return # 历史K线跳过
# 常见场景:K线走完才下单,信号不闪烁
# 使用quickTrade=0(默认值)
passorder(23, 1101, account, '1.SZ', 5, 0, 100, 'K线结束下单', 0, '教学示例', ContextInfo)
print("K线结束时产生信号(quickTrade=0)")
# 如果需要盘中立即响应,可使用quickTrade=1
# passorder(23, 1101, account, '1.SZ', 5, 0, 100, '盘中立即下单', 1, '教学示例', ContextInfo)
3为什么调用`passorder`后立刻查询`get_trade_detail_data`,查不到对应委托,可用资金也没变化?
QMT的交易接口是异步的。以quickTrade=2的passorder为例,调用后会立刻发出委托然后返回,不会等待委托回报,也不会阻塞Python线程。委托、成交、持仓、账号信息的更新是在客户端后台进行的,Python策略无法手动控制。get_trade_detail_data和四种交易回调函数都是从客户端本地缓存中读取数据/触发调用,不是实时查询柜台。客户端本地缓存状态定期接收柜台推送刷新(有交易主推的柜台约50ms一次)。因此不能认为查询到的状态与柜**全实时一致。

例子参考
#coding:gbk
def init(C):
"""初始化:设置账号"""
C.acc = '4' # 账号
def handlebar(C):
"""每根K线触发"""
if not C.is_last_bar():
return
now = timetag_to_datetime(C.get_bar_timetag(C.barpos), '%Y-%m-%d %H:%M:%S')
# ===== 场景演示1:下单前查询资金和持仓 =====
print(f"\n{now} === 下单前状态 ===")
accounts_before = get_trade_detail_data(C.acc, 'stock', 'account')
if accounts_before:
acct = accounts_before
print(f"可用资金: {acct.m_dAvailable}")
positions_before = get_trade_detail_data(C.acc, 'stock', 'position')
for pos in positions_before:
print(f"持仓: {pos.m_strInstrumentID} 可用数量: {pos.m_nCanUseVolume}")
# ===== 下单:立即委托(quickTrade=2) =====
stock_code = '1.SZ'
volume = 100 # 1手
print(f"\n{now} === 开始下单 === 买入 {stock_code} {volume}股")
# 使用quickTrade=2,任何位置调用都立即产生委托
# 23=买入,1101=限价,14=最新价
remark = f"教学示例_{now}"
passorder(23, 1101, C.acc, stock_code, 14, -1, volume,
remark, 2, remark, C)
print(f"passorder调用完毕,已返回")
# ===== 场景演示2:下单后立刻查询 =====
print(f"\n{now} === 下单后立即查询(通常查不到委托)===")
# 查询委托
orders = get_trade_detail_data(C.acc, 'stock', 'order')
print(f"委托数量: {len(orders)}")
for o in orders:
print(f" 委托: {o.m_strInstrumentID} 状态: {o.m_nOrderStatus} "
f"备注: {o.m_strRemark}")
# 查询资金(可用资金不会立即变化)
accounts_after = get_trade_detail_data(C.acc, 'stock', 'account')
if accounts_after:
acct_after = accounts_after
print(f"可用资金: {acct_after.m_dAvailable}")
# 演示查询到的委托数量与下单前相同(可能查不到新委托)
print(f"\n结论:如下单前委托总数 = {len(orders_before)},下单后 = {len(orders_after)}")
print("可能查不到刚下的委托,因为本地缓存尚未刷新")
4实盘策略中如何设计可靠的委托状态管理机制,防止超单?
常见的做法是:
- 使用**全局变量字典**保存委托状态。
- 给每一笔委托一个独立的userOrderId(投资备注)作为字典的key,委托状态(如'待报'、'已报'、'部分成交'、'全部成交'、'已撤'等)作为字典的value。
- 下单后,默认将该笔委托的状态设置为'待报'。
- 之后通过交易回调函数或定期查询get_trade_detail_data来获取委托回报,并更新字典中对应委托的状态。
- 如果某品种股票存在'待报'或'已报'状态的委托,则暂停该品种的后续报单,防止发生超单。可参考实盘示例7(调整至目标持仓Demo)的实现。

参考
#coding:gbk
"""
示例说明:一个简化的交易系统核心部分,演示如何通过维护委托状态字典来防止超单。
此代码并非完整可执行的策略,而是突出了核心的设计模式。
"""
import datetime
import time
class a():
pass
A = a() # 创建一个全局对象来保存状态
def init(C):
"""策略初始化函数"""
A.acct = account # 账号
A.acct_type = accountType # 账号类型
# 定义一个全局字典,用于保存每只股票的委托状态
# key: 股票代码(例如 '01.SZ')
# value: 该股票最近一次未完成的委托的“投资备注”
A.waiting_dict = {}
# 可选:记录所有委托及其发出时间的字典,用于超时撤单
A.all_order_ref_dict = {}
A.withdraw_secs = 60 # 撤单等待时间(秒)
def handlebar(C):
"""每个Bar触发一次的交易处理函数"""
if not C.is_last_bar():
return
now_time = datetime.datetime.now().strftime('%H%M%S')
if now_time < '093000' or now_time > "150000":
return
# 1. 获取账户信息
account_info = get_trade_detail_data(A.acct, A.acct_type, 'account')
if len(account_info) == 0:
print(f'账号{A.acct} 未登录')
return
acct = account_info
available_cash = int(acct.m_dAvailable)
# 2. 获取当前持仓
position_list = get_trade_detail_data(A.acct, A.acct_type, 'position')
current_position = {i.m_strInstrumentID + '.' + i.m_strExchangeID: int(i.m_nCanUseVolume) for i in position_list}
# 3. 更新委托状态字典 (核心防超单机制)
refresh_waiting_dict(C)
# 4. 下新单前检查:如果有品种还处于等待状态,则暂停该品种的后续报单
# 这里的“等待状态”指该品种的委托还在等待被查询到或处理完成
if A.waiting_dict:
print(f"当前有未完成委托的品种: {list(A.waiting_dict.keys())},暂停这些品种的后续报单")
# 假设我们只交易一个股票,这里简化为:如果等待字典非空,就不操作
# 如果是多品种策略,这里需要遍历你的目标持仓列表,跳过在waiting_dict中的品种
return # 简单起见,这里直接返回
# 5. 模拟你的交易信号和下单逻辑
target_stock = '01.SZ' # 假设要交易的股票
target_vol = 1000
current_vol = current_position.get(target_stock, 0)
if current_vol < target_vol and available_cash > 10000:
# 计算需要买入的数量
buy_vol = min((target_vol - current_vol), int(available_cash / 10 / 100) * 100)
if buy_vol >= 100:
# 生成唯一的投资备注
msg = f"调仓买入 {target_stock} {int(time.time())}"
print(f"发出买入委托: {msg}, 数量: {buy_vol}")
# 在下单前,立刻将该品种加入等待字典,标记为等待状态
A.waiting_dict[target_stock] = msg
A.all_order_ref_dict[msg] = time.time()
# 执行下单 (实际开发中这里是 passorder 函数)
# passorder(23, 1101, A.acct, target_stock, 14, 0, buy_vol, '调仓策略', 2, msg, C)
print(f"模拟下单: 股票 {target_stock}, 数量 {buy_vol}, 备注 {msg}")
# 6. (可选)检查并撤销超时委托
cancel_timeout_orders(C)
def refresh_waiting_dict(C):
"""
核心函数:更新委托状态字典
从柜台获取委托信息,如果某品种的委托已经达到终态(已成、已撤、废单等),
则从 waiting_dict 中删除该品种,允许后续重新报单。
"""
if not A.waiting_dict:
return
# 获取当前所有委托
order_list = get_trade_detail_data(A.acct, A.acct_type, 'order')
# 构建一个以投资备注为key的委托状态字典
order_status_dict = {order.m_strRemark: order.m_nOrderStatus for order in order_list}
# 要删除的品种列表
stocks_to_remove = []
for stock, remark in A.waiting_dict.items():
if remark in order_status_dict:
status = order_status_dict[remark]
# 委托终态:56(已成), 53(部撤), 54(已撤), 57(废单)
if status in [56, 53, 54, 57]:
print(f"股票 {stock} 的委托 {remark} 状态为 {status} (终态),从等待字典中删除")
stocks_to_remove.append(stock)
else:
# 如果没有查到该委托(比如刚刚下单还未被柜台接收),保持等待状态,打印日志
pass
# 执行删除
for stock in stocks_to_remove:
del A.waiting_dict[stock]
if stock in A.all_order_ref_dict:
# 同时清理对应的引用字典
del A.all_order_ref_dict[remark_in_dict_for_this_stock]
def cancel_timeout_orders(C):
"""(可选)撤销超时的委托,防止委托一直挂在那里"""
if not A.all_order_ref_dict:
return
order_list = get_trade_detail_data(A.acct, A.acct_type, 'order')
for order in order_list:
if order.m_strRemark in A.all_order_ref_dict:
elapsed_time = time.time() - A.all_order_ref_dict[order.m_strRemark]
# 如果委托超时且处于可撤状态
if elapsed_time > A.withdraw_secs:
# 可撤状态:50(已报), 51(已报待撤), 52(部成待撤), 55(部成), 86(已确认)等
if order.m_nOrderStatus in [50, 51, 52, 55, 86]:
print(f"超时撤单: {order.m_strRemark}")
# cancel(order.m_strOrderSysID, A.acct, 'stock', C)
5QMT的行情数据主要分为哪几种类型?各自的特点和适用场景是什么
主要分为三种:
- **本地数据**:指下载到本地的历史行情数据加密文件。适合回测模式使用,对应接口为get_market_data_ex(subscribe=False)或get_local_data。速度快,无数量限制,但盘中不会更新。
- **全推数据**:客户端启动后自动接收的全市场最新数据快照(包括日线OHLC、成交量、五档盘口等)。只有最新值,无历史值。对应接口为get_full_tick和subscribe_whole_quote。盘中50ms更新一次,速度快,不用订阅,无品种数量限制,适合获取实时快照。
- **订阅数据**:指向行情服务器订阅指定品种的实时行情。有最大数量限制(如300个),订阅超过限制会导致数据重复填充。对应接口为subscribe_quote和get_market_data_ex(subscribe=True)。可以获取实时和历史数据,适合对特定品种进行持续跟踪。

参考
#coding:gbk
"""
QMT 行情数据三种类型教学示例
本文件整合了本地数据、全推数据和订阅数据的简单示例,
仅供学习参考,不构成投资建议。
"""
# =============================================================================
# 示例一:本地数据 (Local Data)
# -- 适合回测模式,速度快,无数量限制,盘中不更新。
# =============================================================================
def handlebar_local_data(C):
"""
在回测模型的 handlebar 函数中,获取本地历史数据。
核心:get_market_data_ex(subscribe=False)
"""
# 获取浦发银行(600000.SH) 在 2022年1月的日线数据
data = C.get_market_data_ex(
fields=['close', 'open', 'high', 'low', 'volume'],
stock_code=['0.SH'],
period='1d',
start_time='20220101',
end_time='20220131',
subscribe=False # 关键参数:不订阅,只读本地
)
# 打印数据查看
print("本地数据示例 - 浦发银行2022年1月数据:")
print(data['0.SH'])
# =============================================================================
# 示例二:全推数据 (Full Push Data)
# -- 适合实盘实时监控,实时更新(50ms),无历史数据,无需订阅,无数量限制。
# =============================================================================
def init_full_push(C):
"""
在实盘模型的 init 函数中,设置要关注的股票列表。
"""
C.stock_list = ["01.SZ", "1.SH", "0.SH"]
def handlebar_full_push(C):
"""
在实盘模型的 handlebar 函数中,获取全推最新数据。
核心:get_full_tick()
"""
# 调用 get_full_tick 获取全推最新行情
full_tick_data = C.get_full_tick(C.stock_list)
# 打印贵州茅台的最新行情
maotai_tick = full_tick_data.get('9.SH')
if maotai_tick:
print("\n全推数据示例 - 最新行情:")
print("最新价:", maotai_tick['lastPrice'])
print("买一价:", maotai_tick['bidPrice'])
print("卖一价:", maotai_tick['askPrice'])
# =============================================================================
# 示例三:订阅数据 (Subscribed Data)
# -- 适合实盘精细跟踪,可获取指定品种的实时和历史数据,有数量限制(如300个)。
# =============================================================================
def callback_subscribed_data(data):
"""
订阅数据的回调函数,当有新的数据推送时,此函数会被调用。
"""
print("\n订阅数据示例 - 收到行情推送:")
print(data)
def init_subscribed_data(C):
"""
在实盘模型的 init 函数中,订阅指定品种的行情。
核心:subscribe_quote()
"""
# 订阅贵州茅台(600519.SH)的日线行情,并指定回调函数
C.sub_id = C.subscribe_quote(
stock_code='19.SH',
period='1d', # 订阅周期
dividend_type='none', # 不复权
callback=callback_subscribed_data # 指定回调函数
)
print("\n订阅成功,订阅号:", C.sub_id)
"""
使用说明:
1. 在实际使用时,您需要根据策略类型(回测/实盘)选择对应的函数。
2. 示例一(handlebar_local_data)主要用于回测模型的 handlebar 中。
3. 示例二(handlebar_full_push)通常需要配合 init(init_full_push)使用,适用于实盘。
4. 示例三(init_subscribed_data 和回调函数 callback_subscribed_data)适用于实盘,用于精细跟踪特定品种。
"""
更多的内容后面继续更新

不懂的问我就可以,加我备注入群可以加入量化研究群

量化需要的找我就可以,专业技术支持服务,解答
