| name | a-stock-data |
| description | 当任务需要写代码实际获取A股数据时使用——拉取行情/K线(mootdx+腾讯+百度)、研报(东财+同花顺+iwencai)、信号(热点/北向/龙虎榜/解禁/行业)、资金面(融资融券/大宗/股东户数/分红/资金流)、新闻、财务三表/F10、公告(巨潮)、打板(涨停池/连板/炸板率/重点监控池/日内异动)、ETF期权(T型报价/希腊字母/IV)、舆情互动(互动易/热榜/人气榜)、筹码分布(获利比例/成本区间)、复权因子、估值历史(PE/PB/PS+换手率+ST)、申万行业变迁史、宏观(社融/PMI)等真实数据。十一层数据源·54端点(含3官方备胎)·内嵌全部可运行代码,自包含零依赖外部文件;优先用通达信(mootdx)/腾讯(不封IP),东财接口已内置限流防封,主源被封可查「备用源速查」降级。仅在需要调用数据接口取数时使用:A股概念解释、投资观点讨论、策略问答等无需取数的话题不要加载本skill。 |
| origin | custom |
| version | 3.7.1 |
📦 项目主页:https://github.com/simonlin1212/a-stock-data — 更新、反馈、支持作者
作者:Simon 林 · X @linsizhen · 邮箱:simonlin0423@gmail.com
A股全栈数据工具包 V3.7.1
十一层数据架构,54 个端点实测可用(51 主端点 + 3 官方备胎,2026-08 验证),覆盖主板/中小板/科创板/ST。每类数据在「备用源速查」列有独立备胎,主源被封时可降级。
V3.7.1(后缀路由修复,2026-08-20):get_prefix() 认显式前缀(sh000016)却不认等价的后缀写法(000016.SH)——而 norm_ticker() 文档明文支持后缀式,且 em_market_code()/em_secid()(V3.7.0 新增)把未归一化的原串直接喂给 get_prefix():000016.SH 以 0 开头落到默认深市分支,secid 拼成 0.000016(深康佳A)而非 1.000016(上证50 指数),静默返回另一只标的的数据。已在 get_prefix() 开头加后缀识别分支(.sh/.sz/.bj 与显式前缀等价透传),号段推断与沪指数白名单逻辑不变。⚠️ 走「前缀+原串拼接」的端点(tencent_quote()、新浪财报等)仍只认纯 6 位或前缀式——后缀式会把 .SH 拼进请求串,先过 norm_ticker() 再传的总原则不变。
**V3.7.0(宏观层 + 筹码分布 + 复权因子 + 估值历史,2026-08-19):**新增 1 个数据层、7 个端点、4 个数据源(baostock / 申万 / 人民银行 / 国家统计局,全部零注册零 key)。全部端点于 2026-08-19 实跑验证。
- §4.6 筹码分布 CYQ — 补上本层名实不符的窟窿:Layer 4 叫「资金面 / 筹码层」但 §4.1~§4.5 全是资金面数据,一直没有真正的筹码分布。东财没有公开 CYQ 接口(实测
push2/push2his 的 cyq/get 均 404),本端点用 OHLC + 换手率本地推演,零新增数据源。输出获利比例 / 平均成本 / 90-70 成本区间与集中度 / 筹码峰。⚠️ 初始筹码播种为首日全部流通盘——从全零起步会把窗口前的存量持仓一笔勾销(两个 1% 换手日会被算成 50/50,真实应约 99%/1%)。
- §6.5 估值历史 — §1.2 腾讯只有当日估值快照,本端点给日频历史序列(实测茅台 2016-01-04 起 2581 行),并一次补齐此前完全缺失的四项:换手率(筹码分布的必需输入)、停牌状态、ST 标记(实测 000004 有 276 天 isST=1)、历史 PE/PB/PS/PCF。⚠️ baostock 不支持北交所,服务端报
10004011,本实现在登录前就拦掉抛 ValueError。
- §1.4 复权因子 — §1.1 通达信
bars() 是不复权数据,跨除权日直接比价必错。新浪 qfq/hfq 因子序列一次 HTTP 约 1.8KB。⚠️ 响应末尾挂着 /* base64 */ 注释块,不能用 $ 锚定正则,须用 raw_decode。
- §6.6 上市/退市日 — 唯一能拿到退市日期的零鉴权源,配合 §1.2
is_stale 可在筛选阶段剔除僵尸标的。
- §6.7 申万行业变迁史 — §3.7 东财只有当前行业归属,用它做历史研究是前视偏差。本端点给每只股票的行业变迁(实测 12,893 行 / 5,905 只 / 38 个一级行业;有标的历史变更过 10 次)。⚠️ 申万官方只发代码不发中文名。
- §11.1/§11.2 宏观层(新) — 人民银行社融(月度 12 列)+ 国家统计局 PMI。⚠️ 社融链路是三级跳,未发布月份整行丢弃而非返回 NaN(否则调用方会把 12 行当 12 个月真数据);PMI 正文是全角括号内带空格,空白必须整个删掉才匹配得到。
V3.6.1(龙虎榜空窗口崩溃修复,2026-08-09 · #45):dragon_tiger_board() 在回看窗口内无上榜记录时抛 UnboundLocalError——而大市值 / 低换手率标的(如贵州茅台)常态无上榜,等于调用即崩,调用方还无法区分「无数据」与「接口异常」。已修,空窗口返回语义一致的空结构。
V3.6.0(静默失败修复 + 重点监控池/日内异动,2026-07-31 · #15):
- 🔴 北交所老号段(43/83/87)会返回僵尸数据且不报错:实测在市 342 只中 336 只已迁至
920xxx(锦波生物 832982→920982、贝特瑞 835185→920185),老码在东财研报静默返回 0 篇、在腾讯行情返回定格报价(成交量 0,价差达 17%~100%+)却仍是 HTTP 200。新增全局警告章节;tencent_quote() 增加 is_stale/stale_reason 标志(实测老码 2/2 命中、正常票 4/4 无误报);eastmoney_reports() 遇老码抛 ValueError 而非返回空。
- 🔴 §2.1/§2.2 研报层 ticker 未归一化(静默空):
eastmoney_reports("SH600519") / "600519.SH" 一律返回 0 篇(reportapi 只认纯 6 位),而文档「Ticker 格式归一化」明文承诺全接口支持带前缀写法——承诺与实现不符,且失败方式是静默空,调用方会误读成「该标的无研报覆盖」。新增 norm_ticker() 实现(解析失败抛 ValueError,绝不返回空串),eastmoney_reports() / ths_eps_forecast() 接入。
- §8.4
em_stock_monitor() 东财重点监控池新增(#15):交易所风险警示 / 重点监控名单 + 生效时间窗,零鉴权静态 JSON。
- §8.5
em_price_anomaly() / em_price_anomaly_count() 日内异动池新增(#15):交易所「严重异常波动」口径的异动明细与按标的聚合统计,含 12 条异动规则码全解释。⚠️ 必须带 team=h5 等固定参数,缺失返回 unknow team——已做 result!=0 冒泡而非静默返回空。
- §2.1 行业研报去硬编码日期:
begin 默认由固定的 2024-01-01 改为「相对今天往前两年」,避免时间窗越用越旧。
- 端点 44 → 47。实测 24/24 通过(含前缀格式 4/4、老号段拦截、新端点真数据、异动接口拒绝冒泡)。
V3.5.0(板块资金流向,2026-07-23 · #37):
- §3.8
board_fund_flow() 板块资金流向新增:补上此前缺失的板块级资金流——行业/概念/地域三类板块 × 今日/5日/10日三周期,主力净流入额/净占比 + 超大/大/中/小单四档明细 + 领涨股。与 §3.7 板块排名同源同接口(东财 push2 clist),此前只请求了价格/涨跌家数字段,本版补请求 f62/f184/f66... 资金流字段即覆盖。走 em_get 限流防封。端点 43 → 44。
- 实测(2026-07-23):行业今日 100 个板块主力净额降序(电力设备 64.66亿 = 超大 43.55亿 + 大 21.11亿)、概念 5 日、地域 10 日均真实返回;参数校验拒绝非法 board_type/period。
V3.2.2(失效接口替换 + 隐藏 Bug 修复):
- §3.3 概念板块归属(#18):百度 PAE
getrelatedblock 失效(ResultCode 10003 + 空数组)→ 改用东财 slist(spt=3)eastmoney_concept_blocks(),一次请求拿全个股所属板块(行业/概念/地域 + BK码 + 涨跌幅 + 龙头股),零鉴权走 em_get 限流。
- §7.1 巨潮公告 orgId(#19):硬编码
gssx0{code} 致大量 601xxx 股票 totalAnnouncement=0 → 新增 _cninfo_orgid() 动态查官方映射表 szse_stock.json(6198 只股,模块级缓存),硬编码降为 fallback。
- 综合示例修复:示例仍调用 v3.1 已删的
baidu_fund_flow_history → 改 eastmoney_fund_flow_minute。
- §4.5/§5.1 风控说明:部分大陆住宅 IP 被东财间歇风控(
HTTP 000/空)非代码 Bug,加重试/换网络提示。
- 新代码原样 exec smoke test 实测:板块归属 茅台27/五粮液28/绿的谐波21;公告 平安601318/工行601398 原失效股恢复。
V3.2.1(Bug 修复): 修复两个内嵌函数的解析逻辑(预先存在,非 V3.2 引入)——
- §5.1 东财个股新闻:东财实际返回里
result.cmsArticleWebOld 直接就是文章列表,旧写法对 list 调 .get("list") 触发 AttributeError / 返回空 → 改为遍历 cmsArticleWebOld 列表本身。
- §6.4 新浪财报三表:新浪实际结构是
result.data.report_list(按报告期为键的 dict,每期 data 才是行项列表),旧写法取 result.data.{lrb} 永久返回空 → 改为遍历 report_list 期次、从每期 data 按 item_title 提取。
- 两函数均用茅台 600519 公开 API(零 key)实测返回非空、字段正确。
V3.2(防封 + 失效修复):
- 数据源优先级 + 东财防封:明确「通达信(mootdx)/腾讯不封IP 优先用,东财仅用于其独有数据」原则;新增统一节流入口
em_get(),所有东财接口内置串行限流(间隔≥1s+随机抖动)+ 会话复用,AI 抄代码即自带防封。详见「数据源优先级 & 东财防封」章节。
- 财联社快讯下线(#14):
cls.cn 旧 API 全面 404,标注弃用并改用东财全球资讯。
V3.1 修复: 替换 4 个失效接口(百度 PAE 资金流→东财 push2、大宗交易 RPT 报表名更新、机构席位改用 BUY/SELL 明细筛选)+ 修复东财全球资讯 req_trace 参数 + 修复巨潮公告 orgId 格式。
V3.0 Breaking Change:彻底移除 akshare 依赖,所有数据源改为直连 HTTP API(仅 mootdx 保留 TCP)。
⚠️ V3.7 起 §6.5/§6.6 另需第三方客户端 baostock(TCP),因此「零第三方封装依赖」现仅适用于其余端点。
使用方式: 将本文件放入 ~/.claude/skills/a-stock-data/SKILL.md,Claude Code 会自动识别并在 A 股相关对话中激活。
行情层(实时,不封IP)
├── mootdx → K线 + 五档盘口 + 逐笔成交 (TCP 7709)
├── 腾讯财经 API → PE/PB/市值/换手率/涨跌停/指数/ETF (HTTP)
├── 百度股市通 → K线带MA5/10/20 (V3.0 新增,HTTP)
└── 新浪复权因子 → qfq/hfq 因子序列 + 套用到不复权K线 (V3.7 新增)
研报层
├── 东财 reportapi → 个股研报 + 行业研报 + PDF下载 + 评级 + 三年EPS
├── 同花顺 THS → 一致预期EPS (直连 basic.10jqka.com.cn)
└── iwencai → NL语义搜索研报 (唯一能力,需X-Claw)
信号层
├── 同花顺热点 → 当日强势股 + 题材归因 reason tags (零鉴权 73ms)
├── 同花顺北向 → hgt/sgt 分钟资金流向 + 本地自缓存历史
├── 东财 slist → 个股所属板块/概念归属 (V3.2.2 替换百度PAE)
├── 东财 push2 → 个股资金流向 分钟级 (V3.1 替换百度PAE)
├── 龙虎榜席位 → 上榜记录 + 买卖席位 TOP5 + 机构动向 (datacenter-web)
├── 全市场龙虎榜 → 每日全市场上榜股票 + 净买额排名 (datacenter-web)
├── 限售解禁日历 → 历史解禁 + 未来90天待解禁 (datacenter-web)
├── 行业板块排名 → 东财行业涨跌/上涨下跌家数 (V3.0 替换同花顺)
└── 板块资金流向 → 行业/概念/地域 × 今日/5日/10日 主力+超大/大/中/小四档 (push2, V3.5)
资金面 / 筹码层
├── 融资融券明细 → 日级融资余额/买入/偿还 + 融券 (datacenter-web)
├── 大宗交易 → 成交价/量 + 买卖方营业部 (datacenter-web)
├── 股东户数变化 → 季度股东户数 + 环比变化 (datacenter-web)
├── 分红送转 → 历史每股派息/送股/转增 (datacenter-web)
├── 个股资金流120日 → 主力/大单/中单/小单 日级净流入 (push2his)
└── 筹码分布 CYQ → 获利比例/平均成本/90-70成本区间/筹码峰 (本地计算,V3.7 新增)
新闻层
├── 东财个股新闻 → 个股相关新闻 (search-api-web JSONP)
├── 财联社快讯 → 全市场实时电报 (v1 API+本地签名零key,✅V3.4 复活)
└── 东财全球资讯 → 7×24 财经快讯 (np-weblist,与财联社互备)
基础数据层
├── mootdx finance → 季报快照 (37字段, EPS/ROE/净利)
├── mootdx F10 → 公司资料 (9大类文本)
├── 东财个股信息 → 行业/总股本/流通股/市值/上市日期 (push2)
├── 新浪财报三表 → 资产负债表/利润表/现金流量表 (quotes.sina.cn)
├── baostock 估值历史 → 日频 PE/PB/PS/PCF + 换手率 + 停牌 + ST (不支持北交所,V3.7 新增)
├── baostock 标的信息 → 上市日/退市日/状态 (V3.7 新增)
└── 申万行业分类 → 行业归属变迁史,消除前视偏差 (仅代码无中文名,V3.7 新增)
公告层
├── 巨潮 cninfo → 公告全文检索+下载 (cninfo.com.cn)
└── mootdx F10 → 最新公告摘要
打板层 (V3.3 新增)
├── 东财涨停池 → 连板数/几天几板/封板资金/炸板次数/行业 (push2ex)
├── 东财炸板池 → 涨停后开板 + 振幅/涨速 (push2ex)
├── 东财跌停池 → 封单资金/连续跌停/开板次数 (push2ex)
├── 东财昨涨停池 → 昨涨停今表现,算晋级率/赚钱效应 (push2ex)
└── 同花顺涨停揭秘 → 涨停原因题材/封板成功率/板型 (10jqka)
ETF期权层 (V3.3 新增)
├── 合约清单 → 50ETF/300ETF/科创50/500ETF 各月认购认沽 (新浪)
├── T型报价 → 买卖五档/持仓量/行权价/最新价 (新浪)
└── 希腊字母+IV → Delta/Gamma/Theta/Vega/隐含波动率 (新浪)
舆情互动层 (V3.3 新增)
├── 互动易问答 → 投资者提问+公司回复 (巨潮,AI问答独家)
├── 同花顺热榜 → 人气值/概念标签/排名变化 (10jqka)
├── 东财人气榜 → 排名+排名变化+名称价格 (emappdata)
└── 东财概念命中 → 个股被归到哪些概念在炒+热度 (emappdata)
宏观层 (V3.7 新增)
├── 人民银行社融 → 社会融资规模增量 月度12列 (pbc.gov.cn,三级跳取 xls 附件)
└── 国家统计局PMI → 制造业/非制造业/综合 + 大中小型企业分档 (stats.gov.cn)
端点路由速查(按需定位,不必通读全文)
只需一类数据时,按下表定位章节(§)局部读取。除 iwencai 需 API Key 外全部零 key。
| § | 函数 | 拿什么 | 源 |
|---|
| 前置 | norm_ticker(code) | 任意写法→纯6位(SH600519/600519.SH 皆可;解析失败抛错不返空) | 本地 |
| 1.1 | tdx_client() → .bars() / .quotes() / .transaction() | K线(多周期,不复权) / 五档盘口 / 逐笔成交 | 通达信 |
| 1.2 | tencent_quote(codes) | 实时价/PE/PB/市值/换手/涨跌停/指数/ETF(带 is_stale 僵尸报价标志) | 腾讯 |
| 1.3 | baidu_kline_with_ma(code) | 日K线带 MA5/10/20 | 百度 |
| 1.4 | sina_adjust_factor(code, kind) / apply_adjust(bars, factors) | 复权因子 qfq/hfq + 套用到不复权K线 | 新浪 |
| 2.1 | eastmoney_reports(code) / download_pdf(rec) | 个股研报+评级+三年EPS / 研报PDF | 东财 |
| 2.1 | eastmoney_industry_reports(industry_code) | 行业研报 | 东财 |
| 2.2 | ths_eps_forecast(code) | 机构一致预期 EPS | 同花顺 |
| 2.3 | iwencai_search(query) / iwencai_query(query) | NL 语义搜研报/选股(需 Key) | iwencai |
| 3.1 | ths_hot_reason() | 当日强势股+题材归因 | 同花顺 |
| 3.2 | hsgt_realtime() | 北向分钟流向(hgt 可用 / sgt 仅参考) | 同花顺 |
| 3.3 | eastmoney_concept_blocks(code) | 个股所属板块/概念归属 | 东财 |
| 3.4 | eastmoney_fund_flow_minute(code) | 个股资金流(分钟级) | 东财 |
| 3.5 | dragon_tiger_board(code, date) | 个股龙虎榜+买卖席位 TOP5 | 东财 |
| 3.6 | lockup_expiry(code, date) | 解禁历史+未来90天待解禁 | 东财 |
数据源优先级 & 东财防封(重要,先读)
优先级原则:能用通达信/腾讯,就别用东财
| 优先级 | 数据源 | 协议 | 封 IP 风险 | 覆盖 |
|---|
| 1(首选) | mootdx(通达信) | TCP 7709 二进制 | 不封 IP | K线、五档盘口、逐笔成交、财务快照、F10 |
| 2 | 腾讯财经 | HTTP GBK | 不封 IP | 实时价、PE/PB/市值/换手率/涨跌停、指数、ETF |
| 3 | 新浪 / 巨潮 / 同花顺 | HTTP | 低 | 财报三表、公告、一致预期/热点 |
| 4(仅独有数据才用) | 东财 eastmoney | HTTP | 有风控,会封 IP | 见下 |
凡是行情 / K线 / 实时价 / 市值 / 财务三表能从 mootdx 或腾讯拿到的,一律走它们——TCP 协议和腾讯接口实测不封 IP,可放心高频调用。
东财只用于它「独有、别处拿不到」的数据
下列数据只有东财有,通达信/腾讯/新浪都没有,必须用东财(但要限流):
龙虎榜席位 · 全市场龙虎榜 · 限售解禁日历 · 融资融券 · 大宗交易 · 股东户数 · 分红送转 · 个股资金流向(分钟/日级)· 行业板块排名 · 研报列表/PDF · 个股新闻 · 全球资讯
东财风控阈值(社区实测,2026-05)
| 行为 | 触发封禁的阈值 | 风险 |
|---|
| 每秒请求数 | > 5 次/秒 | 高 |
| 单 IP 并发连接 | ≥ 10 | 高 |
| 1 分钟请求总数 | ≥ 200 次 | 中高 |
| 5 分钟请求总数 | ≥ 300 次 | 触发封禁 |
| User-Agent | 空 UA / 无浏览器特征 | 中 |
被封表现:连续请求后 403 / 429 / 连接超时 / 返回空数据。临时封禁通常几分钟到几小时。
⚠️ 实测封禁案例(2026-06-30,一手数据,感谢 @luodada99 issue #36)
上表是社区口径;下面这条是真实踩到 IP 级封禁的完整记录,比阈值表更有参考价值:
- 触发方式:选股脚本 10 线程并发、完全不走
em_get() 限流,1 小时内发出 45000+ 请求(三个版本的脚本同时跑全市场 5208 只)
- 后果:
push2 / push2his 全系列 RemoteDisconnected,IP 级封禁持续 20+ 小时——不是"几分钟到几小时"那种临时限速
- 关键观察一:
datacenter-web.eastmoney.com 不受影响——东财不同子域走不同 WAF,push2 被封不代表整个东财都不能用
- 关键观察二:腾讯 K 线(
web.ifzq.gtimg.cn)连续 5000+ 次后会返回空,但这是限流不是封 IP,降速或换新浪即可恢复
- 降级实测:东财被封时,第一只股票花 10.9s 完成"检测被封 + 降级",之后每只 0.4s 走腾讯,数据准确
这个案例正是「限流是铁律」的实证:em_get() 的默认间隔(1s + 抖动、串行)下,1 小时最多约 3000 次请求,与踩坑者的 45000 次差一个数量级。
被封后的降级路径(各层备胎详见「备用源速查」章节):
| 被封端点 | 替代方案 | 差异 |
|---|
push2/clist/get(股票列表) | datacenter-web + 腾讯行情批量 | 行业字段来自 datacenter 的 BOARD_NAME |
push2his/kline/get(K线) | 腾讯 fqkline/get(前复权)→ 新浪 getKLineData(不复权) | 腾讯有前复权,新浪没有 |
push2/stock/get(个股) | 腾讯 qt.gtimg.cn | 腾讯无行业/概念字段 |
防封铁律(调用东财时必须遵守)
- 串行,不并发——绝不对东财开多线程/协程并发请求
- 每次间隔 ≥ 1 秒 + 随机抖动(QPS ≤ 2),批量筛选时调大到 1.5~2 秒
- 复用 HTTP 会话(Keep-Alive),不要每次新建连接
- 带正常 UA + Referer(本 SKILL 各端点已配好)
- 批量场景每只股票之间 sleep——AI 跑批量循环(如筛选 100 只股逐个拉龙虎榜/资金流)是被封的头号元凶
已内置限流:所有东财请求走 em_get()
本 SKILL 提供统一的节流入口 em_get()(定义见下方「东财数据中心统一查询(共用 helper)」),它自动做到:串行限流(最小间隔 EM_MIN_INTERVAL=1.0s + 随机抖动)+ 复用 EM_SESSION(Keep-Alive)+ 默认 UA。所有 eastmoney.com 端点的代码块都已改用 em_get 而非裸 requests.get,AI 直接抄代码即自带防封。批量任务把 EM_MIN_INTERVAL 调大即可进一步降速。
注:em_get / EM_SESSION / EM_MIN_INTERVAL 是所有东财代码块共用的前置定义,使用任一东财端点前需先执行「共用 helper」代码块。
When to Activate
- 用户要查 A 股个股估值(一致预期 / PE / PEG / PE消化)
- 用户要拉实时行情(价格 / 五档盘口 / K线 / 涨跌停价)
- 用户要搜研报(按主题 / 按标的 / 按行业 / 下载PDF)
- 用户要看当日强势股 / 题材归因 / 概念热点
- 用户要看北向资金动向(沪股通/深股通分钟流向)
- 用户要看概念板块归属(行业/概念/地域)
- 用户要看个股资金流向(主力/散户/超大单/大单分钟级)
- 用户要看龙虎榜席位(营业部 + 机构买卖)
- 用户要看全市场龙虎榜(当日所有上榜股票 + 净买额排名)
- 用户要看限售解禁日历(历史解禁 + 未来待解禁)
- 用户要做行业横向对比(涨跌排名 / 资金流入 / 领涨股)
- 用户要看融资融券 / 两融数据(融资余额 + 融券余额)
- 用户要看大宗交易(成交价/量 + 买卖方营业部)
- 用户要看股东户数变化(筹码集中度)
- 用户要看分红送转历史(每股派息 + 送股 + 转增)
- 用户要看指数/ETF行情(上证指数 / 沪深300 / 创业板指 / ETF)
- 用户要看涨停 / 打板情绪(涨停池 / 连板梯队 / 炸板率 / 跌停 / 涨停原因题材)
- 用户要看ETF 期权(T型报价 / 希腊字母 Delta·Gamma·Theta·Vega / 隐含波动率 IV)
- 用户要看投资者互动问答(公司如何回应某传闻/利好 · 互动易)
- 用户要看市场热度 / 人气榜(同花顺热榜 / 东财人气榜 / 个股概念命中)
- 用户要看新闻资讯(个股新闻 / 财联社快讯 / 全球资讯)
- 用户要查公告(巨潮公告全文)
- 用户要做产业链调研 / 批量横向对比
- 关键词:估值、一致预期、机构预测、市盈率、PEG、市值、研报、产业链、行业研究、K线、盘口、公告、新闻、强势股、题材、热点、概念归因、北向资金、沪股通、深股通、概念板块、资金流向、主力、龙虎榜、席位、营业部、全市场龙虎榜、净买入、解禁、限售、行业对比、行业轮动、融资融券、两融、大宗交易、股东户数、筹码集中、分红、派息、送股、指数、ETF、涨停、打板、连板、炸板、跌停、涨停原因、封板、晋级率、ETF期权、希腊字母、隐含波动率、互动易、投资者关系、热榜、人气榜、市场热度
Prerequisites
pip install mootdx requests pandas stockstats numpy baostock xlrd openpyxl
| 依赖 | 版本要求 | 用途 |
|---|
| mootdx | >= 0.10 | TCP行情+财务+F10(非 HTTP 依赖之一);0.11.x 用 tdx_client() 规避 BESTIP bug,见上节 |
| requests | any | 所有HTTP API直连 |
| pandas | any | 数据处理+HTML表格解析 |
| stockstats | any | 技术指标计算(RSI/MACD/BOLL等) |
| numpy | any | §4.6 筹码分布的网格计算 |
| baostock | >= 0.8 | §6.5/§6.6 估值历史·换手率·停牌·ST·退市日(TCP,免注册免 key;不支持北交所) |
| xlrd | >= 2.0 | 读 .xls(§6.7 申万行业分类) |
| openpyxl | any | 读 .xlsx(§11.1 人民银行社融) |
架构: 除 mootdx 与 baostock(均为 TCP 客户端库)外,所有数据源均为直连 HTTP API,不经第三方数据封装。每个 HTTP 端点的底层 URL/参数完全暴露,方便调试和定制。
iwencai API Key(仅语义搜索需要)
export IWENCAI_API_KEY="your_key_here"
export IWENCAI_BASE_URL="https://openapi.iwencai.com"
其他数据源(mootdx / 腾讯 / 东财 / 同花顺 / 百度股市通 / 新浪 / 巨潮 / baostock / 申万 / 人民银行 / 国家统计局)全部免费,无需 key。
mootdx 客户端(必读,规避 0.11.x BESTIP 空串 bug)
已知 bug(mootdx 0.11.x): 全新安装后 Quotes.factory(market='std') 裸调用可能抛 ValueError: not enough values to unpack (expected 2, got 0)。
根因:~/.mootdx/config.json 的 BESTIP.HQ 初始是空字符串 ""(不是缺失键),mootdx 用 dict.get(key, default) 取不到 default,拆包失败。老用户(config 曾填充过 IP)不会触发,所以容易漏测。
不要靠锁版本解决: 锁 mootdx==0.10.12 在部分环境(如干净的 Python 3.9)下 import mootdx 会因 numpy/pandas 二进制不兼容直接崩。正确做法是用下面的 tdx_client()——显式传 server 绕过 BESTIP,对 0.10 / 0.11 都适用。
统一用以下 helper 创建客户端(所有 mootdx 调用都走它):
import socket
from mootdx.quotes import Quotes
_TDX_SERVERS = [
('119.97.185.59', 7709), ('124.70.133.119', 7709), ('116.205.183.150', 7709),
('123.60.73.44', 7709), ('116.205.163.254', 7709), ('121.36.225.169', 7709),
('123.60.70.228', 7709), ('124.71.9.153', 7709), ('110.41.147.114', 7709),
('124.71.187.122', 7709),
]
def _probe(ip, port, timeout=2.0):
"""TCP 握手探测(快速粗筛)。注意:握手成功 ≠ 能取数,必须再经 _validate 验活。"""
try:
with socket.create_connection((ip, port), timeout=timeout):
return True
except Exception:
return False
def _validate(client, market: str = 'std') -> bool:
"""真实取数验活:坏服务器可 TCP 握手通过却回 2 字节空 body → 静默空表。用一次真实 K 线请求兜底。
验活样本 '000001' 是 A 股代码,只对 market='std' 有意义。其它市场(如扩展行情 'ext')
用它必然取不到数,会把所有正常服务器都判死、误报「全部不可达」,故非 std 时跳过验活。
"""
if market != 'std':
return True
try:
df = client.bars(symbol=, frequency=, offset=)
df df.empty
Exception:
():
ip, port _TDX_SERVERS:
_probe(ip, port):
:
c = Quotes.factory(market=market, server=(ip, port))
_validate(c, market):
c
Exception:
kwargs ({: }, {}):
:
c = Quotes.factory(market=market, **kwargs)
_validate(c, market):
c
Exception:
RuntimeError(
)
海外 IP 用户: mootdx 走通达信 TCP 7709,海外环境通常全部超时。tdx_client() 会快速失败给出明确报错,而非死等。
市场前缀规则(全局通用)
SH_INDEX = {"000300", "000905", "000016", "000688", "000852", "000010"}
def get_prefix(code: str) -> str:
"""6位代码 → 市场前缀(sh/sz/bj)。支持显式前缀/后缀(sh000016 / 000016.SH)透传以解决歧义。"""
c = code.lower().strip()
if c.endswith((".sh", ".sz", ".bj")):
return c[-2:]
if c.startswith(("sh", "sz", "bj")):
return c[:2]
if c.startswith("92"):
return "bj"
if c.startswith(("5", "6", "9")):
return "sh"
if c.startswith(("4", "8")):
return "bj"
if c in SH_INDEX:
歧义说明: 000001 默认按个股→sz000001(平安银行);要上证指数请显式传 sh000001。000016 默认按沪指数→上证50;要深康佳A 请传 sz000016。
⚠️ 北交所老号段(43/83/87)已基本作废 — 会拿到僵尸数据且不报错
2026-07-31 实测: 东财北交所在市 342 只中 336 只已是 920xxx 号段,仅剩 3 只老码且全部停牌。存量公司代码已整体迁移(如 锦波生物 832982→920982、贝特瑞 835185→920185)。
危险在于老码不会报错,而是返回看似正常的脏数据:
| 接口 | 传老码 832982 | 传新码 920982 |
|---|
| 腾讯行情 | 返回 112.60、成交量 0(定格在迁移日) | 131.74,正常成交 ✅ |
| 腾讯行情(贝特瑞) | 835185 → 45.91、成交量 0 | 920185 → 21.05 ✅ |
| 东财研报 | 0 篇(静默空) | 79 篇 ✅ |
老码行情价与真实价可差 17%~100%+,直接拿去算估值会得出完全错误的结论。
判定僵尸报价: 成交量 == 0 且 最新价 == 昨收 → 极可能是已迁移的废码(真停牌股同样满足,两者都不该用于估值)。tencent_quote() 已内置该检测并置 is_stale 标志,见 §1.2。
拿新码: 用 push2 北交所全量清单 fs=m:0+t:81+s:2048 按名称反查现行代码。
Ticker 格式归一化
norm_ticker() 把下列写法统一成纯 6 位数字:
⚠️ 不是所有端点都自动归一化(V3.6.0 前这里写的是「所有接口统一支持」,与实现不符,已改正)。
- 已内置归一化:§2.1
eastmoney_reports()、§2.2 ths_eps_forecast()。
- 只认纯 6 位或显式
sh/sz/bj 前缀(其余端点):tencent_quote() 等走 get_prefix() 路由的函数
支持 600519 和 sh600519,但不认后缀式 600519.SH——实测会拼成 sh600519.SH 并返回空载荷(静默失败)。
- 结论:拿到用户输入的代码,先过一遍
norm_ticker() 再传给任何端点,最省事也最安全。
| 输入 | 归一化结果 |
|---|
688017 | 688017 |
SH688017 / sh688017 | 688017 |
688017.SH / 688017.sh | 688017 |
SZ000001 | 000001 |
BJ920982 | 920982 |
import re
_TICKER_RE = re.compile(
r"^(?:(sh|sz|bj)(\d{6})|(\d{6})(?:\.(sh|sz|bj))?)$", re.IGNORECASE)
def _natural_market(digits: str) -> str:
"""6 位码的自然归属市场。仅用于校验显式前缀是否自相矛盾。
注意 000xxx 是沪指数/深个股共用的歧义段,由调用处单独处理,不走这里。"""
if digits.startswith("92") or digits[:2] in ("43", "83", "87"):
return "bj"
if digits[0] in ("5", "6", "9"):
return "sh"
return "sz"
def norm_ticker(code: str, stock_only: bool = False) -> str:
raw = (code).strip()
m = _TICKER_RE.(raw)
m:
ValueError(
)
digits = m.group() m.group()
market = (m.group() m.group() ).lower()
market:
digits.startswith():
market == :
ValueError()
stock_only market == :
ValueError(
)
:
nat = _natural_market(digits)
market != nat:
ValueError(
)
digits
norm_ticker()
norm_ticker()
norm_ticker()
norm_ticker()
norm_ticker()
norm_ticker()
norm_ticker(, stock_only=)
norm_ticker(, stock_only=)
norm_ticker()
norm_ticker()
() -> :
get_prefix(code) ==
() -> :
东财数据中心统一查询(共用 helper)
龙虎榜/解禁/融资融券/大宗交易/股东户数/分红 共用同一 base URL:
import time
import random
import requests
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
DATACENTER_URL = "https://datacenter-web.eastmoney.com/api/data/v1/get"
EM_SESSION = requests.Session()
EM_SESSION.headers.update({"User-Agent": UA})
try:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
_em_adapter = HTTPAdapter(max_retries=Retry(
total=3, connect=3, backoff_factor=0.6,
status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET"]))
EM_SESSION.mount("https://", _em_adapter)
EM_SESSION.mount("http://", _em_adapter)
except Exception:
pass
from typing import Optional
EM_MIN_INTERVAL = 1.0
_em_last_call = [0.0]
():
wait = EM_MIN_INTERVAL - (time.time() - _em_last_call[])
wait > :
time.sleep(wait + random.uniform(, ))
:
EM_SESSION.get(url, params=params, headers=headers, timeout=timeout, **kwargs)
:
_em_last_call[] = time.time()
() -> []:
params = {
: report_name, : columns,
: filter_str, : , : (page_size),
: sort_columns, : sort_types,
: , : ,
}
r = em_get(DATACENTER_URL, params=params, timeout=)
d = r.json()
d.get() d[].get():
d[][]
[]
Layer 1: 行情层(实时,不封IP)
1.1 mootdx — K线 + 五档盘口 + 逐笔成交
TCP 二进制协议,连通达信服务器(7709),无需注册,不封IP。
from mootdx.quotes import Quotes
client = tdx_client()
klines = client.bars(symbol='688017', frequency=9, offset=10)
min1 = client.bars(symbol='688017', frequency=8, offset=240)
min5 = client.bars(symbol='688017', frequency=0, offset=48)
quotes = client.quotes(symbol=['688017', '300476'])
trades = client.transaction(symbol='688017', date='20260502')
mootdx 不提供 PE / PB / 市值 / 换手率 / 涨跌停价 — 这些走腾讯财经。
1.2 腾讯财经 API — PE/PB/市值/换手率/涨跌停/指数/ETF
HTTP GET,GBK 编码,~ 分隔 88 个字段,不封IP。
import urllib.request
def tencent_quote(codes: list[str]) -> dict[str, dict]:
"""
批量拉取腾讯财经实时行情。
codes: ["688017", "300476", "002463"]
也支持指数: ["000001", "000300", "399006"]
也支持ETF: ["510050", "510300"]
返回: {code: {name, price, pe_ttm, pb, mcap, ...}}
"""
SH_INDEX = {"000300", "000905", "000016", "000688", "000852", "000010"}
prefixed = []
key_of = {}
for c in codes:
low = c.lower()
if low.startswith(("sh", "sz", "bj")):
p = low
elif c.startswith("92"):
p = f"bj{c}"
elif c in SH_INDEX or c.startswith(("5", "6", "9")):
p = f"sh{c}"
elif c.startswith(("4", "8")):
p = f"bj{c}"
else:
p = f"sz{c}"
prefixed.append(p)
key_of[p] = c
url = + .join(prefixed)
req = urllib.request.Request(url)
req.add_header(, )
resp = urllib.request.urlopen(req, timeout=)
data = resp.read().decode()
result = {}
line data.strip().split():
line.strip() line line:
key = line.split()[].split()[-]
vals = line.split()[].split()
(vals) < :
code = key_of.get(key, key[:])
result[code] = {
: vals[],
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
:(vals[]) vals[] ,
:(vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
: (vals[]) vals[] ,
}
q = result[code]
q[] = (q[] == q[] == q[] q[] > )
q[] key[:] (, , ):
q[] =
q[]:
q[] =
result
quotes = tencent_quote([, , ])
code, q quotes.items():
()
index_quotes = tencent_quote([, , ])
etf_quotes = tencent_quote([, ])
腾讯财经字段索引速查(实测校准 2026-05-03)
| 索引 | 含义 | 示例 |
|---|
| 1 | 名称 | 绿的谐波 |
| 3 | 当前价 | 224.12 |
| 4 | 昨收 | 215.01 |
| 5 | 今开 | 214.10 |
| 9-18 | 买一~买五(价+量) | |
| 19-28 | 卖一~卖五(价+量) | |
| 31 | 涨跌额 | 9.11 |
| 32 | 涨跌幅% | 4.24 |
| 33 | 最高 | 229.62 |
| 34 | 最低 | 214.10 |
| 37 | 成交额(万) | 187040 |
| 38 | 换手率% | 4.55 |
| 39 | PE(TTM) | 300.45 |
| 43 | 振幅%(不是PB!) | 7.22 |
| 44 | 流通市值(亿) | 410.88 |
| 45 | 总市值(亿) | 410.88 |
| 46 | PB(市净率) | 11.51 |
| 47 | 涨停价 | 258.01 |
| 48 | 跌停价 | 172.01 |
| 49 | 量比 | 1.20 |
| 52 | PE(静) | 314.76 |
踩坑提醒一: 网上很多教程把索引 43 写成 PB,实测是振幅%。PB 在索引 46。
踩坑提醒二(2026-07-26 修正): 44 是流通市值、45 才是总市值,此前本表标反了。
多数股票两者相等,所以看不出来;但总股本 ≠ 流通股本的票(科创板/次新股/有限售股)会差出数倍。
实测中船特气(688146):f[44]=356.15亿(流通股本 1.45亿股)、f[45]=1300.61亿(总股本 5.29亿股),差 3.65 倍。
用市值做筛选时取错会把大市值公司误判成小盘股。可用 f[45] ÷ 现价 反推总股本核对(与东财 f84 一致)。
参考:东财 push2 的 f116=总市值 / f117=流通市值 方向与腾讯相反,实测确认无误,勿混用。
1.3 百度股市通 K线 — 带MA5/MA10/MA20(V3.0 新增)
核心价值: 返回时自带均线数据,无需本地计算。
import requests
def baidu_kline_with_ma(code: str, start_time: str = "") -> dict:
"""百度股市通K线 — 独有能力: 返回时自带 ma5/ma10/ma20 均价"""
url = "https://finance.pae.baidu.com/selfselect/getstockquotation"
params = {
"all": "1", "isIndex": "false", "isBk": "false", "isBlock": "false",
"isFutures": "false", "isStock": "true", "newFormat": "1",
"group": "quotation_kline_ab", "finClientType": "pc",
"code": code, "start_time": start_time, "ktype": "1",
}
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/vnd.finance-web.v1+json",
"Origin": "https://gushitong.baidu.com",
"Referer": "https://gushitong.baidu.com/",
}
r = requests.get(url, params=params, headers=headers, timeout=10)
d = r.json()
result = d.get("Result", {})
md = result.get("newMarketData", {})
keys = md.get("keys", [])
rows = md.get("marketData", ).split()
{: keys, : rows}
data = baidu_kline_with_ma()
(, data[][:])
(, data[][-:])
1.4 新浪复权因子 — qfq / hfq(V3.7.0 新增)
核心价值: §1.1 tdx_client().bars() 返回的是不复权数据,跨除权日直接比价必然出错。
本端点给出复权因子序列,一次 HTTP、约 1.8KB、零鉴权。
import json
import re
import requests
def sina_adjust_factor(code: str, kind: str = "qfq") -> list:
"""新浪复权因子序列 — kind='qfq'(前复权) | 'hfq'(后复权),按日期倒序(最新在前)"""
if kind not in ("qfq", "hfq"):
raise ValueError(f"kind 只能是 'qfq' 或 'hfq',收到 {kind!r}")
raw = str(code).strip()
digits = norm_ticker(raw)
m = re.match(r"^(sh|sz|bj)", raw, re.I) or re.search(r"\.(sh|sz|bj)$", raw, re.I)
prefix = m.group(1).lower() if m else get_prefix(digits)
symbol = f"{prefix}{digits}"
url = f"https://finance.sina.com.cn/realstock/company/{symbol}/{kind}.js"
r = requests.get(url, headers={"User-Agent": "Mozilla/5.0",
"Referer": "https://finance.sina.com.cn/"}, timeout=10)
r.raise_for_status()
text = r.text
brace = text.find()
brace < :
RuntimeError()
:
data, _ = json.JSONDecoder().raw_decode(text[brace:])
json.JSONDecodeError e:
RuntimeError() e
[{: it[], : (it[])} it data.get(, [])]
():
kind (, ):
ValueError()
factors:
ValueError(
)
is_df = (bars, ) (bars, )
is_df:
date_col = ((c c (, ) c bars.columns), )
date_col :
ValueError()
rows = bars.to_dict()
r rows:
r[] = (r[date_col])[:]
:
rows = [(b) b bars]
r rows:
r:
ValueError()
r[] = (r[])[:]
fac = (factors, key= x: x[])
out, i, cur = [], ,
bar (rows, key= b: b[]):
i < (fac) fac[i][] <= bar[]:
cur = fac[i][]
i +=
cur :
RuntimeError(
)
cur == :
RuntimeError()
nb = (bar)
k price_keys:
k nb nb[k] :
v = (nb[k])
nb[k] = (v / cur kind == v * cur, )
nb[] = cur
out.append(nb)
is_df:
pandas pd
res = pd.DataFrame(out)
(bars, , ) (
bars.index, pd.RangeIndex
):
order = (((bars)), key= n: (bars.iloc[n][date_col])[:])
res.index = bars.index[order]
res.index.name = bars.index.name
res
out
qfq = sina_adjust_factor(, )
hfq = sina_adjust_factor(, )
((qfq), , qfq[], , qfq[-])
bars = [{: , : }]
(apply_adjust(bars, qfq, kind=))
(apply_adjust(bars, hfq, kind=))
方向实测对照(2026-08-19,以 baostock adjustflag 为基准交叉验证)
| 日期 | 不复权 | baostock 前复权 | raw × qfq | raw ÷ qfq |
|---|
| 2015-01-05 | 202.52 | 143.46 | 285.90 ❌ | 143.46 ✅ |
| 2026-08-14 | 1341.99 | 1341.99 | 1341.99 ✅ | 1341.99 ✅ |
qfq 因子恒 ≥ 1 且越往历史越大,乘上去会把历史价格放大,必须做除法。
2026 那行两种算法都对,是因为最新日因子恰为 1.0 —— 只用最近日期做验证会漏掉这个 bug。
⚠️ hfq 的基准与 baostock 不同:新浪 raw × hfq 与 baostock 后复权价差一个恒定倍数
(实测 1.1582,2015 与 2026 两点一致)。后复权序列整体缩放不影响收益率与形态,
但不要把新浪后复权价与其它源的后复权价直接比数值。
⚠️ 北交所无复权因子:实测 bj920982 返回 404(新浪未提供北交所的 qfq/hfq 文件),
本函数会抛 HTTPError。北交所标的请改用 §1.1 通达信不复权价,并自行按分红送转推导。
自检口径(实测 2026-08-19 校准):
qfq 序列最新一条因子恒为 1.0;hfq 序列最早一条恒为 1.0。
- 同一日期上
qfq(d) × hfq(d) 恒等于一个常数(该标的全期总复权系数,茅台实测 8.882513)。
⚠️ 两者不是倒数(乘积不为 1),比值 hfq/qfq 也不恒定(茅台 33 个日期有 32 种取值)——
两个基准不同的归一化序列,只有乘积守恒。
不满足以上任一条,说明响应被截断或标的代码写错。
Layer 2: 研报层
2.1 东财研报 API — 研报列表 + PDF下载(主力)
A级接口(公开JSON API),reportapi.eastmoney.com,免费无key。
import requests
import re
import time
from datetime import date, timedelta
from pathlib import Path
from typing import Optional
REPORT_API = "https://reportapi.eastmoney.com/report/list"
PDF_TPL = "https://pdf.dfcfw.com/pdf/H3_{info_code}_1.pdf"
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
def eastmoney_reports(code: str, max_pages: int = 5) -> list[dict]:
"""拉取指定股票的研报列表。
code 支持 600519 / SH600519 / 600519.SH 等写法(内部归一化为纯 6 位)。
⚠️ reportapi 只认纯 6 位数字:传 "SH600519" 会返回 hits=0,
看起来像「这只票没研报」,实际是格式没归一化——务必先过 norm_ticker()。
返回 [] 仅表示东财确无该标的研报覆盖(格式错误已在上游抛 ValueError)。
"""
code = norm_ticker(code, stock_only=True)
all_records = []
for page in range(1, max_pages + 1):
params = {
"industryCode": "*", "pageSize": "100", "industry": "*",
"rating": "*", "ratingChange": "*",
"beginTime": , : ,
: (page), : , : ,
: , : code, : ,
: (page), : (page), : (page),
}
r = em_get(REPORT_API, params=params,
headers={: }, timeout=)
d = r.json()
rows = d.get() []
rows:
all_records.extend(rows)
page >= (d.get(, ) ):
all_records code[:] (, , ):
ValueError(
)
all_records
() -> []:
info_code = record.get(, )
info_code:
date = (record.get() )[:]
org = re.sub(, , record.get() )[:]
title = re.sub(, , record.get(, ))[:]
fname =
target = Path(target_dir) / fname
target.exists():
(target)
url = PDF_TPL.(info_code=info_code)
r = em_get(url, headers={: }, timeout=)
r.status_code == (r.content) >= :
target.parent.mkdir(parents=, exist_ok=)
target.write_bytes(r.content)
(target)
reports = eastmoney_reports()
()
r reports[:]:
()
研报 record 关键字段
| 字段 | 含义 |
|---|
| title | 研报标题 |
| publishDate | 发布日期 |
| orgSName | 机构简称 |
| infoCode | 用于拼 PDF URL |
| predictThisYearEps | 今年EPS预测 |
| predictNextYearEps | 明年EPS预测 |
| predictNextTwoYearEps | 后年EPS预测 |
| emRatingName | 评级(买入/增持/...) |
| indvInduName | 行业分类 |
行业研报列表(qType=1)
与个股研报同一端点(reportapi.eastmoney.com/report/list),仅 qType 不同:qType=0 个股研报,qType=1 行业研报。返回 record 可直接喂给上面的 download_pdf()(PDF 模板通用)。
from datetime import date, timedelta
def eastmoney_industry_reports(industry_code: str = "*", max_pages: int = 5,
begin: str = "") -> list[dict]:
"""拉取行业研报列表(qType=1)。
industry_code="*" = 全行业;传东财行业码(如 "1238"=IT服务Ⅱ)= 单行业。
行业名 / 行业码在每条 record 的 industryName / industryCode 字段。
begin 留空 = 近两年(相对今天算,避免硬编码日期越用越旧)。"""
if not begin:
begin = (date.today() - timedelta(days=730)).isoformat()
all_records = []
for page in range(1, max_pages + 1):
params = {
"industryCode": industry_code, "pageSize": "100", "industry": "*",
"rating": "*", "ratingChange": "*",
"beginTime": begin, "endTime": "2030-01-01",
"pageNo": str(page), "fields": "", "qType": "1",
}
r = em_get(REPORT_API, params=params,
headers={"Referer": "https://data.eastmoney.com/"}, timeout=30)
d = r.json()
rows = d.get() []
rows:
all_records.extend(rows)
page >= (d.get(, ) ):
all_records
reports = eastmoney_industry_reports(, max_pages=)
()
r reports[:]:
()
it = eastmoney_industry_reports(, max_pages=)
it:
download_pdf(it[])
行业研报特有/常用字段(其余字段同 2.1 个股研报):
| 字段 | 含义 |
|---|
| industryName | 行业名称(如 IT服务Ⅱ、风电设备、光伏设备) |
| industryCode | 东财行业代码(用于 industry_code 精确过滤) |
| emRatingName | 行业评级(买入/增持/中性/...) |
| reportType | 报告类型 |
| attachPages / attachSize | PDF 页数 / 大小(KB) |
| infoCode | 喂给 download_pdf() 拼 PDF URL |
行业码怎么拿: 东财行业码不是通用记忆码,没有公开的码表端点(bxpa 等已 404)。常用做法:先用 industry_code="*" 拉一批,从结果的 industryName/industryCode 找到目标行业的码,再用该码精确过滤。
2.2 同花顺一致预期EPS(直连 basic.10jqka.com.cn)
import requests
import pandas as pd
from io import StringIO
def ths_eps_forecast(code: str) -> pd.DataFrame:
"""
同花顺机构一致预期EPS。
直连 basic.10jqka.com.cn,解析HTML表格。
code 支持 688017 / SH688017 / 688017.SH 等写法(内部归一化为纯 6 位)。
返回 DataFrame: 年度, 预测机构数, 最小值, 均值, 最大值
"均值" = 机构一致预期EPS
"""
code = norm_ticker(code, stock_only=True)
url = f"https://basic.10jqka.com.cn/new/{code}/worth.html"
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Referer": "https://basic.10jqka.com.cn/",
}
r = requests.get(url, headers=headers, timeout=15)
r.encoding = "gbk"
dfs = pd.read_html(StringIO(r.text))
for df in dfs:
cols = [str(c) for c in df.columns]
if any("每股收益" in c or "均值" in c for c in cols):
return df
return dfs[0] if dfs else pd.DataFrame()
df = ths_eps_forecast("688017")
print(df)
2.3 iwencai — NL语义搜索研报(唯一能力)
需要 API Key + X-Claw Headers(SkillHub 2.0 强制要求)。
import os
import json
import secrets
import requests
IWENCAI_BASE = os.environ.get("IWENCAI_BASE_URL", "https://openapi.iwencai.com")
IWENCAI_KEY = os.environ.get("IWENCAI_API_KEY", "")
def _claw_headers(call_type: str = "normal") -> dict:
"""SkillHub 2.0 必须的 X-Claw 鉴权头"""
return {
"X-Claw-Call-Type": call_type,
"X-Claw-Skill-Id": "report-search",
"X-Claw-Skill-Version": "2.0.0",
"X-Claw-Plugin-Id": "none",
"X-Claw-Plugin-Version": "none",
"X-Claw-Trace-Id": secrets.token_hex(32),
}
def iwencai_search(query: str, channel: str = "report", size: int = 50) -> list[dict]:
"""
iwencai 语义搜索。
channel: "report"(研报) / "announcement"(公告) / "news"(新闻)
size: 默认10, 实测可调到50(隐藏参数)
"""
headers = {
"Authorization": f"Bearer {IWENCAI_KEY}",
"Content-Type": "application/json",
**_claw_headers(),
}
payload = {
"channels": [channel],
"app_id": "AIME_SKILL",
: query,
: size,
}
r = requests.post(
,
json=payload, headers=headers, timeout=,
)
r.status_code != :
RuntimeError()
data = r.json()
data.get(, ) != :
RuntimeError()
data.get() []
() -> []:
headers = {
: ,
: ,
**_claw_headers(),
}
payload = {
: query,
: (page),
: (limit),
: ,
: ,
}
r = requests.post(
,
json=payload, headers=headers, timeout=,
)
r.status_code != :
RuntimeError()
data = r.json()
data.get(, ) != :
RuntimeError()
data.get() []
() -> []:
best = {}
a articles:
uid = a.get(, )
score = (a.get(, ))
uid best score > (best[uid].get(, )):
best[uid] = a
(best.values(), key= x: x.get(, ), reverse=)
articles = iwencai_search(, channel=, size=)
articles = dedup_articles(articles)
a articles[:]:
extra = a.get() {}
(extra, ):
extra = json.loads(extra)
()
iwencai 的唯一价值: NL 主题搜索。"人形机器人 行星滚柱丝杠" 这种跨主题检索只有 iwencai 能做。按标的搜研报走东财 reportapi 更稳定。
Layer 3: 信号层
3.1 同花顺热点 — 当日强势股 + 题材归因 reason tags(独家)
核心价值: 不只告诉你"哪些走强",还告诉你**"为什么走强"** —— 同花顺编辑部人工运营的题材标签。
import requests
import pandas as pd
def ths_hot_reason(date: str = None) -> pd.DataFrame:
"""
同花顺当日强势股归因。
date: 'YYYY-MM-DD' 格式,None=今天
返回 DataFrame,含每只股票的题材标签 (reason)。
实测: 73ms 拿到 ~125 只 + 完整字段
"""
from datetime import date as _date
if date is None:
date = _date.today().strftime("%Y-%m-%d")
url = (
f"http://zx.10jqka.com.cn/event/api/getharden/"
f"date/{date}/orderby/date/orderway/desc/charset/GBK/"
)
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"Chrome/117.0.0.0 Safari/537.36"
)
}
r = requests.get(url, headers=headers, timeout=10)
data = r.json()
if data.get("errocode", 0) != 0:
raise RuntimeError(f"同花顺热点错误: {data.get('errormsg', '')}")
rows = data.get("data") or []
df = pd.DataFrame(rows)
if df.empty:
return df
rename_map = {
"name": "名称", "code": "代码", "reason": "题材归因",
"close": "收盘价", : , : ,
: , : ,
: , : ,
: ,
}
df = df.rename(columns=rename_map)
df
df = ths_hot_reason()
()
(df[[, , , ]].head())
同花顺热点字段速查
| 原字段 | 中文 | 说明 |
|---|
| code | 代码 | 6 位股票代码 |
| name | 名称 | 简称 |
| reason | 题材归因 | 核心字段,人工运营 tags,如"算力租赁+Token工厂+AI政务" |
| zhangfu | 涨幅% | 当日涨幅 |
| huanshou | 换手率% | 当日换手 |
| chengjiaoe | 成交额 | 元 |
| chengjiaoliang | 成交量 | 股 |
| ddejingliang | 大单净量 | 主力净流入指标 |
| close | 收盘价 | 元 |
| zhangdie | 涨跌额 | 元 |
| market | 市场 | 沪/深/北 |
3.2 同花顺北向资金 — hsgtApi 实时分钟流向 + 本地自缓存历史
⚠️ 深股通实时流向近期不可靠(2026-07 实测): 沪股通(hgt)分钟序列完整,但深股通(sgt)
常只回传零星几个点、末值量级异常。根因是北向自 2024-08 起收紧盘中实时披露,非本代码问题。
结论:hgt 可用于当日情绪,sgt 仅供参考;要权威北向数据用 HKEX 官方日统计
(hkex.com.hk/chi/csm/DailyStat/data_tab_daily_YYYYMMDDc.js,见文末「备用源速查」)。
已知行业性问题: eastmoney 全系北向数据自 2024-08 后净买额字段返回 NaN/0,属上游断供。已改为本地 CSV 自缓存模式——每次拉实时数据后自动写入本地 CSV,历史越跑越丰富。
import requests
import pandas as pd
from pathlib import Path
HSGT_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"Chrome/117.0.0.0 Safari/537.36"
),
"Host": "data.hexin.cn",
"Referer": "https://data.hexin.cn/",
}
def hsgt_realtime() -> pd.DataFrame:
"""
沪深股通当日实时分钟流向(含集合竞价 09:10–15:00,262 个时间点)。
返回字段: time, hgt(沪股通累计净买入), sgt(深股通累计净买入)
单位: 亿元
"""
url = "https://data.hexin.cn/market/hsgtApi/method/dayChart/"
r = requests.get(url, headers=HSGT_HEADERS, timeout=10)
d = r.json()
times = d.get("time", [])
hgt = d.get("hgt", [])
sgt = d.get("sgt", [])
n = len(times)
return pd.DataFrame({
"time": times,
"hgt_yi": hgt[:n] + [None] * (n - len(hgt)),
"sgt_yi": sgt[:n] + [None] * (n - len(sgt)),
})
def _northbound_cache_path() -> Path:
"""北向资金本地 CSV 缓存路径"""
p = Path.home() / ".tradingagents" / "cache" / "northbound_daily.csv"
p.parent.mkdir(parents=True, exist_ok=True)
return p
def _save_northbound_snapshot(date: str, hgt: , sgt: ):
path = _northbound_cache_path()
rows = {}
path.exists():
line path.read_text().strip().split()[:]:
parts = line.split()
(parts) == :
rows[parts[]] = line
rows[date] =
(path, ) f:
f.write()
d (rows.keys()):
f.write(rows[d] + )
() -> pd.DataFrame:
path = _northbound_cache_path()
path.exists():
pd.DataFrame()
df = pd.read_csv(path)
df.tail(n)
df = hsgt_realtime()
()
(df.tail())
df.empty:
last = df.dropna().iloc[-]
_save_northbound_snapshot(, last[], last[])
hist = _load_northbound_history()
(hist)
3.3 东财 slist — 个股所属板块/概念归属(V3.2.2 替换百度)
核心价值: 一次调用拿到个股所属的全部板块(行业 + 概念 + 地域混合),含板块代码(BK码)、当日涨跌幅、板块龙头股。题材归因、板块联动分析必备。
V3.2.2 替换说明: 百度 PAE getrelatedblock 接口已失效(实测返回 ResultCode 10003 + 空数组,#18),改用东财 slist 个股所属板块接口(spt=3,一次请求拿全,零鉴权)。东财把行业/概念/地域混在一个列表里返回,板块名本身已自解释(如「食品饮料」是行业、「贵州板块」是地域、「酿酒概念」是概念),AI 直接用板块名做题材归因即可。
def eastmoney_concept_blocks(code: str) -> dict:
"""
个股所属板块/概念归属(东财 slist,一次请求拿全,已内置限流)。
返回: {total, boards: [{name, code(BK码), change_pct, lead_stock}], concept_tags: [板块名...]}
boards 混合 行业/概念/地域,板块名自解释;concept_tags 是所有板块名的便捷列表。
"""
market_code = em_market_code(code)
params = {
"fltt": "2", "invt": "2",
"secid": f"{market_code}.{code}",
"spt": "3", "pi": "0", "pz": "200", "po": "1",
"fields": "f12,f14,f3,f128",
}
headers = {"User-Agent": UA, "Referer": "https://quote.eastmoney.com/"}
try:
r = em_get("https://push2.eastmoney.com/api/qt/slist/get",
params=params, headers=headers, timeout=15)
d = r.json()
except Exception as e:
print(f"[WARN] 东财板块归属请求失败: {e}")
return {"total": 0, "boards": [], "concept_tags": []}
diff = (d.get("data") or {}).get("diff") or {}
items = diff.values() if isinstance(diff, ) diff
boards = []
it items:
boards.append({
: it.get(, ),
: it.get(, ),
: it.get(, ),
: it.get(, ),
})
{
: (boards),
: boards,
: [b[] b boards],
}
blocks = eastmoney_concept_blocks()
()
(, blocks[])
注意: 东财不区分行业/概念/地域类型(混在一个列表返回)。如需精确分类可按板块名判断,或另查全市场板块清单(clist + m:90+t:1/2/3)——但后者每次需多发请求、大页易触发风控,不推荐在批量场景用。
3.4 东财 push2 — 个股资金流向(分钟级)
盘中实时分钟级资金流(主力/大单/中单/小单/超大单净流入)。
V3.1 替换说明: 百度 PAE fundflow 和 fundsortlist 接口已于 2026-05 下线(返回 null),改用东财 push2 资金流 API。日级资金流见 Layer 4.5 stock_fund_flow_120d()。
import requests
def eastmoney_fund_flow_minute(code: str) -> list[dict]:
"""
个股资金流向(分钟级,当日盘中)。
code: 6位股票代码
返回: [{time, main_net, small_net, mid_net, large_net, super_net}, ...]
单位: 元
"""
secid = em_secid(code)
url = "https://push2.eastmoney.com/api/qt/stock/fflow/kline/get"
params = {
"secid": secid, "klt": 1,
"fields1": "f1,f2,f3,f7",
"fields2": "f51,f52,f53,f54,f55,f56,f57",
}
headers = {
"User-Agent": UA,
"Referer": "https://quote.eastmoney.com/",
"Origin": "https://quote.eastmoney.com",
}
try:
r = em_get(url, params=params, headers=headers, timeout=10)
d = r.json()
except Exception as e:
print(f"[WARN] push2 资金流请求失败: {e}")
return []
rows = []
for line in (d.get("data") or {}).get() []:
parts = line.split()
(parts) >= :
rows.append({
: parts[],
: (parts[]),
: (parts[]),
: (parts[]),
: (parts[]),
: (parts[]),
})
rows
realtime = eastmoney_fund_flow_minute()
realtime:
last = realtime[-]
signal = last[] >
()
total = (r[] r realtime)
()
注意: push2 资金流金额单位是元(非万元),使用时注意换算。klt=1 分钟级,klt=101 日级。
3.5 龙虎榜席位 — 个股上榜记录 + 买卖席位 TOP5 + 机构动向
直连东财 datacenter API,不依赖第三方封装。
import requests
from datetime import datetime, timedelta
def dragon_tiger_board(code: str, trade_date: str, look_back: int = 30) -> dict:
"""
龙虎榜数据聚合。
trade_date: YYYY-MM-DD
look_back: 回看天数
返回: {records: [...], seats: {buy: [...], sell: [...]}, institution: {...}}
"""
start = datetime.strptime(trade_date, "%Y-%m-%d") - timedelta(days=look_back)
start_str = start.strftime("%Y-%m-%d")
records = []
data = eastmoney_datacenter(
"RPT_DAILYBILLBOARD_DETAILSNEW",
filter_str=f"(TRADE_DATE>='{start_str}')(TRADE_DATE<='{trade_date}')(SECURITY_CODE=\"{code}\")",
page_size=50,
sort_columns="TRADE_DATE", sort_types="-1",
)
for row in data:
records.append({
"date": str(row.get("TRADE_DATE", ""))[:10],
"reason": row.get("EXPLANATION", ""),
"net_buy": round((row.get("BILLBOARD_NET_AMT") or 0) / 10000, 1),
"turnover": round(float(row.get("TURNOVERRATE") or 0), ),
})
buy_data, sell_data = [], []
seats = {: [], : []}
records:
latest_date = records[][]
buy_data = eastmoney_datacenter(
,
filter_str=,
page_size=,
sort_columns=, sort_types=,
)
row buy_data[:]:
seats[].append({
: row.get(, ),
: ((row.get() ) / , ),
: ((row.get() ) / , ),
: ((row.get() ) / , ),
})
sell_data = eastmoney_datacenter(
,
filter_str=,
page_size=,
sort_columns=, sort_types=,
)
row sell_data[:]:
seats[].append({
: row.get(, ),
: ((row.get() ) / , ),
: ((row.get() ) / , ),
: ((row.get() ) / , ),
})
institution = {: , : , : }
detail_data, side [(buy_data, ), (sell_data, )]:
row detail_data:
(row.get(, )) == :
amt = (row.get() ) side == (row.get() )
side == :
institution[] += amt
:
institution[] += amt
institution[] = (institution[] / , )
institution[] = (institution[] / , )
institution[] = (institution[] - institution[], )
{: records, : seats, : institution}
data = dragon_tiger_board(, )
()
r data[]:
()
data[][]:
()
s data[][]:
()
ST 股注意: 5% 涨跌停更容易触发龙虎榜("连续三日偏离值累计达12%"),科创板 20% 涨跌停则较少触发。
3.6 限售解禁日历 — 历史解禁 + 未来 90 天待解禁
from datetime import datetime, timedelta
def lockup_expiry(code: str, trade_date: str, forward_days: int = 90) -> dict:
"""
限售解禁日历。
返回: {history: [...], upcoming: [...]}
"""
history_data = eastmoney_datacenter(
"RPT_LIFT_STAGE",
filter_str=f"(SECURITY_CODE=\"{code}\")",
page_size=15,
sort_columns="FREE_DATE", sort_types="-1",
)
history = []
for row in history_data:
history.append({
"date": str(row.get("FREE_DATE", ""))[:10],
"type": row.get("FREE_SHARES_TYPE", ""),
"shares": row.get("FREE_SHARES", 0),
"able_shares": row.get("ABLE_FREE_SHARES", 0),
"ratio": row.get("FREE_RATIO", 0),
})
end_date = datetime.strptime(trade_date, "%Y-%m-%d") + timedelta(days=forward_days)
end_str = end_date.strftime("%Y-%m-%d")
upcoming_data = eastmoney_datacenter(
,
filter_str=,
page_size=,
sort_columns=, sort_types=,
)
upcoming = []
row upcoming_data:
upcoming.append({
: (row.get(, ))[:],
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
})
{: history, : upcoming}
data = lockup_expiry(, )
()
h data[][:]:
()
data[]:
()
:
()
限售股类型参考:
- 首发原股东限售股份(IPO 后 1-3 年)
- 首发机构配售股份(IPO 战略配售)
- 定向增发机构配售股份(6-18 个月)
- 股权激励限售股份
3.7 行业板块排名(V3.0 改用东财 — 同花顺加了反爬401)
东财行业板块涨跌幅排名,一次调用看全市场行业轮动。
import requests
def industry_comparison(top_n: int = 20) -> dict:
"""
全行业涨跌幅排名(东财行业板块,~100 个行业)。
返回: {top: [...], bottom: [...], total: int}
"""
url = "https://push2.eastmoney.com/api/qt/clist/get"
params = {
"pn": "1", "pz": "100", "po": "1", "np": "1",
"fltt": "2", "invt": "2", "fid": "f3",
"fs": "m:90+t:2",
"fields": "f2,f3,f4,f12,f13,f14,f104,f105,f128,f136,f140,f141,f207",
}
headers = {"User-Agent": UA}
r = em_get(url, params=params, headers=headers, timeout=15)
d = r.json()
items = (d.get("data") or {}).get("diff") or []
if not items:
return {"top": [], "bottom": [], "total": 0}
rows = []
for i, item in enumerate(items):
rows.append({
"rank": i + 1,
"name": item.get(, ),
: item.get(, ),
: item.get(, ),
: item.get(, ),
: item.get(, ),
: item.get(, ),
: item.get(, ),
})
{
: rows[:top_n],
: rows[-top_n:],
: (rows),
}
data = industry_comparison()
()
()
r data[][:]:
()
()
r data[][-:]:
()
3.8 板块资金流向(行业/概念/地域 × 今日/5日/10日)
东财板块资金流向——主力净流入额/净占比 + 超大/大/中/小单四档,覆盖行业、概念、地域三类板块,今日/5日/10日三个周期。与 §3.7 板块排名同源同接口(push2 clist),只是补请求了资金流字段(f62/f184/f66...)。走 em_get 限流防封。
import requests
_BOARD_FS = {"industry": "m:90+t:2", "concept": "m:90+t:3", "region": "m:90+t:1"}
_BOARD_PERIOD = {
"today": ("f62", "f62", "f184", "f3", "f204"),
"5d": ("f164", "f164", "f165", "f109", "f257"),
"10d": ("f174", "f174", "f175", "f160", None),
}
def board_fund_flow(board_type: str = "industry", period: str = "today",
top_n: int = 20) -> dict:
"""
板块资金流向排名(按主力净流入降序)。
board_type: industry(行业) / concept(概念) / region(地域)
period: today(今日) / 5d(5日) / 10d(10日)
返回: {board_type, period, total, rows:[{rank, name, code, change_pct,
main_net(主力净额,元), main_pct(主力净占比,%), leader(领涨股),
# 仅 today:super_large_net/large_net/medium_net/small_net(超大/大/中/小单净额,元)}]}
注:板块级只有 今日/5日/10日(无 3日,个股级才有)。主力净额 = 超大单 + 大单。
"""
if board_type not in _BOARD_FS:
raise ValueError(f"board_type 须为 {list(_BOARD_FS)}")
period _BOARD_PERIOD:
ValueError()
fid, f_main, f_pct, f_chg, f_leader = _BOARD_PERIOD[period]
fields = [, , f_chg, f_main, f_pct]
f_leader:
fields.append(f_leader)
period == :
fields += [, , , ]
url =
base = {
: , : , : ,
: , : , : fid,
: _BOARD_FS[board_type],
: .join(.fromkeys(fields)),
}
():
r = em_get(url, params={**base, : (pn)},
headers={: UA}, timeout=)
d = r.json().get() {}
(d.get() []), (d.get() )
_PAGE =
items, total = _page()
pn =
(items) < top_n:
total (items) >= total:
more, _ = _page(pn)
more:
items += more
pn +=
(more) < _PAGE:
total = (total, (items))
rows = []
i, it (items):
row = {
: i + ,
: it.get(, ),
: it.get(, ),
: it.get(f_chg, ),
: it.get(f_main, ),
: it.get(f_pct, ),
: it.get(f_leader, ) f_leader ,
}
period == :
row.update({
: it.get(, ),
: it.get(, ),
: it.get(, ),
: it.get(, ),
})
rows.append(row)
{: board_type, : period,
: total, : rows[:top_n]}
d = board_fund_flow(, , )
()
r d[]:
(
)
concept_5d = board_fund_flow(, , )
region_10d = board_fund_flow(, , )
3.9 全市场龙虎榜
每日全市场龙虎榜汇总——当日所有触发龙虎榜的股票 + 上榜原因 + 买卖净额 + 换手率。
from datetime import datetime
def daily_dragon_tiger(trade_date: str = None, min_net_buy: float = None) -> dict:
"""
全市场龙虎榜。
trade_date: YYYY-MM-DD(默认当日)
min_net_buy: 净买入下限(万元),None 不过滤
返回: {date, total_records, stocks: [{code, name, reason, close, change_pct,
net_buy_wan, buy_wan, sell_wan, turnover_pct}]}
"""
if trade_date is None:
trade_date = datetime.now().strftime("%Y-%m-%d")
data = eastmoney_datacenter(
"RPT_DAILYBILLBOARD_DETAILSNEW",
filter_str=f"(TRADE_DATE>='{trade_date}')(TRADE_DATE<='{trade_date}')",
page_size=500,
sort_columns="BILLBOARD_NET_AMT", sort_types="-1",
)
if not data:
return {"date": trade_date, "total_records": 0, "stocks": [],
"note": "无数据(非交易日或盘后未更新)"}
actual_date = str(data[0].get("TRADE_DATE", ""))[:10] if data else trade_date
stocks = []
for row in data:
net_buy = (row.get("BILLBOARD_NET_AMT") or 0) / 10000
if min_net_buy is net_buy < min_net_buy:
stocks.append({
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get() ,
: ((row.get() ), ),
: (net_buy, ),
: ((row.get() ) / , ),
: ((row.get() ) / , ),
: ((row.get() ), ),
})
{: actual_date, : (stocks), : stocks}
data = daily_dragon_tiger()
()
s data[][:]:
()
data = daily_dragon_tiger(, min_net_buy=)
()
3.10 信号层组合用法:题材热度 + 资金验证
df_hot = ths_hot_reason()
from collections import Counter
all_tags = []
for r in df_hot["题材归因"].dropna():
tags = [t.strip() for t in str(r).split("+") if t.strip()]
all_tags.extend(tags)
cnt = Counter(all_tags)
print("当日 TOP 10 题材热度:")
for tag, n in cnt.most_common(10):
print(f" {tag}: {n} 只")
df_north = hsgt_realtime()
hgt_close = df_north["hgt_yi"].dropna().iloc[-1] if not df_north.empty else 0
sgt_close = df_north["sgt_yi"].dropna().iloc[-1] if not df_north.empty else 0
print(f"\n北向收盘累计: 沪股通 {hgt_close} 亿 / 深股通 {sgt_close} 亿")
comp = industry_comparison(10)
print("\n行业涨幅 TOP 5:")
for r in comp["top"][:5]:
print(f" {r['name']}: {r[]}% 涨跌")
Layer 4: 资金面 / 筹码层(V3.0 新增)
4.1 融资融券明细
def margin_trading(code: str, page_size: int = 30) -> list[dict]:
"""
融资融券明细(日级)。
返回: [{date, rzye(融资余额), rzmre(融资买入), rqye(融券余额), ...}]
"""
data = eastmoney_datacenter(
"RPTA_WEB_RZRQ_GGMX",
filter_str=f'(SCODE="{code}")',
page_size=page_size,
sort_columns="DATE", sort_types="-1",
)
rows = []
for row in data:
rows.append({
"date": str(row.get("DATE", ""))[:10],
"rzye": row.get("RZYE", 0),
"rzmre": row.get("RZMRE", 0),
"rzche": row.get("RZCHE", 0),
"rqye": row.get("RQYE", 0),
"rqmcl": row.get("RQMCL", 0),
"rqchl": row.get("RQCHL", 0),
"rzrqye": row.get("RZRQYE", 0),
})
rows
data = margin_trading()
d data[:]:
()
4.2 大宗交易
def block_trade(code: str, page_size: int = 20) -> list[dict]:
"""
大宗交易记录。
返回: [{date, price, vol, amount, buyer, seller, premium_pct}]
"""
data = eastmoney_datacenter(
"RPT_DATA_BLOCKTRADE",
filter_str=f'(SECURITY_CODE="{code}")',
page_size=page_size,
sort_columns="TRADE_DATE", sort_types="-1",
)
rows = []
for row in data:
close = row.get("CLOSE_PRICE") or 0
deal_price = row.get("DEAL_PRICE") or 0
premium = ((deal_price / close - 1) * 100) if close else 0
rows.append({
"date": str(row.get("TRADE_DATE", ""))[:10],
"price": deal_price,
"close": close,
"premium_pct": round(premium, 2),
"vol": row.get("DEAL_VOLUME", 0),
"amount": row.get("DEAL_AMT", 0),
"buyer": row.get("BUYER_NAME", ""),
"seller": row.get("SELLER_NAME", ),
})
rows
data = block_trade()
d data[:]:
()
4.3 股东户数变化
def holder_num_change(code: str, page_size: int = 10) -> list[dict]:
"""
股东户数变化(季度级)。
返回: [{date, holder_num, change_num, change_ratio, avg_shares}]
"""
data = eastmoney_datacenter(
"RPT_HOLDERNUMLATEST",
filter_str=f'(SECURITY_CODE="{code}")',
page_size=page_size,
sort_columns="END_DATE", sort_types="-1",
)
rows = []
for row in data:
rows.append({
"date": str(row.get("END_DATE", ""))[:10],
"holder_num": row.get("HOLDER_NUM", 0),
"change_num": row.get("HOLDER_NUM_CHANGE", 0),
"change_ratio": row.get("HOLDER_NUM_RATIO", 0),
"avg_shares": row.get("AVG_FREE_SHARES", 0),
})
return rows
data = holder_num_change("600519")
for d in data[:5]: