2. 行业ETF数据获取与清洗

构建行业ETF轮动标的池,第一步不是选行业,而是把“数据地基”打牢。行业ETF的数据分散在交易所、基金公司、行情服务商等多个来源,字段口径、更新频率、复权方式各不相同。如果直接拿原始数据做轮动信号,很容易因为一条缺失的净值、一个未复权的价格或一条重复记录,导致动量排序失真、行业误判。本节的任务很明确:搞清楚ETF列表、行情、份额、净值四类数据从哪里来、怎么取,然后通过缺失值、异常值、重复记录和停牌数据的系统清洗,形成一张字段规范、口径统一、可反复调用的数据底表。

四类核心数据的获取渠道与接口

行业ETF的数据可以拆成四个层次,每个层次对应不同的获取渠道和更新节奏。

ETF列表数据是标的池的“花名册”,核心字段包括基金代码、基金简称、跟踪指数、上市日期、基金公司、管理费率等。最直接的渠道是交易所官网的ETF产品列表,上交所和深交所均提供ETF成交概况与产品清单下载;其次是基金公司官网和第三方基金信息平台。实操中更常用的是行情接口的“基金列表”或“板块成分”接口,例如通过AkShare的fund_etf_category_sina或fund_etf_spot_em获取全市场ETF实时快照,其中已包含代码、名称、跟踪标的等基础信息。这一步的关键是保留“跟踪指数”字段,因为行业ETF轮动需要按行业归类,而归类依据正是其跟踪的行业指数。

行情数据包括日频的开盘、最高、最低、收盘、成交额、成交量。获取渠道主要有三类:交易所历史行情下载、行情服务商接口(如Tushare的fund_daily、AkShare的fund_etf_hist_em)、以及券商量化终端。需要特别注意的是复权问题:ETF虽然分红较少,但仍有份额折算和分红除权,做轮动动量计算时应使用后复权价格,避免除权日出现虚假跳空。接口通常提供adjust="hfq"参数,取数时统一指定,不要混用不复权与复权数据。

份额数据反映ETF的规模变化和资金申赎动向,字段包括基金份额、流通份额、份额变化。份额数据更新频率低于行情,通常为日频或周频,可从基金公司公告、交易所基金规模数据或第三方接口获取。份额的连续变化是判断ETF流动性和资金关注度的重要辅助指标,清洗时要允许其更新滞后于行情一至两个交易日,不能简单按行情日期对齐后全部判为缺失。

净值数据包括单位净值和累计净值,是计算折溢价、跟踪误差的基础。净值通常在交易日收盘后由基金公司披露,获取渠道为基金公司官网、第三方基金平台或接口的净值序列。净值与行情价格的时间戳存在天然错位——价格是盘中连续撮合结果,净值是盘后结算结果,合并时必须以交易日期为键,而不是以精确时间戳对齐。

行业ETF数据获取与清洗流程 数据源 交易所ETF列表 代码/名称/跟踪指数 行情接口 日线OHLC/成交额(后复权) 份额数据 基金份额/流通份额 净值数据 单位净值/累计净值 按交易日期 合并宽表 代码+日期为主键 清洗四关 ① 重复记录 同代码同日期去重 ② 缺失值 前值填充/标记剔除 ③ 异常值 涨跌幅阈值/价格逻辑 ④ 停牌数据 成交额=0识别/标记 可复用数据底表 代码 · 日期 · 行业 后复权收盘 · 成交额 份额 · 净值 · 停牌标记 数据质量标记列 关键口径约定 · 主键:基金代码 + 交易日期,合并前先统一日期格式为 YYYY-MM-DD · 价格:统一使用后复权收盘价,避免分红除权造成虚假跳空 · 停牌:成交额=0 或成交量=0 且价格无变化,标记为停牌而非删除,保留时间连续性

清洗四关:重复、缺失、异常、停牌

第一关是重复记录。多源合并最容易产生重复:同一代码同一日期,可能因为接口分页重复拉取、或行情与净值表连接时产生笛卡尔积而出现多行。处理原则是以“基金代码+交易日期”为唯一主键,用drop_duplicates(subset=["code","date"], keep="last")去重,并保留数据质量标记列记录被丢弃的行数。去重后应校验行数是否等于“标的数量×交易日数量”的理论值,偏差过大说明主键设计或合并逻辑有问题。

第二关是缺失值。缺失分两种性质:一是行情缺失,通常由停牌或数据源漏采造成;二是份额、净值缺失,多因披露滞后。对行情缺失,若前后交易日均有价格且间隔较短,可用前收盘价填充并标记fill_flag=1;若连续缺失超过5个交易日,应将该区间标记为不可用,而不是强行填充。对份额和净值,允许按基金代码做前向填充(ffill),但填充长度需设上限,超过上限的保持空值并在轮动计算时排除该标的。关键原则是:填充必须留痕,底表中保留原始值和填充标记两列,后续回测才能区分真实数据与推断数据。

第三关是异常值。ETF行情的异常值主要有三类:价格逻辑错误(最高价低于最低价、收盘价超出当日高低区间)、涨跌幅越界(单日涨跌幅超过合理阈值,如±11%)、成交额突变(较前20日均值放大数十倍且无对应市场事件)。处理时先做逻辑校验,把high < low或close > high的记录标为异常;再用滚动窗口计算涨跌幅,对超过阈值的点回查原始接口,确认是数据错误还是真实极端行情。真实极端行情应保留,数据错误则置空后按缺失值规则处理。异常值处理不能简单“一刀切”删除,否则会把真实的行业暴涨暴跌误删,反而破坏轮动信号。

第四关是停牌数据。ETF停牌相对个股较少,但仍会因标的指数调整、基金份额折算等临时停牌。识别方法是:成交额和成交量同时为0,且收盘价与前一日相同。停牌期间不应删除记录,因为轮动计算依赖连续的时间序列;正确做法是保留行、将价格标记为停牌、在动量计算时用停牌前最后有效价格参与,并在信号层对长期停牌标的设置剔除规则(如连续停牌超过10个交易日则移出候选池)。

形成可复用数据底表的字段设计

清洗完成后,底表应具备“一次构建、多次调用”的能力。建议的核心字段包括:code(基金代码)、name(基金简称)、industry(所属行业,由跟踪指数映射)、date(交易日期)、close_hfq(后复权收盘价)、amount(成交额)、shares(基金份额)、nav(单位净值)、is_suspend(停牌标记)、fill_flag(填充标记)、abnormal_flag(异常标记)。行业字段的映射需要单独维护一张“跟踪指数—行业”对照表,因为同一行业可能有多只ETF跟踪不同指数,轮动时通常选取规模最大、流动性最好的那只作为行业代表。

底表构建完成后,建议做一次完整性验收:统计每个交易日的有效标的数量、每个标的的有效交易日占比、停牌与填充比例。若某行业ETF的有效交易日占比低于90%,说明其数据质量不足以支撑轮动信号,应在标的池构建阶段就予以剔除,而不是等到回测时才发现问题。

数据底表不是一次性产物,而是需要随新交易日滚动更新的基础设施。把上述取数、合并、清洗逻辑封装成可重复执行的脚本,每次运行时只增量拉取最新交易日数据并追加到底表,同时保留历史清洗标记,才能让后续的行业轮动策略建立在稳定、可信、可追溯的数据之上。