快速开始
- 确认权限:查看我的权限;尚未开通所需数据时,前往权限中心联系开通。
- 获取密钥:创建或复制 API Key,请求时放入
X-Api-Key请求头。 - 开始查询:从已开通接口进入对应文档,按示例发起请求并查看返回;也可选择MCP 接入或Python SDK。
使用前请查看调用规则与标准调用代码。不要把 API Key 写进浏览器前端、公开仓库或截图。
基础地址:
https://jinniu.lingxideai.com。日期使用 YYYYMMDD,例如 20260805。所有 API 的响应压缩:客户端请求头带
Accept-Encoding: gzip 时,服务端使用 gzip 压缩传输;未携带时按默认 JSON 返回。该请求头不是业务参数,不需要写入各接口的请求示例。MCP 接入
支持无状态 Streamable HTTP MCP。将服务地址设为 https://jinniu.lingxideai.com/mcp,并在请求头中使用 Authorization: Bearer YOUR_API_KEY;也兼容 X-Api-Key。
MCP 复用当前账号的数据权限。
请帮我添加并连接这个MCP服务:
名称:金牛行情数据
类型:Streamable HTTP
地址:https://jinniu.lingxideai.com/mcp
鉴权:Authorization: Bearer YOUR_API_KEY
安装后请读取tools/list验证连接;如果不能自动添加,请告诉我配置入口。
调用建议:最多3个并行工作线程,每个线程内约3秒调用1次,错峰发起,所有线程与客户端合计不超过60次/分钟。以后台实际限制和Retry-After为准,不修改或绕过后台限额。
连接验证
curl -X POST 'https://jinniu.lingxideai.com/mcp' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example","version":"1.0"}}}'
连接后通过 tools/list 获取当前工具及输入参数说明;通过 tools/call 查询数据。返回数据使用 data.fields + data.items 结构。
MCP 调用建议:最多 3 个并行工作线程,每个线程内约 3 秒调用 1 次,错峰发起,合计不超过 60 次/分钟。这是客户端建议,不锁定后台;实际限制更严格时以后台配置及 Retry-After 为准。
faq_search 可搜索已核验、可解释的FAQ,参数为 q(关键词,可省略)和 page(默认1),固定每页20条。未确认的问题不返回,查询不扣行情日额度;答案适用范围见返回的 scope,分页信息见 data.meta。已连接的AI如看不到新工具,请刷新工具列表或重新连接MCP。
调用规则
调用频率与并发
以下为文档建议调用配置,不锁定后台设置。管理员可在后台调整实际限额;后台要求更严格时,必须按实际限额调用。
- 历史、实时及 MCP 调用均为每账户每分钟最多 60 次。
- 建议同一账户最多同时发起 3 个并发请求。每个线程内的调用频率约为 3 秒 1 次:线程 A 可在第 0、3、6 秒调用,线程 B 在第 1、4、7 秒调用,线程 C 在第 2、5、8 秒调用。不是所有线程共用一个 3 秒间隔;所有线程和客户端合计每分钟不超过 60 次。
- 请求尚未完成时,不要为了追赶频率增加并发。达到实际限制时返回
429,以响应中的Retry-After为准等待;网关并发限制通常返回Retry-After: 2。 - 账户数据权限到期、权限关闭或超限时,接口会拒绝请求。删除或重新生成 API Key 不会重置账户调用次数。
单次返回与批量查询
| 适用接口 | 单次返回上限 | 查询方式 |
|---|---|---|
| 股票、ETF、交易所指数历史日线;股票复权因子;股票、ETF、交易所指数、期货及期权历史分钟线;期权日线 | 6000 条 | 按代码、日期或时间区间分批查询。 |
| 公募基金列表 | 6000 条 | 按市场、状态筛选,使用 offset 分页。 |
| 基金经理 | 5000 条 | 按基金、姓名或公告日期筛选。 |
| 基金管理人、净值、分红及持仓 | 2000 条 | 按支持的筛选条件与 offset 分页;持仓为报告期披露。 |
| 期货合约信息 | 6000 条 | 按交易所、产品筛选,使用 offset 分页。 |
| 期货日线、主力映射、持仓排名 | 2000 条 | 按合约、日期或 offset 分页。 |
| 期货复权因子、复权日线 | 3000 条 | 主力合约换月复权。 |
| 期货仓单日报 | 1000 条 | 按产品、日期或 offset 分页。 |
| 申万指数历史分钟线 | 5000 条 | 单个指数代码,按时间区间分批查询。 |
| 股票基础信息:股票列表、交易日历、ST 历史、名称变更、公司主体、北交所代码映射;期权合约信息 | 5,000 条 | 通过各接口支持的代码、日期等条件筛选。 |
| 申万指数基本信息、行业分类、行业成分及历史成员;ETF 复权因子 | 2,000 条 | 通过代码、分类、日期等条件筛选。 |
| 申万指数历史日线 | 4,000 条 | 按指数代码与日期区间查询。 |
| 股票、ETF、交易所指数、期货实时分钟线;股票及 ETF 实时日线;申万指数实时行情 | 1,000 条 | 每次最多查询 100 个代码或合约。 |
| 股票日内分钟线;ETF、交易所指数及期货盘中分钟线 | 1,000 条 | 每次仅查询 1 个代码或合约。 |
- 分钟线不限制固定自然日跨度;超过单次返回量时请按时间范围分批查询。
- 所有 API 支持可选
gzip响应压缩:带Accept-Encoding: gzip时返回压缩内容;不带时按默认 JSON 返回。常见浏览器和 HTTP 客户端会自动协商并解压。 - 请妥善保管 API Key。发现泄露时,在个人中心轮换 Key,旧 Key 会立即失效。
- 数据仅限研究、学习和个人使用,使用者应自行核验并遵守适用的法律、平台与数据使用规则。
规范调用建议
- 按代码和时间范围分批查询,并通过
fields仅请求所需字段;已成功获取的数据请保存在本地,避免重复拉取。 - 收到
429时必须按照Retry-After等待后再重试,不要立即连续重发;网络异常建议间隔 2、5、10 秒逐步重试,最多 3 次。 - 大批量历史数据建议安排在非交易时段执行;实时接口仅查询当前业务需要的代码,不要循环请求全市场。
import json
import math
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import requests
API_KEY = "YOUR_API_KEY"
URL = "https://jinniu.lingxideai.com/api/stk_mins"
# Client recommendations only; use lower values if your account requires them.
WORKERS = 3
SECONDS_PER_THREAD = 3.0
TOTAL_REQUESTS_PER_MINUTE = 60
class CallControl:
def __init__(self):
self.lock = threading.Lock()
self.local = threading.local()
self.stop = threading.Event()
self.next_global = 0.0
self.cooldown_until = 0.0
def wait(self):
while not self.stop.is_set():
with self.lock:
if self.stop.is_set():
break
now = time.monotonic()
due = max(getattr(self.local, "next_at", 0.0),
self.next_global, self.cooldown_until)
if now >= due:
self.local.next_at = now + SECONDS_PER_THREAD
self.next_global = now + 60.0 / TOTAL_REQUESTS_PER_MINUTE
return
self.stop.wait(min(due - now, 0.25))
raise RuntimeError("Batch stopped; keep completed files and fix the error.")
def cooldown(self, seconds):
with self.lock:
self.cooldown_until = max(self.cooldown_until, time.monotonic() + seconds)
control = CallControl()
def retry_after(response, body):
waits = []
for value in (response.headers.get("Retry-After"),
(body.get("data") or {}).get("retry_after_seconds")):
try:
seconds = float(value)
if math.isfinite(seconds) and seconds >= 0:
waits.append(seconds)
continue
except (TypeError, ValueError):
pass
try:
date = parsedate_to_datetime(str(value))
date = date if date.tzinfo else date.replace(tzinfo=timezone.utc)
waits.append(max(0.0, (date - datetime.now(timezone.utc)).total_seconds()))
except (TypeError, ValueError, OverflowError):
pass
return max(waits, default=2.0)
def query_minutes(session, trade_date):
payload = {
"ts_code": "000001.SZ", "freq": "1min",
"start_date": f"{trade_date} 09:30:00",
"end_date": f"{trade_date} 15:00:00",
"fields": "ts_code,trade_time,open,close,high,low,vol,amount",
"limit": 4000,
}
for attempt in range(4):
control.wait() # Every HTTP attempt, including retries, is paced.
try:
with session.post(URL, json=payload, timeout=(5, 60)) as response:
if response.status_code in (400, 401, 403):
control.stop.set()
raise RuntimeError(f"HTTP {response.status_code}: fix parameters, key or permission.")
if response.status_code == 429:
try:
body = response.json()
except ValueError:
body = {}
if (body.get("data") or {}).get("limit_type") == "daily":
control.stop.set()
raise RuntimeError("Daily quota exhausted; resume after reset.")
if attempt == 3:
raise RuntimeError("Rate limit persists after 3 retries.")
control.cooldown(retry_after(response, body))
continue
response.raise_for_status()
result = response.json()
if not isinstance(result, dict) or result.get("success") is not True:
raise RuntimeError("API returned an unsuccessful response.")
return result
except requests.RequestException:
if attempt == 3:
raise
if control.stop.wait((2, 5, 10)[attempt]):
raise RuntimeError("Batch stopped.") from None
raise RuntimeError("No successful response.")
def worker(index, dates, epoch):
control.local.next_at = epoch + index
# Each worker owns its Session and reuses its own connections.
with requests.Session() as session:
session.headers.update({"X-Api-Key": API_KEY, "Accept-Encoding": "gzip"})
try:
for trade_date in dates[index::WORKERS]:
result = query_minutes(session, trade_date)
with open(f"000001.SZ_{trade_date}.json", "w", encoding="utf-8") as file:
json.dump(result, file, ensure_ascii=False)
except Exception:
control.stop.set()
raise
def main():
dates = ["2026-09-08", "2026-09-09", "2026-09-10"]
epoch = time.monotonic()
with ThreadPoolExecutor(max_workers=WORKERS) as pool:
futures = [pool.submit(worker, index, dates, epoch) for index in range(WORKERS)]
for future in as_completed(futures):
future.result()
if __name__ == "__main__":
main()
错误码
400 参数错误,401 API Key 无效或已删除,403 账户期限或数据权限不满足,429 请求超过限额,503 行情数据暂不可用。错误响应包含 code、message 和 data 字段。
请求频率超限时,HTTP 429 响应头会返回
Retry-After;响应体的 data 会提供 limit_type、retry_after_seconds 和 reset_at,客户端应等待后再重试。