← 返回博客列表

【零依赖量化数据实战 #13】ETF 盘中实时估值与估算涨跌:2 个 URL 做盯估扫描

2026年08月25日 08:03 · 智兔数服 · 零依赖量化数据实战

摘要:【零依赖量化数据实战 #13】ETF 盘中实时估值与估算涨跌:2 个 URL 做盯估扫描 系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK 适用:想做 ETF 盘中盯估、估

系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK
适用:想做 ETF 盘中盯估、估算涨跌异动扫描、估值偏离监控,不想装库或对接多个数据源的开发者

你将得到什么

  • 2 个「基金估值」端点的路径、返回字段和语义(估值列表 / 单只估值),照公开文档核对过的,不是猜的
  • 一段 scan_gz_movers(threshold) 拉全市场估值列表,按估算涨跌幅筛出异动基金
  • 字段名容错写法(候选键命中),对字段名不敏感、对形态(list / dict)不敏感
  • 完整可复制运行代码,把 你的智兔token 换成真实智兔证书即可直接跑

一、两个端点,一张语义表

「盘中盯估」拆成 2 个端点,路径前缀统一 /jh/gz/

GET https://api.zhituapi.com/jh/gz/gz          估值列表(全市场基金盘中估算值/估算涨跌)
GET https://api.zhituapi.com/jh/gz/zx/{code}   单只基金实时估值(估算净值 + 估算涨跌幅)

鉴权统一 ?token=<你的智兔token>gz全市场估值板(一次性返回所有基金的估算值/估算涨跌),zx单只维度,{code} 换成基金代码。估值接口的「估算涨跌幅」是相对昨日单位净值的变动,因此无需再拉历史净值即可扫描盘中异动。

一个实际用法:先用 gz 拿到全市场估值板 → 按估算涨跌幅排序/筛出异动 → 再用 zx/{code} 对其中某只看它的估算净值与估算涨跌细节。这就是盘中盯估。

代码形态提醒gz/gz 是整板列表(可能上千行),适合扫描;gz/zx/{code} 是单只深度。与 #10 的 ssjy(实时价)不同,估值是「盘中估算净值」,通常在收盘前与实时价有微小偏差——别把两者当同一个数。

二、字段名不固定?用候选键命中

估值接口的返回字段名可能因数据源更新而变化。与其硬编码字段名(改了就全崩),不如用候选键列表命中抽取:

def _hit_key(d, candidates):
    """从字典中按候选键名列表命中第一个存在的键。"""
    for k in candidates:
        if k in d:
            return k
    return None

def _to_float(v):
    """把可能是字符串/数字/None 的值安全转 float,转不了返回 None。"""
    try:
        if v is None:
            return None
        return float(v)
    except (TypeError, ValueError):
        return None

def scan_gz_movers_safe(board, threshold=1.0):
    """从估值列表中筛出估算涨跌绝对值 >= 阈值的异动基金。"""
    ck = ["code", "dm", "fundcode", "jjdm", "symbol"]
    zk = ["gz_zdf", "est_chg", "zdf", "chg", "est_zdf", "gsz_zdf", "pchg"]
    out = []
    for r in board:
        if not isinstance(r, dict):
            continue
        z = _to_float(r.get(_hit_key(r, zk))) if _hit_key(r, zk) else None
        if z is not None and abs(z) >= threshold:
            out.append((r.get(_hit_key(r, ck)), z))
    return out

这样就算数据源把 zdf 改成 gz_zdf,代码不用动。命不中也不报错——只是那只基金取不到涨跌。

三、盘中盯估:全市场估值板扫异动

核心不是「把两个接口都调通」,而是把整板估值归一后做异动扫描。我们关心一件事:估算涨跌幅绝对值超过阈值的基金有哪些

import sys, time
import requests

TOKEN = "你的智兔token"                  # ← 换成你的真实智兔token
BASE = "https://api.zhituapi.com"

GZ_ENDPOINTS = {
    "gz": ("/jh/gz/gz", "估值列表(全市场)"),
    "zx": ("/jh/gz/zx/{code}", "单只基金实时估值"),
}


