天勤TqSdk从零基础安装部署到独立策略编写【保姆喂饭级教程】 第 5 天

手把手详细学习内容:合约查询、Quote 字段与行情对象读法

今天继续根据 TqSdk 文档做具体学习:不急着写下单逻辑,而是先学会"找合约、看合约身份、读 Quote 字段、判断字段是否有效"。只有你能稳定识别合约和字段,后面的交易、回测、风控脚本才不会建立在错误行情上。

今天的学习方式
不是计划表,而是边读边做。每个脚本都会告诉你:现在做什么、为什么做、应该看到什么、出错怎么查。
今天仍然以行情学习为主,通常不需要真实交易账户;示例只需要填写 TqAuth 账号密码。
示例优先使用主力合约符号,减少固定交割月份过期导致的学习干扰。

0. 先接上第 4 天:今天要把"行情数据"读得更准确

第 4 天你已经会订阅 K 线和 Tick,并知道它们会随着 wait_update 原地更新。今天继续向前一步:当你拿到一个 quote、K 线或 Tick 时,你要知道哪些字段该读、字段代表什么、字段什么时候可能还没有有效值。

**今天你要形成的核心感觉:**TqSdk 里的行情对象不是一堆随便打印的属性,而是有用途分层的:先看合约身份,再看交易状态,再看价格盘口,最后才做价差、最小变动价位、合约乘数等计算。

今天不要犯的第一个错
不要在策略里只凭 symbol 字符串猜合约身份。
正确做法是:用 quote 或 query_symbol_info 看 instrument_name、instrument_id、exchange_id、ins_class、expired 等字段。

1. 先认识 Quote:它是实时行情对象,不是一次性快照

TqSdk 文档中,get_quote 用来获取单个合约的实时行情。它返回的 quote 对象会在 wait_update 之后被更新。因此你的代码结构仍然是:循环外 get_quote 一次,循环里 wait_update,然后读取字段变化。

2. 现在动手:打印 Quote 的最小可用字段集

请新建 day5_quote_min_fields.py。这个脚本只做一件事:订阅螺纹钢主力合约 quote,等行情更新后打印最小字段集。

day5_quote_min_fields.py

from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("KQ.m@SHFE.rb")

try:
    while True:
        api.wait_update()

        # 只在你关心的价格和盘口字段变化时打印
        if api.is_changing(quote, ["last_price", "ask_price1", "bid_price1"]):
            print("时间:", quote.datetime)
            print("合约名:", quote.instrument_name)
            print("最新价:", quote.last_price)
            print("买一/卖一:", quote.bid_price1, quote.ask_price1)
            print("成交量:", quote.volume)
            print("持仓量:", quote.open_interest)
            break
finally:
    api.close()

2.1 运行脚本

命令行运行

python day5_quote_min_fields.py

你应该看到时间、合约名、最新价、买一、卖一、成交量和持仓量。今天先不要把 quote 全部打印出来;先养成"只打印当前任务需要的字段"的习惯。

如果没有很快看到输出
先确认是否为交易时段,或者该品种当前是否有行情变化。
确认 TqAuth 的账号密码是否正确。
确认 symbol 没写错;主力合约 KQ.m@SHFE.rb 通常比固定月份更适合作为学习样例。
如果行情字段一开始为空或不是有效数字,先等待 wait_update 收到业务数据。

3. 逐行拆解:为什么只监听三个字段

**老师提醒:**is_changing 不是必须永远只监听三个字段。今天这样写,是为了训练你"按任务过滤变化"的习惯;后面写策略时,要根据触发条件选择字段。

4. 现在动手:看清合约身份,不要只看价格

交易脚本出错,有时不是算法错,而是合约认错。下面脚本专门打印合约身份字段,帮助你确认:这个 symbol 最终对应什么合约、是否过期、交易所和品种是什么。

day5_contract_identity.py

from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("KQ.m@SHFE.rb")

