gangtise-openapi-cli 0.49.0 → 0.50.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 +25 -5
- package/dist/src/commands/indicator.js +8 -13
- package/dist/src/commands/insight.js +10 -3
- package/dist/src/commands/quote.js +19 -0
- package/dist/src/commands/vault.js +49 -4
- package/dist/src/core/endpoints.js +36 -0
- package/dist/src/core/errors.js +26 -12
- package/dist/src/core/etfComponents.js +74 -17
- package/dist/src/core/normalize.js +1 -1
- package/gangtise-openapi/SKILL.md +20 -14
- package/gangtise-openapi/references/commands/fund.md +2 -2
- package/gangtise-openapi/references/commands/indicator.md +39 -18
- package/gangtise-openapi/references/commands/insight.md +4 -1
- package/gangtise-openapi/references/commands/quote.md +20 -0
- package/gangtise-openapi/references/commands/reference-and-lookup.md +1 -1
- package/gangtise-openapi/references/commands/vault.md +54 -8
- package/gangtise-openapi/references/errors.md +2 -2
- package/gangtise-openapi/references/examples.md +1 -1
- package/gangtise-openapi/references/fields.md +1 -1
- package/gangtise-openapi/references/response-schema.md +8 -3
- package/package.json +1 -1
- package/dist/src/core/calendarType.js +0 -44
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
README 仅列最近 5 个版本摘要:
|
|
8
8
|
|
|
9
|
+
- **v0.50.0 — 2026-11-01**:① **新增个股笔记**:`vault stock-note-list` 查询(全部 / 按证券 / 按笔记 ID)、`stock-note-add` 新增(一条关联 1~50 只证券)、`stock-note-update` 改写正文、`stock-note-delete` 删除(须 `--yes`),均免费。② **新增 `quote sector-fund-flow` 板块日资金流向**(免费;按当日成分股加总、只计沪深京;撞 `100006` 要缩区间或减板块 / 字段,调小 `--limit` 无效)。③ `vault stock-pool-list` / `stock-pool-stocks` 加 `--space-type 2` 查所属租户的股票池。④ 路演 / 调研 / 策略会 / 论坛加 `--publish-start-time` / `--publish-end-time`,按收录时间筛选,可与举办时间同时用。⑤ `indicator time-series` 不传 `--calendar-type` 时由服务端按指标定日期轴(不再先查 `indicator search`);区间里没有报告期末时报告期类指标返回空结果(此前是逐日 `null` 行);⚠️ 一致预期 `frcst_*` 要逐日序列须显式传 `ND`。⑥ ETF 成分补代码在名称查询失败、A / H 定不了等情形下留 `null`,不再按其余行推断;A / H 价格都对得上时按其余行所在的单一市场取。⑦ 指标参数报错的改法提示按服务端新写法修正。⑧ 文档:量比、自由流通、流通股本与市值、AH 溢价率、涨跌停、TTM 利润类等 EDE 指标;⚠️ 基金收益率同类排名的分母不一定等于所属分类的基金数,同类比较前先核对。
|
|
10
|
+
|
|
9
11
|
- **v0.49.0 — 2026-10-28**:① **新增 `gangtise update`**:一条命令更新到最新版(从 npmjs.org `npm install -g`),并替换 `~/.claude` / `~/.codex` / `~/.openclaw/workspace` / `~/.hermes` 下版本较旧的 Agent Skill 副本;`--check` 只查不装,`--no-skill` 不动 skill 副本。② **新版本提示**:在终端里运行任何命令时,若已有新版本,结束后 stderr 提示一行(同一新版本约一天一次);版本信息在后台刷新、命令不等网络,脚本 / Agent / CI 中不提示,`GANGTISE_NO_UPDATE_CHECK=1` 关闭。③ **管理层情绪的 `publishTime` 现为北京时间**,与 `--start-time` / `--end-time` 同一口径;按 v0.48.0 说明「先加 8 小时」处理过的脚本要去掉这一步。④ **路演 / 调研 / 策略会 / 论坛列表的翻页去重改按各自的 ID**(`roadshowId` / `siteVisitId` / `strategyMeetingId` / `forumId`):跨页重复的行会被去掉、结果标为不完整(退出码 3),提示缩短时间范围重拉;`response-schema.md` 同步更正这四个列表的字段名。⑤ **公募基金**:`fund holder-structure` 的 `holderCount` 现为整数,按 v0.44.0 说明「先去千分位逗号再转数字」的处理要去掉(对整数做字符串替换会报错);`fund etf-pcf-components` 的接口只返回成分名称,CLI 按名称匹配补上 `componentCode`(带市场后缀,如 `600519.SH` / `00941.HK`;用该行收盘价核对,A+H 同名两行分别匹配;现金行等匹配不到的为 `null`,stderr 列出),`--no-component-code` 关闭。⑥ `ai security-clue` 的 `--query-mode` / `--gts-code` 补充帮助说明。
|
|
10
12
|
|
|
11
13
|
- **v0.48.0 — 2026-10-24**:① **新增管理层情绪三个命令**:`ai management-sentiment-indicator`(每场业绩会的正面回应程度 / 回复乐观度 / 回复确定度评分与按问题类型拆分)、`ai management-sentiment-conclusion`(情绪结论与较上一场的问答对照),各 5 积分/条、自动翻页;`ai management-sentiment-detail` 按列表返回的 `source.sourceId` 取逐条问答评分与依据,10 积分/次。⚠️ 返回的 `publishTime` 比北京时间早 8 小时(时间筛选按北京时间),按时刻展示或按日期归档前先加 8 小时。② **新增 `bond default-list` 违约债券**(0.4 积分/次):按违约日期区间(不超过 1 年)/ `--security` / `--issuer` 三选一;`--issuer` 一次一个、用发行人全称,简称只返回最匹配的一家主体,核对 `issuerName`。③ 文档:EDE 基金指标(分类、复权净值、区间收益与风险指标、同类排名),区间指标的参数须显式传。④ 依赖 `undici` 升级到 7.30.1(含上游安全修复)。
|
|
@@ -14,8 +16,6 @@ README 仅列最近 5 个版本摘要:
|
|
|
14
16
|
|
|
15
17
|
- **v0.46.0 — 2026-10-14**:① **`reference securities-search --category bond`**:按代码或简称搜债券,代码用于 `bond` 命令组。⚠️ **可转债可能出现在任何 `--category` 的结果里**(按简称查股票时常排第一),**只取返回行 `category` 等于所查类别的那一行**;债券代码传给 `fund` 命令会返回空结果、不报错。② **场外基金可按名称搜到**(`--category fund` 同时返回 `.OF` 与场内代码)。③ **计费单价按平台最新价目更新**:一页通等 AI 生成 10/次、业绩点评 / 观点 PK 20/次、热点话题 10/篇、个股看点 1/条、路演四件套 10/条、独立观点列表 1/条、纪要 / 外资研报 / 我的会议下载 30/篇、公告下载 A 股 1 / 港美股 2、`edb-data` 30/指标;额度保护随之调整。④ **数据范围**:`SKILL.md` 按官方权限表逐命令列出正式 / 试用两档,规划查询区间按表来。⑤ **报错口径**:EDE 漏传必填的 `fiscalYear` 报 `100001`;`main-business` / `valuation-analysis` 的 `--field` 写错名字报 `100005`;`screener` 计费按筛选前范围保守估算。完整列表见 CHANGELOG。
|
|
16
18
|
|
|
17
|
-
- **v0.45.0 — 2026-10-07**:① **`vault drive-copy` 可复制整个文件夹**:`--folder-id` + `--target-parent-id`,连同子文件夹与文件复制到另一空间,返回 `{folderId, newFolderId}`;复制文件的写法不变,两组参数二选一。② **下载文件名**:响应同时给出 UTF-8 文件名与普通文件名时用 UTF-8 的那个。③ `GANGTISE_BASE_URL` 指向非本机地址却用 `http://` 时 stderr 提示一次(凭证会明文传输)。④ 文档:`bond daily-quote` / `bond valuation` 的数据可能比账号窗口短,**按年份取长序列时核对首行日期**;`bond announcement` 按日期查询时有代码的行都带后缀;基金只知道名称时怎么换代码;分钟 K 的可查历史是滚动的短窗口。
|
|
18
|
-
|
|
19
19
|
### 历史里程碑
|
|
20
20
|
|
|
21
21
|
- **v0.44.0**:新增公募基金 `fund` 命令组 18 个命令(0.4 积分/次),额度保护覆盖显式 `--size`。
|
|
@@ -190,6 +190,7 @@ install_skill ~/.hermes/skills # Hermes
|
|
|
190
190
|
| | `minute-kline` | 分钟K线——沪深A股 / ETF + 各类指数含全球指数(`--security` 可重复,逐只并发合并) |
|
|
191
191
|
| | `realtime` | 实时行情快照——A股/港股/美股个股 + 沪深 ETF + 各类指数含全球指数 |
|
|
192
192
|
| | `fund-flow` | A股个股日资金流向(沪深京;小/中/大/特大单 + 主力净流入) |
|
|
193
|
+
| | `sector-fund-flow` | 板块日资金流向(行业 / 概念 / 指数板块,按当日成分股加总,只计沪深京上市公司;免费) |
|
|
193
194
|
| **Fundamental** | `income-statement` / `balance-sheet` / `cash-flow` | A股三大财务报表(累计) |
|
|
194
195
|
| | `income-statement-quarterly` / `cash-flow-quarterly` | A股利润表/现金流量表(单季度) |
|
|
195
196
|
| | `income-statement-hk` / `balance-sheet-hk` / `cash-flow-hk` | 港股三大财务报表(中国会计准则) |
|
|
@@ -246,9 +247,11 @@ install_skill ~/.hermes/skills # Hermes
|
|
|
246
247
|
| | `record-list` / `record-download` | 录音速记列表与下载 |
|
|
247
248
|
| | `my-conference-list` / `my-conference-download` | 我的会议列表与下载 |
|
|
248
249
|
| | `wechat-message-list` / `wechat-chatroom-list` | 群消息列表与群ID查询 |
|
|
249
|
-
| | `stock-pool-list` / `stock-pool-stocks` |
|
|
250
|
+
| | `stock-pool-list` / `stock-pool-stocks` | 自选股股票池列表与证券明细(`--space-type 2` 查所属租户的股票池) |
|
|
250
251
|
| | `stock-pool-create` / `stock-pool-rename` / `stock-pool-delete` | 建池 / 改名 / 删池(删池须加 `--yes`) |
|
|
251
252
|
| | `stock-pool-add-stock` / `stock-pool-remove-stock` | 批量关注 / 批量取消关注个股 |
|
|
253
|
+
| | `stock-note-list` | 个股投资笔记查询(全部 / 按证券 / 按笔记 ID) |
|
|
254
|
+
| | `stock-note-add` / `stock-note-update` / `stock-note-delete` | 新增笔记(一条关联 1~50 只证券)/ 改写正文 / 删除(删除须加 `--yes`) |
|
|
252
255
|
| **Indicator** | `search` | 证券级数据指标搜索(按名称匹配,返回 indicatorCode 及可传参数 parameterList) |
|
|
253
256
|
| | `cross-section` | 指标截面数据(多指标 × 多证券,单日快照;前置 `search` 拿 code) |
|
|
254
257
|
| | `time-series` | 指标时间序列(多指标 × 单证券 或 单指标 × 多证券,按区间) |
|
|
@@ -316,7 +319,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
|
|
|
316
319
|
- **流式下载**:二进制响应(PDF 等)直接 `pipeline` 到磁盘,不经过内存缓冲;不带 `--output` 时先写入临时文件,定好文件名后再改名。50MB PDF 内存占用近乎为零。
|
|
317
320
|
- **流式输出**:`--format jsonl` 或 `csv` 加 `--output <file>` 时,翻页 / 全市场分片 / 多证券分批请求的行**按到达顺序逐批写盘**,取数阶段不持有整份结果,内存不随行数增长;csv 先落临时行文件、收尾时按列并集写表头再转成 csv(磁盘两遍、内存不变)。jsonl / csv 无论写文件还是输出到终端都按批写出,每行以换行结尾。任一只 / 页 / 片失败,命令退出时后台已无取数与写盘,不留 `.part`。
|
|
318
321
|
- **导出元信息**:`csv` / `jsonl` 落盘时旁边生成 `<文件>.meta.json`——命令行、数据行数、列名、`complete`(写元信息时的判定:退出 3 即 `false`)、`total` / `partial` / `failedPages` / `failedShards` / `truncatedShards` / `droppedColumns` / `missingFields` / `duplicateRows` / `changedRows` / `totalCapped` 等全部完整性标记(以及不影响完整性判定的 `outOfWindowShards`)、抓取时间与时区、CLI 版本,以及数据文件的字节数 `bytes` 与内容哈希 `sha256`。命令行与结果里的 key / secret / token 类字段写成 `[redacted]`。元信息在数据文件发布之后才就位,导出失败不会留下新数据配旧元信息。**转交或归档前建议核一次**:`shasum -a 256 <文件>` 与元信息里的 `sha256` 相等,才说明这份元信息描述的就是旁边这份数据——两个进程同时导出到**同一个 `--output`** 时,最终文件一定是其中某一次的完整产物,但旁边的元信息有可能来自另一次,核哈希就能发现。**输的那一次自己也会报**:收尾时核对 `--output` 是否仍是本次写出的文件(仍是同一个文件就不必回读,否则回读比对哈希),不符就在 stderr 说明「这个文件不是本次命令的产物」并**退出码 4**。这两种格式的文件本身只有数据行,转交之后靠它核验是否完整;`json` 自带标记,不生成。
|
|
319
|
-
- **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**不重放的端点例外**(贵档:one-pager 等生成/提交类 + `tool file-parse` 提交 + 30/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载 + 题材 `concept-info` / `concept-securities`(含 `--full`)+ `ai management-sentiment-detail`;`bond` 全部 13 个命令(多数按次,三个评级命令按条 / 按债券 / 按发行人)、`fund` 全部 18 个命令(按次)与按次计费的 `tool web-search`;按条计费的 `insight highlight list`、观点 `detail` 与 `list --with-content`,以及单次最多 6000 只、按只计费的 `ai stock-summary` 与行数随日期区间增长的 `fundamental earning-forecast`;另加不计分但不可重复执行的 `vault stock-pool-create` 与云盘的上传 / 新建文件夹 / 复制 / 两个删除,共
|
|
322
|
+
- **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**不重放的端点例外**(贵档:one-pager 等生成/提交类 + `tool file-parse` 提交 + 30/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载 + 题材 `concept-info` / `concept-securities`(含 `--full`)+ `ai management-sentiment-detail`;`bond` 全部 13 个命令(多数按次,三个评级命令按条 / 按债券 / 按发行人)、`fund` 全部 18 个命令(按次)与按次计费的 `tool web-search`;按条计费的 `insight highlight list`、观点 `detail` 与 `list --with-content`,以及单次最多 6000 只、按只计费的 `ai stock-summary` 与行数随日期区间增长的 `fundamental earning-forecast`;另加不计分但不可重复执行的 `vault stock-pool-create`、`vault stock-note-add` 与云盘的上传 / 新建文件夹 / 复制 / 两个删除,共 67 个):5xx/超时不重放,仅连接失败、429 与 token 自愈重试。**两类端点各有各的理由**:贵档是**重放会重复扣分**——服务端可能已经执行并计费,重发按次计费的再扣一次,重发按篇/按条计费的会把已交付的行再计一次;不计分的那几个是**重放会造成副作用或把成功报成失败**——股票池名不允许重复,重发一个其实已经建成的请求,回来的是 `230006 股票池名称重复`;笔记没有防重机制,重发一个其实已经写入的新增会多出一条笔记;云盘允许同名,重发上传 / 新建 / 复制会多出一份;重发一个其实已经删掉的删除,回来的是「文件不存在」或 `130002`。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(EDE 无数据不用此码,而是保留行列的占位单元格 `null`;代码或参数名写错报 `100003`、缺必填参数报 `100001`)。**终态码 `999011`(凭证无效)/ `140002`(终态失败:异步生成失败、参数错误或业务处理失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,`140002` 立即重发得到的还是同一结果。
|
|
320
323
|
|
|
321
324
|
<!-- no-replay-endpoints
|
|
322
325
|
上面那句点名的「不重放」端点,完整清单如下(endpoint key,与 `gangtise raw list` 一致):
|
|
@@ -385,6 +388,7 @@ vault.drive.delete-file
|
|
|
385
388
|
vault.drive.delete-folder
|
|
386
389
|
vault.drive.upload
|
|
387
390
|
vault.my-conference.download
|
|
391
|
+
vault.stock-note.add
|
|
388
392
|
vault.stock-pool.create
|
|
389
393
|
-->
|
|
390
394
|
- **Token 自愈**:服务端判定 Token 失效时,先换用本进程其他请求或其他进程已刷新好的 token,不行再重新登录一次,然后重发请求。换 token 的重发不占用网络重试次数;多个并发请求同时失效只登录一次。
|
|
@@ -421,6 +425,7 @@ vault.stock-pool.create
|
|
|
421
425
|
- `vault my-conference-list`
|
|
422
426
|
- `vault wechat-message-list`
|
|
423
427
|
- `vault wechat-chatroom-list`
|
|
428
|
+
- `vault stock-note-list`
|
|
424
429
|
- `ai hot-topic`
|
|
425
430
|
- `ai management-sentiment-indicator`
|
|
426
431
|
- `ai management-sentiment-conclusion`
|
|
@@ -433,7 +438,7 @@ vault.stock-pool.create
|
|
|
433
438
|
- `--from` 必须是非负整数,`--size` 必须是正整数;非法数字会在本地直接报 `ValidationError`,不会继续请求 API
|
|
434
439
|
- 安全上限:自动翻页最多 1000 页,防止异常循环
|
|
435
440
|
- 部分页失败、或服务端实际返回行数与 `total` 矛盾(提前短页)时,不丢弃已取到的数据:结果带 `partial: true`(页失败时另有 `failedPages`;K线分片为 `failedShards`——早于账号可回溯窗口而跳过的分片不算失败、也不标 `partial`,另列在 `outOfWindowShards`;只有部分分片返回、合并时放不下的列为 `droppedColumns`;`quote` 系带 `--field` 而服务端没回的列为 `missingFields`;翻页时同一行在相邻两页各出现一次为 `duplicateRows`,按时间排序的列表在同一时刻的一组数据跨页时会发生,重复了几行也就漏了几行,重复行已去掉,缩短时间范围重拉可补齐;同一 ID 在后面的页以不同内容再次出现为 `changedRows`,说明翻页期间列表有变动、也可能漏了行,两版都保留(按 ID 去重只留一版),重拉即可——重拉后仍在同一处出现,说明这个列表里同一 ID 对应多条记录,按 ID 去重前先核对;`--format json` 可见),stderr 输出警告,**进程退出码为 3**(完整成功为 0)
|
|
436
|
-
- **`indicator` 命令的退出码 3**(脚本按 `!= 0` 判失败的需留意):服务端整指标/整证券没返回时标 `partial` + `omittedIndicators` / `omittedSecurities` 并退出 3。这个分支很少触发——服务端对解析不了的代码直接报 `100003` 并点名是哪个(指标码拼错 →「指标 xxx 不存在」;证券后缀错,如美股写成 `AAPL.US` 而非 `AAPL.O` →「xxx 不是有效证券或者板块ID」),**无论同批有没有正确的代码都会报**,CLI 相应退出 1。真实的无数据/无覆盖仍是占位单元格 + 退出码 0。占位值统一是 `null`。⚠️ **报告期类指标(`is_
|
|
441
|
+
- **`indicator` 命令的退出码 3**(脚本按 `!= 0` 判失败的需留意):服务端整指标/整证券没返回时标 `partial` + `omittedIndicators` / `omittedSecurities` 并退出 3。这个分支很少触发——服务端对解析不了的代码直接报 `100003` 并点名是哪个(指标码拼错 →「指标 xxx 不存在」;证券后缀错,如美股写成 `AAPL.US` 而非 `AAPL.O` →「xxx 不是有效证券或者板块ID」),**无论同批有没有正确的代码都会报**,CLI 相应退出 1。真实的无数据/无覆盖仍是占位单元格 + 退出码 0。占位值统一是 `null`。⚠️ **报告期类指标(`is_*`)与交易日类指标混查、或显式传 `--calendar-type ND` / `TD` / `WD` 时,时序上大部分行都是占位**(只有报告期末那几行是真值,`TD` 下落在非交易日的报告期末那几期整期缺失;单独查报告期类指标且不传 `--calendar-type` 时只返回报告期末),`null` 虽被 Excel / pandas / SQL 的聚合跳过,**但行数不变**,手工「总和 ÷ 行数」仍会差几十倍;详见 skill 的 `references/commands/indicator.md`。**条件选股的缺列另有更严的一档**:把缺列的变量当作无法求值,若表达式(按 `&&`/`||` 的布尔结构)再无任何可成立的分支,则**退出码 1 且不输出**——那些行以「通过了该条件」的名义呈现,而条件根本无法证明被执行过。⚠️ 这一档以「服务端返回了命中行」为前提;**零命中时一律退出码 0**(没有行需要被质疑),所以空集不能直接当成「无标的符合条件」——另有两种成因产生**逐字相同**的输出:**日期没落在报告期末**(报告期类指标此时整批 `null`),或**该指标不覆盖这批证券**(如拿 A 股专属指标查港美股)。语义约定:`0` 完整成功(含合法空结果)/`3` 有数据但不完整/`4` 数据写出完整、但 `--output` 指向的文件在收尾期间被另一次写向同一路径的导出替换掉了(不是本次命令的产物,stderr 会说明并给出两边的 sha256 前缀;与 `3` 同时发生时退出 `3`)/`1` 硬失败/`130` / `143` / `129` 被 Ctrl-C / `kill` / 终端断开中断(进程以该信号结束,shell 里的 `$?` 即此值,脚本循环会随之停下;本次导出的暂存文件已删除;自动命名的下载被中断时可能留下一个 0 字节的同名文件,删掉即可;中断前已发出的请求服务端照常处理并计费)。接 `| head` 等管道提前关掉读端时,退出码同样保留,脚本需 `set -o pipefail` 才看得到
|
|
437
442
|
- **分页端点返回 `null` 也退出 3**:分页端点的正常响应是 `{total, list}`,真实的空结果是 `{total: 0, list: []}`。若响应体是 `null`,CLI 在 stderr 告警并**退出码 3**——只给告警的话,脚本无法区分「这个筛选确实没命中」和「这个筛选没生效」。机器格式(jsonl/csv)此时 **stdout 不输出任何字节**(不是空行),`--format json` 仍忠实打印 `null`。⚠️ 带 `--output` 时文件仍会被创建,为 0 字节(旁边的 `.meta.json` 标 `complete: false`)。
|
|
438
443
|
- 🔴 **`total` 被服务端封顶时会标 `totalCapped` 并退出 3**:分页端点的 `total` 若被服务端封顶(返回一个固定上限而非真实条数),省略 `--size` 的全量拉取会**正好取满那个上限就停、且不报任何异常**——导出的文件是截断的却看不出来。全量拉取结束后会**多探一行**(`from = total`):探到数据就标 `partial` + `totalCapped` 并退出 3;探针被服务端以偏移窗口类错误拒绝、或 `total` 正好等于端点声明的偏移窗口(`vault wechat-message-list` / `insight highlight list` 为 10000,窗口外的行取不到也数不到)时,同样按封顶处理。判据不写死 10000,服务端改配置仍然有效;`total` 诚实时探针返回空、不产生计费。传了 `--size` 的有界请求不做此探测。
|
|
439
444
|
- 分页结果中 `total` 字段会被保留(json 格式输出 `{total, list}`);其他格式下 stderr 输出 `Total: N, showing: M`(json 格式不输出该行)
|
|
@@ -512,6 +517,9 @@ gangtise insight announcement download --announcement-id 123456 --file-type 2
|
|
|
512
517
|
gangtise insight research download --report-id 12345 --output ./report.pdf
|
|
513
518
|
|
|
514
519
|
gangtise insight roadshow list --institution C100000017
|
|
520
|
+
# 增量同步:昨天新收录、下周举办的路演(--publish-* 按收录时间 publishTime,--start/--end-time 按举办时间,两组同时生效;--publish-end-time 只写日期时按整天算)
|
|
521
|
+
gangtise insight roadshow list --publish-start-time 2026-10-29 --publish-end-time 2026-10-29 \
|
|
522
|
+
--start-time 2026-11-02 --end-time "2026-11-08 23:59:59" --format json
|
|
515
523
|
|
|
516
524
|
# 港股公告
|
|
517
525
|
gangtise insight announcement-hk list --security 01913.HK --rank-type 2 --size 20 --format json
|
|
@@ -625,6 +633,8 @@ gangtise quote realtime --security 512800.SH --security SPX.SPI --security HSI.H
|
|
|
625
633
|
gangtise quote realtime --security aShares --field securityCode --field latestPrice --field pctChange --field volume --format json
|
|
626
634
|
# A股个股日资金流向(沪深京;--security aShares 全市场;--limit 上限 10000,超限缩短日期区间分批)
|
|
627
635
|
gangtise quote fund-flow --security 600519.SH --security 000001.SZ --start-date 2026-06-01 --end-date 2026-06-05 --field mainNetInflow --field largeInflow --field xlargeInflow --format json
|
|
636
|
+
# 板块日资金流向(金额单位元,不点名字段时返回全部;板块 ID 用 reference sector-search 查)
|
|
637
|
+
gangtise quote sector-fund-flow --sector 1000001141 --sector 1000001065 --start-date 2026-10-26 --end-date 2026-10-30 --field sectorMainNetInflow --field sectorTotalInflow
|
|
628
638
|
```
|
|
629
639
|
|
|
630
640
|
> **历史 vs 实时**:`day-kline*` 仅返回历史数据(当日数据入库时间:A 股 ~15:30 / 港股 ~16:30 / 美股 ~07:00 北京时间)。盘中需要最新成交价、振幅等实时字段必须走 `quote realtime`。
|
|
@@ -833,6 +843,9 @@ gangtise vault stock-pool-list
|
|
|
833
843
|
gangtise vault stock-pool-stocks --pool-id 808477293
|
|
834
844
|
# 查询所有股票池中的全量证券(默认行为)
|
|
835
845
|
gangtise vault stock-pool-stocks
|
|
846
|
+
# 所属租户的股票池:列表与明细都加 --space-type 2,明细的 --pool-id 要取自同一 --space-type 的列表
|
|
847
|
+
gangtise vault stock-pool-list --space-type 2
|
|
848
|
+
gangtise vault stock-pool-stocks --space-type 2 --pool-id 908477301
|
|
836
849
|
|
|
837
850
|
# 维护股票池:建池 → 批量关注 → 改名 → 取消关注 → 删池
|
|
838
851
|
# (这五个命令会改动账号数据;云盘的上传 / 新建 / 重命名 / 移动 / 复制 / 删除同样会,见上方 Vault 云盘管理)
|
|
@@ -842,6 +855,13 @@ gangtise vault stock-pool-rename --pool-id 808477293 --name "核心持仓"
|
|
|
842
855
|
gangtise vault stock-pool-remove-stock --pool-id 808477293 --security 000858.SZ
|
|
843
856
|
# 删池会连带移除池内全部关注关系且不可恢复,必须显式 --yes
|
|
844
857
|
gangtise vault stock-pool-delete --pool-id 808477293 --yes
|
|
858
|
+
|
|
859
|
+
# 个股投资笔记:查(不传筛选即全部笔记)→ 新增 → 改写 → 删除(四个命令都免费)
|
|
860
|
+
gangtise vault stock-note-list
|
|
861
|
+
gangtise vault stock-note-list --security 600519.SH --security 00700.HK
|
|
862
|
+
gangtise vault stock-note-add --security 688106.SH --security 600111.SH --content "上游材料涨价叠加国产替代,产业链同步受益。"
|
|
863
|
+
gangtise vault stock-note-update --note-id 1835 --content "电子特气国产替代逻辑清晰,最新季报订单加速落地。"
|
|
864
|
+
gangtise vault stock-note-delete --note-id 1835 --yes
|
|
845
865
|
```
|
|
846
866
|
|
|
847
867
|
> 云盘允许同名文件与文件夹(以 ID 区分),上传 / 新建 / 复制每执行一次就多一份,所以这三个命令超时不会自动重发;超时后重试前先用 `drive-folder-list` 看一眼是否已经存入。
|
|
@@ -2,13 +2,16 @@ import { Command, Option } from "commander";
|
|
|
2
2
|
import { collectList, collectNames, dateArg, screenerExpressionFields, parseNumberOption, parseScreenerIndicators } from "../core/args.js";
|
|
3
3
|
import { buildIndicatorCrossSectionBody, buildIndicatorScreenerBody, buildIndicatorTimeSeriesBody } from "../core/commandBodies.js";
|
|
4
4
|
import { checkScreenerBindings, droppedFromMatrix, flattenCrossSection, flattenTimeSeries, isEmptyMatrix, requireIndicatorMatrix, unwrapIndicatorData } from "../core/indicatorMatrix.js";
|
|
5
|
-
import { resolveCalendarType } from "../core/calendarType.js";
|
|
6
5
|
import { ValidationError } from "../core/errors.js";
|
|
7
6
|
import { parseOutputFormat } from "../core/output.js";
|
|
8
7
|
import { printData } from "../core/printer.js";
|
|
9
8
|
import { withClient } from "./shared.js";
|
|
10
|
-
function flagDropped(rows, data, requestedSecurities, requestedIndicators) {
|
|
9
|
+
function flagDropped(rows, data, requestedSecurities, requestedIndicators, timeSeries = false) {
|
|
11
10
|
if (isEmptyMatrix(data)) {
|
|
11
|
+
if (timeSeries) {
|
|
12
|
+
process.stderr.write("[gangtise] note: the query returned no data at all. In a time series that usually means the range holds no date of its calendar: no trading day for a trading-day indicator (a holiday range), no report-period end (03-31 / 06-30 / 09-30 / 12-31) for a report-period one when --calendar-type is omitted or RD. Widen the range, or pass --calendar-type ND for one row per day. If the range does hold such dates, cross-check the codes against 'gangtise indicator search --format json' and 'gangtise reference securities-search'; if they are right, report it with the command.\n");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
12
15
|
process.stderr.write("[gangtise] note: the query returned no data at all. A missing value comes back as a null cell and an unrecognised code or parameter name as an error (100003), so an empty answer is neither. Cross-check the codes against 'gangtise indicator search --format json' and 'gangtise reference securities-search'; if they are right, report it with the command.\n");
|
|
13
16
|
return;
|
|
14
17
|
}
|
|
@@ -74,7 +77,7 @@ indicator.command("time-series")
|
|
|
74
77
|
.option("--security <code>", "Security code, e.g. 600519.SH, or a sector ID from 'gangtise reference sector-search' (REQUIRED, repeat; union, deduped)", collectList, [])
|
|
75
78
|
.requiredOption("--start-date <date>", "Start date (yyyy-MM-dd)", dateArg("--start-date"))
|
|
76
79
|
.requiredOption("--end-date <date>", "End date (yyyy-MM-dd)", dateArg("--end-date"))
|
|
77
|
-
.option("--calendar-type <type>", "Calendar: ND=natural TD=trading WD=weekday. Omit it and the
|
|
80
|
+
.option("--calendar-type <type>", "Calendar: ND=natural TD=trading WD=weekday RD=report period (03-31/06-30/09-30/12-31). Omit it and the server picks by indicator: TD for trading-day indicators, RD for report-period ones, the union of both dates when they are mixed. Forecast indicators (frcst_*) get TD that way, though their values also change on non-trading days — pass ND to keep those")
|
|
78
81
|
.option("--currency <code>", "Currency: DFT/CNY/HKD/USD/EUR/GBP/JPY/TWD/MOP/AUD (default DFT)")
|
|
79
82
|
.option("--scale <code>", "Scale: 0=个 3=千 4=万 6=百万 8=亿 9=十亿 (default 0)")
|
|
80
83
|
.option("--indicator-param <spec>", "Per-indicator param 'code:key=value', e.g. qte_close:adjustType=2 for 前复权 (repeat); read exact keys from 'indicator search'", collectNames, [])
|
|
@@ -84,18 +87,10 @@ indicator.command("time-series")
|
|
|
84
87
|
.action((options) => withClient(async (client) => {
|
|
85
88
|
const format = parseOutputFormat(options.format);
|
|
86
89
|
requireIndicatorScope(options.indicator, options.security);
|
|
87
|
-
const
|
|
88
|
-
if (!body.calendarType) {
|
|
89
|
-
const resolved = await resolveCalendarType(client, options.indicator);
|
|
90
|
-
if (resolved) {
|
|
91
|
-
body.calendarType = resolved;
|
|
92
|
-
process.stderr.write(`[gangtise] note: every requested indicator takes tradeDate, so this series was fetched on the trading-day calendar (calendarType=TD) instead of the server default ND — no all-null non-trading rows, and fewer cells against the 30000 cap. Pass --calendar-type ND to override.\n`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const raw = await client.call("indicator.time-series", body);
|
|
90
|
+
const raw = await client.call("indicator.time-series", buildIndicatorTimeSeriesBody(options));
|
|
96
91
|
const data = requireIndicatorMatrix(raw);
|
|
97
92
|
const rows = flattenTimeSeries(data, options.keyBy, options.security);
|
|
98
|
-
flagDropped(rows, data, options.security, options.indicator);
|
|
93
|
+
flagDropped(rows, data, options.security, options.indicator, options.calendarType?.toUpperCase() !== "ND");
|
|
99
94
|
await printData(rows, format, options.output);
|
|
100
95
|
}));
|
|
101
96
|
indicator.command("screener")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { collectList, dateArg, maybeArray, parseChoiceList, parseEndTimestamp13, parseFrom, parseSize, parseTimestamp13 } from "../core/args.js";
|
|
1
|
+
import { Command, Option } from "commander";
|
|
2
|
+
import { collectList, dateArg, datetimeArg, maybeArray, parseChoiceList, parseEndTimestamp13, parseFrom, parseSize, parseTimestamp13 } from "../core/args.js";
|
|
3
3
|
import { ValidationError } from "../core/errors.js";
|
|
4
4
|
import { fetchOpinionDetails } from "../core/opinionDetail.js";
|
|
5
5
|
import { rowCount } from "../core/rowSink.js";
|
|
6
|
-
import { emit, addDownloadCommand, confirmCostly, multiChoice, value, required, list, numberList, choiceList, count, rankType, searchType, top, flag, format, output, from, size, startTime, endTime, timeFilters, query, RESEARCH_AREA_CITIC_OR_DIRECTION } from "./shared.js";
|
|
6
|
+
import { emit, addDownloadCommand, confirmCostly, multiChoice, field, value, required, list, numberList, choiceList, count, rankType, searchType, top, flag, format, output, from, size, startTime, endTime, timeFilters, query, RESEARCH_AREA_CITIC_OR_DIRECTION } from "./shared.js";
|
|
7
7
|
export const insight = new Command("insight").description("Insight APIs");
|
|
8
8
|
const SW_INDUSTRY_ONLY = "Industry ID -- swIndustry codes only (104xx0000); citicIndustry codes are rejected with 100005 even where constant-category declares them";
|
|
9
9
|
const opinion = new Command("opinion");
|
|
@@ -84,6 +84,11 @@ query(pamirsSummary, "list", {
|
|
|
84
84
|
],
|
|
85
85
|
});
|
|
86
86
|
addDownloadCommand(pamirsSummary, { endpointKey: "insight.pamirs-summary.download", idOption: "--summary-id", idField: "summaryId", fallbackPrefix: "pamirs-summary", fileType: { description: "File type: 1=original(default) 2=HTML", choices: ["1", "2"] }, titleListEndpoint: "insight.pamirs-summary.list" });
|
|
87
|
+
const publishTime = (flags, description, key) => {
|
|
88
|
+
const option = new Option(flags, description);
|
|
89
|
+
option.argParser(datetimeArg(option.long));
|
|
90
|
+
return field(option, (v) => ({ [key]: v }));
|
|
91
|
+
};
|
|
87
92
|
const addScheduleList = (command, endpointKey, fields) => query(command, "list", {
|
|
88
93
|
endpoint: endpointKey,
|
|
89
94
|
fields: [
|
|
@@ -99,6 +104,8 @@ const addScheduleList = (command, endpointKey, fields) => query(command, "list",
|
|
|
99
104
|
fields.location && list("--location <id>", "Location ID (domesticCity constant, via 'reference constant-list')", "locationList"),
|
|
100
105
|
format(), output("Output path"),
|
|
101
106
|
...timeFilters(),
|
|
107
|
+
publishTime("--publish-start-time <datetime>", "Collected from (publishTime, when the schedule first came in — not when it is held); combines with --start-time/--end-time", "publishStartTime"),
|
|
108
|
+
publishTime("--publish-end-time <datetime>", "Collected until (publishTime); a bare date covers that whole day", "publishEndTime"),
|
|
102
109
|
].filter((f) => Boolean(f)),
|
|
103
110
|
});
|
|
104
111
|
addScheduleList(roadshow, "insight.roadshow.list", {
|
|
@@ -179,3 +179,22 @@ quote.command("fund-flow")
|
|
|
179
179
|
await printData(data, format, options.output);
|
|
180
180
|
});
|
|
181
181
|
});
|
|
182
|
+
quote.command("sector-fund-flow")
|
|
183
|
+
.description("Sector daily fund flow: each sector's constituents on that day summed, SH/SZ/BJ listings only (free)")
|
|
184
|
+
.requiredOption("--sector <id>", "Sector ID from 'gangtise reference sector-search' (repeat or comma-separate)", collectList)
|
|
185
|
+
.option("--start-date <date>", "Start date yyyy-MM-dd (default: endDate minus 1 year)", dateArg("--start-date"))
|
|
186
|
+
.option("--end-date <date>", "End date yyyy-MM-dd (default: latest trading day)", dateArg("--end-date"))
|
|
187
|
+
.option("--limit <number>", `Max rows returned (default: ${DEFAULT_QUOTE_LIMIT}, max: ${MAX_QUOTE_LIMIT}). It cuts the answer only: a query too heavy to compute fails with 100006 whatever the limit — shorten the range, or pass fewer sectors or fields`)
|
|
188
|
+
.option("--field <field>", "Field, e.g. sectorMainNetInflow / sectorLargeInflow (repeat); omit for all. sectorId and tradeDate always come first", collectList, [])
|
|
189
|
+
.option("--format <format>", "Output format", "table")
|
|
190
|
+
.option("--output <path>")
|
|
191
|
+
.action((options) => emit(options, async (client) => {
|
|
192
|
+
const limit = parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1, max: MAX_QUOTE_LIMIT }) ?? DEFAULT_QUOTE_LIMIT;
|
|
193
|
+
const fieldList = maybeArray(options.field);
|
|
194
|
+
const data = await client.call("quote.sector-fund-flow", { sectorList: options.sector, startDate: options.startDate, endDate: options.endDate, limit, fieldList });
|
|
195
|
+
if (fieldList?.every((field) => field === "sectorId" || field === "tradeDate") && rowCount(data) === 0) {
|
|
196
|
+
process.stderr.write(`[gangtise] note: no rows came back, and --field names no fund-flow column (${fieldList.join(", ")}). This endpoint answers such a request with an empty list rather than an error, so it says nothing about whether data exists: add a value field such as sectorMainNetInflow, or drop --field.\n`);
|
|
197
|
+
}
|
|
198
|
+
flagIfLimitTruncated(data, limit, "sector-fund-flow");
|
|
199
|
+
return data;
|
|
200
|
+
}));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Command, Option } from "commander";
|
|
2
|
-
import { collectList, collectNames } from "../core/args.js";
|
|
2
|
+
import { collectList, collectNames, parseFrom, parseSize } from "../core/args.js";
|
|
3
3
|
import { uploadDriveFile } from "../core/driveUpload.js";
|
|
4
4
|
import { ValidationError } from "../core/errors.js";
|
|
5
5
|
import { flagFailedItems } from "../core/normalize.js";
|
|
6
|
-
import { emit, addDownloadCommand, assertConfirmed, field, value, required, list, numberList, format, output, from, size, startTime, endTime, query, RESEARCH_AREA_CITIC_OR_DIRECTION } from "./shared.js";
|
|
6
|
+
import { emit, addDownloadCommand, assertConfirmed, field, value, required, requiredList, list, numberList, format, output, from, size, startTime, endTime, query, RESEARCH_AREA_CITIC_OR_DIRECTION } from "./shared.js";
|
|
7
7
|
export const vault = new Command("vault").description("Vault APIs");
|
|
8
8
|
query(vault, "drive-list", {
|
|
9
9
|
endpoint: "vault.drive.list",
|
|
@@ -163,11 +163,13 @@ query(vault, "wechat-chatroom-list", {
|
|
|
163
163
|
format(), output(),
|
|
164
164
|
],
|
|
165
165
|
});
|
|
166
|
-
|
|
166
|
+
const poolSpaceType = () => field(new Option("--space-type <n>", "Pools: 1=my pools (default) 2=tenant pools").choices(["1", "2"]), (v) => ({ spaceType: v === undefined ? undefined : Number(v) }));
|
|
167
|
+
query(vault, "stock-pool-list", { endpoint: "vault.stock-pool.list", fields: [poolSpaceType(), format(), output()] });
|
|
167
168
|
query(vault, "stock-pool-stocks", {
|
|
168
169
|
endpoint: "vault.stock-pool.stocks",
|
|
169
170
|
fields: [
|
|
170
|
-
|
|
171
|
+
poolSpaceType(),
|
|
172
|
+
field(new Option("--pool-id <id>", "Pool ID from 'stock-pool-list' with the same --space-type (an ID of the other space is not matched); repeat for multiple; omit (or 'all') for all pools of that space").argParser(collectList), (v) => ({ poolIdList: v?.length ? v : ["all"] })),
|
|
171
173
|
format(), output(),
|
|
172
174
|
],
|
|
173
175
|
});
|
|
@@ -219,3 +221,46 @@ vault.command("stock-pool-delete")
|
|
|
219
221
|
return data;
|
|
220
222
|
});
|
|
221
223
|
});
|
|
224
|
+
vault.command("stock-note-list").description("List your stock notes: all, by security, or by note ID (free)")
|
|
225
|
+
.option("--security <code>", "Security code (repeat or comma-separate): notes linked to ANY of them; omit both filters for all your notes", collectList)
|
|
226
|
+
.option("--note-id <id>", "Note ID (repeat or comma-separate); not together with --security", collectList)
|
|
227
|
+
.option("--from <number>", "Starting offset", "0")
|
|
228
|
+
.option("--size <number>", "Total rows to return; omit to fetch all")
|
|
229
|
+
.option("--format <format>", "Output format", "json").option("--output <path>")
|
|
230
|
+
.action((options) => {
|
|
231
|
+
if (options.security?.length && options.noteId?.length)
|
|
232
|
+
throw new ValidationError("pass --security or --note-id, not both");
|
|
233
|
+
const filter = options.noteId?.length ? { stockNoteIdList: options.noteId } : { securityList: options.security?.length ? options.security : ["all"] };
|
|
234
|
+
const body = { ...filter, from: parseFrom(options.from), size: parseSize(options.size) };
|
|
235
|
+
return emit(options, (client) => client.call("vault.stock-note.list", body));
|
|
236
|
+
});
|
|
237
|
+
query(vault, "stock-note-add", {
|
|
238
|
+
description: "Add a stock note linked to 1-50 securities (free). Not retried on a timeout: check 'stock-note-list' before re-running, or you get two notes",
|
|
239
|
+
endpoint: "vault.stock-note.add",
|
|
240
|
+
fields: [
|
|
241
|
+
requiredList("--security <code>", "Security code to link, 1-50 (repeat or comma-separate); fixed once created. Stocks, ETFs, exchange / concept / CITIC industry / global indices; .SWI is refused (120001)", "securityList"),
|
|
242
|
+
required("--content <text>", "Note text, plain (Markdown is kept as text), max 10000 characters", "content"),
|
|
243
|
+
format("json"), output(),
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
query(vault, "stock-note-update", {
|
|
247
|
+
description: "Replace the whole content of a stock note (free); its securities cannot be changed",
|
|
248
|
+
endpoint: "vault.stock-note.update",
|
|
249
|
+
fields: [
|
|
250
|
+
required("--note-id <id>", "Note ID from 'stock-note-list'", "stockNoteId"),
|
|
251
|
+
required("--content <text>", "New text, replacing the old in full; max 10000 characters", "content"),
|
|
252
|
+
format("json"), output(),
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
vault.command("stock-note-delete").description("Delete stock notes — irreversible (free)")
|
|
256
|
+
.requiredOption("--note-id <id>", "Note ID to delete; repeat or comma-separate, max 100", collectList)
|
|
257
|
+
.option("--yes", "Required: confirm the deletion")
|
|
258
|
+
.option("--format <format>", "Output format", "json").option("--output <path>")
|
|
259
|
+
.action(async (options) => {
|
|
260
|
+
assertConfirmed("vault.stock-note.delete", Boolean(options.yes), options.noteId.join("、"));
|
|
261
|
+
await emit(options, async (client) => {
|
|
262
|
+
const data = await client.call("vault.stock-note.delete", { stockNoteIdList: options.noteId });
|
|
263
|
+
flagFailedItems(data, "vault stock-note-delete");
|
|
264
|
+
return data;
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -439,6 +439,13 @@ const ENDPOINT_DEFS = {
|
|
|
439
439
|
description: "Query A-share daily fund flow (SH/SZ/BJ; small/medium/large/xlarge orders + main net inflow)",
|
|
440
440
|
expects: "list",
|
|
441
441
|
},
|
|
442
|
+
"quote.sector-fund-flow": {
|
|
443
|
+
method: "POST",
|
|
444
|
+
path: "/application/open-quote/fund-flow/sector-daily",
|
|
445
|
+
kind: "json",
|
|
446
|
+
description: "Query sector daily fund flow (constituents summed, SH/SZ/BJ only)",
|
|
447
|
+
expects: "list",
|
|
448
|
+
},
|
|
442
449
|
"fundamental.income-statement": {
|
|
443
450
|
method: "POST",
|
|
444
451
|
path: "/application/open-fundamental/financial-report/income-statement/accumulated",
|
|
@@ -1151,6 +1158,35 @@ const ENDPOINT_DEFS = {
|
|
|
1151
1158
|
itemFailures: true,
|
|
1152
1159
|
description: "Remove securities from a stock pool",
|
|
1153
1160
|
},
|
|
1161
|
+
"vault.stock-note.list": {
|
|
1162
|
+
method: "POST",
|
|
1163
|
+
path: "/application/open-vault/stock-pool/getNoteList",
|
|
1164
|
+
kind: "json",
|
|
1165
|
+
description: "List your stock notes (all, by security, or by note ID)",
|
|
1166
|
+
pagination: { enabled: true, maxPageSize: 50 },
|
|
1167
|
+
rowId: "stockNoteId",
|
|
1168
|
+
},
|
|
1169
|
+
"vault.stock-note.add": {
|
|
1170
|
+
method: "POST",
|
|
1171
|
+
path: "/application/open-vault/stock-pool/addNote",
|
|
1172
|
+
kind: "json",
|
|
1173
|
+
retry: "no-replay",
|
|
1174
|
+
description: "Add a stock note linked to 1-50 securities",
|
|
1175
|
+
},
|
|
1176
|
+
"vault.stock-note.update": {
|
|
1177
|
+
method: "POST",
|
|
1178
|
+
path: "/application/open-vault/stock-pool/updateNote",
|
|
1179
|
+
kind: "json",
|
|
1180
|
+
description: "Replace the content of a stock note",
|
|
1181
|
+
},
|
|
1182
|
+
"vault.stock-note.delete": {
|
|
1183
|
+
method: "POST",
|
|
1184
|
+
path: "/application/open-vault/stock-pool/deleteNote",
|
|
1185
|
+
kind: "json",
|
|
1186
|
+
destructive: { warning: "删除笔记会删掉整条笔记(含它与全部关联证券的关系),不可恢复;先用 'gangtise vault stock-note-list --note-id <id>' 核对内容。" },
|
|
1187
|
+
itemFailures: true,
|
|
1188
|
+
description: "Delete stock notes",
|
|
1189
|
+
},
|
|
1154
1190
|
"alternative.edb-search": {
|
|
1155
1191
|
method: "POST",
|
|
1156
1192
|
path: "/application/open-alternative/EDB/search",
|
package/dist/src/core/errors.js
CHANGED
|
@@ -48,13 +48,13 @@ const ERROR_HINTS = {
|
|
|
48
48
|
"100003": "msg 已指明字段名或取值范围时直接按 msg 改;msg 只说「参数值非法」时多为枚举参数拼写错误(如 --source / --question-category / --answer-important),对照命令 --help 列出的合法值检查。",
|
|
49
49
|
"100004": "检查 --size / --from 是否为非负数且未超单页上限。",
|
|
50
50
|
"100005": "对照命令 --help 列出的合法取值检查。",
|
|
51
|
-
"100006": "缩短日期范围或调小 --size / --limit(list 类端点单页上限为 50,CLI 自动翻页,直发接口时要自己遵守);indicator cross-section / time-series 撞此码是单次单元格数超 30000(截面=证券数×指标数,时序=证券数或指标数×日期数),按这个乘积拆批重跑;fund 命令不分页,撞此码是单次超过 10000 行,减少 --security
|
|
51
|
+
"100006": "缩短日期范围或调小 --size / --limit(list 类端点单页上限为 50,CLI 自动翻页,直发接口时要自己遵守);indicator cross-section / time-series 撞此码是单次单元格数超 30000(截面=证券数×指标数,时序=证券数或指标数×日期数),按这个乘积拆批重跑;fund 命令不分页,撞此码是单次超过 10000 行,减少 --security 只数或缩短日期区间分批查;quote sector-fund-flow 撞此码是计算量超限,缩短日期区间、减少 --sector 或 --field(调小 --limit 无效)。",
|
|
52
52
|
"110001": "看参数名:`--*-date` 用 YYYY-MM-DD,`--*-time` 用 \"YYYY-MM-DD HH:mm:ss\"(`ai knowledge-batch` 的 --start-time/--end-time 收时间戳或 datetime,CLI 统一转 13 位毫秒)。",
|
|
53
53
|
"110002": "起始晚于结束——检查 --start-date/--end-date 或 --start-time/--end-time 的先后。",
|
|
54
54
|
"110003": "查询时间超出本账号的数据权限范围——把日期移进范围内(整个区间都早于下界时缩短窗口无用),或联系客户经理开通更长历史。",
|
|
55
55
|
"120001": "用 `gangtise reference securities-search` 确认代码与后缀(如 600519.SH / 00700.HK)。",
|
|
56
56
|
"130001": "未找到数据——先核对查询条件;EDE 指标端点此码也可能是未开通该指标权限,仍失败联系客户经理。",
|
|
57
|
-
"130002": "确认 ID 有效且本账号可见(云盘的文件 / 文件夹 ID 用 `vault drive-folder-list` 核对;删除类命令收到此码可能是目标已被删掉);下载类还需检查 --file-type 取值是否合法。",
|
|
57
|
+
"130002": "确认 ID 有效且本账号可见(云盘的文件 / 文件夹 ID 用 `vault drive-folder-list` 核对,笔记 ID 用 `vault stock-note-list` 核对;删除类命令收到此码可能是目标已被删掉);下载类还需检查 --file-type 取值是否合法。",
|
|
58
58
|
"130003": "资源未生成或该条记录未附带文件——换一条有附件的记录(列表里 hasAttachment/hasFile 为 true 的),或稍后再试。",
|
|
59
59
|
"130004": "下载 ID 需为数字,检查该命令的 --*-id 参数是否传对。",
|
|
60
60
|
"130005": "对照命令 --help 检查 --file-type / --content-type 取值。",
|
|
@@ -97,11 +97,24 @@ const ERROR_HINTS = {
|
|
|
97
97
|
};
|
|
98
98
|
function distinctFailureShapes(message) {
|
|
99
99
|
const byCode = new Map();
|
|
100
|
+
let current;
|
|
100
101
|
for (const clause of message.split(/[;;]/)) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
let code, kind, key;
|
|
103
|
+
const old = /指标\s+(\S+)\s+(不支持参数|缺少必填参数)[::]?\s*(\S+)/.exec(clause);
|
|
104
|
+
if (old) {
|
|
105
|
+
[, code, kind, key] = old;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const head = /^\s*([^\s::]+)(?:\s*\(指标代码\s*[^)]*\))?\s*[::]/.exec(clause);
|
|
109
|
+
if (head)
|
|
110
|
+
current = head[1];
|
|
111
|
+
const body = /不支持参数\s*(\S+)|必填参数\s*(\S+)\s*不能为空/.exec(clause);
|
|
112
|
+
if (!body || !current)
|
|
113
|
+
continue;
|
|
114
|
+
code = current;
|
|
115
|
+
kind = body[1] ? "不支持参数" : "缺少必填参数";
|
|
116
|
+
key = body[1] ?? body[2];
|
|
117
|
+
}
|
|
105
118
|
let shape = byCode.get(code);
|
|
106
119
|
if (!shape) {
|
|
107
120
|
shape = new Set();
|
|
@@ -113,36 +126,37 @@ function distinctFailureShapes(message) {
|
|
|
113
126
|
return 1;
|
|
114
127
|
return new Set([...byCode.values()].map((shape) => [...shape].sort().join("|"))).size;
|
|
115
128
|
}
|
|
129
|
+
const missing = (keys) => new RegExp(`缺少必填参数[::]?\\s*(?:${keys})|必填参数\\s*(?:${keys})\\s*不能为空`);
|
|
116
130
|
const MESSAGE_HINTS = [
|
|
117
131
|
{
|
|
118
132
|
codes: ["100001", "100003"],
|
|
119
|
-
match:
|
|
133
|
+
match: new RegExp(`不支持参数\\s*(?:tradeDate|reportDate)|${missing("tradeDate|reportDate").source}`),
|
|
120
134
|
when: (message) => distinctFailureShapes(message) > 1,
|
|
121
|
-
hint: "🔴 **这条报错涉及多个指标,而且它们失败的方式不同**——同一个改法套不到全部,按 msg 里每个指标各自说的话分别处理:msg 里对某个指标说「不支持参数 X;
|
|
135
|
+
hint: "🔴 **这条报错涉及多个指标,而且它们失败的方式不同**——同一个改法套不到全部,按 msg 里每个指标各自说的话分别处理:msg 里对某个指标说「不支持参数 X; 必填参数 Y 不能为空」(旧写法「缺少必填参数 Y」)= 把 X 换成 Y(`--indicator-param \"<该指标code>:Y=...\"`);只说「不支持参数 X」而没说缺什么 = 该指标一个日期都不要,给它加 `--indicator-param \"<该指标code>:\"`(冒号后留空);只说 Y 必填 = 补上 Y。**最省事的办法是把这批指标拆成几次单独查**,那样每条报错只对应一个指标,提示也能给准。以 `indicator search --keyword <指标code> --format json` 的 parameterList 为准。",
|
|
122
136
|
},
|
|
123
137
|
{
|
|
124
138
|
codes: ["100001", "100003"],
|
|
125
|
-
match:
|
|
139
|
+
match: missing("reportDate"),
|
|
126
140
|
when: (message) => distinctFailureShapes(message) <= 1,
|
|
127
141
|
hint: "报错点名的指标要的是 reportDate(报告期),不是 --date 下发的 tradeDate。给每个被点名的指标各补一条 --indicator-param \"<指标code>:reportDate=YYYY-MM-DD\"(screener 用 \"F1:reportDate=...\"),--date 仍要保留——补上后 CLI 就不再为该指标注入 tradeDate。⚠️ 少数指标两个日期都要(如 div_cash_yld),补完若再报缺 tradeDate,就把 tradeDate 也显式传上。**以 `indicator search` 返回的 parameterList 为准,别按 code 前缀推断**。",
|
|
128
142
|
},
|
|
129
143
|
{
|
|
130
144
|
codes: ["100001", "100003"],
|
|
131
145
|
match: /不支持参数\s*(?:tradeDate|reportDate)/,
|
|
132
|
-
also:
|
|
146
|
+
also: missing("tradeDate"),
|
|
133
147
|
when: (message) => distinctFailureShapes(message) <= 1,
|
|
134
148
|
hint: "服务端在同一句里**已经点名了两边**:一个键不该出现,另一个键缺了——把前者换成后者即可,别再往里加键。多半是两个日期键写反了:删掉 --indicator-param 里被拒的那个,改成 msg 里说缺的那个(如 --indicator-param \"<指标code>:tradeDate=YYYY-MM-DD\",screener 用 \"F1:tradeDate=...\")。⚠️ 别在这一步用 --indicator-param \"<指标code>:\"(「不要日期」的写法)——服务端明说了它要一个日期,那样只会撞下一个报错。",
|
|
135
149
|
},
|
|
136
150
|
{
|
|
137
151
|
codes: ["100001", "100003"],
|
|
138
152
|
match: /不支持参数\s*(?:tradeDate|reportDate)/,
|
|
139
|
-
notMatch:
|
|
153
|
+
notMatch: missing("reportDate|tradeDate"),
|
|
140
154
|
when: (message) => distinctFailureShapes(message) <= 1,
|
|
141
155
|
hint: "服务端只说这个键不该出现,**没说该换成哪个**——别照着「改用 reportDate」做,那多半同样被拒。报错点名的指标,其 parameterList 里没有 --date 下发的那个日期键,给每个被点名的指标各加一条 --indicator-param \"<指标code>:\"(冒号后留空)声明「本指标不要查询日期」即可,它与真实参数可以共存。跑 `indicator search --keyword <指标code> --format json` 确认还缺什么:parameterList 为空就只加这一条;还列了 fiscalYear 之类的就连同 --indicator-param \"<指标code>:fiscalYear=2025\" 一起给。screener 上是同一个写法,只是按变量写:--indicator-param \"F1:\"。",
|
|
142
156
|
},
|
|
143
157
|
{
|
|
144
158
|
codes: ["100001", "100003"],
|
|
145
|
-
match:
|
|
159
|
+
match: missing("tradeDate"),
|
|
146
160
|
when: (message) => distinctFailureShapes(message) <= 1,
|
|
147
161
|
hint: "--date 的 tradeDate 这次没有下发——你为该指标传了 reportDate/tradeDate,或者用了 --indicator-param \"<指标code>:\" 这个「不要日期」的写法,两者都会关掉自动注入。对症改:如果这个指标两个日期都必填(如 div_cash_yld),把 tradeDate 也显式传上 --indicator-param \"<指标code>:tradeDate=YYYY-MM-DD\"(screener 用 \"F1:tradeDate=...\");如果是空冒号那条加错了指标,删掉它即可。",
|
|
148
162
|
},
|