news 2026/9/16 8:37:29

QMT获取ETF申赎清单实战:从接口调用到折溢价监控

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QMT获取ETF申赎清单实战:从接口调用到折溢价监控

1. 为什么第67期先写“获取ETF申赎清单”

写这一期之前,我翻了一下之前的笔记目录,前60多期基本都在讲QMT的基础行情、下单函数和策略框架搭建。如果一直停留在“看K线、发委托”这个层面,说实话还谈不上真正的量化交易。量化交易策略的核心竞争力,在于你能拿到别人不容易拿、或者说拿到之后不知道怎么用的数据。ETF申赎清单(PCF,Portfolio Composition File)就是这么一类容易被大多数人忽略、但对套利和事件驱动策略极其关键的数据。

先解释一下ETF申赎清单是什么。你打开任何一个券商软件,搜一只ETF,按F10,里面有一个栏目叫“基金资料”或者“申购赎回清单”,它公布的都是这只ETF当天申购一篮子股票需要哪些成份股、各需要多少股、现金替代溢价比例是多少、净现金差额是多少。这个清单每个交易日开盘前由基金公司通过交易所公布,QMT的数据接口能够直接拉取。

那这个数据能用来干什么?举几个实例:

  • ETF套利:当二级市场价格和一级市场申赎净值之间出现价差时,你需要在“买入一篮子成份股—申购ETF—卖出ETF”或反向操作之间快速决策。没有清单数据,你连这一篮子股票是什么都不知道。
  • T+0回转交易:部分跨境ETF、债券ETF支持T+0,日内波段和申赎联动密切。
  • 折溢价监控:把清单里的现金替代、预估现金部分算清楚,你才能算出真实的申赎成本,从而判断盘口折溢价是“真机会”还是“假机会”。

所以,这一期先解决“怎么把申赎清单用代码拉下来、解析成结构化数据”,算是把ETF量化交易的地基打牢。

2. QMT获取申赎清单的两种主流方式

2.1 接口准备和环境依赖

先说环境。我假设你已经装好了QMT终端,并且能正常登录。如果你还是第一次接触QMT,先把下面这张依赖清单过一遍:

  • Python版本:3.6到3.9之间,推荐3.8或3.9。QMT自带的Python内核版本比较老,外部IDE连接时要注意解释器路径。
  • 必要库:pandas、numpy、requests。清单解析主要靠pandas。
  • 关键文件:在QMT安装目录的bin.x64目录下,有一个Lib文件夹,里面是xtquant(部分版本叫xtquant)。如果你用外部IDE跑策略,需要把bin.x64的路径加入sys.path,否则会报“找不到xtquant”之类的错误。

我的建议是,首次用QMT做开发,不要直接在终端内置编辑器里写太长的代码。终端内置编辑器保存、调试都不够舒服。先在外部IDE里把脚本跑通,再把稳定逻辑搬回策略文件里。外部IDE连接QMT有几种模式,常见的两种:

  • 极简模式:QMT终端上登录后,直接用xtquant连接本地端口。终端要保持在线。
  • 独立模式:通过xtquant的XtQuantTrader直接连接,不依赖策略编辑器运行,但同样依赖终端已经登录。

不管哪种模式,QMT终端登录状态是硬前提。后面常见问题里我会专门讲“client is null”这个典型报错,基本都是登录态或者初始化顺序的问题。

2.2 用get_etf_info获取基础信息

先说最基础的函数:get_etf_info。这个函数返回的是这个ETF的基本档案,包括基金名称、基金代码、管理人、成立日期、基金规模等等,类似数据库里的一张主表。对于申赎清单来说,它是辅助信息,但还是建议先拉一遍,因为后续要按基金代码做字典映射,需要有一份完整的基金列表。

调用示例:

from xtquant.xtdata import get_etf_info # 以沪深300ETF为例 info = get_etf_info('510300.SH') print(info)