try:
    while True:
        api.wait_update()
        if api.is_changing(quote):
            print("instrument_name:", quote.instrument_name)
            print("instrument_id:", quote.instrument_id)
            print("exchange_id:", quote.exchange_id)
            print("ins_class:", quote.ins_class)
            print("underlying_symbol:", quote.underlying_symbol)
            print("expired:", quote.expired)
            break
finally:
    api.close()

今天的关键习惯
看到一个 symbol,不要只问“价格是多少”。
还要问:它是不是我要的品种?是不是当前可用?有没有过期?交易所和合约类型是否符合预期?

5. 现在动手:用 query_cont_quotes 找主力合约

固定交割月份会过期,学习文档和长期样例中更适合使用主力合约或先查询当前可用合约。下面脚本用 query_cont_quotes 查询上海期货交易所螺纹钢相关连续/主力合约。

day5_query_cont_quotes.py

from tqsdk import TqApi, TqAuth

with TqApi(auth=TqAuth("快期账户", "账户密码")) as api:
    conts = api.query_cont_quotes(exchange_id="SHFE", product_id="rb")
    print("查询到的连续/主力合约:")
    for symbol in conts:
        print(symbol)

你应该看到一组连续或主力相关合约代码。实际返回内容会随交易所、品种和当前合约状态变化。这里的学习重点是:不要死记一个固定月份,而是学会查询。

**实战迁移:**写教学代码、监控脚本或长期运行工具时,优先考虑主力/连续合约或先查询当前合约;写真实交易时,则必须非常清楚自己下单的是具体哪个合约。

6. 现在动手:用 query_symbol_info 查看静态信息表

query_symbol_info 返回的是静态合约信息表,不是 quote 这样的实时对象。它适合一次性查看合约名称、类型、最小变动价位、合约乘数等基础信息。

day5_symbol_info.py

from tqsdk import TqApi, TqAuth

with TqApi(auth=TqAuth("快期账户", "账户密码")) as api:
    symbols = api.query_cont_quotes(exchange_id="SHFE", product_id="rb")
    info = api.query_symbol_info(list(symbols))

    cols = ["instrument_id", "instrument_name", "ins_class", "price_tick", "volume_multiple"]
    print(info[cols])

7. 认识 price_tick:价格不能随便写小数

TqSdk 文档中的 quote.price_tick 表示最小价格变动单位。以后你下限价单时,价格必须符合交易所允许的最小变动价位。今天先不下单,只做价格检查。

day5_price_tick_and_spread.py

from math import isfinite
from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("KQ.m@SHFE.rb")

try:
    while True:
        api.wait_update()
        if api.is_changing(quote, ["ask_price1", "bid_price1", "price_tick"]):
            print("最小变动价位:", quote.price_tick)
            print("买一:", quote.bid_price1)
            print("卖一:", quote.ask_price1)

            if isfinite(quote.ask_price1) and isfinite(quote.bid_price1):
                spread = quote.ask_price1 - quote.bid_price1
                print("买卖价差:", spread)
            break
finally:
    api.close()
为什么这里用 isfinite
行情刚订阅时,某些价格字段可能还没有有效数值。
计算价差前先判断是否为有效数字,可以避免把无效字段拿去做运算。
这个习惯后面写下单价格、止损价格、滑点逻辑时非常重要。

quote.volume_multiple 表示合约乘数。期货价格变化 1 个点对应的盈亏,通常要乘以合约乘数;如果价格只变化一个 price_tick,也要用 price_tick * volume_multiple 估算每手跳动价值。

day5_tick_value.py

from math import isfinite
from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("KQ.m@SHFE.rb")

try:
    while True:
        api.wait_update()
        if api.is_changing(quote):
            print("合约:", quote.instrument_name)
            print("最小变动价位:", quote.price_tick)
            print("合约乘数:", quote.volume_multiple)

            if isfinite(quote.price_tick) and isfinite(quote.volume_multiple):
                tick_value = quote.price_tick * quote.volume_multiple
                print("每手每跳价值估算:", tick_value)
            break
finally:
    api.close()

