gangtise-openapi-cli 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -194
- package/dist/src/cli.js +10 -3
- package/dist/src/core/args.js +56 -15
- package/dist/src/core/commandBodies.js +42 -6
- package/dist/src/core/endpoints.js +9 -0
- package/dist/src/core/indicatorMatrix.js +51 -25
- package/dist/src/version.js +1 -1
- package/gangtise-openapi/SKILL.md +9 -7
- package/gangtise-openapi/references/commands/indicator.md +79 -37
- package/gangtise-openapi/references/examples.md +2 -2
- package/gangtise-openapi/references/response-schema.md +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,196 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
## Changelog
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
README 仅列最近 5 个版本摘要:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **v0.30.0 — 2026-08-02**:适配 EDE 接口重构(`universe` 取代 `securityCodeList`、截面矩阵转置、指标元数据结构化),新增 `indicator screener` 条件选股,并修正复权参数名。
|
|
10
|
+
- **v0.29.0 — 2026-07-25**:新增财报日历与 PDF 解析工具,群消息补 `quoteMsg`,并加强大整数 ID 与高积分调用防护。
|
|
11
|
+
- **v0.28.3 — 2026-07-24**:修复列式响应在错误 `--field` 下静默错列的问题,并校正 EDE 与字段文档。
|
|
12
|
+
- **v0.28.2 — 2026-07-24**:EDE 批量取数新增 `--key-by name|code`,优化无数据诊断与相关 Skill 文档。
|
|
13
|
+
- **v0.28.1 — 2026-07-23**:调整 Agent Skill 取数路由,多证券财务与估值指标优先使用 EDE 批量接口。
|
|
14
|
+
- **v0.28.0 — 2026-07-21**:适配新版错误码,强化日期校验、异步状态处理、可观测性与计费安全。
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
- `insight performance-calendar list` — 财报日历:业绩预告 / 业绩快报 / 业绩公告三类事件,按 `--start-date`/`--end-date`(过滤 `publishDate`)、`--market`、`--security`、`--category` 筛选,自动翻页(单页上限 50)。**它是唯一按 `--*-date` 过滤的 insight list**(其余用 `--start-time`);`--market` / `--category` 拼错本地直接报错,不会静默返全量。**全表 >12 万条(0.1/条),裸跑会被本地拦下**——必须给一个约束:日期范围、`--security` 或显式 `--size`;`--security` 作为唯一约束时另加 1000 行隐式上限(**仅当 `total` 显示还有未取行时**才标 `partial` + 退出码 3——恰好取满 1000 行且 total 也是 1000 属完整结果,不会误报)
|
|
13
|
-
- `insight performance-calendar download --performance-report-id <id>` — 下载业绩报告原文 PDF(A股 10 积分 / 港美股 20 积分);仅 `hasAttachment: true` 的记录可下。省略 `--output` 时沿用标题命名
|
|
14
|
-
- `gangtise tool file-parse --file <x.pdf>` — PDF 解析(异步):上传拿 `taskId`,`--wait` 阻塞轮询(≈316s 预算)直接落盘结果 ZIP(内含 `file.md` + `images/`);不带 `--wait` 时用 `gangtise tool file-parse-check --task-id <id>` 取结果。**0.8 积分/页,提交时一次性扣费**,取结果免费。本地先校验 PDF 后缀 / 非空 / ≤100MB 再上传;提交端点标 `no-replay`(超时不重放,防重复扣分),单请求超时下限 300s
|
|
16
|
+
### 历史里程碑
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
18
|
+
- **v0.26.0–v0.27.0**:建立高积分端点 `no-replay`、原子下载与容错分页机制,并补齐 Skill 分发和发布质量门禁。
|
|
19
|
+
- **v0.22.0–v0.23.0**:统一“省略 `--size` 即拉全量”的分页语义,引入机器可识别的部分结果、Token 自愈,并完成 API 域名迁移与资金流向、机构搜索支持。
|
|
20
|
+
- **v0.19.0–v0.20.0**:上线 EDE 证券指标接口,扩展美股公告与财务报表,同时加强凭证脱敏、CSV 正确性和分页容错。
|
|
21
|
+
- **v0.16.0–v0.18.0**:以服务端参考数据替代多数本地静态表,收紧端点参数,并加入产业公众号资讯。
|
|
22
|
+
- **v0.14.0–v0.15.0**:新增跨市场实时行情、美股日 K 与题材数据,完善全市场 K 线分片和部分失败容错。
|
|
23
|
+
- **v0.12.0–v0.13.0**:奠定并发翻页、连接复用、流式输出与 K 线分片架构,并扩展港股财报、EDB 和股票池。
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
🔴 **数据完整性修复**:`--field` 传错字段名会导致**静默错列**(值贴到错误的字段上)。
|
|
25
|
-
|
|
26
|
-
**修复**
|
|
27
|
-
- 列式响应(`{fieldList, list}`)拍平时校验字段数与行长度,不匹配直接报错(`ValidationError`,退出码 1),不再输出错位数据。上游对不存在的字段名有两套处理:`day-kline` / `minute-kline` / `fund-flow` 名值同丢、三大报表补 `null`(长度相等,安全);但 **`quote realtime` / `fundamental main-business` / `valuation-analysis` 只丢值、字段名照请求回显**——实测 `quote realtime --field securityCode --field close --field turnoverRate`(realtime 根本没有 `close`)把换手率 `28.5573` 拍成了 `close`,茅台真实价 1297.41。不报错、数字看着合理、却完全是另一个指标。`alternative edb-data` 的同款拍平一并纳入校验
|
|
28
|
-
|
|
29
|
-
**文档(随包 skill)**
|
|
30
|
-
- SKILL.md 必备规则加第 10 条:`--field` 不确定就别传;`quote realtime` **无 `close`**(用 `latestPrice`)、**无市值**(总市值走 `indicator cross-section --indicator qte_mkt_cptl`,仅 A 股)
|
|
31
|
-
- `quote realtime` 补全 16 个实测字段(此前 `fields.md` 漏 `turnoverRate` / `volumeRatio`);`valuation-analysis` 标注只有 7 个字段、无 `securityCode`(误传会拿到一列重复的 `tradeDate`,长度相等拦不住)
|
|
32
|
-
- **推翻 07-23 关于 EDE `reportType` 的结论**(复测 2026-07-24):旧文档写「枚举不可信、`value=2/4` 直接 `999999`、指定口径请改用 `fundamental --report-type`」。实测是 label 与 value **错位但映射稳定**:`1`=合并(默认)、`2`=合并(调整)、`3`=母公司、`4`=母公司(调整),四值与三大报表逐一对得上;`2`/`4` 为空只是该报告期尚无调整表。**EDE 可以指定口径——母公司传 `3`,合并省略即可**
|
|
33
|
-
- 修正 `response-schema.md` 里会**反向诱导传错字段**的陈旧记录:`main-business` 行原写 `endDate` / `breakdownName` / `revenue`(实测均不存在),改为真实的 `periodName` / `periodEndDate` / `categoryName` + `opRevenue` / `grossProfit` 等 15 个字段
|
|
34
|
-
- 标注上游 meta 字段错位(实测边界):**A 股累计口径的 `balance-sheet` / `cash-flow`** 的 `companyType` 与 `currency` 值互换(`companyType=人民币`、`currency=银行`),A 股 `income-statement`、港股/美股三表均正确;A 股 `*-quarterly` 单季表另有 `companyType` 返回未映射数字码(`102119999`)的问题——读这两列按值判断语义,科目数字不受影响
|
|
35
|
-
|
|
36
|
-
### v0.28.2 — 2026-07-24
|
|
37
|
-
|
|
38
|
-
EDE 指标批量取数优化(基于对上游 990 个指标的实测)。
|
|
39
|
-
|
|
40
|
-
**新增**
|
|
41
|
-
- `indicator cross-section` / `time-series` 加 `--key-by name|code`(默认 `name`):`code` 模式列头用 `indicatorCode`(时序多证券侧用 `securityCode`),唯一且与服务端返回列序无关。**多证券批量按 code 回填必用**——此前拍平只按指标显示名,而多个指标同名(如 `cf_finc_exp`/`_qtr` 都叫「财务费用」)+ 服务端会重排返回列序,导致按名/按位置都错位、只能绕道 raw API 手工回填
|
|
42
|
-
|
|
43
|
-
**修复**
|
|
44
|
-
- EDE `999999` 无数据提示只对取数端点(`cross-section`/`time-series`)套用,`indicator.search`(同为 no-999999 策略、仅关键词入参)回落通用提示;文案改为「日期匹配指标周期(财务/MRQ 用报告期末、日频估值用交易日)、`scopeList`、`parameterList` 中 required 参数」——修正此前「行情/估值用交易日」与 `finc_pb_mrq` 仅报告期末的矛盾
|
|
45
|
-
|
|
46
|
-
**文档(随包 skill)**
|
|
47
|
-
- `indicator.md`:`--key-by` 文档 + 两处 synopsis;`periodNum` 补「部分需配年报日期」、`startDate` 补「取值须匹配指标周期」
|
|
48
|
-
- `examples.md` 例15:批量三截面示例改用 `--key-by code`
|
|
49
|
-
|
|
50
|
-
### v0.28.1 — 2026-07-23
|
|
51
|
-
|
|
52
|
-
Agent Skill 文档取数路由对齐(对齐 gangtise-mcp 0.1.46):多证券取一批**已实现**财务/估值指标优先走 EDE `indicator cross-section`/`time-series` 一次拉取,替代逐只 `fundamental` 循环。**本版仅改随包分发的 skill 文档(`gangtise-openapi/`),无 CLI 代码/命令/参数变更。**
|
|
53
|
-
|
|
54
|
-
**路由规则**
|
|
55
|
-
- 单票财务/估值/盈利预测/股东/主营、单票完整三大报表 → 仍走 `fundamental` 专用命令;行情/K 线 → `quote`(免费批量)
|
|
56
|
-
- 多证券已实现财务/估值指标 → 优先 EDE(`cross-section` 单日快照 / `time-series` 单指标×多证券区间)
|
|
57
|
-
- 始终排除 EDE:盈利预测·一致预期、估值历史分位(实测 EDE 无此两类)、OHLCV/K 线、单票完整报表
|
|
58
|
-
- EDE 取数前三项校验:`indicatorName`+`description` 语义 / `scopeList` 覆盖全部目标市场 / `parameterList` 必填参数,任一不符即回退专用接口
|
|
59
|
-
|
|
60
|
-
**实测校正(2026-07-23)**
|
|
61
|
-
- `scope` 字段更正为 `scopeList[].market/.securityType`(服务端已返回实际覆盖),覆盖按指标而异:`finc_pe_ttm`/`finc_pb_mrq` 仅 A 股、`is_op_rev` A 股+港股,均不含美股;`valuation-analysis`/`earning-forecast` 仅 A 股
|
|
62
|
-
- `finc_pb_mrq`(市净率 MRQ) 只在报告期末打值(交易日取 `null`),非日频;EDE 财务指标 `reportType` 枚举 label 与实测取数不符(`value=2/4` 直接 `999999`),要指定报表口径改用 `fundamental` 三大报表 `--report-type`
|
|
63
|
-
- ⚠️ **本条 `reportType` 结论已被 v0.28.3 复测推翻,勿据此路由**:实际是 `1`=合并 / `2`=合并(调整) / `3`=母公司 / `4`=母公司(调整),EDE 可直接指定口径,见上方 v0.28.3 条目(`finc_pb_mrq` 部分仍成立)
|
|
64
|
-
|
|
65
|
-
### v0.28.0 — 2026-07-21
|
|
66
|
-
|
|
67
|
-
对齐服务端 2026-07-17 更新(内资研报下载调价 + 41 个公开错误码重排)。**41 个码逐个打了线上探针**,结论是迁移按「错误处理层」而非按业务模块进行、文档并不等于现状:同一个接口内,参数校验层与路由层已发新码,方法路由层和 token 过滤器仍发旧码,异步生成状态也仍是旧码。CLI 对两代都识别。
|
|
68
|
-
|
|
69
|
-
**错误码体系**
|
|
70
|
-
- `errors.ts` 错误码表按新三层结构(`999xxx` 服务统一层 / `1xxxxx` 业务通用层 / `2xxxxx` 接口专有层)重写,覆盖 41 个公开码 + 实测仍在线的旧码
|
|
71
|
-
- 异步轮询同时识别 `410110`/`140001`(生成中)与 `410111`/`140002`(终态失败)。实测服务端**仍在用旧码**(HTTP 400、无 `errorType`),新码为预置——服务端切换那天 `--wait` 不会在首次轮询就抛错中止,把已扣的 50 积分作废
|
|
72
|
-
- `140002`(异步 PROCESSING_FAILED,`410111` 的新码)纳入 transport 终态码集合、任何 HTTP 状态都不重试——异步 `*-check`(get-content)端点无 retry 声明、走默认策略,`140002@500` 会被白重试 2 次才轮到 `asyncContent` 的 `FAILED_CODES` 识别(后者在 `client.call` 的 `withRetry` 之上、拦不到重试);`140002` 语义即「生成失败·终态」、只有那些异步端点会返回它,故全局终态化既安全又省掉白重试。实测服务端仍用 `410111`,此为预置
|
|
73
|
-
- token 自愈补上 `999002`(`0000001008` 的新码);`999011`(AK/SK 不匹配)加入**终态码集合**,任何 HTTP 状态下都不重试——凭证错不会自己好。注意它只来自 `auth.login`,而 login 走 `useAuth=false` 压根不经过自愈码表,所以「不列进自愈表」并拦不住 `auth.login` 在 5xx 上按默认策略重放两次,必须落在终态码上
|
|
74
|
-
- 修正 `900002` 的错误释义:实测服务端用它表示「请求方法不正确」(HTTP 405),旧文档写作「请求缺少 uid」,据此排查会走错方向
|
|
75
|
-
- 错误提示改为只给下一步动作,不再复述服务端 msg(此前输出形如 `资源不存在 资源不存在,确认 ID 有效`)——新旧两代都过了一遍:留用的 `903301` / `8000016` / `8000018` / `999995` / `999997` / `900001` / `130001` / `410004` / `410110` / `410111` 原本是逐字重复 msg
|
|
76
|
-
- 补上 `410001` / `410106` 两个 EDE 专有旧码的提示——它们没被 2026-07-17 重排收编,却是 `indicator` 取数最常见的两个报错(漏传 `--indicator`/`--security`、漏传 `periodNum` 等必填参数),`indicator.md` 早已把它们列为首要排查项
|
|
77
|
-
- `110002`(日期区间非法)提示改为同时覆盖 `--start-date/--end-date` 与 `--start-time/--end-time`——此前只提 date 参数,而 `insight` 系 list 按 `--start-time` 排序,旧提示指向的是命令根本没有的参数
|
|
78
|
-
- `999006`(限流)提示不再断言「CLI 已退避重试」——仅限流以 429 返回时才全局按 `Retry-After` 退避重试;5xx 形态只有默认策略端点会重试、贵档 no-replay 端点不会,200 错误信封则不重试
|
|
79
|
-
- `130001` 提示改为先给通用「未找到数据/核对查询条件」再限定 EDE 指标权限(此前把通用 DATA_NOT_FOUND 一律导向「未开通指标」);`130002` 提示补上「非法 `--file-type` 也归此码」(下载类兜底)
|
|
80
|
-
|
|
81
|
-
**行为变更(本地校验)**
|
|
82
|
-
- 所有 date 参数(`--start-date`/`--end-date`/`--date`/`--report-date`,覆盖 Quote/Fundamental、AI `theme-tracking`/`hot-topic`/`management-discuss-*`、Alternative `edb-data`、Indicator)只接受 `YYYY-MM-DD`,其余格式在发请求前报 `ValidationError`——**服务端额外接受的两种「年在后」格式日月顺序相反**:实测 `07/01/2026`(斜杠)读成 `2026-01-07`、`07-01-2026`(横杠)读成 `2026-07-01`,同样三个数字差半年且都返回 HTTP 200,响应里不回显服务端实际采用的日期(用 `25/12` 与 `12/25` 的互补接受结果交叉验证)。CLI 无从判断用户想要哪个读法,故只转发无歧义写法。`20260701` / `2026/07/01` 这类服务端同样能正确处理的写法也一并拒掉——统一成一种入参形态,好过按端点逐一探针维护白名单;报错文案说明该用哪种写法,不再断言输入本身有歧义
|
|
83
|
-
- **datetime 参数(`--start-time`/`--end-time`)本地拦截覆盖全部透传命令**(insight research/summary/announcement-hk/us、vault 各 list、`quote minute-kline`、`ai security-clue` 等原样透传的 18 处,外加转时间戳的 A 股 `announcement` / `knowledge-batch`)。**服务端对透传的年在后格式静默误解析、并不报 `110001`**:实测 `insight research list` 对 `07/01/2026` 返回 1562 条(=`2026-01-07`)、`07-01-2026` 返回 210 条(=`2026-07-01`),差半年、都 HTTP 200、响应不回显实际日期。新增 `parseDatetimeOption` 做**时区无关**的字段校验(算术闰年、不构造本地 `Date`,故 DST 缺口时刻等对服务端合法的字符串不被客户端时区误伤)后**原样透传**
|
|
84
|
-
- 本地时间校验只认 `YYYY-MM-DD`、`YYYY-MM-DD HH:mm[:ss]`(空格或 `T` 分隔、秒可省)或 10/13 位时间戳;此前 `parseTimestamp13` 用 `new Date()` 兜底还能吞 `.SSS` 毫秒尾、`+08:00` 时区尾、以及 `Infinity`/`1e309`/非整数(数字分支只查 `NaN`,这类会序列化成 null 静默取消过滤),现一律拒绝(时间戳分支改用严格 `^\d{10}$`/`^\d{13}$` 位数正则——这也是科学计数法 / 16 进制 / 空白 / 非标准位数被拒、且 13 位 `1000000000000` 不再落进秒分支的原因)
|
|
85
|
-
|
|
86
|
-
**修复**
|
|
87
|
-
- EDE 内层信封的报错(`indicator` 取数失败的 `999999` / `130001` 等)此前**永远拿不到 traceId**:实测 `traceId` 只挂在外层信封上,而外层在解包时即被丢弃,内层抛错又没传 details。现在外层 id 以不可枚举属性随 payload 带下去(不进 JSON/CSV 输出),`ApiError.traceId` 兜底读它——这类错误恰恰最需要报障,此前与 README「报错行会带 trace」的表述对不上
|
|
88
|
-
- HTTP 200 包裹的错误信封(Gangtise 也用这种形态)此前会丢掉服务端的 `Retry-After`:主 JSON 路径与下载 JSON 路径两处 `unwrapEnvelope` 都补上(此前只有 4xx/5xx 的 `throwHttpError` 保留),限流响应的退避窗口不再被丢弃
|
|
89
|
-
- `toTimestamp13` 的日历校验补年份与时间 round-trip:`0050-06-15` 曾被 `Date(50,…)` 构造器映射成 1950、DST 缺失时刻(如 America/New_York 的 `02:30`)曾被静默移到 `03:30`——均改为拒绝
|
|
90
|
-
- 异步终态失败(`410111`/`140002`)的报错行补上 code / msg / `traceId` 并提示重提会再次计费——此前只打印一句 "Content generation failed",把本版新增的 trace 信息吞掉了,与 README「报错行会带 trace」的表述矛盾
|
|
91
|
-
|
|
92
|
-
**可观测性**
|
|
93
|
-
- 响应信封新增的 `traceId` 透出到 CLI 报错行:`API error (130002) [trace 830965044897325056]: 资源不存在 确认 ID 有效…`——这是 Gangtise 侧唯一能回溯一次失败的抓手,报障时请带上
|
|
94
|
-
|
|
95
|
-
**计费**
|
|
96
|
-
- `insight research download`(内资研报)**20 → 10 积分/篇**,SKILL.md 积分速查表与 `insight.md` 同步
|
|
97
|
-
|
|
98
|
-
**文档(实测结论沉淀)**
|
|
99
|
-
- SKILL.md 异常处理表重写为「实测确认在用」与「文档列出但未触发」两组,标注每个码的实测状态与兜底关系(`100003` 是参数类兜底、`130002` 是下载类兜底,`130003`/`130004`/`130005` 均未启用)
|
|
100
|
-
- 记录两个实测坑:**枚举值拼错与分页越界服务端不报错**(静默忽略该筛选条件,拼错会伪装成"结果正常");**`viewpoint-debate` 敏感内容不被提前拦截**,扣满 50 积分后才以 `410111` 失败
|
|
101
|
-
- 纠正 SKILL 异常表 `110001`/`110002` 行的日期分类:此前按命令组(「Quote/Fundamental 用 date、Insight/AI 用 datetime」「110001 只有 Insight 系报」)与 AI `management-discuss --report-date`(date 型)及实测都冲突——实测 `fundamental` 对 `2020/01/01` 报 110001、`insight research list` 对 `30/06/2025` 反而宽松解析返回数据,改为按参数名分类、不按命令组预判
|
|
102
|
-
- 新增判别法:新码信封 `code` 是 JSON 数字且带 `errorType`,旧码是字符串且没有——但它判断的是**单条错误路径**切没切,不是整个接口(成功响应也没有 `errorType`,别拿它当判据)
|
|
103
|
-
- README 常见错误表同步重写;Troubleshooting 的 `8000014/8000015` → `999011`、`430007` → `100006`
|
|
104
|
-
|
|
105
|
-
### v0.27.0 — 2026-07-11
|
|
106
|
-
|
|
107
|
-
**EDE 指标(体验修复)**
|
|
108
|
-
- `indicator` 三端点对 `999999` 不再自动重试——实测服务端用 `999999` + HTTP 500 表示「查询无数据」(节假日 / 未来日期 / 未覆盖标的),此前每次空查询白烧 3 个请求 + ~4 秒;错误提示改为指向检查查询条件而非「稍后重试」
|
|
109
|
-
|
|
110
|
-
**资金与下载加固(承接 v0.26.0)**
|
|
111
|
-
- 下载路径同样接入重试策略:50/篇 的 `summary` / `foreign-report` / `my-conference` download 改为 no-replay(与 AI Agent 同价档;下载中断重试可能重复计费),10-30/篇 的下载维持默认重试
|
|
112
|
-
- 签名 URL 下载增加整体硬截止(10× 单请求超时)——headers/body 超时是空闲型,慢滴速传输可无限续命;最终 rename 失败时清理 `.part`
|
|
113
|
-
- `GANGTISE_PAGE_CONCURRENCY` 防御性解析:非法/非正数回退默认 5、上限 32——负值此前被底层钳制成**单 worker 串行**(静默变慢),过大值可能造成过度并发触发限流
|
|
114
|
-
- `--version` 更新提示改为数值分段版本比较(不处理预发布号;本项目只发 x.y.z)——刚发版的 registry 滞后窗口不再把旧版本提示成"可更新"
|
|
115
|
-
|
|
116
|
-
**体验与正确性小修**
|
|
117
|
-
- `--wait` 异步轮询容忍瞬态错误:5xx/网络抖动只消耗一次尝试并继续等待,不再作废整段等待(积分不足等终态错误仍立即中止)
|
|
118
|
-
- table 输出单元格显示宽度上限 120(超长截断加 `…`)——一个超长字段不再把整列所有行 pad 成同宽(行数 × 宽度的空格放大)
|
|
119
|
-
- markdown 输出先转义反斜杠再转义竖线,字面 `\|` 单元格不再错位列;table/markdown 过滤 C1 控制符(U+009B 单字节 CSI 注入面)
|
|
120
|
-
- 自动文件名按码点截断,emoji 不再被截成 `�`;EDE 矩阵中与 `date`/`security`/`name` 同名的指标列自动加后缀,不再覆盖元数据列
|
|
121
|
-
- 全市场分片截断时输出 `truncatedShards`(具体日期区间,与 `failedShards` 对称),脚本/AI 消费者可定向缩窗补拉
|
|
122
|
-
- 分页端点首页形状漂移(如 `total` 变字符串)时 `--verbose` 下告警,不再完全静默退化单页
|
|
123
|
-
|
|
124
|
-
**Skill 分发**
|
|
125
|
-
- `gangtise-openapi/` 目录纳入 npm 包;README 安装命令改为从 `$(npm root -g)` 复制——此前的相对路径命令对 npm 用户不可执行
|
|
126
|
-
|
|
127
|
-
**防漂移门禁(工程,不影响 CLI 行为)**
|
|
128
|
-
- 新增 README↔ENDPOINTS 一致性测试:「自动翻页」清单与注册表 pagination 标记双向比对(此类手抄清单漂移已发生两次);insight/reference 子命令的 `--help` 覆盖改为从端点注册表派生,新命令漏接线直接测试失败
|
|
129
|
-
- `npm run prepare` 前置断言 README/CHANGELOG 含当前版本条目(写盘前检查,失败零残留);`npm run typecheck` 纳入 tests/(tsconfig.test.json)
|
|
130
|
-
- CI:`npm pack` 装包冒烟(`--help` + skill 文件存在校验)、测试矩阵 Node 下限改精确 20.18.1、CI typecheck;publish 的 `workflow_dispatch` 必须指向 `v*` tag(关闭无护栏发布通道)
|
|
131
|
-
|
|
132
|
-
### v0.26.0 — 2026-07-11
|
|
133
|
-
|
|
134
|
-
**资金安全(重要)**
|
|
135
|
-
- 13 个贵档端点(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `theme-tracking` / `management-discuss-*`×2 / `hot-topic` / `knowledge-batch` / `earnings-review get-id` / `viewpoint-debate get-id` / `concept-info` / `concept-securities`)改为 **no-replay 重试策略**:5xx / 超时 / `999999` 不再自动重放——实测(2026-07-11)平台按次计费且**缓存命中不豁免**,同参数重放每次都扣分;仅连接期错误(`ECONNREFUSED`/DNS,请求未发出)、429 限流和 token 自愈仍重试。便宜按条计费的 list 类维持原全量重试(失败响应没有数据行、不计费)
|
|
136
|
-
- 连接失败 `ECONNREFUSED` / `UND_ERR_CONNECT_TIMEOUT` 纳入默认重试范围(此前这两类不重试)
|
|
137
|
-
|
|
138
|
-
**文件安全**
|
|
139
|
-
- 所有 `--output` 落盘(导出、流式下载主路径、签名 URL 跟随下载)改为原子写:先写同目录 `.part` 成功后 rename——重跑失败不再毁掉已有旧文件;顺带修掉中止路径上 `.part` 因流懒打开竞态残留的问题
|
|
140
|
-
- 签名 URL 跟随下载改走 transport 层:遵守 `GANGTISE_TIMEOUT_MS`(此前裸 `fetch` 无超时,慢滴速 CDN 可无限挂起)、网络错误自动重试、跟随最多 3 跳重定向(undici 不自动跟随,超限/缺 `Location` 报错而非把跳转页存成文件)、`--verbose` 日志剥离签名 query 只留 origin+path
|
|
141
|
-
- 自动命名去重后缀试尽 `-1`…`-99` 仍冲突时报错,不再静默覆盖最早的文件
|
|
142
|
-
|
|
143
|
-
**修复 / 加固**
|
|
144
|
-
- 下载重定向超过 3 跳或缺 `Location` 时报错,不再把跳转页 HTML 当文件内容保存
|
|
145
|
-
- 损坏的 gzip 响应包装为带请求上下文的 `ApiError`(此前抛裸 zlib `Z_DATA_ERROR`,与请求无关联且不可定位)
|
|
146
|
-
- `alternative edb-search --limit` ≤200、`indicator search --limit` ≤100 本地上限校验——实测服务端对超限值静默截断(201→200、101→100),与 v0.25.0 的 `--top` 同类同修法
|
|
147
|
-
|
|
148
|
-
### v0.25.0 — 2026-07-10
|
|
149
|
-
|
|
150
|
-
**新增接口(4)**
|
|
151
|
-
- `insight qa list` — 投资者问答 QA:按证券提取互动平台 / 电话会议 / 调研纪要的提问与回答;`--security-code`(必填)、`--source`(`conference`/`interactive`/`survey`)、`--question-category`(11 类,见 `insight.md`)、`--answer-important`(`1` 是 / `0` 否)、`--start-time`/`--end-time`(字符串直传);自动翻页(单页上限 500);0.1 积分/条
|
|
152
|
-
- `insight report-image list` / `download` — 研报图表:按关键词搜索研报图片,返回 `chunkId` + 元数据(`--keyword` 必填、`--top` 默认 10 上限 20、`--source-id`、时间过滤;**免费**),再 `download --chunk-id` 下二进制原图(JPEG,0.1 积分/张)
|
|
153
|
-
- `reference official-account-search` — 公众号 ID 搜索:输入公众号名 / 机构 / 关键字返回 `accountId`(喂 `insight official-account list --account-id`);`--keyword`(必填)、`--category`(`listedCompany`/`broker`/`government`/`media`,可重复;未分类公众号 `category` 为 `null`,传 `--category` 会漏掉)、`--top`(默认 10 上限 10);免费
|
|
154
|
-
|
|
155
|
-
**变更**
|
|
156
|
-
- `indicator search` / `cross-section` / `time-series` 市场范围从仅 A 股扩展至 A 股 / 港股 / 美股(服务端变更;CLI 早已支持 `--currency` 与多市场证券代码,无需改动)。⚠️ 美股代码用交易所后缀 `.O`(NASDAQ) / `.N`(NYSE),**非 `.US`**——官方示例的 `AAPL.US` 查不到数据,实测须 `AAPL.O`
|
|
157
|
-
|
|
158
|
-
**修复 / 加固**(承接上一批未单独发版的改动)
|
|
159
|
-
- 分页 / 分片 `partial` 检测补全:`requestPaginated` 的短后续页、`MAX_PAGES` 上限、`total` 漂移、失败页四种场景统一触发 `partial`(退出码 3)——失败页独立成判定条件,避免超额返回的兄弟页把行数补满、掩盖失败页空洞;`quote` 全市场分片硬错后熔断、破损形状分片计入 `failedShards`
|
|
160
|
-
- `--top` 本地上限校验(`report-image` / `knowledge-batch` ≤20,reference 六个搜索命令 ≤10)——实测服务端对超限值**静默截断**不报错,现在发请求前本地报错;`securities-search` / `institution-search` / `official-account-search` 的 `--category` 加本地白名单——实测服务端对拼错的分类**不报错**(securities-search 静默忽略过滤返回全类别、另两个静默返回空),拼写错误不再伪装成"无结果"(`insight qa` 的枚举服务端会报 `100003`,故不做本地白名单)
|
|
161
|
-
- 错误码 `100003`(参数值非法)补充中文提示——服务端不指明是哪个参数,提示对照命令 `--help` 检查枚举参数拼写
|
|
162
|
-
- undici `^7.16.0` → `^7.28.0`(修 keep-alive 队列污染 GHSA-35p6-xmwp-9g52),`engines.node` `>=20` → `>=20.18.1` 对齐 undici 实际最低要求
|
|
163
|
-
|
|
164
|
-
### v0.24.0 — 2026-07-07
|
|
165
|
-
|
|
166
|
-
**新增**
|
|
167
|
-
- `raw list` — 列出所有已注册的 endpoint key(含 method / path / description),配合 `raw call <key>` 使用,不必再翻文档记 key;支持 `--format`(默认 table)/ `--output`
|
|
168
|
-
- AI 同步生成端点内置 120s 超时下限(`one-pager` / `investment-logic` / `peer-comparison` / `theme-tracking` / `research-outline` / `management-discuss-announcement` / `management-discuss-earnings-call`)——生成耗时长不再撞 30s 默认超时触发重试,**不必再手动前缀 `GANGTISE_TIMEOUT_MS`**;显式设更大值仍生效(取 max)
|
|
169
|
-
- 429 响应尊重 `Retry-After`(秒或 HTTP-date;覆盖 JSON、非 JSON、下载三类错误路径),优先于默认指数退避,封顶 60s 防挂死
|
|
170
|
-
- 超大结果(≥5 万行且走非流式渲染:table/json/markdown,或 jsonl/csv 未带 `--output`)在 stderr 提示改用 `--format jsonl --output <path>` 流式落盘
|
|
171
|
-
|
|
172
|
-
**性能**
|
|
173
|
-
- JSON 请求启用 gzip(`accept-encoding: gzip` + 本地解压)——实测 `reference constant-list` 2110B→586B(3.6x),K 线类高重复大 JSON 收益更高;下载二进制路径不变
|
|
174
|
-
- 全市场按日分片(`quote fund-flow` / `day-kline` / `day-kline-us`,均 1 天/片)自动跳过周六日(A/港/美股周末闭市必空),省 ~28% 请求与每日调用配额;多日分片(`day-kline-hk` 2 天、`index-day-kline` 30 天)不受影响
|
|
175
|
-
|
|
176
|
-
**修复**
|
|
177
|
-
- 表格(table/markdown)显示宽度纳入 emoji 码位区(0x1F000–0x1FAFF),含 emoji 的微信群名/消息不再错位
|
|
178
|
-
- `fundamental earning-forecast` 默认 `--end-date`("today")改用运行机器本地日期;此前用 UTC 日期,CST 凌晨 0–8 点会算成"昨天"
|
|
179
|
-
|
|
180
|
-
**文档 / 工程**(不影响已发布 CLI 行为)
|
|
181
|
-
- `insight announcement`(A 股公告)时间过滤时区说明:`--start-time`/`--end-time` 按运行机器时区换算,跨机器精确边界改传 13 位毫秒时间戳
|
|
182
|
-
- CI 测试矩阵增加 Node 24(此前仅 20;发布用 24)
|
|
183
|
-
|
|
184
|
-
### v0.23.0 — 2026-07-05
|
|
185
|
-
|
|
186
|
-
**行为变更(注意)**
|
|
187
|
-
- ⚠️ 默认 API 域名迁移:`https://open.gangtise.com` → `https://openapi.gangtise.com`。旧域名仍可用,CLI 只是切换了默认值(新旧域名多接口实测等价);如需固定旧域名设 `GANGTISE_BASE_URL=https://open.gangtise.com`
|
|
188
|
-
- `vault wechat-chatroom-list`:服务端接口改版为返回 `{ total, list }`(此前无 `total`、列名 `chatRoomList`)。CLI 相应改为按 `total` 并发翻页(不再串行翻页);省略 `--size` 仍拉全量、传 `--size N` 取前 N 条,`Total:` 提示恢复
|
|
189
|
-
- 无翻页的行情端点(`quote fund-flow` / `minute-kline` / 显式多标的的日 K:`day-kline`·`-hk`·`-us`·`index-day-kline`)返回行数撞上单次 `--limit` 时标 `partial`(退出码 3)+ stderr 警告,避免被静默截断;`--limit` 现本地校验 ≤ 10000(撞服务端上限也不漏标)。K 线 `--security all` 仍走日期分片自动补全,不受影响
|
|
190
|
-
|
|
191
|
-
**新增**
|
|
192
|
-
- `quote fund-flow` — A股个股日资金流向(沪深京),含小/中/大/特大单流入流出金额及占比、主力净流入等字段;`--security`(或 `aShares` 全市场)、`--start-date` / `--end-date`、`--limit`(默认 6000,上限 10000)、`--field` 指定返回字段;无积分消耗(单只证券无翻页,撞 `--limit` 时的截断处理见上「行为变更」;**`aShares` 全市场须显式传 `--start-date`/`--end-date`,CLI 按日自动分片并发合并——缺日期会本地报错**)
|
|
193
|
-
- `reference institution-search` — 机构 ID 搜索,输入机构名/简称返回 `institutionId` 及适用接口参数(`usageScopes`);`--keyword`(必填)、`--category`(`domesticBroker`/`foreignInstitution`/`leadInstitution`/`opinionInstitution`/`foreignOpinionInstitution`,可重复)、`--top`(默认 10,上限 10);免费。覆盖既有 `--broker`/`--institution` 全部机构类(research/foreign-report/opinion/foreign-opinion/summary/roadshow/site-visit/strategy/my-conference)
|
|
194
|
-
- `vault my-conference-list` 新增 `--source` — 按录制来源筛选(`1`=企微会议助理 `2`=会议服务微信群,可重复;不传返回全部)
|
|
195
|
-
|
|
196
|
-
> 更早版本及完整更新历史见 [CHANGELOG.md](CHANGELOG.md)。
|
|
25
|
+
> 完整更新明细及更早版本见 [CHANGELOG.md](CHANGELOG.md)。
|
|
197
26
|
|
|
198
27
|
## 首次安装
|
|
199
28
|
|
|
@@ -263,7 +92,7 @@ gangtise-openapi/
|
|
|
263
92
|
│ ├── ai.md # AI 能力命令(one-pager / earnings-review / viewpoint-debate 等)
|
|
264
93
|
│ ├── alternative.md # 行业指标数据库(EDB search / EDB data)
|
|
265
94
|
│ ├── fundamental.md # 财务数据命令(A股/港股三大报表 / 估值 / 盈利预测 / 股东)
|
|
266
|
-
│ ├── indicator.md # 证券级数据指标 EDE(search / 截面 /
|
|
95
|
+
│ ├── indicator.md # 证券级数据指标 EDE(search / 截面 / 时序 / 条件选股)
|
|
267
96
|
│ ├── insight.md # 投研内容命令(研报 / 观点 / 纪要 / 公告 / 外资)
|
|
268
97
|
│ ├── quote.md # 行情命令(A股/港股/指数 K 线)
|
|
269
98
|
│ ├── reference-and-lookup.md # GTS Code 搜索与枚举速查
|
|
@@ -375,6 +204,7 @@ cp -r "$SKILL_SRC" ~/.hermes/skills/gangtise-openapi
|
|
|
375
204
|
| **Indicator** | `search` | 证券级数据指标搜索(按名称匹配,返回 indicatorCode 及可传参数 parameterList) |
|
|
376
205
|
| | `cross-section` | 指标截面数据(多指标 × 多证券,单日快照;前置 `search` 拿 code) |
|
|
377
206
|
| | `time-series` | 指标时间序列(多指标 × 单证券 或 单指标 × 多证券,按区间) |
|
|
207
|
+
| | `screener` | 条件选股(按指标表达式从证券/板块范围筛股;前置 `search` 拿 code) |
|
|
378
208
|
| **Alternative** | `edb-search` | 行业指标搜索(按关键词匹配,返回 indicatorId 等元信息) |
|
|
379
209
|
| | `edb-data` | 行业指标时序数据(批量拉取,最多10个指标) |
|
|
380
210
|
| | `concept-info` | 题材指数基本信息(投资逻辑/行业空间/竞争格局/催化事件) |
|
|
@@ -428,7 +258,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
|
|
|
428
258
|
- **HTTP keep-alive**:所有请求复用同一个 `undici.Agent`(连接池 16),避免重复 TLS 握手。
|
|
429
259
|
- **流式下载**:指定 `--output` 时,二进制响应(PDF 等)直接 `pipeline` 到磁盘,不经过内存缓冲;50MB PDF 内存占用近乎为零。
|
|
430
260
|
- **流式输出**:`jsonl`/`csv` 格式且 `--output` 指定时,超过 1000 行自动切换为逐行写盘,避免一次性构建百 MB 字符串。
|
|
431
|
-
- **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**贵档端点例外**(one-pager 等 13 个生成/提交类 + 50/篇 的 summary/foreign-report/my-conference 下载,共 16 个):5xx/超时不重放——按次计费不幂等,重放即重复扣分;仅连接失败、429 与 token 自愈重试。**`indicator`(EDE)端点对 `999999`
|
|
261
|
+
- **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**贵档端点例外**(one-pager 等 13 个生成/提交类 + 50/篇 的 summary/foreign-report/my-conference 下载,共 16 个):5xx/超时不重放——按次计费不幂等,重放即重复扣分;仅连接失败、429 与 token 自愈重试。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(该码 2026-08-01 前还兼表「查询无数据」,现已改为返回空数组)。**终态码 `999011`(凭证无效)/ `140002`(异步生成失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,异步生成失败是终态。
|
|
432
262
|
- **Token 自愈**:调用返回 `0000001008` / `999002` 时自动强制刷新 Token 并重试一次。
|
|
433
263
|
- **K线/资金流向自动分片**:`quote day-kline --security all`、`quote fund-flow --security aShares` 等全市场查询自动按日期切分(A股 K线/资金流向 1 天/片、美股 1 天/片、HK 2 天/片、指数 30 天/片),并发执行后合并结果;按日分片自动跳过周六日。分片时如果用户未传 `--limit`,自动注入 `limit: 10000`(API 上限)避免默认 6000 截断。
|
|
434
264
|
- **Token 内存缓存**:Token 在进程内存中缓存,避免每次请求读盘。
|
|
@@ -739,19 +569,31 @@ gangtise indicator search --keyword 收盘价 --format table # → q
|
|
|
739
569
|
gangtise indicator search --keyword 平均ROE --limit 5 --format json # 看 parameterList
|
|
740
570
|
|
|
741
571
|
# 截面:多指标 × 多证券,单日快照(行情类用交易日;财务类用报告期末,如 2026-03-31)
|
|
572
|
+
# --security 也接受板块 ID(reference sector-search 的 10 位 sectorId),与代码混传取并集
|
|
742
573
|
gangtise indicator cross-section \
|
|
743
574
|
--indicator qte_close --indicator qte_vol --indicator qte_mkt_cptl \
|
|
744
575
|
--security 600519.SH --security 09992.HK \
|
|
745
|
-
--date 2026-
|
|
576
|
+
--date 2026-07-31 --format table
|
|
577
|
+
# 输出列:security / name / <各指标名>…(v0.30.0 起无 date 列——日期挂在每个指标的参数上)
|
|
746
578
|
|
|
747
579
|
# 时间序列:多指标 × 单证券 或 单指标 × 多证券(不能多 × 多,否则报 410001)
|
|
748
580
|
gangtise indicator time-series --indicator qte_close \
|
|
749
581
|
--security 600519.SH --security 09992.HK \
|
|
750
|
-
--start-date 2026-
|
|
751
|
-
|
|
752
|
-
#
|
|
582
|
+
--start-date 2026-07-29 --end-date 2026-07-31 --format table
|
|
583
|
+
|
|
584
|
+
# 条件选股:F1/F2… 绑定指标,用表达式组合筛选(--indicator-param 按变量索引,不是按 code)
|
|
585
|
+
gangtise indicator screener \
|
|
586
|
+
--indicator F1:qte_mkt_cptl --indicator F2:finc_pe_ttm \
|
|
587
|
+
--indicator-param "F1:scale=8" \
|
|
588
|
+
--security 1000000287 \
|
|
589
|
+
--expression "F1 >= 500 && F2 <= 30" \
|
|
590
|
+
--date 2026-07-31 --format table
|
|
591
|
+
|
|
592
|
+
# 复权 / 指标专属参数用 --indicator-param "code:key=value"
|
|
593
|
+
# ⚠️ 参数名必须以 search 的 parameterList 为准:传错名服务端静默忽略、退回默认值,不报错
|
|
753
594
|
gangtise indicator cross-section --indicator qte_close --security 600519.SH \
|
|
754
|
-
--date
|
|
595
|
+
--date 2024-01-02 --indicator-param "qte_close:adjustType=3" # 1不复权/2前复权/3后复权/4定点
|
|
596
|
+
# 不复权 1685.01 → 前复权 1531.225 → 后复权 13609.6168(前复权在最新交易日等于不复权,验证要用历史日)
|
|
755
597
|
|
|
756
598
|
# 必填参数:很多指标默认调用报 410106(缺必填参数),按 parameterList 的 required 补齐再取:
|
|
757
599
|
# N 期统计补 periodNum、区间/周期类(如 qte_amp_mo 月振幅)补 startDate、年度/分红类补 fiscalYear
|
|
@@ -853,7 +695,7 @@ CLI 会在本地校验常见数值参数,避免把明显非法的请求发到
|
|
|
853
695
|
| `999010` | 接口地址不存在(`raw call` 的 key 可能已下线,用 `raw list` 核对) |
|
|
854
696
|
| `999012` / `999013` / `999014` | 账号禁用 / 已过期 / 租户失效 |
|
|
855
697
|
| `999016` | 调用方 IP 不在允许范围 |
|
|
856
|
-
| `999999` | Gangtise 系统错误,请稍后重试(`indicator`
|
|
698
|
+
| `999999` | Gangtise 系统错误,请稍后重试(`indicator` 端点无数据已改为返回空表,此码基本只剩真故障) |
|
|
857
699
|
| `100003` | 参数值非法——**最宽的兜底码**;msg 通常已指明字段(如「limit 最小为 1,最大为 10000」),先读 msg |
|
|
858
700
|
| `100001` | 缺必填参数(msg 带字段名,如「缺少必填参数: reportId」) |
|
|
859
701
|
| `100006` | 查询/下载数量超限——**取代旧 `430007`** |
|
package/dist/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command, Option } from "commander";
|
|
|
3
3
|
import { checkAsyncContent, pollAsyncContent, POLL_MAX_ATTEMPTS } from "./core/asyncContent.js";
|
|
4
4
|
import { readTokenCache, redactTokenCache } from "./core/auth.js";
|
|
5
5
|
import { collectKeyValue, collectList, collectNumberList, dateArg, datetimeArg, isVersionNewer, localDateString, maybeArray, parseChoiceList, parseFrom, parseNumberOption, parseOptionalNumberOption, parseSize, parseTimestamp13 } from "./core/args.js";
|
|
6
|
-
import { buildIndicatorCrossSectionBody, buildIndicatorTimeSeriesBody, buildQuoteKlineBody, buildStockPoolStocksBody, buildWechatChatroomListBody, buildWechatMessageListBody } from "./core/commandBodies.js";
|
|
6
|
+
import { buildIndicatorCrossSectionBody, buildIndicatorScreenerBody, buildIndicatorTimeSeriesBody, buildQuoteKlineBody, buildStockPoolStocksBody, buildWechatChatroomListBody, buildWechatMessageListBody } from "./core/commandBodies.js";
|
|
7
7
|
import { flattenCrossSection, flattenTimeSeries, unwrapIndicatorData } from "./core/indicatorMatrix.js";
|
|
8
8
|
import { callKlineWithSharding, isAllMarket, isFullMarket } from "./core/quoteSharding.js";
|
|
9
9
|
import { loadConfig } from "./core/config.js";
|
|
@@ -706,16 +706,23 @@ indicator.command("search").requiredOption("--keyword <text>", "Search keyword,
|
|
|
706
706
|
});
|
|
707
707
|
await printData(unwrapIndicatorData(raw), format, options.output);
|
|
708
708
|
}));
|
|
709
|
-
indicator.command("cross-section").option("--indicator <code>", "Indicator code, e.g. qte_close (repeat for multiple)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH (repeat
|
|
709
|
+
indicator.command("cross-section").option("--indicator <code>", "Indicator code, e.g. qte_close (repeat for multiple)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH, or a sector ID from 'gangtise reference sector-search' (repeat; union, deduped)", collectList, []).requiredOption("--date <date>", "Data date (yyyy-MM-dd); sent as each indicator's tradeDate — report-period indicators need --indicator-param 'code:reportDate=...' instead", dateArg("--date")).option("--currency <code>", "Currency: DFT/CNY/HKD/USD/EUR/GBP/JPY/TWD/MOP/AUD (default DFT)").option("--scale <code>", "Scale: 0=个 3=千 4=万 6=百万 8=亿 9=十亿 (default 0)").option("--indicator-param <spec>", "Per-indicator param 'code:key=value', e.g. qte_close:adjustType=2 for 前复权 (repeat); read exact keys from 'indicator search'", collectList, []).addOption(new Option("--key-by <mode>", "Column key: name=display name (default) | code=indicatorCode, unique & order-stable for batch code→value mapping").choices(["name", "code"]).default("name")).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
710
710
|
const format = parseOutputFormat(options.format);
|
|
711
711
|
const raw = await client.call("indicator.cross-section", buildIndicatorCrossSectionBody(options));
|
|
712
712
|
await printData(flattenCrossSection(unwrapIndicatorData(raw), options.keyBy), format, options.output);
|
|
713
713
|
}));
|
|
714
|
-
indicator.command("time-series").option("--indicator <code>", "Indicator code, e.g. qte_close (repeat for multiple)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH (repeat
|
|
714
|
+
indicator.command("time-series").option("--indicator <code>", "Indicator code, e.g. qte_close (repeat for multiple)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH, or a sector ID from 'gangtise reference sector-search' (repeat; union, deduped)", collectList, []).requiredOption("--start-date <date>", "Start date (yyyy-MM-dd)", dateArg("--start-date")).requiredOption("--end-date <date>", "End date (yyyy-MM-dd)", dateArg("--end-date")).option("--calendar-type <type>", "Calendar: ND=natural TD=trading WD=weekday (default TD)").option("--currency <code>", "Currency: DFT/CNY/HKD/USD/EUR/GBP/JPY/TWD/MOP/AUD (default DFT)").option("--scale <code>", "Scale: 0=个 3=千 4=万 6=百万 8=亿 9=十亿 (default 0)").option("--indicator-param <spec>", "Per-indicator param 'code:key=value', e.g. qte_close:adjustType=2 for 前复权 (repeat); read exact keys from 'indicator search'", collectList, []).addOption(new Option("--key-by <mode>", "Column key: name=display name (default) | code=indicatorCode/securityCode, unique & order-stable for batch mapping").choices(["name", "code"]).default("name")).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
715
715
|
const format = parseOutputFormat(options.format);
|
|
716
716
|
const raw = await client.call("indicator.time-series", buildIndicatorTimeSeriesBody(options));
|
|
717
717
|
await printData(flattenTimeSeries(unwrapIndicatorData(raw), options.keyBy), format, options.output);
|
|
718
718
|
}));
|
|
719
|
+
indicator.command("screener").description("Screen securities by an expression over indicator values (条件选股)").option("--indicator <spec>", "Bind a variable to an indicator, 'F1:code', e.g. F1:qte_mkt_cptl (repeat)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH, or a sector ID from 'gangtise reference sector-search' (repeat; union, deduped)", collectList, []).requiredOption("--expression <expr>", "Filter over the bound variables, e.g. 'F1 >= 800 && (F2 >= 20 && F2 <= 30)'; also supports contains/notcontains on string indicators").option("--date <date>", "Data date (yyyy-MM-dd); sent as each indicator's tradeDate unless it already has one", dateArg("--date")).option("--indicator-param <spec>", "Per-variable param 'F1:key=value', e.g. F1:scale=8 (repeat); read exact keys from 'indicator search'", collectList, []).addOption(new Option("--key-by <mode>", "Column key: name=display name (default) | code=indicatorCode").choices(["name", "code"]).default("name")).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
720
|
+
const format = parseOutputFormat(options.format);
|
|
721
|
+
const raw = await client.call("indicator.screener", buildIndicatorScreenerBody(options));
|
|
722
|
+
// Same payload shape as cross-section (one row per matched security, one
|
|
723
|
+
// column per indicator) with a `field` on each indicator entry.
|
|
724
|
+
await printData(flattenCrossSection(unwrapIndicatorData(raw), options.keyBy), format, options.output);
|
|
725
|
+
}));
|
|
719
726
|
program.addCommand(indicator);
|
|
720
727
|
const tool = new Command("tool").description("Research tool APIs: PDF parsing");
|
|
721
728
|
tool.command("file-parse").description("Parse a PDF into Markdown + images (async; 0.8 credits/page, billed at submit)")
|
package/dist/src/core/args.js
CHANGED
|
@@ -247,29 +247,70 @@ export function localDateString(d) {
|
|
|
247
247
|
const day = String(d.getDate()).padStart(2, "0");
|
|
248
248
|
return `${d.getFullYear()}-${month}-${day}`;
|
|
249
249
|
}
|
|
250
|
-
// Parse repeatable
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return undefined;
|
|
250
|
+
// Parse repeatable `"<lhs>:key=value"` specs into per-lhs parameter lists.
|
|
251
|
+
// The left-hand side is an indicator code for cross-section / time-series and a
|
|
252
|
+
// screener variable (F1, F2…) for the screener, so it stays an opaque string
|
|
253
|
+
// here. Multiple specs for the same lhs accumulate, first-seen order.
|
|
254
|
+
function parseParamSpecs(specs, option, syntax) {
|
|
256
255
|
const groups = new Map();
|
|
257
256
|
for (const spec of specs) {
|
|
258
257
|
const colon = spec.indexOf(":");
|
|
259
258
|
const rest = colon === -1 ? "" : spec.slice(colon + 1);
|
|
260
259
|
const eq = rest.indexOf("=");
|
|
261
|
-
const
|
|
260
|
+
const lhs = colon === -1 ? "" : spec.slice(0, colon).trim();
|
|
262
261
|
const paramKey = eq === -1 ? "" : rest.slice(0, eq).trim();
|
|
263
262
|
const paramValue = eq === -1 ? "" : rest.slice(eq + 1).trim();
|
|
264
|
-
if (!
|
|
265
|
-
throw new ValidationError(`Invalid
|
|
263
|
+
if (!lhs || !paramKey) {
|
|
264
|
+
throw new ValidationError(`Invalid ${option}: expected "${syntax}", got "${spec}"`);
|
|
266
265
|
}
|
|
267
|
-
let
|
|
268
|
-
if (!
|
|
269
|
-
|
|
270
|
-
groups.set(
|
|
266
|
+
let params = groups.get(lhs);
|
|
267
|
+
if (!params) {
|
|
268
|
+
params = [];
|
|
269
|
+
groups.set(lhs, params);
|
|
270
|
+
}
|
|
271
|
+
params.push({ paramKey, paramValue });
|
|
272
|
+
}
|
|
273
|
+
return groups;
|
|
274
|
+
}
|
|
275
|
+
// Parse repeatable `--indicator-param "code:key=value"` specs into the nested
|
|
276
|
+
// indicatorParamList the EDE cross-section / time-series endpoints expect.
|
|
277
|
+
export function parseIndicatorParams(specs) {
|
|
278
|
+
if (specs.length === 0)
|
|
279
|
+
return undefined;
|
|
280
|
+
return [...parseParamSpecs(specs, "--indicator-param", "code:key=value")].map(([indicatorCode, parameters]) => ({ indicatorCode, parameters }));
|
|
281
|
+
}
|
|
282
|
+
/** Screener variables are `F` + a positive integer; the server rejects anything
|
|
283
|
+
* else, and a typo'd variable would otherwise surface as an opaque expression
|
|
284
|
+
* error rather than pointing at the binding that is wrong. */
|
|
285
|
+
const SCREENER_FIELD = /^F[1-9][0-9]*$/;
|
|
286
|
+
// Parse the screener's `--indicator "F1:code"` bindings and merge each one's
|
|
287
|
+
// `--indicator-param "F1:key=value"` specs. Params key off the variable, not the
|
|
288
|
+
// code, because the same indicator may legitimately appear under two variables
|
|
289
|
+
// with different parameters (e.g. the same price on two dates).
|
|
290
|
+
export function parseScreenerIndicators(bindings, paramSpecs) {
|
|
291
|
+
const params = parseParamSpecs(paramSpecs, "--indicator-param", "F1:key=value");
|
|
292
|
+
const indicators = new Map();
|
|
293
|
+
for (const spec of bindings) {
|
|
294
|
+
const colon = spec.indexOf(":");
|
|
295
|
+
const field = colon === -1 ? "" : spec.slice(0, colon).trim();
|
|
296
|
+
const indicatorCode = colon === -1 ? "" : spec.slice(colon + 1).trim();
|
|
297
|
+
if (!field || !indicatorCode) {
|
|
298
|
+
throw new ValidationError(`Invalid --indicator: expected "F1:code", got "${spec}"`);
|
|
299
|
+
}
|
|
300
|
+
if (!SCREENER_FIELD.test(field)) {
|
|
301
|
+
throw new ValidationError(`Invalid --indicator variable "${field}": expected F followed by a positive integer, e.g. F1`);
|
|
302
|
+
}
|
|
303
|
+
if (indicators.has(field)) {
|
|
304
|
+
throw new ValidationError(`Duplicate --indicator variable "${field}": each variable must bind exactly one indicator`);
|
|
305
|
+
}
|
|
306
|
+
indicators.set(field, { field, indicatorCode, parameters: params.get(field) ?? [] });
|
|
307
|
+
}
|
|
308
|
+
// A param for an unbound variable is silently dropped by the server, so the
|
|
309
|
+
// query would run with a filter the caller believes is applied but is not.
|
|
310
|
+
for (const field of params.keys()) {
|
|
311
|
+
if (!indicators.has(field)) {
|
|
312
|
+
throw new ValidationError(`--indicator-param references "${field}", which no --indicator binds`);
|
|
271
313
|
}
|
|
272
|
-
group.parameters.push({ paramKey, paramValue });
|
|
273
314
|
}
|
|
274
|
-
return [...
|
|
315
|
+
return [...indicators.values()];
|
|
275
316
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { maybeArray, parseFrom, parseIndicatorParams, parseOptionalNumberOption, parseSize } from "./args.js";
|
|
1
|
+
import { maybeArray, parseFrom, parseIndicatorParams, parseOptionalNumberOption, parseScreenerIndicators, parseSize } from "./args.js";
|
|
2
2
|
export function buildQuoteKlineBody(options) {
|
|
3
3
|
return {
|
|
4
4
|
securityList: maybeArray(options.security),
|
|
@@ -34,25 +34,61 @@ export function buildStockPoolStocksBody(options) {
|
|
|
34
34
|
poolIdList: options.poolId?.length ? options.poolId : ["all"],
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/** Parameter keys that carry an indicator's own query date. The 2026-08-01 EDE
|
|
38
|
+
* revision dropped the root-level `date`, so `--date` now fans out into each
|
|
39
|
+
* indicator's `tradeDate`. Report-period indicators read `reportDate`/`sDate`
|
|
40
|
+
* instead and answer a `tradeDate` with an EMPTY result rather than an error
|
|
41
|
+
* (probed 2026-08-01 on `is_op_rev_mom`), so a caller-supplied date of any of
|
|
42
|
+
* these kinds suppresses the injection for that indicator. Injecting into an
|
|
43
|
+
* indicator that takes no date at all is harmless — `pty_op_scope` still
|
|
44
|
+
* answers normally with a stray `tradeDate` attached. */
|
|
45
|
+
const DATE_PARAM_KEYS = new Set(["tradeDate", "reportDate", "sDate"]);
|
|
46
|
+
function withQueryDate(groups, codes, date) {
|
|
47
|
+
const merged = new Map();
|
|
48
|
+
for (const group of groups ?? [])
|
|
49
|
+
merged.set(group.indicatorCode, group);
|
|
50
|
+
for (const code of codes) {
|
|
51
|
+
const group = merged.get(code);
|
|
52
|
+
if (!group) {
|
|
53
|
+
merged.set(code, { indicatorCode: code, parameters: [{ paramKey: "tradeDate", paramValue: date }] });
|
|
54
|
+
}
|
|
55
|
+
else if (!group.parameters.some((param) => DATE_PARAM_KEYS.has(param.paramKey))) {
|
|
56
|
+
group.parameters.push({ paramKey: "tradeDate", paramValue: date });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return [...merged.values()];
|
|
60
|
+
}
|
|
37
61
|
export function buildIndicatorCrossSectionBody(options) {
|
|
38
62
|
return {
|
|
39
63
|
indicatorCodeList: maybeArray(options.indicator),
|
|
40
|
-
|
|
41
|
-
date: options.date,
|
|
64
|
+
universe: maybeArray(options.security),
|
|
42
65
|
currency: options.currency,
|
|
43
66
|
scale: options.scale,
|
|
44
|
-
indicatorParamList: parseIndicatorParams(options.indicatorParam),
|
|
67
|
+
indicatorParamList: withQueryDate(parseIndicatorParams(options.indicatorParam), options.indicator, options.date),
|
|
45
68
|
};
|
|
46
69
|
}
|
|
47
70
|
export function buildIndicatorTimeSeriesBody(options) {
|
|
48
71
|
return {
|
|
49
72
|
indicatorCodeList: maybeArray(options.indicator),
|
|
50
|
-
|
|
73
|
+
universe: maybeArray(options.security),
|
|
51
74
|
startDate: options.startDate,
|
|
52
75
|
endDate: options.endDate,
|
|
53
76
|
calendarType: options.calendarType,
|
|
54
77
|
currency: options.currency,
|
|
55
78
|
scale: options.scale,
|
|
56
|
-
|
|
79
|
+
// The endpoint requires the key even with nothing to configure.
|
|
80
|
+
indicatorParamList: parseIndicatorParams(options.indicatorParam) ?? [],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function buildIndicatorScreenerBody(options) {
|
|
84
|
+
const indicators = parseScreenerIndicators(options.indicator, options.indicatorParam);
|
|
85
|
+
return {
|
|
86
|
+
universe: maybeArray(options.security),
|
|
87
|
+
expression: options.expression,
|
|
88
|
+
indicatorList: options.date
|
|
89
|
+
? indicators.map((indicator) => (indicator.parameters.some((param) => DATE_PARAM_KEYS.has(param.paramKey))
|
|
90
|
+
? indicator
|
|
91
|
+
: { ...indicator, parameters: [...indicator.parameters, { paramKey: "tradeDate", paramValue: options.date }] }))
|
|
92
|
+
: indicators,
|
|
57
93
|
};
|
|
58
94
|
}
|
|
@@ -632,6 +632,15 @@ const ENDPOINT_DEFS = {
|
|
|
632
632
|
description: "Get time-series data (multi-indicator x single-security OR single-indicator x multi-security)",
|
|
633
633
|
retry: "no-999999",
|
|
634
634
|
},
|
|
635
|
+
// Note the path: the screener sits directly under open-indicator, NOT under
|
|
636
|
+
// the EDE/ prefix its three siblings share.
|
|
637
|
+
"indicator.screener": {
|
|
638
|
+
method: "POST",
|
|
639
|
+
path: "/application/open-indicator/screener",
|
|
640
|
+
kind: "json",
|
|
641
|
+
description: "Screen securities by an expression over indicator values (条件选股)",
|
|
642
|
+
retry: "no-999999",
|
|
643
|
+
},
|
|
635
644
|
// ─── tool (open-tool: async file parsing) ───
|
|
636
645
|
"tool.file-parse.submit": {
|
|
637
646
|
method: "POST",
|
|
@@ -31,23 +31,32 @@ export function unwrapIndicatorData(raw) {
|
|
|
31
31
|
function asStringArray(value) {
|
|
32
32
|
return Array.isArray(value) ? value.map((item) => String(item)) : undefined;
|
|
33
33
|
}
|
|
34
|
+
function asIndicatorMetaList(value) {
|
|
35
|
+
if (!Array.isArray(value))
|
|
36
|
+
return undefined;
|
|
37
|
+
return value.map((item) => (item && typeof item === "object" && !Array.isArray(item) ? item : {}));
|
|
38
|
+
}
|
|
39
|
+
function optionalString(value) {
|
|
40
|
+
return value === undefined || value === null ? undefined : String(value);
|
|
41
|
+
}
|
|
34
42
|
function rowOf(values, index) {
|
|
35
43
|
const row = values[index];
|
|
36
44
|
return Array.isArray(row) ? row : undefined;
|
|
37
45
|
}
|
|
38
46
|
// Build one column header per series. Prefer the human-readable name; on a
|
|
39
|
-
// duplicate name append
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
// duplicate name append a disambiguator so a column is never silently
|
|
48
|
+
// overwritten. `reserved` pre-seeds the metadata column names, so a series
|
|
49
|
+
// literally named "date" / "security" / "name" gets suffixed rather than
|
|
50
|
+
// clobbering the metadata.
|
|
51
|
+
function buildHeaders(bases, suffixes, reserved) {
|
|
52
|
+
const used = new Set(reserved);
|
|
44
53
|
const headers = [];
|
|
45
|
-
for (let i = 0; i <
|
|
46
|
-
const base =
|
|
54
|
+
for (let i = 0; i < bases.length; i++) {
|
|
55
|
+
const base = bases[i] ?? suffixes[i] ?? `col${i}`;
|
|
47
56
|
let header = base;
|
|
48
57
|
let attempt = 1;
|
|
49
58
|
while (used.has(header)) {
|
|
50
|
-
const suffix =
|
|
59
|
+
const suffix = suffixes[i] ?? i;
|
|
51
60
|
header = attempt === 1 ? `${base} (${suffix})` : `${base} (${suffix})_${attempt}`;
|
|
52
61
|
attempt++;
|
|
53
62
|
}
|
|
@@ -56,27 +65,40 @@ function buildHeaders(names, codes, count) {
|
|
|
56
65
|
}
|
|
57
66
|
return headers;
|
|
58
67
|
}
|
|
59
|
-
//
|
|
60
|
-
// `
|
|
61
|
-
// order
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
68
|
+
// Column headers for a list of indicator metadata. keyBy "code" makes each
|
|
69
|
+
// column its `code` (unique, and independent of the display name or the
|
|
70
|
+
// server's column order) instead of the human name — required for batch
|
|
71
|
+
// code→value mapping, where names collide (many indicators share a display
|
|
72
|
+
// name) and the server reorders columns relative to the request. The screener
|
|
73
|
+
// disambiguates by `field` instead, because there the SAME code may legitimately
|
|
74
|
+
// appear twice under two variables with different parameters.
|
|
75
|
+
function indicatorHeaders(indicators, keyBy, reserved) {
|
|
76
|
+
const codes = indicators.map((meta) => optionalString(meta.code));
|
|
77
|
+
const fields = indicators.map((meta, i) => optionalString(meta.field) ?? codes[i]);
|
|
78
|
+
const bases = keyBy === "code" ? codes : indicators.map((meta) => optionalString(meta.name));
|
|
79
|
+
return buildHeaders(bases, fields, reserved);
|
|
80
|
+
}
|
|
81
|
+
const CROSS_SECTION_COLUMNS = ["security", "name"];
|
|
82
|
+
// Cross-section (and the screener, whose payload is the same shape plus a
|
|
83
|
+
// per-indicator `field`): one row per security, one column per indicator.
|
|
84
|
+
// `values` is [security][indicator], so security i's value for indicator j is
|
|
85
|
+
// values[i][j]. There is no row-level date any more — the query date now lives
|
|
86
|
+
// in each indicator's own parameters and may legitimately differ per column.
|
|
66
87
|
export function flattenCrossSection(data, keyBy = "name") {
|
|
67
88
|
if (!data || typeof data !== "object")
|
|
68
89
|
return data;
|
|
69
90
|
const d = data;
|
|
70
91
|
const securityCode = asStringArray(d.securityCodeList);
|
|
71
|
-
const indicators =
|
|
92
|
+
const indicators = asIndicatorMetaList(d.indicatorList);
|
|
72
93
|
if (!Array.isArray(d.values) || !securityCode || !indicators)
|
|
73
94
|
return data;
|
|
74
95
|
const securityName = asStringArray(d.securityNameList);
|
|
75
|
-
const headers =
|
|
76
|
-
const list = securityCode.map((code,
|
|
77
|
-
const row = {
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
const headers = indicatorHeaders(indicators, keyBy, CROSS_SECTION_COLUMNS);
|
|
97
|
+
const list = securityCode.map((code, i) => {
|
|
98
|
+
const row = { security: code, name: securityName?.[i] };
|
|
99
|
+
const cells = rowOf(d.values, i);
|
|
100
|
+
for (let j = 0; j < headers.length; j++) {
|
|
101
|
+
row[headers[j]] = cells?.[j];
|
|
80
102
|
}
|
|
81
103
|
return row;
|
|
82
104
|
});
|
|
@@ -84,20 +106,24 @@ export function flattenCrossSection(data, keyBy = "name") {
|
|
|
84
106
|
}
|
|
85
107
|
// Time-series: one row per date. Columns are the indicators (single-security
|
|
86
108
|
// case) or the securities (single-indicator case) — exactly one dimension
|
|
87
|
-
// varies, per the API contract. `values`
|
|
109
|
+
// varies, per the API contract. `values` stays a 2D [series][date] matrix.
|
|
88
110
|
export function flattenTimeSeries(data, keyBy = "name") {
|
|
89
111
|
if (!data || typeof data !== "object")
|
|
90
112
|
return data;
|
|
91
113
|
const d = data;
|
|
92
114
|
const dates = asStringArray(d.dates);
|
|
93
115
|
const securityCode = asStringArray(d.securityCodeList);
|
|
94
|
-
const indicators =
|
|
116
|
+
const indicators = asIndicatorMetaList(d.indicatorList);
|
|
95
117
|
if (!Array.isArray(d.values) || !dates || !securityCode || !indicators)
|
|
96
118
|
return data;
|
|
119
|
+
const securityName = asStringArray(d.securityNameList);
|
|
97
120
|
const seriesAreIndicators = securityCode.length <= 1;
|
|
121
|
+
// Map over securityCode rather than securityNameList directly: a response that
|
|
122
|
+
// omits the names must still yield one header per security (falling back to
|
|
123
|
+
// the code), not zero columns.
|
|
98
124
|
const headers = seriesAreIndicators
|
|
99
|
-
?
|
|
100
|
-
: buildHeaders(keyBy === "code" ?
|
|
125
|
+
? indicatorHeaders(indicators, keyBy, ["date"])
|
|
126
|
+
: buildHeaders(securityCode.map((code, i) => (keyBy === "code" ? code : securityName?.[i])), securityCode, ["date"]);
|
|
101
127
|
const list = dates.map((date, k) => {
|
|
102
128
|
const row = { date };
|
|
103
129
|
for (let i = 0; i < headers.length; i++) {
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated — DO NOT EDIT
|
|
2
|
-
export const CLI_VERSION = "0.
|
|
2
|
+
export const CLI_VERSION = "0.30.0";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gangtise-openapi
|
|
3
|
-
version: "0.
|
|
3
|
+
version: "0.30.0"
|
|
4
4
|
description: |-
|
|
5
5
|
通过 gangtise CLI 直接调用 Gangtise OpenAPI,拉取投研原始数据、批量导出、下载文件、调用 AI 能力。
|
|
6
6
|
|
|
7
7
|
**触发词**:调接口 / CLI / openapi / 导出 / 下载研报 / 批量查 / 拉数据 / 跑一下 / 钢尼斯 / gtIC(Gangtise 语音误识别)
|
|
8
8
|
|
|
9
|
-
**适用**:原始数据导出、批量 jsonl/csv、下载 PDF/MD、行情 K 线、财务报表、估值指标、证券级数据指标(EDE
|
|
9
|
+
**适用**:原始数据导出、批量 jsonl/csv、下载 PDF/MD、行情 K 线、财务报表、估值指标、证券级数据指标(EDE 截面/时序/条件选股)、财报日历(业绩预告/快报/公告)、PDF 解析为 Markdown、AI 能力(一页通/投资逻辑/同业对比/个股看点·投研总结/投研线索/业绩点评/观点PK·多空辩论/主题跟踪/热点话题/管理层讨论/调研提纲/知识库搜索)、云盘文件管理(Vault)
|
|
10
10
|
|
|
11
11
|
**不适用**:不脱离 OpenAPI 自行撰写研报、编造投研结论或做自由问答——观点总结、多空 PK 等 AI 产物本 skill 只经由 Gangtise 平台 AI 接口获取,不自行生成
|
|
12
12
|
|
|
@@ -32,7 +32,7 @@ description: |-
|
|
|
32
32
|
7. **CLI 已内置自动化,不要手动复刻**:
|
|
33
33
|
- 翻页 → 首页拿 total 后剩余页并发拉取
|
|
34
34
|
- K 线 `--security all` 跨日期 → 自动按日切片并合并
|
|
35
|
-
- 5xx / `429` / 网络错误 / `999999` → 自动指数退避重试(🔴 贵档端点例外:仅连接失败 / 429 / token 自愈重试,5xx/超时不重放防重复扣分,v0.26.0;`indicator` 端点对 `999999`
|
|
35
|
+
- 5xx / `429` / 网络错误 / `999999` → 自动指数退避重试(🔴 贵档端点例外:仅连接失败 / 429 / token 自愈重试,5xx/超时不重放防重复扣分,v0.26.0;`indicator` 端点对 `999999` 不重试,v0.27.0)
|
|
36
36
|
- Token 失效(`0000001008` / `999002`,含已废弃的 `8000014`/`8000015`)→ 自动重新登录并重试一次;凭证错 `999011` → **不重试**(AK/SK 不对不会自己好),查环境变量
|
|
37
37
|
8. **参数命名差异**:Insight/Quote/Vault 用 `--security`,Fundamental/AI 用 `--security-code`(例外:`ai stock-summary` 用 `--security`,`ai security-clue` 用 `--gts-code`)。
|
|
38
38
|
9. **调试**:`--verbose` 或 `GANGTISE_VERBOSE=1` 打印每个请求的耗时/字节数到 stderr。
|
|
@@ -75,7 +75,7 @@ description: |-
|
|
|
75
75
|
- 🔴 **按次贵**:`ai knowledge-batch` 10、`management-discuss-*` 10;AI Agent(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `earnings-review` / `viewpoint-debate` / `theme-tracking`)**50/次**;`ai hot-topic` 50/篇
|
|
76
76
|
- 🔴 **极贵**:`alternative concept-info` / `concept-securities` **500/次**
|
|
77
77
|
- ⚠️ **同参数重复调用不免费**:按次计费无缓存命中豁免(2026-07-11 实测 `one-pager` 重复调用每次扣分,即使秒回缓存内容)——生成类结果拿到后自行留存复用,别为"刷新"重调;CLI 已对上述 🔴 贵档端点关闭 5xx/超时自动重放(v0.26.0),50/篇 的 `summary` / `foreign-report` / `my-conference` download 同样不重放(v0.27.0),正是为防重复扣分
|
|
78
|
-
- **按单元格**:`indicator cross-section` / `time-series`(A股 0.05 / 港股 0.1 / 美股 0.2 积分每 100
|
|
78
|
+
- **按单元格**:`indicator cross-section` / `time-series` / `screener`(A股 0.05 / 港股 0.1 / 美股 0.2 积分每 100 单元格;screener 按**筛选前**范围计费,见 `indicator.md`);`ai knowledge-resource-download` 按下游资源计费
|
|
79
79
|
|
|
80
80
|
### 下载规则(`--file-type` / `--content-type`)
|
|
81
81
|
|
|
@@ -149,6 +149,7 @@ description: |-
|
|
|
149
149
|
| 多证券已实现财务 / 估值指标搜索(含总市值) | `indicator search` |
|
|
150
150
|
| 多证券已实现指标截面(多指标 × 多证券,同一查询日期) | `indicator cross-section`(前置:`indicator search --format json` 通过三项校验) |
|
|
151
151
|
| 多证券已实现指标时序(单指标 × 多证券,按区间) | `indicator time-series`(前置:`indicator search --format json` 通过三项校验) |
|
|
152
|
+
| 条件选股 / 按指标筛股票(市值+PE+经营范围等多条件组合) | `indicator screener`(前置:`indicator search` 拿 code;范围可传板块 ID,见 `reference sector-search`) |
|
|
152
153
|
| 证券代码 / gtsCode 搜索 | `reference securities-search` |
|
|
153
154
|
| 首席 ID / 分析师 ID 搜索 | `reference chiefs-search`(按姓名/机构/团队,用于 `insight opinion --chief`) |
|
|
154
155
|
| 机构 ID 搜索(内资券商/外资/牵头/观点机构) | `reference institution-search`(按机构名,用于 `--institution` / `--broker`;免费) |
|
|
@@ -171,7 +172,8 @@ description: |-
|
|
|
171
172
|
- EDE 取数前必须用 `search --format json` 同时核对:`indicatorName` + `description` 语义准确、`scopeList` 覆盖全部目标市场 / 证券类型、`parameterList` 必填参数与枚举可满足;`scopeList` 缺失 / `null` / 空或任一项不符,都视为无法证明覆盖并回退专用接口。专用接口也不覆盖目标市场时,说明当前不可用,不要硬调。`scopeList` 按指标各不相同,不能因 EDE 服务支持 A / 港 / 美股就假定某个指标三市场都覆盖
|
|
172
173
|
- `indicator search` 免费,`cross-section` / `time-series` 按单元格计费;除多证券批量的效率收益外,仍优先免费 / 低价的 `quote` 或 `fundamental`
|
|
173
174
|
- 行业 / 宏观指标(空调销量、社融等,无证券维度)走 `alternative edb-*`(EDB),不要与证券级 EDE 混用
|
|
174
|
-
- EDE 单元格级缺值返回 `null`
|
|
175
|
+
- EDE 单元格级缺值返回 `null` 且保留证券行;**整个查询无数据返回空表**(2026-08-01 起不再报 `999999`)——所以**参数写错也表现为空表**,拿到空表先查参数名和日期语义,别当成真没数据。日期语义按指标分三类:财务报表指标=报告期末(可为非交易日)、`finc_pe_ttm` 等日频估值=最新交易日、`finc_pb_mrq`(MRQ) 等=最近报告期末(交易日取 `null`);混合取数按各自有效日期分次 `cross-section` 再按证券合并,别塞进同一个 `--date`。详见 `references/commands/indicator.md`
|
|
176
|
+
- **EDE 指标参数名一律以 `indicator search --format json` 的 `parameterList` 为准**,不要凭记忆或照抄示例:服务端会改参数名(`adjustmentType` → `adjustType`,2026-08-01),**传错名是静默忽略、退回默认值**而不是报错——实测 `adjustmentType=3` 返回的是不复权价,看着正常实则错数据。`--indicator` / `--security` 支持板块 ID(`reference sector-search` 的 10 位 `sectorId`)。⚠️ 根级 `--scale` 会污染不声明 `scale` 的指标(`qte_close` 被 `--scale 8` 缩成 `0`),价格与金额混查时改用 `--indicator-param "code:scale=8"`
|
|
175
177
|
- "业绩点评"双义消歧:**检索已有**(研报/纪要里的业绩点评内容)走 `insight ... list --llm-tag earningsReview`(0.1/条);**AI 现生成**一份走 `ai earnings-review`(异步、50/次)。不确定问一句
|
|
176
178
|
|
|
177
179
|
## 公司名 → 证券代码
|
|
@@ -262,7 +264,7 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
|
|
|
262
264
|
| 错误码 | 含义 | CLI 行为 | Agent 是否介入 |
|
|
263
265
|
|--------|------|---------|--------------|
|
|
264
266
|
| ✅ `100003` | 参数值非法——**最宽的兜底码**:类型错、`limit` 越界都归这里。**msg 通常已指明字段**(如「请求体字段类型不匹配: size 期望类型 Integer」「limit 最小为 1,最大为 10000」),先读 msg 再猜 | — | 按 msg 指的字段改;msg 没指明才对照 `--help` 查枚举拼写,**不要重试同命令** |
|
|
265
|
-
| ✅ `999999` |
|
|
267
|
+
| ✅ `999999` | 系统错误。**`indicator`(EDE)2026-08-01 前用此码表示查询无数据,现已改为返回空数组**,所以这个码基本只剩真故障 | 普通端点自动重试 ×2;🔴 贵档与 `indicator` 端点不重试 | 确认参数无误仍报此码即服务端故障 |
|
|
266
268
|
| ✅ `410110` | **异步生成中**(HTTP 400,旧码未切)。新码 `140001`,CLI 两码都认 | 轮询视为 pending | 继续等 |
|
|
267
269
|
| ✅ `410111` | **异步生成失败**(HTTP 400,旧码未切)。新码 `140002`,CLI 两码都认 | 终态 | **不重试**,换参数 |
|
|
268
270
|
| ✅ `130002` | 资源不存在——**下载类的兜底码**:`reportId` 不存在 / 非数字 / `fileType` 非法**全归这里**(`130003`/`130004`/`130005` 实测均未启用) | — | 确认 ID 有效且本账号可见;换 `--file-type` 或换一篇验证 |
|
|
@@ -372,7 +374,7 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
|
|
|
372
374
|
- knowledge-batch / security-clue / 个股看点(stock-summary)/ AI agent / 异步任务 / 主题跟踪 / 热点 / 管理层讨论 → `references/commands/ai.md`
|
|
373
375
|
- drive / record / my-conference / wechat / 股票池 → `references/commands/vault.md`
|
|
374
376
|
- 行业指标数据库(EDB)/ 题材指数画像与成分股(concept-info / concept-securities)→ `references/commands/alternative.md`
|
|
375
|
-
- 数据指标(EDE:search / cross-section / time-series
|
|
377
|
+
- 数据指标(EDE:search / cross-section / time-series / screener,证券级指标截面、时序与条件选股)→ `references/commands/indicator.md`
|
|
376
378
|
- PDF 解析(file-parse:上传 PDF → Markdown + 图片 ZIP)→ `references/commands/tool.md`
|
|
377
379
|
- securities-search / chiefs-search(首席 ID)/ institution-search(机构 ID)/ official-account-search(公众号 ID)/ 常量查询(constant-category / constant-list)/ 题材 ID(concept-search)/ 板块(sector-search / sector-constituents)/ lookup 本地表 / 行业别名 / raw call → `references/commands/reference-and-lookup.md`
|
|
378
380
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Indicator 命令详细参数(数据指标 EDE:证券级指标截面 /
|
|
1
|
+
# Indicator 命令详细参数(数据指标 EDE:证券级指标截面 / 时序 / 条件选股)
|
|
2
2
|
|
|
3
|
-
> 本组覆盖 `/application/open-indicator
|
|
3
|
+
> 本组覆盖 `/application/open-indicator/*`:证券级**数据指标**的检索与取数,主要用于多证券批量取已实现财务 / 估值指标。即使能搜到收盘价、成交量等行情指标,常规行情与 K 线仍走免费的 `quote`。
|
|
4
4
|
> 与 `alternative edb-*`(EDB 行业/宏观指标,无证券维度)是两套接口,别混。
|
|
5
5
|
>
|
|
6
|
-
> **取数前先 `indicator search` 拿 `indicatorCode
|
|
6
|
+
> **取数前先 `indicator search` 拿 `indicatorCode` 和 `parameterList`**,绝不猜测指标编码或参数名——服务端调过参数名(`adjustmentType` → `adjustType`),传错名是**静默失效**不是报错。
|
|
7
7
|
|
|
8
8
|
## EDE 与专用接口的优先级
|
|
9
9
|
|
|
@@ -27,8 +27,10 @@ gangtise indicator search --keyword <text> [--limit <n>]
|
|
|
27
27
|
- `--keyword`(**必选**):按指标名称模糊匹配。用具体词,如 `营业收入` / `基本每股收益` / `市盈率` / `总市值`,**不能用整句白话**("我想查一批公司的财务估值" ✗)
|
|
28
28
|
- `--limit`:返回条数上限,默认 50,最大 100
|
|
29
29
|
- 默认 `--format table` 只适合浏览名称;正式路由 / 取数前必须加 `--format json`,才能完成语义、`scopeList`、`parameterList` 三项校验
|
|
30
|
-
- 返回字段:`indicatorCode` / `indicatorName` / `description`(算法与口径)/ `scopeList`(该指标适用的市场 +
|
|
31
|
-
- **市场范围按指标判断**:`scopeList`
|
|
30
|
+
- 返回字段:`indicatorCode` / `indicatorName` / `description`(算法与口径)/ `scopeList`(该指标适用的市场 + 证券类型 + `usageRestriction`)/ `parameterList`(可传的 `--indicator-param` 参数名及枚举)/ `score`
|
|
31
|
+
- **市场范围按指标判断**:`scopeList` 会返回实际覆盖范围,且指标之间不同;不能笼统写成每个指标都覆盖 A / 港 / 美股。实测 `finc_pe_ttm` / `finc_pb_mrq` 仅 A 股,`is_op_rev_ttm` / `is_shnp_ttm` 覆盖 A 股 + 港股(2026-08-01 新增港股,实测有数)。目标列表含任一 scope 外证券时,本批 EDE 校验不通过,应回退专用接口
|
|
32
|
+
- **`scopeList[].usageRestriction`**:该指标在具体接口上的限制,`null` = 无限制。如「不支持指标时间序列接口」意味着只能用 `cross-section` / `screener`
|
|
33
|
+
- ⚠️ **`scopeList` 是声明不是保证**:`qte_mkt_cptl`/`shr_tot` 声称覆盖港股,实测港股仍返 `null`(2026-08-01)。批量前抽查一行
|
|
32
34
|
- 美股代码用交易所后缀 `.O`(NASDAQ) / `.N`(NYSE),**不是 `.US`**——实测 `AAPL.US` 查不到数据,须用 `AAPL.O`(官方示例里的 `AAPL.US` 是笔误)
|
|
33
35
|
|
|
34
36
|
```bash
|
|
@@ -44,13 +46,15 @@ gangtise indicator cross-section --indicator <code> [--indicator <code2>] \
|
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
- `--indicator`(**至少 1 个**):指标编码,来自 `search`,可重复传多个
|
|
47
|
-
- `--security`(**至少 1 个**):证券代码,如 `600519.SH`(A股)/ `09992.HK`(港股)/ `AAPL.O`(美股,用 `.O`/`.N` 后缀,非 `.US
|
|
48
|
-
- `--date`(**必选**):数据日期 `yyyy-MM-dd
|
|
49
|
-
-
|
|
49
|
+
- `--security`(**至少 1 个**):证券代码,如 `600519.SH`(A股)/ `09992.HK`(港股)/ `AAPL.O`(美股,用 `.O`/`.N` 后缀,非 `.US`),可重复传多个。**也接受板块 ID**(`reference sector-search` 返回的 10 位 `sectorId`,如 `1000000287` 中信白酒 → 19 只成分股),代码与板块可混传,服务端取并集去重。⚠️ 中信行业码那类 9 位 ID(`100800109`)**不是** `sectorId`,传进去返 0 只
|
|
50
|
+
- `--date`(**必选**):数据日期 `yyyy-MM-dd`。**CLI 把它下发为每个指标各自的 `tradeDate`**(2026-08-01 起服务端取消了根级 date)。日期语义按指标分三类——财务报表指标=报告期末(可为非交易日,实测 `2024-03-31` 可取数)、`finc_pe_ttm` 等日频估值=交易日、`finc_pb_mrq`(MRQ) 等=最近报告期末(交易日取 `null`,详见下方「日期路由」)
|
|
51
|
+
- ⚠️ **吃 `reportDate` / `sDate` 的指标必须显式传**:`--indicator-param "code:reportDate=2024-12-31"`。这类指标收到 `tradeDate` 会**静默返回空结果**(不报错,实测 `is_op_rev_mom`)。CLI 检测到你已为某指标传了 `tradeDate`/`reportDate`/`sDate` 就不再注入 `--date`
|
|
52
|
+
- `--currency`:币种 `DFT`(原始,默认)/`CNY`/`HKD`/`USD`/`EUR`/`GBP`/`JPY`/`TWD`/`MOP`/`AUD`(**大写**,2026-08-01 起服务端枚举已统一大写)
|
|
50
53
|
- `--scale`:量纲 `0`(个,默认)/`3`(千)/`4`(万)/`6`(百万)/`8`(亿)/`9`(十亿)
|
|
54
|
+
- ⚠️ **根级 `--scale` 会污染不支持 scale 的指标**:`qte_close` 的 `parameterList` 里没有 `scale`,但根级 `--scale 8` 会把收盘价 1350.6 缩成 `0`(实测 2026-08-01,与文档「根级参数仅对支持的指标生效」不符)。价格类和金额类指标混查时,别用根级 `--scale`,改用 `--indicator-param "code:scale=8"` 只作用于该指标
|
|
51
55
|
- **支持多指标 × 多证券**(单日横截面)
|
|
52
|
-
- **输出(宽表)**:每行一只证券,列为 `
|
|
53
|
-
- **`--key-by name|code`**(默认 `name`):指标列头用显示名还是 `indicatorCode`。**批量按 code 回填必用 `--key-by code`**——指标名会碰撞(多个指标同显示名,如 `cf_finc_exp`/`_qtr`
|
|
56
|
+
- **输出(宽表)**:每行一只证券,列为 `security / name / <各指标名>…`。**没有 `date` 列**——查询日期现在挂在每个指标自己的参数上,各列可以是不同日期
|
|
57
|
+
- **`--key-by name|code`**(默认 `name`):指标列头用显示名还是 `indicatorCode`。**批量按 code 回填必用 `--key-by code`**——指标名会碰撞(多个指标同显示名,如 `cf_finc_exp`/`_qtr` 都叫「财务费用」)、服务端还会重排返回列序(实测请求 `qte_close,qte_vol` 回来是 `qte_vol,qte_close`),唯有 code 唯一且与顺序无关(行轴 `security` 本就是 code,`code` 模式整表可按 code 寻址,免去 raw API 手工回填)
|
|
54
58
|
|
|
55
59
|
```bash
|
|
56
60
|
# 多证券 × 同一报告期的已实现财务指标
|
|
@@ -58,8 +62,8 @@ gangtise indicator cross-section \
|
|
|
58
62
|
--indicator is_op_rev --indicator is_eps_bas \
|
|
59
63
|
--security 600519.SH --security 000858.SZ --security 300750.SZ \
|
|
60
64
|
--date 2025-12-31 --format table
|
|
61
|
-
# 列:
|
|
62
|
-
# 省略 reportType 即取合并口径(茅台2025=1688
|
|
65
|
+
# 列:security / name / 营业收入(利润表,累计) / 基本每股收益(利润表,累计)
|
|
66
|
+
# 省略 reportType 即取合并口径(茅台2025=1688亿);口径映射见下方「reportType 口径」
|
|
63
67
|
```
|
|
64
68
|
|
|
65
69
|
## 指标时间序列 `indicator time-series`
|
|
@@ -70,12 +74,13 @@ gangtise indicator time-series --indicator <code> [--indicator <code2>] \
|
|
|
70
74
|
[--calendar-type <ND|TD|WD>] [--currency <c>] [--scale <s>] [--indicator-param <spec>] [--key-by name|code]
|
|
71
75
|
```
|
|
72
76
|
|
|
73
|
-
- `--indicator` / `--security`:同上,但**只允许「多指标 × 单证券」或「单指标 × 多证券」**,不能两边都多个(要多 × 多用 `cross-section`,否则报 `410001
|
|
74
|
-
- `--start-date` / `--end-date`(**均必选**):区间端点 `yyyy-MM-dd`
|
|
75
|
-
- `--calendar-type`:日期类型 `ND`(自然日)/`TD`(交易日,默认)/`WD`(工作日)
|
|
76
|
-
- `--currency` / `--scale`:同 `cross-section`
|
|
77
|
+
- `--indicator` / `--security`:同上,但**只允许「多指标 × 单证券」或「单指标 × 多证券」**,不能两边都多个(要多 × 多用 `cross-section`,否则报 `410001`)。**单指标时 `--security` 才能传板块 ID**;多指标时只能传一个证券代码、不得传板块 ID
|
|
78
|
+
- `--start-date` / `--end-date`(**均必选**):区间端点 `yyyy-MM-dd`。时序的时间范围由这两个参数统管,**不要**再用 `--indicator-param` 传 `tradeDate` 这类单日期参数
|
|
79
|
+
- `--calendar-type`:日期类型 `ND`(自然日)/`TD`(交易日,默认)/`WD`(工作日)。`TD` 且跨市场时,`date` 列是各市场交易日的**并集**
|
|
80
|
+
- `--currency` / `--scale`:同 `cross-section`(含根级 `--scale` 的污染坑)
|
|
77
81
|
- **输出(宽表)**:每行一个日期,列为 `date / <各序列名>…`;序列在「单证券」时是各**指标**,在「多证券」时是各**证券**
|
|
78
82
|
- **`--key-by name|code`**(默认 `name`):同 `cross-section`;`code` 模式下单证券列=各 `indicatorCode`、多证券列=各 `securityCode`,批量按 code 回填用它
|
|
83
|
+
- ⚠️ 部分指标**不支持时序接口**:`search` 返回的 `scopeList[].usageRestriction` 会写明(如「不支持指标时间序列接口」),`null` 表示无限制。取数前顺手看一眼,省得白花积分
|
|
79
84
|
|
|
80
85
|
```bash
|
|
81
86
|
# 单个已实现估值指标 × 多证券:列 = 证券
|
|
@@ -85,31 +90,66 @@ gangtise indicator time-series --indicator finc_pe_ttm \
|
|
|
85
90
|
# date 贵州茅台 五粮液 宁德时代
|
|
86
91
|
```
|
|
87
92
|
|
|
93
|
+
## 条件选股 `indicator screener`
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
gangtise indicator screener --indicator <F1:code> [--indicator <F2:code2>] \
|
|
97
|
+
--security <code|sectorId> [--security <code2>] --expression <expr> \
|
|
98
|
+
[--date <yyyy-MM-dd>] [--indicator-param <F1:key=value>] [--key-by name|code]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
按指标条件从一个证券范围里筛出符合条件的股票,一次调用完成「取数 + 过滤」,比 `cross-section` 拉全量再本地筛更省事。
|
|
102
|
+
|
|
103
|
+
- `--indicator`(**至少 1 个**):把变量绑定到指标,格式 `F1:指标code`。变量必须是 `F` + 正整数、同一次请求内不重复;建议从 `F1` 开始连续编号
|
|
104
|
+
- `--security`(**至少 1 个**):同 `cross-section`,证券代码与板块 ID 可混传取并集
|
|
105
|
+
- `--expression`(**必选**):筛选表达式,引用上面绑定的变量
|
|
106
|
+
- 比较:`==` `>` `<` `>=` `<=` `!=`
|
|
107
|
+
- 文本:`contains` / `notcontains`(不区分大小写,**仅对 `dataType: string` 的指标有效**)
|
|
108
|
+
- 逻辑:`&&` `||`,分组 `(` `)`
|
|
109
|
+
- `--date`:同 `cross-section`,下发为每个指标的 `tradeDate`(已自带日期参数的指标不覆盖)
|
|
110
|
+
- `--indicator-param`:格式是 **`F1:key=value`(按变量,不是按 code)**——同一个指标 code 允许绑到两个变量取不同参数(如比较两个日期的收盘价),只有变量能区分。引用了没绑定的变量会直接报错,不会静默丢弃
|
|
111
|
+
- **输出(宽表)**:同 `cross-section`,每行一只**命中**的证券,列为 `security / name / <各指标名>…`;无命中返回空表
|
|
112
|
+
- **积分**:与 `cross-section` 同价(按单元格计),但计费基数是**筛选前**的范围 × 指标数,别拿全市场板块随手试
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# 中信白酒板块里,市值≥500亿 且 PE(TTM)≤30
|
|
116
|
+
gangtise indicator screener \
|
|
117
|
+
--indicator F1:qte_mkt_cptl --indicator F2:finc_pe_ttm \
|
|
118
|
+
--indicator-param "F1:scale=8" \
|
|
119
|
+
--security 1000000287 \
|
|
120
|
+
--expression "F1 >= 500 && F2 <= 30" \
|
|
121
|
+
--date 2026-07-31 --format table
|
|
122
|
+
# security / name / 总市值 / 市盈率(TTM) —— 19 只成分股筛出 5 只
|
|
123
|
+
```
|
|
124
|
+
|
|
88
125
|
## 复权 / 指标专属参数 `--indicator-param`
|
|
89
126
|
|
|
90
|
-
通用的币种/量纲用 `--currency` / `--scale`;指标**专属**参数用 `--indicator-param`,格式 `指标code:参数key
|
|
127
|
+
通用的币种/量纲用 `--currency` / `--scale`;指标**专属**参数用 `--indicator-param`,格式 `指标code:参数key=值`,可重复(screener 例外,按变量索引:`F1:key=值`)。下面的行情复权仅演示底层参数语法;常规行情 / K 线仍优先 `quote`,不要照此例改走 EDE:
|
|
91
128
|
|
|
92
129
|
```bash
|
|
93
|
-
# 茅台收盘价后复权(
|
|
130
|
+
# 茅台收盘价后复权(adjustType=3)
|
|
94
131
|
gangtise indicator cross-section --indicator qte_close --security 600519.SH \
|
|
95
|
-
--date
|
|
96
|
-
# 不复权
|
|
132
|
+
--date 2024-01-02 --indicator-param "qte_close:adjustType=3"
|
|
133
|
+
# 不复权 1685.01 → 前复权 1531.225 → 后复权 13609.6168
|
|
97
134
|
```
|
|
98
135
|
|
|
99
|
-
- `
|
|
136
|
+
- `adjustType`(复权方式):`1`=不复权(默认) `2`=前复权 `3`=后复权 `4`=定点复权(配 `baseDate` 基期)
|
|
137
|
+
- ⚠️ **参数名是 `adjustType`,不是 `adjustmentType`**(官方文档示例写的 `adjustmentType` 是错的)。传错名服务端**静默忽略并退回不复权**——实测 `adjustmentType=3` 返回 1685.01(= 不复权),看着正常其实是错数据
|
|
138
|
+
- 前复权以最新交易日为基准,所以在**最新交易日上前复权价 == 不复权价**;验证复权是否生效要用历史日期
|
|
100
139
|
- 同一指标多个参数 → 重复 `--indicator-param "code:k1=v1" --indicator-param "code:k2=v2"`
|
|
101
|
-
-
|
|
140
|
+
- **参数名与取值一律以 `indicator search --format json` 的 `parameterList` 为准**,不要照抄本文档或官方文档的示例名——服务端会调整参数名(`adjustmentType` → `adjustType` 就是一例),且传错名是静默失效而非报错。币种枚举 2026-08-01 起**统一大写**(`DFT`/`CNY`/`HKD`…),历史文档里的小写 `dft`/`cny` 已过时
|
|
102
141
|
- `--indicator-param` 与根级 `--currency`/`--scale` 冲突时,以 `--indicator-param` 为准
|
|
103
142
|
|
|
104
143
|
## 必填参数与错误码(取数前必读)
|
|
105
144
|
|
|
106
|
-
截面/时序**单元格级缺值返回 `null
|
|
145
|
+
截面/时序**单元格级缺值返回 `null`**(证券行保留、不丢行)。**整个查询无数据现在返回空数组**(`securityCodeList`/`values` 皆为 `[]`,CLI 显示 `Total: 0`),不再像 2026-07 那样报 `999999` + HTTP 500 —— 这也意味着**参数写错的失败会以「空表」而不是报错的形式出现**,拿到空表先怀疑日期语义 / 参数名,别当成「确实没数据」。取数报错主要是这几个码:
|
|
107
146
|
|
|
108
147
|
| 错误码 | 实际含义 | 怎么办 |
|
|
109
148
|
| :--- | :--- | :--- |
|
|
110
|
-
| `
|
|
111
|
-
|
|
|
112
|
-
| `
|
|
149
|
+
| `100001` | **缺少必填参数**:如 `universe` 没传 | 补齐 `--indicator`/`--security` |
|
|
150
|
+
| `410001` | 入参错误:`time-series` 传了「多指标 × 多证券」 | 多 × 多改用 `cross-section` |
|
|
151
|
+
| 缺参报错(曾为 `410106`) | **缺指标级必填参数**:服务端会直接指明缺哪个,如「指标 X 的必填参数 periodNum(期数) 不能为空」(仍以 HTTP 500 返回,CLI 重试 2 次后透出该消息) | 读 `search --format json` 的 `parameterList`,把 `required:true` 的参数用 `--indicator-param` 补上 |
|
|
152
|
+
| `999999` | 系统故障。2026-08-01 起「无数据」已改为返回空数组,所以这个码基本只剩真故障(2026-07-26 曾出现 EDE 取数端全线 999999,08-01 已恢复) | CLI 对 indicator 端点**不重试此码**(v0.27.0);确认参数无误仍报错就是服务端问题 |
|
|
113
153
|
| `130001`(旧 `410004`) | 数据未找到,或**该指标无权限**(内层信封失败会带具体 msg,如"指标无权限";此码被服务端复用) | 检查查询条件与指标权限;换证券/日期仍失败多为无权限,联系管理员开通 |
|
|
114
154
|
|
|
115
155
|
### 必填参数(`410106` 的根因)
|
|
@@ -126,7 +166,8 @@ gangtise indicator cross-section --indicator qte_close --security 600519.SH \
|
|
|
126
166
|
|
|
127
167
|
## 取数最佳实践
|
|
128
168
|
|
|
129
|
-
- **先 search 做三项校验**:看 `indicatorName` + `description` 确认语义和口径,看 `scopeList` 确认覆盖全部目标市场 /
|
|
169
|
+
- **先 search 做三项校验**:看 `indicatorName` + `description` 确认语义和口径,看 `scopeList` 确认覆盖全部目标市场 / 证券类型(并看 `usageRestriction` 有无接口限制),再看 `parameterList` 补齐必填参数(required)并核对专属参数**名称**与枚举(`adjustType`/`scale`/`currency` 等);任一不符就回退专用接口。**参数名以 search 返回为准,不要照抄文档示例**——服务端调过参数名,传错名是静默失效不是报错。
|
|
170
|
+
- **`scopeList` 声称覆盖 ≠ 真有数**:`qte_mkt_cptl`/`shr_tot` 的 `scopeList` 写着 A股+港股+美股,但实测港股(`09992.HK`/`00700.HK`)返回 `null`(2026-08-01)。同一次调用里 A 股正常、港股行情类(`qte_close`/`qte_vol`)也正常,说明是这两个指标的港股数据没到位。跨市场批量取数后**逐市场抽查一行**,别默认 scopeList 就是事实。
|
|
130
171
|
- **公司类型决定有没有这个科目**:财务科目分公司类型——银行有「存放同业」、券商有「客户资金存款」、保险有「预收保费」,一般企业没有。某指标对茅台返回 `null`(无此科目),换到对应类型证券(招行/中信/平安)就有数。
|
|
131
172
|
- **日期路由**:
|
|
132
173
|
- 财务报表类(`bs_`/`is_`/`cf_`/`div_`/`shr_`,以及 description 明确按报告期统计的 `finc_`)→ 用**报告期末**(Q1 `2026-03-31`、年报 `2025-12-31`,无需是交易日)
|
|
@@ -134,27 +175,28 @@ gangtise indicator cross-section --indicator qte_close --security 600519.SH \
|
|
|
134
175
|
- 现金流量表附注/间接法科目(多数 `cf_`)→ **只在年报/半年报披露**,季报日期取不到,改用年报日期 `2025-12-31`
|
|
135
176
|
- 行情类(`qte_` 等)→ 用**交易日**,但常规行情仍应改走 `quote`
|
|
136
177
|
- **混合日期语义要拆查询**:同时要“某报告期营收 / EPS”和“估值 PE / PB”时,按各自有效日期分别调用 `cross-section` 再按 `security` 合并(财务=报告期末、PE=最新交易日、PB(MRQ)=最近报告期末);不要把不同日期语义的指标塞进同一个 `--date`
|
|
137
|
-
- **探索性取数**:单元格级缺值返回 `null`
|
|
178
|
+
- **探索性取数**:单元格级缺值返回 `null` 且保留证券行,时序局部无值可为空行;**整个查询无数据返回空表**(不再报 `999999`)。看趋势用 `time-series` + 覆盖报告期的区间,但不能把缺值当成通过语义 / scope 校验。
|
|
138
179
|
- **名称反查 code 要核对,别取首条**:存在同显示名的兄弟指标——单季 `cf_finc_exp_qtr` 与累计 `cf_finc_exp` 都叫「财务费用」,`bs_fmt`/`cf_fmt`/`is_fmt` 都叫「报表格式」。`search` 按名称模糊匹配,目标 code 高概率在 top1 但不绝对,要看 `indicatorCode` 确认。
|
|
139
|
-
- **批量查询做失败拆分**:某指标**缺必填参数**或入参错误时会整批报错(单元格级无数据按 `null`
|
|
140
|
-
-
|
|
141
|
-
-
|
|
180
|
+
- **批量查询做失败拆分**:某指标**缺必填参数**或入参错误时会整批报错(单元格级无数据按 `null` 返回),逐指标单查能定位是哪个指标缺参/不可查。
|
|
181
|
+
- **市值量纲(复测 2026-08-01)**:`qte_mkt_cptl`(总市值)**实测仅 A 股有数**——`scopeList` 已声称覆盖港/美股,但港股实测仍返 `null`(`shr_tot` 总股本同样);**默认返原始「元」**(茅台 ≈ `1.7e12`,即 1.7 万亿),别误当天文数字。用 `scale` 数字码缩放(`0`元 / `3`千 / `4`万 / `6`百万 / `8`亿 / `9`十亿——`scale=8` → `16883` 亿元)、`currency` 换币种(**大写** `DFT`本币 / `CNY` / `HKD` / `USD` …)。**跨证券比市值前先统一 `scale`+`currency`**。
|
|
182
|
+
- **币种与汇率(复测 2026-08-01,已修复)**:`DFT`(原始币种)识别正确——A股=CNY、港股行情=HKD、美股=USD;汇率换算自洽(互逆且三角一致,误差 <0.003%)。⚠️ 但**同一只港股,行情类的原始币种是 HKD、财务类可能是 CNY**(如泡泡玛特财报以人民币计),跨市场比财务数据时显式传 `--currency CNY` 别依赖 `DFT`。另:财务类指标的汇率按**报告期**折算、行情类按查询日折算,两者隐含汇率会有细微差异,属正常口径差别。
|
|
183
|
+
- **EDE 财务指标的 `reportType`(2026-08-01 裁决完毕,悬案关闭)**:服务端 enum label 已改正,现在 **label 与实际取数一致**,直接按 label 传即可:
|
|
142
184
|
|
|
143
|
-
| value |
|
|
185
|
+
| value | 口径 | 说明 |
|
|
144
186
|
|-------|---------|------|
|
|
145
187
|
| `1`(默认,省略即此值) | **合并报表** | 绝大多数场景要的就是这个 |
|
|
146
188
|
| `2` | **合并报表(调整)** | 该报告期无重述数时为空 |
|
|
147
189
|
| `3` | **母公司报表** | |
|
|
148
190
|
| `4` | **母公司报表(调整)** | 该报告期无重述数时为空 |
|
|
149
191
|
|
|
150
|
-
|
|
192
|
+
实测闭环(`is_tot_op_rev` + 中信证券 `600030.SH` FY2024 营业总收入,2026-08-01):`1`→637.8922亿、`2`→581.19亿、`3`→321.924亿、`4`→321.924亿;与 `fundamental income-statement --security-code 600030.SH --fiscal-year 2024 --period annual` 返回的「合并报表」`totalOpRev = 63,789,215,688.23`(637.892亿)在 `1` 上完全吻合。取数值与 2026-07-24 的历史实测一致——**变的只是服务端 label,取数从未变过**。历史文档里「label 与 value 错位、按 label 传会取反」的警告已作废。
|
|
151
193
|
|
|
152
|
-
`2`/`4` 返回空值**不是枚举失效**,是该报告期尚无调整表(如最新年报 FY2025:`1`/`3` 有数、`2`/`4` 无值,与 `fundamental --report-type consolidatedRestated`
|
|
194
|
+
`2`/`4` 返回空值**不是枚举失效**,是该报告期尚无调整表(如最新年报 FY2025:`1`/`3` 有数、`2`/`4` 无值,与 `fundamental --report-type consolidatedRestated` 同期无数据一致)。港股默认口径同样是合并。
|
|
153
195
|
|
|
154
196
|
## 通用说明
|
|
155
197
|
|
|
156
|
-
- **发现流程**:`indicator search --format json` → 核对 `indicatorName` + `description`、`scopeList
|
|
157
|
-
- **积分**:`search` 免费;`cross-section` / `time-series` 按请求单元格数量计费,标价为每 100 单元格 A 股 0.05 / 港股 0.1 / 美股 0.2 积分,每次查询不足 100 单元格按 100 计
|
|
158
|
-
-
|
|
198
|
+
- **发现流程**:`indicator search --format json` → 核对 `indicatorName` + `description`、`scopeList`(含 `usageRestriction`)、`parameterList`(**参数名以此为准**)→ 三项都通过才用 `cross-section` / `time-series` / `screener`
|
|
199
|
+
- **积分**:`search` 免费;`cross-section` / `time-series` / `screener` 按请求单元格数量计费,标价为每 100 单元格 A 股 0.05 / 港股 0.1 / 美股 0.2 积分,每次查询不足 100 单元格按 100 计
|
|
200
|
+
- **空结果排查顺序**:整查询无数据现在返回空表而非报错,所以空表既可能是真无数据、也可能是参数写错。按序排查:① 参数名对不对(`indicator search` 的 `parameterList`)② 日期语义对不对(`tradeDate` vs `reportDate`)③ `scopeList` 覆不覆盖该市场 ④ 才考虑确实没数据
|
|
159
201
|
- **数据权限**:试用账号默认可取近 3 年;正式账号按服务等级
|
|
160
202
|
- 所有格式(table/json/jsonl/csv/markdown)均可用;导出宽表给 Excel 直接用 `--format csv --output xxx.csv`
|
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
- scopeList:覆盖全部三只 A 股;缺失/null/空也视为不通过
|
|
242
242
|
- parameterList:补 required 参数并核对枚举
|
|
243
243
|
任一不符 → 回退相应专用接口。
|
|
244
|
-
3. 三类指标日期语义不同 → 拆三次截面,均加 `--key-by code`(列头用 indicatorCode,跨三张表按 code 稳定合并、免受同名/服务端重排干扰;省略 reportType
|
|
244
|
+
3. 三类指标日期语义不同 → 拆三次截面,均加 `--key-by code`(列头用 indicatorCode,跨三张表按 code 稳定合并、免受同名/服务端重排干扰;省略 reportType 即取合并口径,label 与取数已一致:1=合并 2=合并(调整) 3=母公司 4=母公司(调整)):
|
|
245
245
|
a) 财务(营收/EPS)用报告期末 2025-12-31:
|
|
246
246
|
gangtise indicator cross-section \
|
|
247
247
|
--indicator is_op_rev --indicator is_eps_bas \
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
--date 2026-03-31 --key-by code --format json
|
|
258
258
|
4. 按 security 合并三张宽表(列头即 indicatorCode,各取所需日期的值);不要把不同日期语义的指标塞进同一个 --date。
|
|
259
259
|
5. 计费:search 免费;三次取数各按请求单元格数量计费,每次不足 100 单元格按 100 计。
|
|
260
|
-
6. 无数据:单元格缺值返回 null
|
|
260
|
+
6. 无数据:单元格缺值返回 null 且不丢证券行;整个查询无数据返回空表(不再报 999999)——空表也可能是参数名/日期语义写错,先核对 parameterList、日期语义、scopeList 和公司类型。
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
## 例 16:A 股资金流向(个股 vs 全市场按日分片)
|
|
@@ -82,7 +82,8 @@ CLI 自动处理 envelope:`{code, msg, data}` 信封会按 `code === "000000"`
|
|
|
82
82
|
| alternative edb-data | 列表,每行 `{date, <indicatorId>: value, ...}` 宽表 | `date` + 每个 `--indicator-id` 一列(该日指标值) |
|
|
83
83
|
| alternative concept-info | `{conceptId, conceptName, ...}`(单对象,**非列表**) | `conceptName` / `definition` / `investmentLogic` / `industrySpace` / `competitiveLandscape` / `keyEvents[].date` / `keyEvents[].content`;文本字段未配置为 `null` |
|
|
84
84
|
| alternative concept-securities | `{conceptId, conceptName, securityCount, securityDetail}`(单对象,分组) | `securityCount` / `securityDetail[].groupName` / `securityDetail[].securityList[].securityCode` / `.securityName` / `.isKey` / `.inclusionReason`;无成分股时 `securityDetail` 为 `null` |
|
|
85
|
-
| indicator search | `[{indicatorCode, indicatorName, ...}]`(列表) | `indicatorCode` / `indicatorName` / `description` / `scopeList[].market` / `scopeList[].securityType` / `parameterList[].paramKey
|
|
86
|
-
| indicator cross-section | CLI 拍平为宽表 `{list, total}` | `list[].
|
|
87
|
-
| indicator time-series | CLI 拍平为宽表 `{list, total}` | `list[].date` +
|
|
85
|
+
| indicator search | `[{indicatorCode, indicatorName, ...}]`(列表) | `indicatorCode` / `indicatorName` / `description` / `scopeList[].market` / `scopeList[].securityType` / `scopeList[].usageRestriction`(接口限制,`null`=无限制) / `parameterList[].paramKey`(**参数名以此为准**) / `.enumList[].value` / `score` |
|
|
86
|
+
| indicator cross-section | CLI 拍平为宽表 `{list, total}` | `list[].security` / `list[].name` + 每个指标名一列;**单日多指标 × 多证券**,每行一只证券。**v0.30.0 起没有 `date` 列**(查询日期改挂在每个指标的参数上,各列可为不同日期);原始响应的 `values` 为 `[证券][指标]`(2026-08-01 转置) |
|
|
87
|
+
| indicator time-series | CLI 拍平为宽表 `{list, total}` | `list[].date` + 序列列:单证券时列=各指标、多证券时列=各证券;每行一个日期。原始响应 `values` 仍为 `[序列][日期]` |
|
|
88
|
+
| indicator screener | CLI 拍平为宽表 `{list, total}` | 同 `cross-section`:`list[].security` / `list[].name` + 每个指标名一列,每行一只**命中**的证券;无命中返回空表。原始响应的 `indicatorList[]` 多一个 `field`(F1/F2…),CLI 用它给同 code 的重复列去重 |
|
|
88
89
|
| lookup broker-org / meeting-org list | `[...]` | `[].id` / `[].name` |
|