【零依赖量化数据实战 #11】ETF 历史 K 线与均线:4 个 URL 做技术面回看
摘要:【零依赖量化数据实战 #11】ETF 历史 K 线与均线:4 个 URL 做技术面回看 系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK 适用:想做 ETF 技术面分析、K
系列:《零依赖量化数据实战》|零依赖 · 纯 GET · 不 import 任何 SDK
适用:想做 ETF 技术面分析、K 线落地、均线偏离监控,不想装库或对接多个数据源的开发者
你将得到什么
- 4 个「ETF 历史 K 线/均线」端点的路径、返回字段和语义(历史K线 / 最新K线 / 历史MA / 最新MA),照公开文档核对过的,不是猜的
- 一段
kline_with_ma(code)把历史 K 线 + 历史 MA 拉齐,算出最后一日涨跌幅与收盘价相对均线的偏离 - 字段名容错写法(候选键命中),对字段名不敏感、对形态(list / dict)不敏感
- 完整可复制运行代码,把
你的智兔token换成真实智兔证书即可直接跑
一、四个端点,一张语义表
「技术面回看」拆成 4 个端点,路径前缀统一 /jh/hq/,且分时级别是路径参数(不是查询参数):
GET https://api.zhituapi.com/jh/hq/lskx/{code}/{level} 历史 K 线(日/周/月…)
GET https://api.zhituapi.com/jh/hq/zxkx/{code}/{level} 最新 K 线
GET https://api.zhituapi.com/jh/hq/lsma/{code}/{level} 历史 MA(移动均线,已算好)
GET https://api.zhituapi.com/jh/hq/zxma/{code}/{level} 最新 MA
鉴权统一 ?token=<你的智兔token>。{code} 是基金代码,{level} 是分时级别(d=日,w=周,m=月,具体以文档示例为准)。lskx/zxkx 返回 K 线序列(每根含开高低收),lsma/zxma 返回对应的均线序列。两种形态,代码都要吃得下。
一个实际用法:先
lskx拿历史 K 线 → 用lsma拿现成均线 → 算「收盘价 − 均线」的偏离,偏离为正说明价格在均线上方(偏强),为负则偏弱。这就是技术面回看。
代码形态提醒(接 #10 坑 #4):这些 {code} 用基金代码(如 510300);{level} 是路径参数,必须拼进 URL(/jh/hq/lskx/510300/d),不能写成 ?level=d。
二、字段名不固定?用候选键命中
K 线/均线的返回字段名可能因数据源更新而变化。与其硬编码字段名(改了就全崩),不如用候选键列表命中抽取:
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 summarize_kline(name, data):
"""从 K 线/均线中抽取关键字段做汇总,对字段名不敏感、对形态不敏感。"""
if data is None:
return f" {name}: 无数据"
if isinstance(data, list):
if not data:
return f" {name}: 0 条"
first = data[0] if isinstance(data[0], dict) else {}
date_key = _hit_key(first, ["date", "rq", "day", "time", "rqstr"])
close_key = _hit_key(first, ["close", "c", "last", "dwjz", "nav", "price", "closeprice"])
parts = [f" {name}: {len(data)} 条"]
if date_key:
parts.append(f"首日={first.get(date_key)}")
if close_key:
parts.append(f"收盘键={first.get(close_key)}")
return " | ".join(parts)
if isinstance(data, dict):
return f" {name}: 聚合对象,键={list(data.keys())[:6]}"
return f" {name}: {type(data).__name__}"
这样就算数据源把 close 改成 dwjz,代码不用动。命不中也不报错——只是那行汇总少一个字段。
三、技术面回看:K 线 + 均线算偏离
核心不是「把四个接口都调通」,而是把 K 线与均线归一后算出能用的指标。我们关心两件事:
- 最后一日涨跌幅 =(末日收盘 − 前日收盘)/ 前日收盘
- 收盘价相对均线的偏离 =(末日收盘 − 均线值)/ 均线值
import sys, time
import requests
TOKEN = "你的智兔token" # ← 换成你的真实智兔token
BASE = "https://api.zhituapi.com"
KLINE_ENDPOINTS = {
"lskx": ("/jh/hq/lskx/{code}/{level}", "历史 K 线"),
"zxkx": ("/jh/hq/zxkx/{code}/{level}", "最新 K 线"),
"lsma": ("/jh/hq/lsma/{code}/{level}", "历史 MA"),
"zxma": ("/jh/hq/zxma/{code}/{level}", "最新 MA"),
}
def fetch(name, code, level="d", token=TOKEN, timeout=15):
"""拉一个 K 线/均线端点,返回 (data, err)。"""
path_tmpl, _ = KLINE_ENDPOINTS[name]
url = f"{BASE}{path_tmpl.format(code=code, level=level)}"
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 last_return_and_dev(kline, ma):
"""从 K 线算最后一日涨跌幅;从 K 线+MA 算收盘价相对均线的偏离。"""
rows = [r for r in (kline or []) if isinstance(r, dict)]
ck = ["close", "c", "last", "dwjz", "nav", "price", "closeprice"]
if not rows:
return None, None
last = rows[-1]
prev = rows[-2] if len(rows) >= 2 else None
close_k = _hit_key(last, ck)
close = _to_float(last.get(close_k)) if close_k else None
daily_ret = None
if prev and close_k:
pclose = _to_float(prev.get(close_k))
if pclose:
daily_ret = round((close - pclose) / pclose * 100, 2)
dev = None
ma_rows = [r for r in (ma or []) if isinstance(r, dict)]
if ma_rows and close is not None:
ma_last = ma_rows[-1]
ma_val_k = _hit_key(ma_last, ["ma", "ma5", "ma10", "ma20", "value", "avg", "mean"])
ma_val = _to_float(ma_last.get(ma_val_k)) if ma_val_k else None
if ma_val:
dev = round((close - ma_val) / ma_val * 100, 2)
return daily_ret, dev
def kline_with_ma(code, level="d", token=TOKEN, sleep=0.2):
"""拉历史 K 线 + 历史 MA,合成「收盘价 vs 均线」的技术面快照。"""
if token == "你的智兔token":
print("[演示] TOKEN 为占位符,下面请求会返回错误码契约;请换成真实 token 后再跑。")
kline, kerr = fetch("lskx", code, level, token)
ma, maerr = fetch("lsma", code, level, token)
if kerr:
print(f" lskx: 暂不可用:{kerr}")
else:
print(summarize_kline("lskx", kline))
if maerr:
print(f" lsma: 暂不可用:{maerr}")
else:
print(summarize_kline("lsma", ma))
time.sleep(sleep)
return kline, ma
sleep=0.2 是限频保护——2 个端点 + 0.2 秒间隔 ≈ 0.4 秒。要回看多只基金,把 kline_with_ma 套一层循环,外面再撑一个更大的间隔即可。
四、代码自验结果
离线自测 6 项全 PASS(真实输出,不联网):
selftest PASS: _hit_key 命中 / 列表汇总 / 聚合对象 / 空数据兜底 / 涨跌幅与偏离 / 字段名不敏感 共 6 项
其中「涨跌幅与偏离」用的是合成数据(8/19 收 3.80、8/20 收 3.90、8/21 收 4.00,均线 3.90),断言最后一日涨跌幅 = (4.00−3.90)/3.90 ≈ 2.56%、偏离 = (4.00−3.90)/3.90 ≈ 2.56%——这些是逻辑自验用的假数,不是真实行情。
联网跑(占位 token)真实输出:
[演示] TOKEN 为占位符,下面请求会返回错误码契约;请换成真实 token 后再跑。
lskx: 暂不可用:404 102:Licence证书(你的智兔token)不存在
lsma: 暂不可用:404 102:Licence证书(你的智兔token)不存在
两个端点契约一致,单端点失败不影响整体流程、程序友好退出。本文未编造任何 K 线/均线数值——换成覆盖该接口的正式证书后重跑,即可打印真实技术面快照。
五、坑与注意事项
坑 #1:102 不代表路径写对了。 鉴权发生在路由匹配之前。路径故意写错配无效 token,同样返回 102。路径合法性只能靠客户端按白名单自查——本文 4 个路径是照公开文档核对的。实测 HTTP 状态码是 404 而不是 403,重点是看到 102:Licence证书... 就知道是证书问题。
坑 #2:{level} 是路径参数,不是查询参数。 必须拼成 /jh/hq/lskx/510300/d,写 ?level=d 会因缺路径参数而拿不到数据。这是本篇最容易踩的坑。
坑 #3:K 线序列顺序要确认。 不同数据源返回的 K 线可能是「时间正序」也可能是「倒序」。算「最后一日涨跌幅」前,先确认末位是最近一天(必要时 sorted 一下),否则会把最老一天当最新。
坑 #4:lskx(历史)和 zxkx(最新)别混。 lskx 是整段历史,zxkx 往往只给最近一根;要算涨跌幅用 lskx 足够,zxkx 适合做「盘中最新一根」的轻量刷新。
小结与下篇预告
这篇你拿到了 4 个 ETF 历史 K 线/均线端点(历史K线 / 最新K线 / 历史MA / 最新MA)、字段名容错抽取写法、以及一个把 K 线+均线合成「技术面快照」的模板,算出最后一日涨跌幅与均线偏离,顺带避开了 102 误判、level 路径参数、序列顺序、lskx/zxkx 混淆四个坑。
下一篇(#12)讲ETF 同类排名、规模与分红——用 /js/pm/kfpm/kfsjj_gpxjj_zsx(业绩排名)+ /js/gm/kfgm/kfsjj_gpxjj_zsx(规模)+ /js/jf/kffh/kfsjj_gpxjj_zsx(分红)做一只基金的「选基三维度」对比,把技术面升级成基本面筛选。
免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。
领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。
把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印 ETF 历史 K 线与均线数据。
免责声明
本文仅演示公开数据接口的用法,所有代码示例均以占位 token 自验,未含任何真实 K 线/均线数据;文中合成数据仅为逻辑自验用途,不构成投资建议,亦不承诺收益。投资决策请基于你自己的判断与风险承受力。