**老师提醒:**今天只是行情字段学习,不做保证金和盈亏完整试算。后面学习账户、持仓和 TqScenario 时,才会把手续费、保证金率、持仓方向等因素一起考虑。

9. 现在动手:查看交易状态

get_trading_status 用来查看合约当前交易状态。你在非交易时段运行行情脚本时,行情变化少并不一定代表代码错了,先看交易状态会更稳。

day5_trading_status.py

from tqsdk import TqApi, TqAuth

with TqApi(auth=TqAuth("快期账户", "账户密码")) as api:
    status = api.get_trading_status("KQ.m@SHFE.rb")
    print("交易状态:", status)
如何理解这个脚本
它不是交易信号,只是帮助你判断合约当前交易状态。
如果非交易时段 quote 更新很少,先不要急着改代码。
调试行情脚本时,合约状态、交易时间、品种活跃度都要一起看。

10. 错误实验一:用固定过期合约做长期样例

下面是常见错误:在学习文档或长期脚本中写死某个交割月份。这个代码今天可能能跑,过一段时间可能就因为合约过期而不适合继续使用。

不推荐作为长期学习样例

# 容易失效的学习写法:固定月份可能会过期
quote = api.get_quote("SHFE.rb2505")

改法是:学习行情订阅时优先用主力合约;需要具体月份时,先通过 query_quotes 或 query_symbol_info 确认合约仍然有效,再继续写后续逻辑。

更适合学习阶段

# 更稳的学习写法:使用主力合约符号,或者先查询当前合约
quote = api.get_quote("KQ.m@SHFE.rb")

11. 错误实验二:把 query_symbol_info 当实时行情用

query_symbol_info 返回的是静态信息表,适合看合约基础资料;如果你想监听最新价、买一卖一、成交量变化,应该用 get_quote。

错误思路

# 错误思路:拿静态信息表去等待实时价格变化
info = api.query_symbol_info(["KQ.m@SHFE.rb"])
while True:
    api.wait_update()
    print(info)  # 这不是实时 quote 监听方式

正确思路

# 正确思路:实时行情用 get_quote
quote = api.get_quote("KQ.m@SHFE.rb")
while True:
    api.wait_update()
    if api.is_changing(quote, ["last_price", "ask_price1", "bid_price1"]):
        print(quote.datetime, quote.last_price, quote.bid_price1, quote.ask_price1)

12. 把今天的字段读法整理成一张速查表

13. 常见错误排查:第 5 天专用清单

我是否把 quote 当成一次性快照?如果是,改成 wait_update 循环中读取。

我是否只看 last_price,不看合约是否过期、交易所和合约类型?

我是否用固定交割月份写长期学习样例?如果是,考虑主力合约或先查询合约。

我是否把 query_symbol_info 当成实时行情监听对象?实时价格变化应使用 get_quote。

我计算 bid/ask 价差前,是否确认字段是有效数字?

我是否把 price_tick 和 volume_multiple 忘掉了?以后下单价格和盈亏估算都会用到。

我在非交易时段测试时,是否先看了 trading_status?

我是否在 while True 里面反复 get_quote?正确做法通常是循环外订阅一次。

14. 课堂练习:你现在来写,写完再看答案

练习 1:打印 quote 的身份字段

要求:订阅 KQ.m@SHFE.rb,打印 instrument_name、instrument_id、exchange_id、ins_class、expired。

参考答案

from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("KQ.m@SHFE.rb")

try:
    while True:
        api.wait_update()
        if api.is_changing(quote):
            print(quote.instrument_name, quote.instrument_id, quote.exchange_id, quote.ins_class, quote.expired)
            break
finally:
    api.close()

练习 2:打印买一卖一价差

要求:当 bid_price1 或 ask_price1 变化时,打印价差。计算前要判断两个字段是否有效。

参考答案

from math import isfinite
from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("KQ.m@SHFE.rb")

