前段时间想系统性地研究基金,起点很朴素:把市场上几百只 ETF 按规模、费率、行业暴露过一遍,再挑一批主动基金看看真实的持仓风格。动手之后才发现,基金研究的数据链条比股票长得多。股票研究大部分时候围着K线转,基金研究则要从基金列表一路查到财务报表、持仓明细、规模变动,数据散、口径杂,东一块西一块。我的做法是按列表层、财务层、持仓层、规模层四层结构,用本地数据引擎 ig50 把整套基金档案数据搬回本地,全部以 JSON 文本文件落盘,Python 直接读文件解析。这篇文章按这四层把工程过程过一遍,重点落在每层的口径细节上。
先说列表层,解决有哪些基金的问题。这一层三个接口:ETF基金列表base/etfjj,字段是基金代码和基金名称mc,代码形如 sz159999,带交易所前缀;LOF列表base/lofjj;封闭式基金列表base/fbsjj。ETF几百只,三个列表加起来就是几个小文件。列表层看起来最没技术含量,但它是整个体系的索引,后面三层所有接口都要拿基金代码当入参,所以列表落地后我做的第一件事是代码归一化:交易所前缀统一小写、跨列表去重、标记疑似清盘的代码,保证后面三层引用的代码全集完全一致。这一步偷懒,后面就会出现某只基金在持仓层查不到这类查起来最费时间的脏问题。
第二层财务层,回答这只基金赚不赚钱、靠什么赚、成本多少。主要财务指标走time/jjf10/cwbb/zycwzb/{基金代码},关键字段是本期利润、期末基金资产净值、基金份额净值增长率。三个字段配合起来用才有意义:本期利润受规模影响大,两个规模差十倍的基金利润没法直接比,用基金份额净值增长率才拉平到可比口径。收入分析走cwbb/srfx,字段是股票收入占比、债券收入占比、股息收入占比。这三个字段比基金名字诚实得多,一只自称稳健的基金如果股票收入占比常年七成以上,标签就是摆设,收入结构才是它真实的风格指纹。费用分析走cwbb/fyfx,字段是管理人报酬占比、托管费占比、交易费占比、销售服务费占比,四项加总就是这只基金一年在费用上被拿走的总比例,做长期持有决策时这组数字比历史收益更值得先看。
第三层持仓层,是研究价值最高的一层。股票持仓走time/jjf10/tzzh/jjcc/{基金代码},字段包括季度、股票代码、占净值比例、持股数万股、持仓市值万元;债券持仓走tzzh/zqcc,结构类似。两个文件配合起来还能粗略还原基金的资产配置轮廓:股票持仓市值加总对比期末基金资产净值,得到一个比基金类型标签更真实的股债比例,这个比例随季度的漂移本身就是风格漂移检测最直接的输入。拿到数据只是开始,这一层的两个口径问题直接决定后续分析可不可信,后面单独展开。
第四层规模层。规模变动走time/jjf10/gmfe/gmbd,看每个报告期的份额申购赎回;持有人结构走gmfe/cyrjg,看机构与个人持有占比的变化。这层最容易被忽略,但它经常能解释净值解释不了的事:一只业绩平平的基金规模短期暴增,多半是渠道推动而不是业绩驱动;机构占比单季大幅下降,后续波动往往会放大。规模层和财务层放在一起看,能区分出靠申购冲规模和靠业绩自然长大两类完全不同的基金。
四层数据落地之后,剩下的就是解析环节的细节,几个坑值得单独说。
第一个细节是季度字段的降序排列与去重。持仓和财务数据按季度披露,返回按季度降序排,但同一季度会出现重复记录,基金发生更正公告或者特殊事项时,同一报告期可能有多条披露,直接拿去分析就会产生同一季度两份持仓的幻觉。我的处理是按基金代码加季度分组,组内取披露时间最新的一条,同时统计组内记录数,异常偏多的样本导出来人工过目,避免把更正前后的数据各算一遍。
第二个细节是占净值比例的口径。季报只披露前十大重仓股,占净值比例的分母是全部净资产,十只加起来通常只有百分之三四十,剩下的大头是看不到的。这决定了两件事:不能用前十大占比直接推断基金的真实行业暴露;做持仓类统计时透明度只有三到四成,结论必须打折扣。我在所有基于持仓的统计里都显式记录一个披露覆盖率,也就是前十大占比之和,覆盖率过低的样本在聚合时降权或者剔除,这个习惯让我避免了好几次看起来漂亮实则站不住的结论。
第三个细节是费用核对。费用分析的四项占比加总,应当与基金公告的综合费率基本吻合,我把这个核对做成了固定步骤,偏差超过阈值就回头查解析逻辑。实际跑下来确实抓到过问题:不同报告期的字段有的是百分数形式有的是小数形式,肉眼逐条看根本注意不到,校验脚本一遍就筛出来了。另外销售服务费占比这一项在C类份额里明显偏高,同一基金A/C份额的费用结构差异主要就落在这一项,列表层归一化时我会把同一基金不同份额的代码关联起来,费用对比才有基准。财务层数据我的原则是先核对再进分析流程,核对不过的数据宁可不用。
第四个细节是持仓穿透的聚合思路。把单只基金的持仓往上穿一层,可以聚合成一个基金重仓股重叠度矩阵:以基金代码为行和列,对任意两只基金,统计它们前十大重仓股的交集,共同持有的股票取两边占净值比例的较小值再求和,得到一个取值在0到1之间的对称矩阵,对角线为1,元素越大说明两只基金实际持有的篮子越像。在这个矩阵上做聚类,哪些基金事实上持有几乎同一篮子股票一目了然;矩阵每季度随季报刷新,时间序列上还能观察到聚集度的起落。这个分析全部基于本地文件离线计算,几百只基金两两组合的计算量一台笔记本毫无压力。
最后说更新节奏。基金档案类接口每周六早上6点更新一次,相当于每周一次全量刷新。对基金研究来说这个频率完全够用,季报数据本来就是一个季度一更,本地数据最多滞后一周。我把落盘任务挂在周六早上的定时调度后面,更新完自动跑季度去重和费用核对,周一开工时数据就是新的。体积上也很轻:单只基金十年季报大约40期,几百只ETF加几十只重点跟踪的主动基金,全量数据百MB量级,普通笔记本存下没有任何压力,读取速度比任何远程方式都快,做两两组合这种全量扫描时优势尤其明显。
回头看,基金数据工程的核心不是调通某个接口,而是分层之后每一层的口径管理:列表层管代码全集一致,财务层管费用加总与收入结构核对,持仓层管季度去重与披露覆盖率,规模层管更新节奏。四层口径都稳住了,后面的任何量化分析才有干净的输入,这也是我坚持把整套基金档案搬回本地的根本原因。
接口说明:列表层:ETF基金列表base/etfjj,字段为基金代码(如 sz159999)、基金名称mc;LOF列表base/lofjj;封闭式基金列表base/fbsjj。财务层:主要财务指标time/jjf10/cwbb/zycwzb/{基金代码},字段为本期利润、期末基金资产净值、基金份额净值增长率;收入分析cwbb/srfx,字段为股票收入占比、债券收入占比、股息收入占比;费用分析cwbb/fyfx,字段为管理人报酬占比、托管费占比、交易费占比、销售服务费占比。持仓层:股票持仓time/jjf10/tzzh/jjcc/{基金代码},字段为季度、股票代码、占净值比例、持股数万股、持仓市值万元;债券持仓tzzh/zqcc。规模层:规模变动time/jjf10/gmfe/gmbd;持有人结构gmfe/cyrjg。以上基金档案类接口每周六早上6点更新。
资料参考:ig50
gitee开源地址
github开源地址