
用QMT写策略几乎所有代码的第一行都离不开ContextInfo。很多人刚接触QMT时看官方文档里init(ContextInfo)和handlebar(ContextInfo)这两个模板函数以为它只是个普通入参随便用用就行结果写了两周策略被各种莫名其妙的报错和字段差异折磨到怀疑人生。我这几年用QMT做策略开发和实盘在ContextInfo上踩过的坑不算少有些问题官方文档根本没写清楚有些问题甚至在不同券商版本的QMT里表现完全不一样。所以这次想把关于ContextInfo的注意点系统整理一下不是照抄文档而是从实际使用的角度说清楚哪些地方容易出问题、怎么排查、怎么避免。如果你正在用QMT写策略或者刚从别的量化平台切到QMT这篇文章应该能帮你少走不少弯路。1. 先搞清楚ContextInfo到底是什么别拿它当普通变量1.1 ContextInfo在QMT策略里扮演什么角色QMT的Python策略是一个典型的回调式框架你写两个函数一个叫init一个叫handlebarQMT会在特定时机自动调用它们并且把ContextInfo这个对象传进来。init函数在整个策略生命周期里只执行一次适合做初始化操作比如设置股票池、订阅标的、加载模型参数。handlebar函数则会在每个K线周期、每个tick或者定时任务触发时被反复调用你的交易逻辑基本都写在这里。而ContextInfo就是架在策略代码和QMT终端之间的桥梁。它把行情数据、合约信息、账户资金、持仓、委托、成交、参数配置、日志输出这些能力全封装在一起。可以说没有ContextInfo你的策略就是个没法获取任何数据的空壳。关键点在于ContextInfo不是你自己new出来的而是QMT在每次调用回调函数时动态传入的。这意味着你在init里对ContextInfo做的设置在handlebar里是能拿到的因为这个对象在整个策略进程里是同一个实例至少主流版本里是这样的。这个特性用好了可以在init里统一做一些一次性配置让handlebar里的代码更干净。1.2 一个最基础的使用骨架很多QMT策略模板长这样def init(ContextInfo): # 初始化操作只执行一次 ContextInfo.set_universe([000001.SZ]) pass def handlebar(ContextInfo): # 每个bar周期触发 # 获取当前标的的最新收盘价 close_price ContextInfo.get_market_data([close], stock_code[000001.SZ], period1d) pass这里有个细节要提醒函数签名里的参数名写成ContextInfo只是一种约定并不是强制。你写成C、ctx、context都行QMT不关心参数名它只按位置把对象传进来。但强烈建议统一用ContextInfo方便阅读和搜索也避免你自己在代码里把参数名改动后漏改引用。另一个很容易忽略的问题是不同券商发布的QMT版本对接口的支持存在差异。比如set_universe有些版本里叫ContextInfo.set_universe有些版本里写SetUniverse也一样能跑还有一些版本这俩都存在。这种版本差异是ContextInfo使用中最大的隐形坑后面我会专门说怎么应对。2. 高频接口实测get_market_data返回结构到底长什么样2.1 版本差异导致返回结构完全不同get_market_data是ContextInfo里最常用的接口用来获取K线历史数据。可它恰恰是我见过坑最多的接口没有之一。老版本的QMT里调用get_market_data返回的通常是一个dict外层key是字段名内层是标的代码到数据序列的映射。但新版QMT有些环境下返回的却是pandas DataFrame甚至有的版本里同一个接口在不同调用模式下返回结构都不一样。这意味着你从网上抄来的旧策略代码很可能在这里直接报错。遇到这种情况最有效的办法就是先打印看实际返回的结构到底是什么。比如这样def init(ContextInfo): data ContextInfo.get_market_data([close, volume], stock_code[000001.SZ], period1d, start_time20240101, end_time20240110) print(type(data)) print(data)不要猜不要假设直接把你拿到的数据结构打出来一目了然。我在实际开发中凡是接一个新版本QMT第一件事就是跑这段代码确认结构。2.2 字段命名文档说的和实际存在的不一样比返回结构更折磨人的是字段名。QMT的很多文档里写的是英文完整单词比如lastPrice、volume、turnover但实际代码里取字段时可能要写close、vol甚至不同版本之间这些字段的拼法还有差异。我整理了几个常见的字段差异你在开发时务必以实际打印为准文档或旧代码习惯实际可能需要的写法说明lastPricecloseK线相关数据一般用closevolumevol成交量字段名有双重写法amountturnover成交额的定义也容易混openIntopen_interest持仓量字段m_strInstrumentIDticker / code合约代码在不同数据结构里名字不同不要觉得这是小事字段名写错导致取出来的是NaN或者报KeyError排查起来非常浪费时间。我的习惯是先用print把返回的dict或DataFrame的columns打印出来再对着写代码。2.3 周期、复权与当前K线的细节get_market_data的period参数控制K线周期常见取值有1m、5m、1d、tick等。其中tick数据比较特殊返回的结构和普通K线差异很大实盘策略里如果用到tick一定要单独测试。复权也是一个容易出问题的地方。部分QMT版本默认返回的是不复权数据但很多人写策略回测时直接拿不复权数据计算遇到除权除息日信号会突然变乱。如果策略依赖历史价格做计算建议在init里显式设置复权方式或者获取数据后自己做复权处理总之不要依赖默认值。另外你要特别注意在handlebar里取到的最后一根K线往往是当前正在形成的未收盘K线它的close、high、low是实时变化的。很多新手直接用这根K线的close触发交易信号结果回测和实盘对不上。稳妥的做法是通过ContextInfo.barpos获取当前K线索引只对已经收盘的K线做信号判断。3. 回测与实盘环境ContextInfo的行为差异3.1 init与handlebar在实盘里的执行节奏和回测不一样回测时QMT按历史bar逐根推进handlebar每根bar调用一次时序非常规整。但实盘环境下handlebar的触发频率取决于你选择的行情周期和推送机制有时是每笔tick推送有时是定时触发。最直接的后果是你在回测里写的一段“每根bar执行一次”的逻辑实盘里可能在几秒内被触发多次导致重复下单。我自己就遇到过这种问题回测里信号只出现一次实盘却连下了好几笔单。解决办法是引入节流机制。比如在init里记录上一次交易时间handlebar里判断当前时间与上次交易时间的间隔小于某个阈值就跳过。或者干脆只在特定时间点执行下单逻辑比如收盘前5分钟。3.2 读取持仓、委托、成交数据时字段名有前缀用ContextInfo.get_trading_detail_data可以读取账户的持仓、委托、成交、资金等信息。这个接口返回的不是dict也不是DataFrame而是一个对象列表列表里每个元素代表一条记录。以持仓为例positions ContextInfo.get_trading_detail_data(account_id, stock, position) for p in positions: print(p.m_strInstrumentID, p.m_nVolume, p.m_dAvailable)看到没字段名是m_strInstrumentID、m_nVolume这种带m_前缀的匈牙利命名风格。很多第一次用QMT的人在这里抓狂因为文档里有时候不展示这些字段。而且不同版本返回的字段也不完全一致有的版本持仓对象里没有m_dAvailable只有m_nCanUseVolume之类的字段。读取委托和成交也是类似套路建议写个通用函数把列表里的对象转成dict输出字段名和值这样排查起来方便很多。3.3 实盘数据延迟与登录状态会影响ContextInfo实盘中ContextInfo拿到的行情数据是有延迟的。不同券商机房的推送延迟不一样快照行情和Level-2行情的延迟差异也很大。你拿ContextInfo.get_market_data取到的最新价可能已经是几百毫秒甚至几秒前的价格了做高频交易设计时要把这个因素算进去。另外有一个非常典型的报错叫client is null后面我会专门说。这里先强调一下ContextInfo能不能正常读到交易数据完全取决于QMT终端是否成功登录了交易账户并建立了交易连接。如果终端没登录、账号没激活或者连接断开了你在策略里调ContextInfo的接口拿到的数据就是空的或者直接抛异常。4. 高频报错与排查实录client is null、依赖下载失败4.1 client is null所有交易请求都在裸奔很多QMT用户尤其是用国金券商版本的朋友会在实盘运行时遇到client is null这个报错。从字面上看它表示某个客户端对象为null实际含义是策略想要发起交易请求或读取账户数据时发现交易客户端连接没有建立。排查顺序一般是这样检查QMT终端是否已经成功登录交易账户。只看行情登录是不行的必须确认交易账户也处于登录状态。检查终端里是否选中了正确的资金账号。有些券商的QMT支持多账户如果策略运行时没有指定正确账号就会连到一个未激活的账户上。检查QMT版本和券商站点是否匹配。有些第三方修改版或旧版本会出这类连接问题。要提醒一句网上有人为了解决client is null提供各种绕过方案我的建议是不要用。正确做法是走券商官方通道把登录和连接问题解决好再跑策略否则后患无穷。4.2 Python依赖下载失败与QMT内置环境问题热词里有一条“国金qmt python下载失败”这个我太有感触了。QMT终端内置了一个Python环境但它的版本和第三方库的安装机制跟正常Python发行版不一样。你在系统Python里能正常pip install的包在QMT里可能装不上或者装到了错误的site-packages路径下。解决思路是先找到QMT内置Python的真实路径。通常在QMT安装目录下有一个python或bin文件夹里面是QMT自带的解释器。用这个解释器去安装依赖而不是用系统的pip/path/to/qmt/python/python.exe -m pip install xxx如果下载速度慢或超时可以临时指定国内镜像源/path/to/qmt/python/python.exe -m pip install xxx -i https://pypi.tuna.tsinghua.edu.cn/simple另外还要注意QMT内置环境不一定包含pandas、numpy或者自带的版本比较旧。安装依赖时尽量选兼容QMT内置Python版本的包版本别一味追求最新版否则可能装上后import直接报错。4.3 登录顺序、自动登录和策略运行时机很多人遇到的问题是策略加载先于交易登录完成导致init里读取账户信息时拿到的是空数据。国金等券商版本的QMT支持自动登录但自动登录本身也有状态判断如果登录还没完成你就手动运行策略后面ContextInfo的所有交易相关调用都会失败。实际操作中我会这样做先确保登录完成再启动策略。怎么看登录是否完成看QMT终端的资金账户区域里面能正常显示资金余额和持仓才算真正就绪。千万不要在终端还显示“正在连接...”时就着急跑策略。如果用了自动登录建议在init里加一个短暂延时或者写一个重试机制等交易连接可用后再执行初始化逻辑。这种重试逻辑在实盘里非常重要。5. 我的避坑经验拿到ContextInfo后先做三件事5.1 第一件事打印出它所有的属性和方法每次接触一个新环境、新版本QMT我建议你拿到ContextInfo后第一件事就是给它做个体检。在init里加两行打印def init(ContextInfo): print(dir(ContextInfo)) print(ContextInfo.__dict__ if hasattr(ContextInfo, __dict__) else no dict) passdir(ContextInfo)会列出这个对象所有可访问的属性和方法名这是最真实的接口文档。你会看到set_universe、get_market_data、get_instrument_detail这些你熟悉的名字也会发现文档里没写过但实际存在的方法。多干几次你对自己所用版本的接口认知会非常清晰。5.2 第二件事任何字段都先打印结构再使用这条经验能救命的频率远超你想象。无论是get_market_data返回的dict、get_trading_detail_data返回的对象列表还是get_instrument_detail返回的合约信息一律先打印看清楚有哪些字段、什么类型、嵌套了几层再写具体逻辑。我见过太多人拿着老教程里的代码直接改最后卡在字段名对不上浪费一整天。真的十几秒的打印能省一天的排查时间这笔账怎么算都划算。5.3 第三件事别把外部系统接入的希望寄托在ContextInfo上有朋友想做“类外接 qmt”即在外部Python进程里控制QMT策略。这里必须说清楚ContextInfo只存在于QMT策略进程的回调函数里外部Python脚本根本拿不到这个对象。想在终端外面调策略、读数据需要走QMT官方提供的其他通道而不是在外部程序里模拟ContextInfo。如果你有类似需求建议先确认QMT是否提供了对应的外部API或数据服务再考虑架构设计不要一开始就想着抓取终端窗口、模拟点击这种方式既不安全也不稳定。5.4 日志与性能别在handlebar里无脑print调试的时候print很爽但实盘环境里handlebar可能被高频触发每次print都会往日志文件里写东西时间久了性能会明显下降。更麻烦的是某些版本QMT里print输出会阻塞策略线程导致行情积压、策略延迟放大。我的做法分两步init里设置一个日志开关handlebar里只在开关开启时才print实盘跑一段时间后把这个开关关掉。这样既能保留调试能力又不影响实盘性能。另外QMT自带日志功能可以根据日志级别输出到终端日志面板比print更规范也更方便在实盘部署时查看。写在最后的几个细节用ContextInfo三年多我踩过最大的坑就是太相信文档里的字段名和返回结构。尤其是从老版本QMT切换到新版本或者从一个券商换到另一个券商时接口看起来差不多实际行为可能面目全非。现在我每接一个新环境一定先打印、再验证、最后才写正式逻辑。还有一点要提醒QMT策略本身是进程内运行的ContextInfo里的数据都是内存态不要指望它在策略停止后还能保留。如果你需要在策略重启后恢复状态就得自己把它写入文件或数据库这也是一个很多人忽略的细节。最后再分享一个小技巧在handlebar里写交易逻辑时先判断当前bar是否已经收盘再判断信号是否有效最后用日志开关记录每一笔下单前的关键信息。这套流程看着简单但能帮你避开实盘里至少80%的怪问题。ContextInfo用好了它就是你的得力助手用不好它就是你排查到深夜的噩梦源头。