try:
    while True:
        api.wait_update()
        if api.is_changing(quote, ["bid_price1", "ask_price1"]):
            if isfinite(quote.bid_price1) and isfinite(quote.ask_price1):
                print("价差:", quote.ask_price1 - quote.bid_price1)
                break
finally:
    api.close()

练习 3:说出下面代码的问题

题目代码

info = api.query_symbol_info(["KQ.m@SHFE.rb"])
while True:
    api.wait_update()
    print(info["last_price"])

答案:query_symbol_info 是静态信息表,不是实时 quote;它不适合监听 last_price。实时价格应使用 quote = api.get_quote(…),然后在 wait_update 循环中读取 quote.last_price。

练习 4:什么时候先查询合约?

题目:你准备写一个长期使用的螺纹钢行情监控脚本,应该直接写死 SHFE.rb2505,还是优先考虑 KQ.m@SHFE.rb 或 query_cont_quotes?

答案:长期学习或监控样例优先考虑主力合约或先查询当前可用合约。固定月份合约会过期,适合在你明确要交易某个具体合约时使用。

15. 今日复盘:你要能独立说出来的 10 句话

· get_quote 返回的是会随 wait_update 更新的实时行情对象。

· quote.datetime 可以帮助确认行情时间。

· quote.last_price 是最新成交价,但不能只看它。

· bid_price1 和 ask_price1 是买一和卖一,计算价差前要确认有效。

· price_tick 是最小变动价位,后续下单价格必须考虑它。

· volume_multiple 是合约乘数,后续估算每跳价值和盈亏会用到。

· instrument_name、instrument_id、exchange_id、ins_class 用来确认合约身份。

· expired 用来判断合约是否过期。

· query_symbol_info 是静态信息表,不是实时行情对象。

· 长期学习样例不要随便写死固定交割月份,优先考虑主力合约或先查询。

16. 今天的完整最终版脚本

最后把今天最重要的写法合并成一个脚本:先查询主力合约,再订阅 quote,打印合约身份、交易状态、盘口价差和每手每跳价值。

day5_final_quote_contract_fields.py

from math import isfinite
from tqsdk import TqApi, TqAuth

api = TqApi(auth=TqAuth("快期账户", "账户密码"))

try:
    symbols = api.query_cont_quotes(exchange_id="SHFE", product_id="rb")
    print("查询到的连续/主力合约:", list(symbols))

    symbol = "KQ.m@SHFE.rb"
    quote = api.get_quote(symbol)
    status = api.get_trading_status(symbol)
    print("交易状态:", status)

    while True:
        api.wait_update()

        if api.is_changing(quote, ["last_price", "bid_price1", "ask_price1"]):
            print("合约名称:", quote.instrument_name)
            print("合约代码:", quote.instrument_id)
            print("交易所:", quote.exchange_id)
            print("合约类型:", quote.ins_class)
            print("是否过期:", quote.expired)
            print("行情时间:", quote.datetime)
            print("最新价:", quote.last_price)
            print("买一/卖一:", quote.bid_price1, quote.ask_price1)
            print("最小变动价位:", quote.price_tick)
            print("合约乘数:", quote.volume_multiple)

            if isfinite(quote.bid_price1) and isfinite(quote.ask_price1):
                print("买卖价差:", quote.ask_price1 - quote.bid_price1)

            if isfinite(quote.price_tick) and isfinite(quote.volume_multiple):
                print("每手每跳价值估算:", quote.price_tick * quote.volume_multiple)

            break
finally:
    api.close()

17. 明天开始前的准备

明天会继续向交易账户和账户类型过渡。开始前,你应该确保今天的 final 脚本能运行,并且你能解释:quote 为什么要在 wait_update 后读,query_symbol_info 为什么不是实时行情,price_tick 和 volume_multiple 为什么重要。

第 5 天完成标准
你能独立写出 get_quote 的基本脚本。
你能说出 quote 常用字段的含义。
你能用 query_cont_quotes 或 query_symbol_info 辅助确认合约。
你知道固定月份合约会过期,长期样例要谨慎。
你能在计算价差或跳动价值前判断字段是否有效。