尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【零依赖量化数据实战 #09】基金持仓穿透:一只基金的股票/债券持仓归因一次拉全

【零依赖量化数据实战 #09】基金持仓穿透:一只基金的股票/债券持仓归因一次拉全 【零依赖量化数据实战 #09】基金持仓穿透一只基金的股票/债券持仓归因一次拉全系列《零依赖量化数据实战》零依赖 · 纯 GET · 不 import 任何 SDK适用想做基金持仓穿透、持仓归因、盯 ETF/LOF 底层资产不想装库或对接多个数据源的开发者你将得到什么3 个单只基金穿透端点的路径、返回字段和语义基金概况 / 股票持仓 / 债券持仓照公开文档核对过的不是猜的一段fund_holding_attribution(code)把概况 股票持仓 债券持仓拉齐算出股票/债券权重占比、第一大重仓集中度、类股型占比字段名容错写法候选键命中 净值/持仓类用裸代码的提醒持仓归因的最小可用模板 完整可复制运行代码把你的token换成真实证书即可直接跑一、三个端点一张语义表单只基金的「持仓穿透」拆成 3 个独立端点路径前缀统一/jh/均见于公开文档对客开放GET https://api.zhituapi.com/jh/base/jjgk/{code} 基金概况规模/成立日/类型/经理 GET https://api.zhituapi.com/jh/zh/gpcc/{code} 股票持仓前十大重仓股 GET https://api.zhituapi.com/jh/zh/zccc/{code} 债券持仓鉴权统一?token你的token。三个都是单只基金维度{code}换成基金代码。jjgk返回的是聚合对象规模/类型/经理等gpcc/zccc返回的是持仓列表每行一只底层证券。两种形态代码都要吃得下。一个实际用法先jjgk拿到基金类型和规模 → 再用gpcc看前十大重仓股、zccc看债券仓位 → 三者一合就能回答「这只基到底拿了多少股票、多少债、最押哪只票」。这就是持仓穿透。代码形态提醒接 #08 坑 #2净值/持仓类/jh/base/jjgk、/jh/zh/gpcc、/jh/zh/zccc用裸代码公开文档示例000001/510300行情类/jh/hq/*才带sz/sh前缀。本篇三个端点一律裸代码。二、字段名不固定用候选键命中基金端点的返回字段名可能因数据源更新而变化。与其硬编码字段名改了就全崩不如用候选键列表命中抽取def_hit_key(d,candidates):从字典中按候选键名列表命中第一个存在的键。forkincandidates:ifkind:returnkreturnNonedef_to_float(v):把可能是字符串/数字/None 的值安全转 float转不了返回 None。try:ifvisNone:returnNonereturnfloat(v)except(TypeError,ValueError):returnNonedefsummarize(name,data):从基金端点的返回中抽取关键字段做汇总对字段名不敏感、对形态不敏感。ifdataisNone:returnf{name}: 无数据ifisinstance(data,list):ifnotdata:returnf{name}: 0 条firstdata[0]ifisinstance(data[0],dict)else{}code_key_hit_key(first,[code,dm,fundcode,jjdm,gpdm,stockcode,symbol])name_key_hit_key(first,[name,mc,jjmc,gpmc,stockname,shortname])weight_key_hit_key(first,[weight,zb,proportion,pct,jc])parts[f{name}:{len(data)}条]ifcode_key:parts.append(f首条代码{first.get(code_key)})ifname_key:parts.append(f名称{first.get(name_key)})ifweight_key:parts.append(f权重键{first.get(weight_key)})return | .join(parts)ifisinstance(data,dict):keyslist(data.keys())[:6]returnf{name}: 聚合对象键{keys}returnf{name}:{type(data).__name__}这样就算数据源把dm改成jjdm代码不用动。命不中也不报错——只是那行汇总少一个字段。jjgk返回的是 dictsummarize会走「聚合对象」分支把键名打出来不会 KeyError。三、持仓归因把三个端点合成「资产拼图」穿透的核心不是「把三个接口都调通」而是把三种形态归一后算出能用的指标。我们关心四件事前十大股票权重合计注意这是股票仓位的下界不是全额——见下方坑 #3债券权重合计第一大重仓股权重单票集中度类股型占比 股票权重 /股票 债券权重粗略看这只基「股性」有多重importsys,timeimportrequests TOKEN你的token# ← 换成你的真实 tokenBASEhttps://api.zhituapi.comFUND_HOLDING_ENDPOINTS{jjgk:(/jh/base/jjgk/{code},基金概况规模/成立日/类型/经理),gpcc:(/jh/zh/gpcc/{code},股票持仓前十大重仓股),zccc:(/jh/zh/zccc/{code},债券持仓),}deffetch_holding(name,code,tokenTOKEN,timeout15):拉单个基金穿透端点返回 (data, err)。path_tmpl,_FUND_HOLDING_ENDPOINTS[name]urlf{BASE}{path_tmpl.format(codecode)}try:rrequests.get(url,params{token:token},timeouttimeout)exceptrequests.RequestExceptionase:returnNone,f网络异常{e}ifr.status_code!200:returnNone,f{r.status_code}{r.text.strip()[:140]}try:payloadr.json()exceptValueError:returnNone,f非 JSON 返回{r.text[:140]}ifisinstance(payload,dict):detailpayload.get(detail)orpayload.get(error)returnNone,f业务错误{detailorlist(payload)[:6]}returnpayload,Nonedefattribution(overview,stocks,bonds):对单只基金做持仓穿透归因股票/债券权重聚合、第一大重仓集中度、类股型占比。stock_rows[rforrin(stocksor[])ifisinstance(r,dict)]bond_rows[rforrin(bondsor[])ifisinstance(r,dict)]w_cands[weight,zb,proportion,pct,jc,jz]def_w(rows):total0.0forrinrows:k_hit_key(r,w_cands)ifkisNone:continuew_to_float(r.get(k))ifwisnotNone:totalwreturntotaldef_kf(r):k_hit_key(r,w_cands)ifkisNone:return0.0return_to_float(r.get(k))or0.0stock_w_w(stock_rows)bond_w_w(bond_rows)top_stockmax(stock_rows,key_kf)ifstock_rowselseNoneequity_like_ratioround(stock_w/(stock_wbond_w)*100,1)if(stock_wbond_w)0elseNoneftype_hit_key(overview,[type,lx,category,jjlx])ifisinstance(overview,dict)elseNoneftype_valoverview.get(ftype)ifftypeelseNonereturn{stock_count:len(stock_rows),bond_count:len(bond_rows),stock_weight_sum:round(stock_w,2),bond_weight_sum:round(bond_w,2),top_stock_weight:round(_kf(top_stock),2)iftop_stockelseNone,equity_like_ratio:equity_like_ratio,fund_type:ftype_val,}deffund_holding_attribution(code,tokenTOKEN,sleep0.2):单只基金穿透概况 股票持仓 债券持仓并做持仓归因。iftoken你的token:print([演示] TOKEN 为占位符下面请求会返回错误码契约请换成真实 token 后再跑。)overview,stocks,bondsNone,None,Nonefornamein(jjgk,gpcc,zccc):_,descFUND_HOLDING_ENDPOINTS[name]data,errfetch_holding(name,code,token)iferr:print(f{name}({desc}): 暂不可用{err})else:print(summarize(name,data))ifnamejjgk:overviewdataelifnamegpcc:stocksdataelifnamezccc:bondsdata time.sleep(sleep)attrattribution(overview,stocks,bonds)print( 持仓归因 )print(f 股票持仓条数{attr[stock_count]}债券持仓条数{attr[bond_count]})print(f 前十大股票权重合计{attr[stock_weight_sum]}% 债券权重合计{attr[bond_weight_sum]}%)print(f 第一大重仓股权重{attr[top_stock_weight]}% 类股型占比估算{attr[equity_like_ratio]}%)print(f 基金类型{attr[fund_type]})returnattrdefselftest():# 1. _hit_key 命中逻辑assert_hit_key({dm:1},[code,dm])dmassert_hit_key({symbol:1},[code,dm,symbol])symbolassert_hit_key({x:1},[code,dm])isNone# 2. summarize 持仓列表形态hl[{code:600519,name:贵州茅台,weight:10.5}]ssummarize(gpcc,hl)assert1 条insand600519ins# 3. summarize 概述对象形态ov{type:股票型,scale:100}assert聚合对象insummarize(jjgk,ov)# 4. summarize 空数据兜底assert0 条insummarize(gpcc,[])assert无数据insummarize(gpcc,None)# 5. attribution 股票/债券权重聚合 集中度合成数据非真实行情stocks[{code:600519,name:贵州茅台,weight:10.5},{code:000858,name:五粮液,weight:8.2},{code:300750,name:宁德时代,weight:5.1},]bonds[{code:019742,name:国债,weight:3.0},{code:102100,name:企业债,weight:2.0},]aattribution({type:混合型,scale:50.0},stocks,bonds)asserta[stock_weight_sum]23.8,aasserta[bond_weight_sum]5.0,aasserta[top_stock_weight]10.5,aasserta[stock_count]3anda[bond_count]2asserta[equity_like_ratio]82.6,aasserta[fund_type]混合型,a# 6. 错误结构识别dict 含 detail 被判定为业务错误 空持仓不崩assert({detail:x}.get(detail)or{detail:x}.get(error))isnotNonea0attribution({},[],[])asserta0[stock_count]0anda0[equity_like_ratio]isNoneprint(selftest PASS: _hit_key 命中 / 持仓列表形态 / 概述对象形态 / 空数据兜底 / 权重聚合与集中度 / 错误结构识别 共 6 项)defmain():fund_holding_attribution(510300)if__name____main__:iflen(sys.argv)1andsys.argv[1]--selftest:selftest()else:main()_hit_key/_to_float/summarize见前文拼在一起就是完整脚本。sleep0.2是限频保护——3 个端点 0.2 秒间隔 ≈ 0.6 秒远在限频内。要穿透多只基金把fund_holding_attribution套一层循环外面再撑一个更大的间隔即可。四、代码自验结果离线自测 6 项全 PASS真实输出不联网selftest PASS: _hit_key 命中 / 持仓列表形态 / 概述对象形态 / 空数据兜底 / 权重聚合与集中度 / 错误结构识别 共 6 项其中「权重聚合与集中度」用的是合成数据茅台 10.5 / 五粮液 8.2 / 宁德 5.1债券 3.0 / 2.0断言前十大股票合计 23.8%、债券合计 5.0%、第一大重仓 10.5%、类股型占比 82.6%——这些是逻辑自验用的假数不是真实行情。联网跑占位 token真实输出[演示] TOKEN 为占位符下面请求会返回错误码契约请换成真实 token 后再跑。 jjgk (基金概况规模/成立日/类型/经理): 暂不可用404 102:Licence证书(你的token)不存在 gpcc (股票持仓前十大重仓股): 暂不可用404 102:Licence证书(你的token)不存在 zccc (债券持仓): 暂不可用404 102:Licence证书(你的token)不存在 持仓归因 股票持仓条数0 债券持仓条数0 前十大股票权重合计0.0% 债券权重合计0.0% 第一大重仓股权重None% 类股型占比估算None% 基金类型None三个端点契约一致单端点失败不影响整体流程、程序友好退出、归因函数对空数据不崩。本文未编造任何基金持仓/净值数值——换成覆盖该接口的正式证书后重跑即可打印真实持仓归因。五、坑与注意事项坑 #1102 不代表路径写对了。鉴权发生在路由匹配之前。把路径故意写错/jh/zh/nosuch/510300配无效 token同样返回102。路径合法性只能靠客户端按白名单自查——本文 3 个路径是照公开文档核对的。另外注意本篇实测 HTTP 状态码是404而不是403前几篇写的是 403具体看你的证书档位和网关重点是看到102:Licence证书...就知道是证书问题。坑 #2用裸代码别带sz/sh前缀。净值/持仓类/jh/base/jjgk、/jh/zh/gpcc、/jh/zh/zccc用裸代码如510300行情类/jh/hq/*才带前缀。混用会拿不到数据。坑 #3前十大是「样本」不是「全貌」。gpcc只给前十大重仓股它们权重合计是股票仓位的下界估计不代表全额股票仓位。一只股票型基金的真实权益仓位往往远高于这个和。要看全貌得结合jjgk里若数据源提供的「股票市值占比/债券市值占比」字段或另走资产配置端点——别直接拿前十大合计当权益仓位去做风险判断。坑 #4jjgk是对象、gpcc/zccc是列表两种形态都要吃。attribution对 list持仓和 dict概况分别处理别只按列表解析否则概况对象会被解析成空、类型字段取不到。小结与下篇预告这篇你拿到了 3 个单只基金穿透端点概况 / 股票持仓 / 债券持仓、字段名容错抽取写法、以及一个把三者合成「资产拼图」的持仓归因模板顺带避开了裸代码、样本下界、形态不统一三个坑。下一篇#10讲场内基金实时报价与清单全貌——用/fund/real/ssjy/{code}单只基金实时行情/jh/list/all全部基金清单/jh/hq/etflistETF 行情列表/jh/hq/loflistLOF 行情列表把一只基金的「穿透」升级成「全市场盘中看板」拉齐 ETF/LOF 整板报价与涨跌幅。免责声明本文仅演示公开数据接口的用法所有代码示例均以占位 token 自验未含任何真实基金持仓/净值数据文中合成数据仅为逻辑自验用途不构成投资建议亦不承诺收益。投资决策请基于你自己的判断与风险承受力。
返回列表