def fetch(name, code=None, token=TOKEN, timeout=15):
    """拉一个估值端点,返回 (data, err)。"""
    path_tmpl, _ = GZ_ENDPOINTS[name]
    url = f"{BASE}{path_tmpl.format(code=code) if code is not None else path_tmpl}"
    try:
        r = requests.get(url, 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 scan_gz_movers(threshold=1.0, token=TOKEN, sleep=0.2):
    """拉全市场估值列表,按估算涨跌幅筛出异动基金。"""
    if token == "你的智兔token":
        print("[演示] TOKEN 为占位符,下面请求会返回错误码契约;请换成真实 token 后再跑。")
    data, err = fetch("gz", token=token)
    if err:
        print(f"  gz (估值列表): 暂不可用:{err}")
        return []
    rows = [r for r in (data or []) if isinstance(r, dict)]
    ck = ["code", "dm", "fundcode", "jjdm", "symbol"]
    zk = ["gz_zdf", "est_chg", "zdf", "chg", "est_zdf", "gsz_zdf", "pchg"]
    movers = []
    for r in rows:
        z = _to_float(r.get(_hit_key(r, zk))) if _hit_key(r, zk) else None
        if z is not None and abs(z) >= threshold:
            movers.append((r.get(_hit_key(r, ck)), z))
    print(f"  估值列表 {len(rows)} 条,估算涨跌绝对值 >= {threshold}% 的异动 {len(movers)} 只")
    time.sleep(sleep)
    return movers


def fund_gz(code, token=TOKEN):
    """单只基金盘中估值深度查看。"""
    data, err = fetch("zx", code=code, token=token)
    if err:
        print(f"  zx (单只基金实时估值): 暂不可用:{err}")
        return None
    print(f"  zx (单只基金实时估值): {len(data) if isinstance(data, list) else '聚合对象'} 条/对象")
    return data

sleep=0.2 是限频保护。要扫描多只单基估值,把 fund_gz 套一层循环,外面再撑一个更大的间隔即可。

四、代码自验结果

离线自测 6 项全 PASS(真实输出,不联网):

selftest PASS: _hit_key 命中 / 异动筛选 / 空数据兜底 / 字段名不敏感 / 阈值边界 / 数值容错 共 6 项

其中「异动筛选」用的是合成数据(沪深300ETF 估涨 1.8%、中证500ETF 估跌 -0.4%、创业板ETF 估涨 2.3%),断言阈值 1.0 时命中沪深300ETF 与创业板ETF、不命中中证500ETF——这些是逻辑自验用的假数,不是真实估值

联网跑(占位 token)真实输出:

[演示] TOKEN 为占位符,下面请求会返回错误码契约;请换成真实 token 后再跑。
  gz (估值列表): 暂不可用:404 102:Licence证书(你的智兔token)不存在
  zx (单只基金实时估值): 暂不可用:404 102:Licence证书(你的智兔token)不存在

两个端点契约一致,单端点失败不影响整体流程、程序友好退出。本文未编造任何估值/估算涨跌数值——换成覆盖该接口的正式证书后重跑,即可打印真实盘中盯估。

五、坑与注意事项

坑 #1:102 不代表路径写对了。 鉴权发生在路由匹配之前。路径故意写错配无效 token,同样返回 102。路径合法性只能靠客户端按白名单自查——本文 2 个路径是照公开文档核对的。实测 HTTP 状态码是 404 而不是 403,重点是看到 102:Licence证书... 就知道是证书问题。

坑 #2:估值是「估算净值」,不是实时价。 gz 给的是盘中估算净值与估算涨跌幅,和 ssjy 的实时交易价/实时涨跌是两回事;收盘前两者会有微小偏差,做套利或精确对账时要分清。

坑 #3:估算涨跌幅已是「相对昨日净值」。 估值接口的估算涨跌幅本身就是相对昨日单位净值的变动,不需要再拉历史净值去算。别多此一举去 lsjz 反推,直接用它扫异动即可。

坑 #4:gz/gz 是整板,量可能很大。 全市场估值板可能上千行,排序/扫描前先想清楚阈值和字段,避免在大列表上做无谓的全量打印。

小结与下篇预告

这篇你拿到了 2 个基金估值端点(估值列表 / 单只估值)、字段名容错抽取写法、以及一个把全市场估值板扫异动的模板,顺带避开了 102 误判、估值≠实时价、重复拉净值、整板过大四个坑。

至此,#09–#13 形成了一条完整的基金分析链:持仓穿透(#09) → 盘中看板(#10) → 技术面回看(#11) → 选基三维度(#12) → 盘中盯估(#13)

下一篇(#14)正式把视野从基金扩展到 A 股盘面:A 股异动池——用 /hs/pool/ztgc(涨停)/ /hs/pool/dtgc(跌停)/ /hs/pool/qsgc(强势股)/ /hs/pool/cxgc(创新高)/ /hs/pool/zbgc(炸板)5 个异动池端点,做一张日内异动全景扫描看板。

免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。

领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。

把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印 ETF 盘中估值与估算涨跌数据。

免责声明

本文仅演示公开数据接口的用法,所有代码示例均以占位 token 自验,未含任何真实估值/估算涨跌数据;文中合成数据仅为逻辑自验用途,不构成投资建议,亦不承诺收益。投资决策请基于你自己的判断与风险承受力。

想亲自试一下?免费获取证书