返回结果通常是字典或者列表,具体字段取决于QMT版本。老版本里字段名可能是缩写,新版本更规范。拿到的字段里,我会格外关注两个:一是“基金全称”,后面做舆情分析和公告解析有用;二是“管理人”,同标的、不同管理人的ETF,申赎清单细节会有差异。

2.3 核心函数:get_etf_instrument_detail和申赎清单

这里要先纠正一个容易混淆的地方。QMT里有两个“detail”函数,一个叫get_instrument_detail,另一个叫get_etf_instrument_detail。前者是查询证券品种的基础信息,所有股票、期货、期权都能用;后者是专门查询ETF相关扩展信息,包括最小申赎单位、申赎代码、现金替代标志等等,是获取申赎清单前必须调用的“前置接口”。

它的典型用法:

from xtquant.xtdata import get_etf_instrument_detail detail = get_etf_instrument_detail('510300.SH')

detail里会包含如下关键字段:

  • min_redemption_unit:最小申赎单位,比如100万份或者50万份,这个值直接决定套利门槛。
  • cash_component或类似字段:预估现金差额。
  • subscription_coderedemption_code:申赎代码,场内申赎和交易代码不一定相同,有些ETF的申赎代码和交易代码不一样,下单时填错就废了。

为什么要先取这个detail?因为申赎清单的解析逻辑高度依赖它。最小申赎单位不取出来,你后面算“一篮子股票实际所需股数”就是空算。现在很多公开代码只教到“取到了清单列表”为止,没有讲怎么和最小申赎单位挂钩,结果用户拿到的只是一堆数字,完全没法用。

3. 申赎清单完整获取实操

3.1 用Python代码拉取清单数据

到这一步,有了上文的基础,就可以直接上获取申赎清单的代码。QMT提供的是get_etf_redeem_info或者更底层的get_etf_redeem_list,不同版本命名略有不同。以下代码在新版xtquant里测试通过:

from xtquant.xtdata import get_etf_redeem_info # 获取510300今天的最新申赎清单 redeem_info = get_etf_redeem_info('510300.SH') if redeem_info is None: print("未获取到申赎清单,请检查是否在交易时间或该基金当日无更新") else: for key, value in redeem_info.items(): # 具体字段因版本而异,建议先打印全部key print(key, value)

这里要重点说明,打印出来一般情况下你会看到两类数据:

  • 头部信息:如基金代码、日期、最小申赎单位、预估现金差额、现金替代比例上限等。
  • 成份股明细结构:每一只股票对应的股票代码、股票名称、股票数量、现金替代标志、现金替代溢价比例、赎回替代金额等。

它是嵌套结构,直接打印会比较乱,建议先转成pandas DataFrame再处理。我的处理习惯是:

import pandas as pd # 假设redeem_info里有一个components或stock_list的键 stock_list = redeem_info.get('stock_list', []) df = pd.DataFrame(stock_list) df['stock_code'] = df['stock_code'].astype(str).str.zfill(6) print(df.head(10))

做完这一步之后,你的数据形态就变成了可计算、可排序、可筛选的表格。后续做套利计算,比如按现金替代标志筛选哪些股票需要用现金替代,哪些股票必须用实物股票,都直接在DataFrame上操作。

3.2 解析申赎清单里的关键字段

这部分是非常容易踩坑的,建议认真看。申赎清单的数据不是拿过来就能直接用,不同字段背后有完全不同的业务含义。

先看现金替代标志。它决定了你在申购ETF时,对某只成份股是用股票实物交割,还是直接用现金补足。标志一般有几种,比如“允许现金替代”“必须现金替代”“禁止现金替代”。这一项直接决定了你的套利成本——现金替代不是按市价简单赔付,还牵扯到现金替代溢价比例。

再看现金替代溢价比例。它通常是一个百分比,用作对付“某只股票停牌或者涨跌停无法买入”情况下的成本补偿。举个例子,某只股票在清单里的溢价比例是10%,意味着如果你用现金替代,实际支付金额=股票数量×参考价格×(1+10%)。这个比例上下浮动非常大,对不同ETF、不同股票可能完全不同,是计算申赎成本时最关键的一个参数。

