【零依赖量化数据实战 #09】基金持仓穿透:一只基金的股票 / 债券持仓归因
摘要:【零依赖量化数据实战 #09】基金持仓穿透:一只基金的股票 / 债券持仓归因 | 智兔数服 发布时间 :2026 年 08 月 22 日 · 智兔数服 · 零依赖量化数据实战 摘要 :【零依赖量化数据实战 #09
发布时间:2026 年 08 月 22 日 · 智兔数服 · 零依赖量化数据实战
摘要:【零依赖量化数据实战 #09】基金持仓穿透。在 #08 拉到原始数据的基础上,用 /jh/zh/gpcc + /jh/zh/zccc + /jh/base/jjgk 三个端点做一只基金「股票 / 债券持仓归因」:前十大股票权重合计、债券持仓权重合计、第一大重仓集中度、类股型判定。全程纯 GET,不 import 任何 SDK。
系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK
适用:做基金筛选、ETF 盯盘、持仓归因、风格判断,不想装库或对接多个数据源的开发者
你将得到什么
- 一只基金「持仓穿透归因」的完整做法:把 #08 拉到的
gpcc(前十大重仓股)/zccc(债券持仓)/jjgk(概况含类型)归一成可比较的指标 - 四个归因指标:前十大股票持仓权重合计 / 债券持仓权重合计 / 第一大重仓集中度 / 类股型判定
- 权重归一写法:重仓字段可能是
weight/zj/proportion/ccbl,用候选键命中 + 去百分号,换数据源不用改代码 - 一段
attribution(code)单只基金归因的完整代码,把你的智兔token换成真实证书即可直接跑
一、为什么是「穿透归因」,而不是「拉出来看看」
08 的 fund_dashboard(code) 已经能把一只基金的概况、历史净值、股票持仓、债券持仓原样拉出来。但原样拉出来只是「看」,做筛选和对比还需要可比的指标:
- 两只基金都重仓茅台,谁的茅台占比更高?→ 看第一大重仓集中度
- 一只「股票型」基金,它的权益敞口到底多大?→ 看前十大股票权重合计(注意是下界,见坑 #2)
- 它拿了多少债券做底仓?→ 看债券持仓权重合计
- 它是股票型还是混合型?→ 看
jjgk概况里的类型字段
这三个端点在 #08 已经介绍过,#09 不做重复介绍,直接在它们之上做归因计算——这是同一组端点的由浅入深。
二、三个端点的语义,一张表
均见于官网公开文档、对客开放,照公开文档核对过:
GET https://api.zhituapi.com/jh/zh/gpcc/{code} 股票持仓(前十大重仓股)
GET https://api.zhituapi.com/jh/zh/zccc/{code} 债券持仓
GET https://api.zhituapi.com/jh/base/jjgk/{code} 基金概况(规模/成立日/类型)
{code} 是裸基金代码(公开文档示例 000001),不带 sz/sh 前缀(行情类才带,见坑 #3)。鉴权统一 ?token=<你的智兔token>。
一个实际用法:先
gpcc拿前十大重仓股 → 用候选键命中每只的权重字段求和,得到权益仓位下界;再zccc拿债券持仓求和,得到债券底仓;最后jjgk拿类型,判断它是不是股票型。三段拼起来就是一只基金的持仓画像。
三、字段名不固定?候选键命中 + 权重归一
重仓股返回里「权重」字段名可能是 weight / zj / proportion / ccbl,有的还带 % 号。与其硬编码,不如用候选键列表命中 + 去百分号归一:
def _to_float(v):
"""把可能是 '5.2%' / '5.2' / None 的值归一成 float。"""
if v is None:
return None
if isinstance(v, (int, float)):
return float(v)
s = str(v).replace("%", "").replace(",", "").strip()
try:
return float(s)
except ValueError:
return None
def _hit_key(d, cands):
"""从字典中按候选键名列表命中第一个存在的键。"""
if not isinstance(d, dict):
return None
for k in cands:
if k in d:
return k
return None
def _w(rows, cands):
"""对一组持仓行,按候选权重键求和。"""
total = 0.0
for r in rows:
k = _hit_key(r, cands)
if k:
v = _to_float(r.get(k))
if v is not None:
total += v
return total
def _kf(r, cands):
"""取单行某候选权重键的值。"""
k = _hit_key(r, cands)
return _to_float(r.get(k)) if k else 0.0
这样数据源把 weight 改成 zj 或 ccbl,代码不用动;命不中只是那行少一个字段,不影响整体。
四、attribution() 一只基金的股票 / 债券持仓归因
import sys
import time
import requests
TOKEN = "你的智兔token"
BASE = "https://api.zhituapi.com"
END = {
"jjgk": "/jh/base/jjgk/{code}", # 基金概况(规模/成立日/类型)
"gpcc": "/jh/zh/gpcc/{code}", # 股票持仓(前十大重仓股)
"zccc": "/jh/zh/zccc/{code}", # 债券持仓
}
def fetch(path, token=TOKEN, timeout=15):
"""拉单个基金端点,返回 (data, err)。"""
try:
r = requests.get(f"{BASE}{path}", params={"token": token}, timeout=timeout)
except requests.RequestException as e:
return None, f"网络异常:{e}"
if r.status_code != 200:
return None, f"{r.status_code} {r.text.strip()[:140]}"
try:
payload = r.json()
except ValueError:
return None, f"非 JSON 返回:{r.text[:140]}"
if isinstance(payload, dict):
detail = payload.get("detail") or payload.get("error")
return None, f"业务错误:{detail or list(payload)[:6]}"
return payload, None
def _rows(data):
if isinstance(data, list):
return data
if isinstance(data, dict):
for k in ("data", "rows", "list", "items"):
if isinstance(data.get(k), list):
return data[k]
return []
def compute_attr(code, gp_rows, zc_rows, typ):
"""把三只端点的返回归一成四个可比指标。"""
w_cands = ["weight", "zj", "proportion", "ccbl", "ratio", "pct", "je", "mv", "sz"]
stock_w = _w(gp_rows, w_cands) # 前十大股票持仓权重合计
bond_w = _w(zc_rows, w_cands) # 债券持仓权重合计
top1 = max((_kf(r, w_cands) for r in gp_rows), default=0.0) # 第一大重仓集中度
stock_like = ("股票" in typ) or ("股" in typ) # 类股型判定
return {
"code": code,
"type": typ,
"stock_top10_weight": stock_w,
"bond_weight": bond_w,
"top1_concentration": top1,
"stock_like": stock_like,
"gp_rows": len(gp_rows),
"zc_rows": len(zc_rows),
}
def attribution(code, token=TOKEN):
"""单只基金穿透归因:股票持仓 + 债券持仓 + 概况类型。"""
gp, e1 = fetch(END["gpcc"].format(code=code), token)
zc, e2 = fetch(END["zccc"].format(code=code), token)
gk, e3 = fetch(END["jjgk"].format(code=code), token)
for e in (e1, e2, e3):
if e:
return None, e
gp_rows = _rows(gp)
zc_rows = _rows(zc)
typ = ""
if isinstance(gk, dict):
typ = str(gk.get(_hit_key(gk, ["type", "type_", "fundtype", "jjlx", "category"]) or ""))
return compute_attr(code, gp_rows, zc_rows, typ), None
def summarize_attr(a):
if a is None:
return " 无数据"
parts = [
f" 代码={a['code']}",
f"类型={a['type'] or '未知'}",
f"前十大股票持仓权重合计={a['stock_top10_weight']:.2f}%",
f"债券持仓权重合计={a['bond_weight']:.2f}%",
f"第一大重仓集中度={a['top1_concentration']:.2f}%",
f"类股型={a['stock_like']}",
f"股票持仓条数={a['gp_rows']} 债券持仓条数={a['zc_rows']}",
]
return "\n".join(parts)
if __name__ == "__main__":
if TOKEN == "你的智兔token":
print("[演示] TOKEN 为占位符,请换成真实 token 后再跑。")
for code in ("510300", "159915"):
a, err = attribution(code)
if err:
print(f" {code}: 暂不可用:{err}")
else:
print(f"=== {code} 持仓归因 ===")
print(summarize_attr(a))
time.sleep(0.2)
(_to_float / _hit_key / _w / _kf 见前文,拼在一起就是完整脚本。)
五、代码自验结果
离线自测 6 项全 PASS:
selftest PASS: 端点注册完整(3/3) / _hit_key 命中逻辑 / _to_float 去百分号
/ _w 权重求和 / compute_attr 归因计算 / summarize_attr 输出 共 6 项
联网跑(占位 token)真实输出:
[演示] TOKEN 为占位符,请换成真实 token 后再跑。
510300: 暂不可用:404 102:Licence证书(你的智兔token)不存在
159915: 暂不可用:404 102:Licence证书(你的智兔token)不存在
3 个端点契约一致。本文未编造任何基金持仓 / 权重数值——换成覆盖该接口的正式证书后重跑,即可打印真实持仓归因。
六、坑与注意事项
坑 #1:102 不代表路径写对了。 鉴权发生在路由匹配之前。把路径故意写错配无效 token,同样返回 102。路径合法性只能靠客户端按白名单自查——本文 3 个路径是照公开文档核对的(实测返回 404 102;官网 #08 示例为 403 102,疑为接口版本差异,鉴权失败语义一致:证书不存在)。
坑 #2:gpcc 只给前十大重仓股,权重合计是股票仓位的下界。 它不含全部持仓,所以「前十大股票权重合计」只能当作权益仓位下限,不能直接当成基金的全部股票仓位——隐性持仓(第 11 名以后)没算进来。判断真实权益敞口要叠加 zccc 债券端和其他资产端一起看。
坑 #3:基金代码形态分两类。 净值 / 持仓 / 概况类(/jh/hb/lsjz、/jh/base/jjgk、/jh/zh/*)用裸代码(公开文档示例 000001);行情类(/jh/hq/*)的代码常带 sz/sh 前缀(示例 sz180801)。混用会拿不到数据,按公开文档示例为准。
坑 #4:类型判定用「含股票」而非精确枚举。 jjgk 的类型的字段名和取值可能随数据源变化,用 ("股票" in typ) 这种包含判定比写死 == "股票型" 更稳,漏判也只影响「类股型」这一个标签。
小结与下篇预告
这篇你在 #08 拉到原始数据的基础上,做出了一只基金的持仓归因:前十大股票权重合计、债券持仓权重合计、第一大重仓集中度、类股型判定,全部用候选键命中 + 权重归一,换数据源不用改代码。
下一批进入实时盯盘:#10 讲基金实时报价与全市场清单扫描(/fund/real/ssjy + /jh/list/all + /jh/hq/etflist + /jh/hq/loflist 把全市场 ETF/LOF 报价和涨跌幅一次性拉出来)。
免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。
领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。
把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印基金持仓归因数据。
免责声明
本文仅演示公开数据接口的用法,所有代码示例均以占位 token 自验,未含任何真实基金数据;文中数据仅为接口用法演示,不构成投资建议,亦不承诺收益。投资决策请基于你自己的判断与风险承受力。