ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

QMT获取ETF申赎清单实战:从接口调用到折溢价监控 1. 为什么第67期先写“获取ETF申赎清单”写这一期之前我翻了一下之前的笔记目录前60多期基本都在讲QMT的基础行情、下单函数和策略框架搭建。如果一直停留在“看K线、发委托”这个层面说实话还谈不上真正的量化交易。量化交易策略的核心竞争力在于你能拿到别人不容易拿、或者说拿到之后不知道怎么用的数据。ETF申赎清单PCFPortfolio Composition File就是这么一类容易被大多数人忽略、但对套利和事件驱动策略极其关键的数据。先解释一下ETF申赎清单是什么。你打开任何一个券商软件搜一只ETF按F10里面有一个栏目叫“基金资料”或者“申购赎回清单”它公布的都是这只ETF当天申购一篮子股票需要哪些成份股、各需要多少股、现金替代溢价比例是多少、净现金差额是多少。这个清单每个交易日开盘前由基金公司通过交易所公布QMT的数据接口能够直接拉取。那这个数据能用来干什么举几个实例ETF套利当二级市场价格和一级市场申赎净值之间出现价差时你需要在“买入一篮子成份股—申购ETF—卖出ETF”或反向操作之间快速决策。没有清单数据你连这一篮子股票是什么都不知道。T0回转交易部分跨境ETF、债券ETF支持T0日内波段和申赎联动密切。折溢价监控把清单里的现金替代、预估现金部分算清楚你才能算出真实的申赎成本从而判断盘口折溢价是“真机会”还是“假机会”。所以这一期先解决“怎么把申赎清单用代码拉下来、解析成结构化数据”算是把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_code和redemption_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%意味着如果你用现金替代实际支付金额股票数量×参考价格×110%。这个比例上下浮动非常大对不同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”。排查步骤按照下面顺序来打开QMT终端确认账号处于已登录状态不要是最小化到托盘后自动断开。确认你使用的行情账号有权限。有些模拟账号只能看行情没有申赎清单的接口权限返回的就是空。检查xtquant版本和QMT终端版本是否匹配。终端升级后旧版xtquant很可能出现兼容性问题重新安装最新版lib文件即可。如果你是外部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的折溢价算一遍理解整个流程。这个手动过程只要做过一次再读代码逻辑就会非常通顺。简化版流程从明细清单中找出所有成份股及对应的股数。对每只股票按实时价格计算市值累加得到“一篮子股票市值”。加上预估现金差额除以最小申赎单位对应的份额数得到“一单位ETF的申赎成本价”。和二级市场上该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这个工具坑不少但把数据链路一步步打通之后后面写策略就会顺手很多。
返回列表