然后看股票数量。这里的数量不是按“手”算,而是按“股”算。ETF申购时成份股数量一般有严格要求,最小申赎单位是多少份额,对应到每只股票就是固定的股数。如果数量对不上,说明你用的清单日期不对,或者基金有过份额折算、分红等变动,要以最新公布为准。

最后是预估现金差额。这个字段在实盘里变化很快,盘中会实时刷新。它是申购时“多退少补”的那部分现金。做套利时,你必须把它纳入成本计算,否则算出来的折溢价根本不准。

3.3 数据落库与更新策略

取到清单只是第一步,怎么把清单变成可以反复回测、分析的数据资产,才是真正拉开差距的地方。我自己的做法是每天收盘后做一次全市场ETF申赎清单的批量抓取,存到本地数据库里,日积月累就形成了一个“申赎清单历史库”。

简单说下思路,代码不难:

import sqlite3 import datetime conn = sqlite3.connect('etf_redeem_history.db') def save_redeem_info(etf_code, redeem_info, trade_date): stock_df = redeem_info['stock_list'] for _, row in stock_df.iterrows(): conn.execute( "INSERT INTO redeem_daily (etf_code, trade_date, stock_code, stock_name, quantity, cash_flag, premium_rate) VALUES (?,?,?,?,?,?,?)", (etf_code, trade_date, row['stock_code'], row['stock_name'], row['quantity'], row['cash_flag'], row['premium_rate']) ) conn.commit()

存储格式我用SQLite,因为单机研究完全够用,免去配置数据库服务的麻烦。如果你要服务化或者多机访问,再换MySQL或PostgreSQL不迟。历史数据积累几个月之后,你可以统计“每只ETF的申赎清单变更频率”、“哪些股票经常出现必须现金替代”,这些统计结果就是构建套利策略的先验特征。

更新频率建议:普通交易日盘中每30分钟拉一次,收盘后17点左右做一次最终落库。盘中拉太频繁会频繁触发接口限制,不拉又容易错过申赎清单的盘中调整,半小时是平衡下来比较合理的方案。

4. 常见报错与排查技巧

4.1 client is null到底怎么回事

网络热词里“qmt终端 client is null”绝对是高频问题,几乎每一个QMT新手都会遇到。这个报错80%以上不是因为你的代码写错,而是终端登录状态或者客户端初始化顺序出了问题

xtquant的运行机理是:外部Python进程调用接口时,需要经由本地的QMT终端进程中转数据。如果终端没登录、登录已过期、或者终端处于“断线重连”状态,那么外部调用就拿不到可用的client通道,接口内部就会返回空值,表现到你的代码里就是is None或者直接抛“client is null”。

排查步骤按照下面顺序来:

  1. 打开QMT终端,确认账号处于已登录状态,不要是最小化到托盘后自动断开。
  2. 确认你使用的行情账号有权限。有些模拟账号只能看行情,没有申赎清单的接口权限,返回的就是空。
  3. 检查xtquant版本和QMT终端版本是否匹配。终端升级后,旧版xtquant很可能出现兼容性问题,重新安装最新版lib文件即可。
  4. 如果你是外部IDE连接,确认XtQuantTrader初始化之后又调用了start()方法。忘了start,连接通道没有建立,后面所有调用都可能返回空。

排到最后还没解决,就重启终端,重启之后重新初始化,基本能解决90%的问题。别嫌土,这个方法在QMT这种本地中转架构下就是最有效的。

4.2 申赎清单为空的另一类原因

排除client问题之后,清单为空还有一个常见原因:你查询的标的不支持申赎清单接口

不是所有ETF都有完整的申赎清单。跨境ETF、部分债券ETF,它们的申赎机制和股票ETF不同,接口返回的字段可能很少,甚至什么都不返回。做策略前,先确认标的类型,一个简单办法是把get_etf_info('代码')里的基金类型字段打出来看一下,确认属于“股票型ETF或其他支持申赎的类型”。

