Vibe-Trading 行业概念板块工具实战:get_sector_info 的两种模式、东财 push2 端点与源码级解析
【免费下载链接】Vibe-Trading"Vibe-Trading: Your Personal Trading Agent"项目地址: https://gitcode.com/GitHub_Trending/vi/Vibe-Trading
本技术指南围绕 Vibe-Trading 开源仓库中的get_sector_info板块工具(SectorInfoTool)展开,讲解它如何通过东方财富(Eastmoney)免费免鉴权接口提供 A 股行业板块 / 概念板块的两种只读视图:按个股查询所属板块(membership)与按盘中涨跌幅对行业板块排名(ranking)。读完本文,你将掌握该工具的输入参数、端点查询参数、返回信封结构、底层 secid 解析与节流机制,并能结合源码与测试用例理解其防御性解析细节,直接在本地复现调用。
本文关联文档位于 agent/src/skills/eastmoney/references/板块/行业概念板块.md,核心实现位于 agent/src/tools/sector_tool.py,配套测试见 agent/tests/test_sector_tool.py。
工具概览:免费的 A 股板块分类数据
东方财富发布了一套免费、免鉴权的板块分类体系,把 A 股个股归入两类集合:
- 行业板块(如"白酒""酿酒行业""银行"):按申万/东财行业口径划分;
- 主题概念板块(如"白酒概念""人工智能"):按市场主题与热点概念划分。
get_sector_info只对这套分类提供两种只读视图,全部请求经由共享的eastmoneyper-host 节流层发出,避免触发东财按源 IP 的限流(东财对突发式请求会封禁客户端 IP)。
在源码中,该工具由SectorInfoTool类实现(agent/src/tools/sector_tool.py#L301-L347),继承自src.agent.tools.BaseTool,工具名(name)即get_sector_info,并声明了标准的 JSON Schema 参数(parameters),因此既能被 Agent 直接调用,也能以 LLM function-calling 形式暴露给上层编排。
两种模式:membership 与 ranking
工具支持两个互斥的只读视图,对应两个不同的 push2 端点:
| 模式 | 端点 URL | 语义 |
|---|---|---|
| membership | https://push2.eastmoney.com/api/qt/slist/get | 给定个股code,列出它所属的行业/概念板块(按 secid 寻址) |
| ranking | https://push2.eastmoney.com/api/qt/clist/get | 把行业板块全集按盘中涨跌幅排名(板块全集选择器fs=m:90+t:2) |
- membership:以
spt=3参数一次性混合返回个股所属的行业板块与概念板块,覆盖 A 股(.SH/.SZ/.BJ); - ranking:
fs=m:90+t:2表示"板块市场(m:90)+ 行业板块子类型(t:2)",即行业板块全集,按fid=f3(涨跌幅)降序排列。
两个端点 URL 在源码中以模块常量定义(agent/src/tools/sector_tool.py#L34-L35):
_MEMBERSHIP_URL = "https://push2.eastmoney.com/api/qt/slist/get" _RANKING_URL = "https://push2.eastmoney.com/api/qt/clist/get"字段选择器(agent/src/tools/sector_tool.py#L39-L44):
- 通用:
f12=板块/证券代码,f14=名称,f3=涨跌幅,f2=最新价/板块指数; - ranking 专属:
f104/f105=上涨/下跌成分数,f140=领涨股; - membership:
f12,f13,f14,f3,f2;ranking:f12,f14,f3,f2,f104,f105,f128,f140。
输入参数详解
SectorInfoTool.execute()接受三个关键字参数,与工具 Schema 中的properties一一对应:
| 名称 | 类型 | 必选 | 描述 |
|---|---|---|---|
| mode | str | N | "membership"(默认,列个股所属板块,需code)或"ranking"(行业板块按当日涨幅排名,忽略code) |
| code | str | membership 必选 | A 股 symbol 带后缀,如600519.SH/000001.SZ/430139.BJ |
| limit | int | N | ranking 时返回 top 板块数(1–100,默认 30);membership 忽略 |
源码中的默认值与防御性上限(agent/src/tools/sector_tool.py#L50-L53):
_MAX_RANKING = 100 # 防御性上限,防止载荷撑爆 LLM 上下文 _DEFAULT_RANKING = 30 _VALID_MODES = ("membership", "ranking")分发逻辑(agent/src/tools/sector_tool.py#L349-L375)值得注意的几个校验细节:
mode必须属于("membership", "ranking"),否则返回{"ok": false, "error": "mode must be one of ['membership', 'ranking']"};limit必须是正整数,且布尔值(isinstance(limit, bool))会被显式拒绝;实际请求的pz会被min(limit, _MAX_RANKING)钳制到 100;- membership 模式下
code必须是非空字符串(空白会被拒绝),传入前会做code.strip()去除首尾空格。
端点查询参数:与源码逐项对应
membership(slist 端点)
secid=<secid> spt=3 pi=0 pz=100 fields=f12,f13,f14,f3,f2 fltt=2 po=1在源码中对应_fetch_membership(agent/src/tools/sector_tool.py#L160-L203):
payload = get_json( _MEMBERSHIP_URL, params={ "secid": secid, "spt": "3", # 同时返回行业 + 概念板块 "pi": "0", # 页码(从 0 开始) "pz": "100", # 页大小 "fields": _MEMBERSHIP_FIELDS, "fltt": "2", # 返回浮点数值而非文本 "po": "1", }, )secid通过resolve_secid(code)解析:若解析失败(如符号无后缀或无法识别),工具不会发请求,而是直接返回错误信封{"ok": false, "error": "unresolvable symbol: ..."}。
ranking(clist 端点)
fs=m:90+t:2 fields=f12,f14,f3,f2,f104,f105,f128,f140 pn=1 pz=<limit> po=1 fid=f3 fltt=2对应_fetch_ranking(agent/src/tools/sector_tool.py#L257-L298):
payload = get_json( _RANKING_URL, params={ "fs": _RANKING_FS, # "m:90+t:2" 行业板块全集 "fields": _RANKING_FIELDS, "pn": "1", "pz": str(limit), # 已校验并钳制到 100 "po": "1", "fid": "f3", # 按涨跌幅排序 "fltt": "2", }, )ranking 视图不依赖 secid:它枚举整个行业板块全集,因此即使传入code也会被忽略(测试test_ranking_ignores_code_and_skips_resolve验证了resolve_secid不会被调用,见 agent/tests/test_sector_tool.py#L118-L127)。
返回信封与字段语义
成功时两种模式统一返回{"ok": true, "market": "stock", "source": "eastmoney", "mode": <mode>, "data": {...}}结构;失败时返回{"ok": false, "error": ...}。东财返回的"-"或空值统一映射为None,由_as_float(agent/src/tools/sector_tool.py#L68-L84)与领涨股解析共同保证。
membership 返回字段(每行一个板块)
| 字段 | 类型 | 描述 | 来源 |
|---|---|---|---|
| board_code | str | 板块代码 | f12 |
| board_name | str | 板块名称 | f14 |
| change_pct | float | 涨跌幅 | f3 |
| price | float | 最新价 | f2 |
信封的data额外包含code(原始 symbol)与secid(解析结果),便于追溯。解析时行若缺少f12或f14会被静默丢弃(测试中"missing-code"行即被过滤,见 agent/tests/test_sector_tool.py#L19)。
ranking 返回字段(每行一个行业板块)
| 字段 | 类型 | 描述 | 来源 |
|---|---|---|---|
| board_code / board_name | str | 板块代码 / 名称 | f12 / f14 |
| change_pct | float | 涨跌幅 | f3 |
| index | float | 板块指数 | f2 |
| up_count / down_count | float | 上涨 / 下跌成分数 | f104 / f105 |
| leader | str | 领涨股(-→ None) | f140 |
防御性载荷解析
push2 接口的data.diff在不同端点可能返回列表或按字符串索引键控的字典两种形态。_diff_rows(agent/src/tools/sector_tool.py#L139-L157)对两者都做了兼容,测试test_diff_as_dict_is_handled(agent/tests/test_sector_tool.py#L138-L143)验证了字典形态的解析。
此外_fetch_ranking在拿到解析结果后会再次执行boards[:limit]截断,确保即使上游返回多余行也不会突破请求的上限。
底层支撑:secid 解析与共享节流层
secid 寻址规则
get_sector_info的 membership 模式与 K 线数据共用同一套 secid 寻址方案(agent/backtest/loaders/eastmoney_client.py#L105-L114):
- 上交所(
.SH)→ 市场号1,如600519.SH→1.600519; - 深交所与北交所(
.SZ/.BJ)→ 市场号0,如000001.SZ→0.000001、430139.BJ→0.430139; - 非 A 股后缀(
.HK补零五位为116.xxxxx、.US走 suggest 搜索并缓存)不在板块工具的支持范围内——membership 只覆盖 A 股。
resolve_secid的完整分支在 agent/backtest/loaders/eastmoney_client.py#L208-L236,无法识别时返回None,工具随即返回"unresolvable symbol"错误信封(测试见 agent/tests/test_sector_tool.py#L172-L176)。
共享 per-host 节流
板块请求与东财 K 线请求共用同一个节流桶_HOST_KEY = "eastmoney"(agent/backtest/loaders/eastmoney_client.py#L38-L40)。get_json内部调用throttled_get_json(agent/backtest/loaders/_http.py#L176-L200),每次请求前都会等待满足 per-host 最小间隔:
- 默认最小间隔
1.0秒; - 可通过环境变量
VIBE_TRADING_EASTMONEY_MIN_INTERVAL覆盖(resolve_min_interval,agent/backtest/loaders/_http.py#L127-L138); - 同一 host key 复用同一
requests.Session,并带默认浏览器 UA; - 请求超时默认 15 秒;非 2xx 状态或无法解析的 JSON 会抛出异常,由工具层捕获并转为干净的错误信封(测试用
HTTP 429/HTTP 503模拟,见 agent/tests/test_sector_tool.py#L178-L194)。
这也意味着:任何并发调用(如批量板块查询)都必须经过该节流层,这正是文档中"东财按源 IP 限流"的落地实现。
进阶用法一:单行业板块解析resolve_industry_board
除SectorInfoTool外,模块还导出了一个非工具函数resolve_industry_board(code)(agent/src/tools/sector_tool.py#L206-L254),用于把一只 A 股解析为唯一的行业板块名称(如600519.SH→白酒Ⅱ):
- 与 membership 的
spt=3(混合行业+概念)不同,它请求spt=1,返回"个股本身 + 其唯一行业板块"两行; - 板块行以
f13 == 90(板块市场标记)区分——个股行f13为1/0,板块行为90; - 仅当
_detect_market(code) == "a_share"时才会发请求(agent/backtest/engines/_market_hooks.py#L214-L233); - 任何失败(不可解析、请求异常、无板块行)都静默降级为
None,绝不抛出异常。
进阶用法二:在 API 中的实际应用(持仓行业解析)
resolve_industry_board已被用于回测运行的持仓行业归因:在 agent/src/api/runs_routes.py#L522-L543 的_resolve_industries_concurrent中,通过线程池对符号集合做有界并发解析(不超过_POSITIONS_SECTOR_MAX_SYMBOLS个网络查询、_POSITIONS_SECTOR_WORKERS个并发 worker),结果缓存到artifacts/sector_map.json。单个符号解析失败不会中断整批任务,这与resolve_industry_board"永不抛异常"的约定一致。
调用范例与联动研究
最小可运行示例
from src.tools.sector_tool import SectorInfoTool print(SectorInfoTool().execute(code="600519.SH")) # membership print(SectorInfoTool().execute(mode="ranking", limit=20)) # 行业板块涨幅榜运行前提:在agent/目录下执行(导入根为agent/),无需任何 token;所有请求自动经过东财共享节流层。
资金流向 + 板块联动
仓库自带的示例脚本 agent/src/skills/eastmoney/scripts/fund_flow_example.py 演示了把板块工具与资金流向工具组合的研究流程:
def study_sectors(code: str) -> None: membership = json.loads(SectorInfoTool().execute(code=code)) if membership.get("ok"): boards = membership["data"].get("boards", []) print(f"{code} 所属板块:{[b['board_name'] for b in boards]}") ranking = json.loads(SectorInfoTool().execute(mode="ranking", limit=5)) if ranking.get("ok"): for board in ranking["data"]["boards"]: print(f" {board['board_name']}: {board['change_pct']}%")该脚本以600519.SH为例:先读取近 30 日主力净流入序列(FundFlowTool),再列出其所属板块与当日行业涨幅榜前 5,形成"个股资金流 → 板块归属 → 板块热度"的联动研判链条。
测试验证:信封形状、解析与校验全覆盖
agent/tests/test_sector_tool.py 对工具行为做了完整的单元验证,所有 HTTP 均 mock 在get_json/resolve_secid层,不触网。可归纳为四组:
- 信封形状:
ok/market="stock"/source="eastmoney"/mode字段齐备,data.code与data.secid回显正确; - 解析正确性:membership 与 ranking 的 diff 行正确映射到带标签的 dict,
"-"值映射为None,缺f12的行被丢弃; - 模式分发:ranking 忽略
code且不触发 secid 解析;limit=10000时请求pz被钳制为"100";diff 为字典形态也能解析; - 错误信封:缺
code、空白code、非法mode、非正limit、布尔limit、无法解析的符号、HTTP 失败(429/503)均返回{"ok": false, ...}。
使用建议与边界
- membership 用于个股画像:快速回答"这只股票属于哪些行业/概念板块",适合投研的持仓归因与风格刻画;
- ranking 用于板块轮动:盘中实时观测行业涨幅榜、上涨/下跌成分数(市场宽度)与领涨股,是板块热度与情绪监测的低成本数据源;
- 注意市场范围:membership 仅覆盖 A 股(
.SH/.SZ/.BJ);非 A 股符号在 membership 模式下会返回 unresolvable 错误; - 注意节流:数据免费免鉴权,但东财按源 IP 限流,并发批量查询务必经由共享节流层并考虑缓存(如
sector_map.json模式)。
【免费下载链接】Vibe-Trading"Vibe-Trading: Your Personal Trading Agent"项目地址: https://gitcode.com/GitHub_Trending/vi/Vibe-Trading
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考