Quote 为什么会自己变化?TqSdk 盘口对象的读取方法
TqSdk 的 Quote 不是一次请求得到的一张静态字典,而是与事件循环相连的动态引用。调用get_quote后,变量一直指向同一个行情对象;每次wait_update收到新数据,TqSdk 会原地更新它。于是代码没有重新赋值,last_price、买卖盘和时间仍会变化。理解这种“同一对象、内容更新”的模式,是正确读取实时行情的第一步。
先建立订阅,再让数据进入循环
下面的程序订阅螺纹钢主连行情,只在最新价变化时输出盘口。主连适合行情观察,示例不包含任何交易动作。
import math import os from tqsdk import TqApi, TqAuth credentials = TqAuth( os.environ["TQ_USER"], os.environ["TQ_PASSWORD"], ) api = TqApi(auth=credentials) quote = api.get_quote("KQ.m@SHFE.rb") try: while True: api.wait_update() if api.is_changing(quote, "last_price"): if math.isfinite(quote.last_price): print( quote.datetime, quote.bid_price1, quote.last_price, quote.ask_price1, ) finally: api.close()get_quote建立读取入口,wait_update才让订阅请求发送并接收后续数据。若删掉事件循环,只在while True中反复打印quote.last_price,读取到的仍是旧快照。给循环加延时也不能替代数据更新。
初次订阅时,一些数值字段可能暂时是无效值。示例使用math.isfinite过滤尚未准备好的最新价,避免把空值直接用于公式。正式程序还应分别检查真正依赖的字段,而不是因为最新价有效,就假设所有盘口档位都完整。
盘口字段回答的是当前可见状态
last_price表示最新价,bid_price1和ask_price1是一档买卖价,配套的bid_volume1与ask_volume1是一档挂单量。datetime用于识别行情时间。把这些字段放在一起,可以看到最新成交与当前买卖盘之间的位置关系。
盘口是某一时刻的可见快照,不是参与者真实意图的证明。买量增加可能来自新增挂单,也可能伴随其他档位撤单;下一次更新就可能消失。不能仅凭一档数量变化断言市场一定上涨或下跌,更不能把买一价当成自己的委托必然成交价。
还要注意“行情中出现过某个价格”和“自己的订单成交”是两件事。交易回报需要从委托和成交对象确认,受到报单时间、价格、队列和撮合影响。Quote 适合产生观察条件,不负责证明交易结果。
is_changing 应监听真正关心的字段
一次wait_update返回时,Quote 中可能只改变了时间、某一档盘口或成交量。如果程序只关心最新价,就监听last_price;若逻辑依赖买卖盘,则分别监听对应字段。不要每轮无条件重算全部条件。
while True: api.wait_update() price_changed = api.is_changing(quote, "last_price") bid_changed = api.is_changing(quote, "bid_price1") ask_changed = api.is_changing(quote, "ask_price1") if price_changed: update_last_price(quote.last_price) if bid_changed or ask_changed: update_spread(quote.bid_price1, quote.ask_price1)这里的两个函数只是业务结构占位。它表达的重点是:不同字段变化可以触发不同处理。若所有变化都进入同一段交易代码,盘口每跳一次就可能重复发出相同意图。
变化检测只描述本轮数据包是否改了字段,不代表这个变化一定具有交易意义。价格从一个值变到另一个值是事实,是否构成信号则由策略规则决定。把数据变化与业务判断分成两层,代码更容易测试。
动态引用最容易造成哪些误解
第一个误解是把初始化值当成正式行情。订阅后立刻读取对象时,数据可能尚未到齐,应先经过事件循环并验证字段。第二个误解是把变量保存下来就等于保存历史。Quote 会继续更新,若需要记录当时状态,应复制具体字段和值,而不是只保存这个对象的引用。
第三个误解是跨函数共享对象时忽略更新时间。一个函数可能在本轮更新后读取,另一个函数可能使用上轮缓存。可以在状态中同时保存行情时间,只有时间符合预期才允许组合计算。
第四个误解是同时订阅很多合约后,每次循环都遍历并处理全部对象。更稳妥的方式是逐个检查是否变化,只更新有事件的合约。合约数量增加时,这种差异会明显影响日志可读性和重复计算量。
若要把 Quote 状态写入日志,建议先抽取一份普通字典,内容至少包括合约、行情时间、最新价和实际使用的盘口字段。不要直接把整个动态对象反复序列化:字段太多会掩盖真正关心的变化,也可能让同一条日志在查看时失去清楚含义。
def quote_snapshot(symbol, quote): return { "symbol": symbol, "datetime": quote.datetime, "last_price": quote.last_price, "bid_price1": quote.bid_price1, "ask_price1": quote.ask_price1, }这份快照只表达函数调用时看到的值,后续 Quote 更新不会反向修改它。测试时可以保存连续几份快照,检查时间是否递增、价格是否在合理字段中变化,以及业务函数是否只响应预定字段。
从 Quote 进入策略前要补的边界
Quote 只能提供行情输入。完整策略还要明确信号使用哪些字段、连续满足时是否重复触发、已有持仓和活动委托如何处理、异常数据是否暂停动作。若规则只写“买盘强就开仓”,没有定义强度、持续时间和重复条件,问题不在行情对象,而在规则尚未量化。
主连 Quote 适合连续观察品种,但主连不是具体月份合约。研究信号若准备执行,需要选择实际可交易合约,并处理换月与持仓迁移。不能把研究代码中的主连代码直接交给下单函数。
行情时间也不能被本机打印时间替代。网络延迟、程序阻塞和系统时钟都会让本地接收时间与市场数据时间不同。诊断延迟时可以同时记录两者,但策略对齐应清楚自己使用的是哪一个时间概念。
不同交易时段还会出现行情长时间不变化的正常情况。程序应知道当前是等待、休市还是数据异常,而不是设置一个固定秒数后自动认定连接失败。可以结合合约交易时间和连接日志做判断,但任何不确定状态都不应自动转成交易信号。
最后,Quote 校验应使用少量可解释样本。手工观察几次datetime、买一、卖一和最新价的变化,再与程序触发日志对照,能快速发现字段写错或变化入口不对。直接运行数小时后只看最终交易结果,往往无法追溯早期的行情读取错误。
Quote 读取清单
get_quote后持续调用wait_update,不靠重复读取制造“更新”。- 初始阶段检查字段有效性,只使用真正准备好的数据。
- 通过
is_changing监听相关字段,把行情事件与交易判断分开。 - 需要保存历史时复制字段和值,不把动态引用当成静态记录。
- 盘口、市场成交和自己的成交回报分别核验,主连研究与具体合约执行分离。
Quote 看似只是几个价格字段,真正重要的是它背后的更新模型。只要把引用、事件和业务状态分清,程序就能知道何时得到新行情、何时应该计算,以及哪些结论不能从盘口直接推出。