还有一类场景是非交易日或非交易时段。申赎清单是日频数据,每个交易日早上由基金公司公布,盘中可能调整。如果你在晚上或者周末去调用,拿到的是上一交易日的旧数据,如果接口设计成“只返回当日新鲜数据”,那你可能直接拿到空值。这时候可以看看接口是否支持传入日期参数,明确指定要哪个交易日的清单。

4.3 实战中容易混淆的同类函数

QMT的API数量不算少,命名上好几个函数都很像,新手很容易搞混。我把易混淆的几个整理成了表格:

函数名作用典型用途
get_instrument_detail查询证券基础信息获取股票、ETF的代码、名称、上市日期等
get_etf_info查询基金基础档案获取基金规模、管理人、成立日期
get_etf_instrument_detail查询ETF申赎业务扩展信息获取最小申赎单位、申赎代码等
get_etf_redeem_info获取当日申赎清单获取成份股明细、现金替代参数

如果发现拿不到数据,先确认是不是函数用错了。比如,有人想取申赎清单,结果调用的是get_instrument_detail,拿回来一大堆基础档案,肯定不会出现成份股信息。

5. 拿到申赎清单之后怎么用

5.1 手动算一次折溢价

写代码做自动套利之前,我建议你手动用Excel把当前510300的折溢价算一遍,理解整个流程。这个手动过程只要做过一次,再读代码逻辑就会非常通顺。

简化版流程:

  1. 从明细清单中找出所有成份股及对应的股数。
  2. 对每只股票,按实时价格计算市值,累加得到“一篮子股票市值”。
  3. 加上预估现金差额,除以最小申赎单位对应的份额数,得到“一单位ETF的申赎成本价”。
  4. 和二级市场上该ETF的实时价格比较,如果申赎成本价低于市场价格,说明存在溢价套利空间(申购ETF后在二级市场卖出);反之,则看反向套利。

流程看起来简单,实际最麻烦的是第2步:盘中实时行情和申赎清单里参考价格之间有时间差。所以,我把每次拉取申赎清单动作和行情订阅做了绑定,同一时刻触发,尽量减少时间差带来的误差。

5.2 自动监控折溢价的雏形

有了历史数据库,你就可以开始写自动监控脚本。基本结构:

  • 每30分钟触发一次,全市场遍历核心ETF池。
  • 对每只ETF,拉取最新申赎清单。
  • 对清单中的每只成份股,调用QMT行情接口获取实时价格。
  • 实时计算折溢价率,超过阈值(比如0.5%)就推送提醒。

需要注意,全市场遍历会频繁拉取接口,有可能触发频率限制,所以要做限频调度和失败重试。实际我不会每一轮遍历所有ETF,而是先通过行情涨幅、资金流等前置指标缩小候选池,再对候选池里的ETF拉取申赎清单。这样做既高效又不容易触限。

5.3 进阶:把申赎清单特征加入策略因子

有一定量化功底之后,申赎清单本身还能用来构造因子。比如:

  • 现金替代溢价比例偏高的股票,反映了该股流动性的紧张程度。
  • 最小申赎单位的变化,可能体现基金规模调整的信号。
  • 盯住“必须现金替代”的名单变化,可以辅助判断某些股票是否长期停牌或流动性极差。
  • 将每日申赎清单的成份股调整与二级市场价格波动做相关性分析,找到具有领先意义的结构性信号。

这些因子单独用可能都不够强,但合并进多因子模型里,或作为筛选器过滤掉风险过高的套利标的,有一定增强效果。

6. 我踩过的几个坑

先说说QMT的账户体系。QMT分为模拟盘和实盘,两类账户对申赎清单接口的权限有差异。模拟盘账号有时候能查到行情,但申赎清单接口返回空,这个在刚开始学的时候特别容易让人迷茫。我现在调试套利逻辑,优先用实盘账号看数据,策略跑通了再在模拟盘验证执行环节。

