上一篇讲完了环境、生命周期和下单。这篇讲三个同样高频、但更容易被文档误导的部分:品种判定、行情取数、交易对象字段。
最后一节讲一个我认为最有用的方法:怎么让客户端自己回答「这个语法到底行不行」。
一、品种判定:优先用 C.is_stock,别手写代码前缀表
早期判品种基本靠手写前缀(110 是沪市转债、123 是深市转债……),又长又容易漏。QMT 其实给了现成的判定函数,实测准确:
C.is_stock('600901.SH') # True —— 沪深A股(主板 / 创业板 / 科创板)
C.is_stock('510300.SH') # False —— ETF 不是股票
C.is_stock('000001.SH') # False —— 指数不是股票
C.is_fund('510300.SH') # True —— ETF注意只传 1 个参数,而且是完整代码。 写成 C.is_stock('SH', '600901') 会报 takes 2 positional arguments but 3 were given。
有两个函数名很像,但不能盲用:
C.is_suspended_stock('000001.SH')返回 True —— 它按「当前是否可交易」判定,非股票代码一律返回 True,不能当个股停牌查询。C.get_stock_type(code)在客户端里恒返回 0,不可用。
一个会静默返回空的板块名陷阱
get_stock_list_in_sector(name) 是取板块成分股的主力接口。但名字不认识时它不报错,只返回空列表 —— 这是最容易误判的地方:
+------------------+---------+------------------------------------------+
| 板块名 | 数量 | 说明 |
+------------------+---------+------------------------------------------+
| 沪深A股 | 5226 | 可用 |
+------------------+---------+------------------------------------------+
| 沪深京A股 | 5574 | 可用(含北交所) |
+------------------+---------+------------------------------------------+
| 沪深ETF | 1727 | 可用 |
+------------------+---------+------------------------------------------+
| 沪深转债 | 318 | 可用 —— 判可转债就用这个 |
+------------------+---------+------------------------------------------+
| 沪深300 | 300 | 可用 |
+------------------+---------+------------------------------------------+
| 中证500 | 500 | 可用 |
+------------------+---------+------------------------------------------+
| 沪深可转债 | 0 | X 最顺口的名字反而返回空 |
+------------------+---------+------------------------------------------+
| 可转债 / 转债 | 0 | X |
+------------------+---------+------------------------------------------+
| ETF | 0 | X 必须写「沪深ETF」 |
+------------------+---------+------------------------------------------+判可转债时直觉会写 '沪深可转债',返回 0,然后你会以为「今天没有可转债」。正确名字是 '沪深转债'。
is_typed_stock 的类型码:手册有表,但不全生效
is_typed_stock(type_num, market, code) 返回 1 / 0。手册里有完整分类表,实测只有一部分在本机生效:
+------------------+--------------------------------------------+---------+
| 类型码 | 实测 | 可用 |
+------------------+--------------------------------------------+---------+
| 100001 | 沪深A股,不命中 ETF / 债 / 回购 / 指数 | OK |
+------------------+--------------------------------------------+---------+
| 100013 | ETF(沪、深) | OK |
+------------------+--------------------------------------------+---------+
| 100029 | 主板,不命中创业板 / 科创板 | OK |
+------------------+--------------------------------------------+---------+
| 100005 | 指数 | OK |
+------------------+--------------------------------------------+---------+
| 100016 | 债券(含可转债,不区分) | OK |
+------------------+--------------------------------------------+---------+
| 100020 / 100021 | 债券回购 / 质押式回购,判逆回购用 | OK |
+------------------+--------------------------------------------+---------+
| 135 | 沪市可转债(真实样本命中) | OK |
+------------------+--------------------------------------------+---------+
| 100011 | 深市可转债(真实样本命中) | OK |
+------------------+--------------------------------------------+---------+
| 100004 | 会把 159xxx ETF 一起判成封基 | X 陷阱 |
+------------------+--------------------------------------------+---------+
| 100019 | 手册写「深市可转债」,实测不生效 | X |
+------------------+--------------------------------------------+---------+
| 100040~100047 | 各期限逆回购,实测不生效 | X |
+------------------+--------------------------------------------+---------+两个结论值得单独说。
第一,深市可转债是 100011,不是手册写的 100019。 我用板块名单里的真实转债复核过:110075.SH 命中 135,127025.SZ、127027.SZ 命中 100011,而 100019 对真代码、假代码都不命中。第一轮验证时我拿的是一个已摘牌的 110055.SH,导致得出「135 未实现」的错误结论 —— 样本一定要取自板块名单,不要凭记忆写代码。
第二,它不校验标的真实存在,但也不是纯代码段匹配。 用一个不存在的代码 159999.SZ 去问,命中的类型码和真实的 159915.SZ 完全一样,说明未登记代码走代码段兜底。但真实存在的 110075.SH 命中 135、已摘牌的 110055.SH 不命中,又说明已登记代码会查类型表。
所以:不要用它判断「这个代码存不存在」,也不要拿「未命中」去反推真实标的的类型。
判品种的优先级:C.is_stock / C.is_fund → 板块名单 → is_typed_stock。
顺带两个能省不少事的函数:
is_sector_stock('沪深300', 'SH', '600000') # -> 1 浦发银行在沪深300内
is_sector_stock('沪深300', 'SH', '600901') # -> 0
get_industry_name_of_stock('SW', '600901.SH') # -> 'SW1非银金融'(申万)
get_industry_name_of_stock('CSRC', '600901.SH') # -> 'CSRC1金融业'(证监会)二、交易日判断:一个能把日志刷爆的坑
策略的第一件事通常是判今天是不是交易日。写法就一行:
today = datetime.datetime.now().strftime('%Y%m%d')
is_trading = bool(C.get_trading_dates('000001.SH', today, today, 1, '1d'))但它有两个坑,我都踩过。
坑一:返回 [] 不代表接口坏了。 实测同一组调用:
C.get_trading_dates('000001.SH', '20261003', '20261003', -1, '1d') -> []
C.get_trading_dates('000001.SH', '20261003', '20261003', 1, '1d') -> []
C.get_trading_dates('000001.SH', '20261003', '20261003', 30, '1d') -> []20261003 是个周六。start == end == 非交易日 → 请求区间为空 → 无论 count 传什么都返回 []。 空返回跟 count 完全无关。
我一开始把这个归因成「文档说 count 必须大于 0,实盘代码却传 -1,所以有问题」,结论是错的。实盘里那个「传 1 判 bool」的写法本来就是对的 —— 交易日返回 [today] → True,非交易日返回 [] → False。
坑二:count=-1 配空区间会拉出 30 多年的数据。
C.get_trading_dates('000001.SH', '', '', -1, '1d')
# -> 1990-12-19 至今的全部交易日,一万多条这是真事儿:我在探针脚本里写了这么一行,结果整份日志被刷爆,后面几个探测段全部丢失,白跑一次。
规则:count=-1 只在有明确 start / end 时用;交易日判断固定写 (今天, 今天, 1, '1d')。
count 的完整语义(用非空区间实测):
+----------+----------------------------------------------+
| count | 返回 |
+----------+----------------------------------------------+
| -1 | 区间内全部交易日 |
+----------+----------------------------------------------+
| 1 | 区间内最后一个交易日 |
+----------+----------------------------------------------+
| 5 | 区间内最后 5 个交易日 |
+----------+----------------------------------------------+
| 30 | 区间内全部(被区间长度截断) |
+----------+----------------------------------------------+另外 start_date / end_date 是 'YYYYMMDD' 或 'YYYYMMDDHHMMSS' 字符串,不传时用 '',不是 None。
三、行情:三套数据源分开用
+--------------+------------------------------+----------------------------+
| 数据源 | 接口 | 何时用 |
+--------------+------------------------------+----------------------------+
| 本地数据 | get_market_data_ex() | 回测、历史统计 |
+--------------+------------------------------+----------------------------+
| 全推数据 | get_full_tick(codes) | 盘中最新快照,数量无上限 |
+--------------+------------------------------+----------------------------+
| 订阅数据 | subscribe_quote(...) | 需要 tick 事件驱动 |
+--------------+------------------------------+----------------------------+(全部写 C. 前缀)
三个必须知道的限制:
init里get_market_data系列只能读到本地数据,取不到实时行情。要取实时得放after_init或定时器。- 全推行情默认可能只给一档盘口。 要用对手价 14 或读五档,必须先把客户端的全推行情级别改成五档,否则会报「对手价无效,无法下单」。这是「策略不报单」的高频原因。
- 超过订阅上限时不会报错,客户端会用前值填充、返回重复数据。 这是「数据看起来对但全是重复值」的根因。
四、交易对象字段:一个会让止盈止损彻底失效的哨兵值
这部分最值得说的是一个实测挖出来的坑。
1.7976931348623157e+308 是「柜台没给这个字段」
实测持仓对象里读到:
m_dAvgOpenPrice = 1.7976931348623157e+308
m_dStaticHoldMargin = 1.7976931348623157e+3081.7976931348623157e+308 是 IEEE 754 双精度最大值(DBL_MAX),在这里的含义是「这个字段无效」,不是数据。
如果拿 m_dAvgOpenPrice 当持仓成本去算止盈止损,价格会变成天文数字,现价 < 成本 × 0.9 恒真、> 恒假 —— 止损会立刻误触发,或者永远不触发。
持仓成本要用 m_dOpenPrice。 顺手加个通用防御:
def valid(x):
return isinstance(x, float) and abs(x) < 1e300m_nDirection 恒为 48,在哪个对象上都是
判买卖方向是 QMT 最经典的字段级坑:m_nDirection 对股票恒为 48,真正的买卖在 m_nOffsetFlag(48 = 买、49 = 卖)。
实测更狠一点:这个 48 不只在委托对象上,账户对象和持仓对象上读到的也是 48。 所以这个字段在股票语境下毫无鉴别力,任何 if x.m_nDirection == 48 都是恒真。
# 正确
is_buy = (int(getattr(o, 'm_nOffsetFlag', 0)) == 48)其它实测确认的字段行为
+----------------------------+------------------------------------------+
| 项 | 实测结论 |
+----------------------------+------------------------------------------+
| m_nCanUseVolume | 正常填充,卖出封顶可以放心用 |
+----------------------------+------------------------------------------+
| get_last_order_id | 返回字符串 '-1'(不是 int),判空写 == ' |
| | -1' |
+----------------------------+------------------------------------------+
| 持仓 m_nVolume 单位 | 是「股」:1300 × 6.94 = 9022.0 = 市值 |
+----------------------------+------------------------------------------+
| 交易函数关键字参数 | 不支持,一律位置传参 |
+----------------------------+------------------------------------------+
| 对象类型名 | 账户 CAccountDetail,持仓 CPositionDetai |
| | l |
+----------------------------+------------------------------------------+
| m_dFloatProfit 与 | 两个不同口径,同一次实测里 117.00 vs 843 |
| | .18 |
+----------------------------+------------------------------------------+
| m_dPositionProfit | |
+----------------------------+------------------------------------------+「持仓量单位是股」这条是用市值反推的:1300 × 6.94 = 9022.0,与 m_dMarketValue 完全相等,三个样本全中。注意这只证明持仓量;行情 tick 里的 bidVol 单位是另一个问题,不能混为一谈。
五、最有用的方法:让客户端自己回答语法问题
前面所有结论,没有一条是「我觉得」。全部来自一个笨办法:写探针脚本,扔进客户端跑,看它自己怎么说。
流程只有四步:
- 不确定的语法不猜。 先查文档,查不到就登记下来。
- 写一个只读的探针脚本,只做查询、不产生任何委托。
- 交给客户端跑,把日志拿回来。
- 结论落进规范文档,状态从「待验证」改成「已证实」。
- 用真实标的交叉验证。 判类型码时不要只试自己记得的代码,要从
get_stock_list_in_sector('沪深转债')里取板块名单里的真代码。我第一次就是拿了一个已摘牌的代码,得出完全相反的结论。 - 用不存在的代码做对照实验。 传一个未发行的代码进去,如果它和真代码命中完全相同,说明规则是按代码段兜底,而不是查标的。
- 看 C++ 报错反推签名。 参数传错时,报错信息会直接列出全部重载签名 —— 这是最权威的签名来源,比任何文档都准。
- 把「读」和「写」分开。 只读探针随便跑;要测下单路径,就用
prType=11 + price=0这种文档明确记为「必然废单」的组合,或者干脆传一个不存在的代码,做到零真实委托。 - 日志里放一行 ASCII 哨兵字符串。 如果这行显示正常,说明编码链路没问题,整份日志都可信。
小结
三句话收尾:
- 判品种优先
C.is_stock/C.is_fund;板块名要精确(沪深转债,不是沪深可转债)。 - 行情三套源分开用:全推只管最新值,本地数据要和历史拼,订阅超限会静默填重复值。
- 字段上最大的雷是
DBL_MAX哨兵值,最一贯的错误是拿m_nDirection判方向。

💬 评论 (0)
还没有评论,来抢沙发吧。