第二个坑是数据日期问题。有一次我回测时发现某天的申赎清单数据量异常,排查了很久,发现是当天有只ETF进行了份额折算,最小申赎单位变了,但历史库里没有同步更新该字段。从那以后,我每次落库时会把get_etf_instrument_detail里的最小申赎单位也存一份,回测时按当天字段计算,而不是统一用最新的,避免算错一篮子股数。

第三个坑是500ETF和300ETF这种大盘标的,申赎清单特别长,直接打印会看不到全貌。处理大清单,建议不要用print,而是直接落库后按股票代码排序查看,或者用df.describe()快速看统计特征。有一阵子我为了调试,print整个清单,终端直接卡死,那叫一个酸爽。

第四个坑是关于现金替代标志的中文字段乱码。老版本接口返回的字段可能是中文编码,外部IDE的字符集设置不对,会显示成一堆乱码。遇到这种情况,把文本编码强制转为utf-8或者gbk试试,总有一个是对的。

最后,分享一个个人习惯,尤其是刚起步时特别有用:每周复盘一次申赎清单解析代码的错误日志。QMT接口偶尔会返回半包数据,或者某个字段缺值,如果不记录日志,你根本不知道哪一步计算错了。我在代码里给每一条原始返回都加了hash值存档,出现数据异常时对比hash,能快速定位是接口返回变化还是解析逻辑出了问题。

7. 关于本期内容的一个收尾

第67期我们解决了“用QMT把ETF申赎清单拿下来并存起来”的问题,这是ETF套利策略的地基。下一期我会接着讲如何把申赎清单和分钟级行情联动,实现盘中折溢价的实时盯盘与自动预警。如果你自己调试时遇到其他奇奇怪怪的报错,或者发现不同版本API的返回结构差异比较大,欢迎按你自己的实测结果继续完善这份笔记的细节。QMT这个工具坑不少,但把数据链路一步步打通之后,后面写策略就会顺手很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 8:37:12

AI论文写作工具对比:千笔写作与PaperRed测评

1. 项目概述:AI论文写作工具横评的必要性最近在本科生群体中,关于AI论文写作工具的讨论越来越热。作为经历过本科论文煎熬的老学长,我完全理解大家寻找高效工具的迫切心情。今天我们就来深度测评两款热门AI论文辅助工具——千笔写作和PaperRe…

作者头像 李华
网站建设 2026/9/16 8:36:31

WordPress垃圾注册拦截实战:FckSignups分层防护机制与实现

做站这些年,最烦的事情不是服务器宕机,也不是被攻击,而是后台注册列表里一夜之间多出几百个用户名像乱码一样的账号,邮箱全是临时邮箱,昵称全是广告词。你说删吧,删到手软;不删吧,数…

作者头像 李华
网站建设 2026/9/16 8:36:00

人工超级智能:概念边界、潜在风险与安全防护实践

人工超级智能这个概念,近几年在技术圈和投资圈都快被聊烂了。有人把它当终极理想,有人觉得它只是资本故事里的下一个噱头,但真正动手去研究它的人,往往最先碰到的不是算法问题,而是“这东西到底该怎么定义”“做到什么…

作者头像 李华
网站建设 2026/9/16 8:35:07

OpenMontage:面向AI工程师的智能体编排与可视化调试工作台

1. OpenMontage 不是视频剪辑软件,而是面向 AI 工程师的“智能体编排工作台”很多人第一次看到OpenMontage这个名字,下意识会联想到 Adobe Premiere 或 DaVinci Resolve——毕竟 “Montage” 在法语里就是“剪辑”的意思,加上前缀 “Open”&a…

作者头像 李华
网站建设 2026/9/16 8:33:19

新型电力系统中Q(V)控制策略的稳定性分析与实现

1. 项目背景与核心问题在新型电力系统快速发展背景下,配电网中分布式电源渗透率持续攀升,变流器作为新能源并网的关键接口设备,其动态特性直接影响系统稳定性。传统配电网电压控制主要依赖无功补偿装置和变压器分接头调节,但面对高…

作者头像 李华
网站建设 2026/9/16 8:32:41

Docker与Compose生产级部署实战:从安装、编排到踩坑全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华