gangtise-openapi-cli 0.35.0 → 0.36.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 +28 -8
- package/dist/src/cli.js +1 -1
- package/dist/src/core/args.js +30 -17
- package/dist/src/core/client.js +1 -5
- package/dist/src/core/commandBodies.js +3 -3
- package/dist/src/core/errors.js +1 -1
- package/dist/src/version.js +1 -1
- package/gangtise-openapi/SKILL.md +10 -7
- package/gangtise-openapi/references/commands/ai.md +3 -1
- package/gangtise-openapi/references/commands/indicator.md +7 -6
- package/gangtise-openapi/references/commands/insight.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,14 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
README 仅列最近 5 个版本摘要:
|
|
8
8
|
|
|
9
|
-
- **v0.
|
|
9
|
+
- **v0.36.0 — 2026-08-18**:**日期写法放宽**——`YYYY-MM-DD`、`YYYY/MM/DD`、`YYYYMMDD` 三种「年在前」写法都收,统一归一成 `YYYY-MM-DD` 发出(datetime 只归一日期部分,Unix 时间戳原样透传);「年在后」写法(`01-07-2026`)仍在本地拒绝——接口会按美式「月在前」解析它,欧洲习惯写法会静默拿到差半年的数据,详见「关于日期格式」。**`indicator screener` 支持无日期指标**:`--indicator-param "F1:"`(冒号后留空)声明该指标不要查询日期,`pty_*` / `scr_*` 静态属性两族与 `div_cash_paid_ratio` / `div_cash_yr` / `pty_shr_reg` 现在可以直接用于条件选股(此前只能在 `cross-section` 取回后本地筛),写法与截面一致、可与真实参数共存。**全量拉取的 `total` 封顶探测恢复覆盖 `ai hot-topic`**(v0.35.0 曾跳过它,前提有误——见下)。另:EDE 报错提示同步更新;补充计费说明(`ai hot-topic` 50/篇 的「篇」= 一整份报告;按篇/按条计费的接口查不到内容不扣分)。
|
|
10
|
+
- **v0.35.0 — 2026-08-16**:新增 `--indicator-param "<code>:"`(冒号后留空)声明「这个指标不要查询日期」,用于 `parameterList` 里没有日期参数的指标——`pty_*`(经营范围/注册地/法定代表人…)、`scr_*`(上市市场/上市板块/ISIN…)两族,以及 `div_cash_paid_ratio` / `div_cash_yr` / `pty_shr_reg`。这类指标此前在 `indicator cross-section` 上取不到数(`--date` 必填且会注入 `tradeDate`,而它们不收,整条请求被拒);该写法可与真实参数共存(`"code:" + "code:fiscalYear=2025"`)。**EDE 日期参数报错提示重写**:拆成五种报文形态分别给建议,服务端同时点名「不该有的键」和「缺的键」时直说换哪个,只说了一半时不再瞎猜,多指标批量报错时不再用单数口吻指向其中一个指标。另:`--rank-type` 的说明按实测更正(差别大小取决于关键词,`--search-type` 不影响 `--rank-type 1` 取回哪些条目);`ai hot-topic` 全量拉取跳过 `total` 封顶探测(**该改动已撤回,见下一版**——它的前提「`hot-topic` 按次计费」是错的,实际按篇计费,而按篇/按条计费的接口查不到内容不扣分,所以那次探测本来就不花钱)。
|
|
10
11
|
- **v0.34.1 — 2026-08-15**:EDE 报告期类指标传错日期参数时,报错里直接给出该改的 CLI 写法(此前只有服务端那句「缺少必填参数 reportDate」,要自己推断该用 `--indicator-param`);该提示对 `100001` / `100003` 两个错误码都生效,并注明少数指标两个日期都要、另有指标要 `fiscalYear`,一律以 `indicator search` 的 `parameterList` 为准。另修复下载文件名缓存在并发写入下可能丢条目的问题(单进程使用不受影响)。
|
|
11
12
|
- **v0.34.0 — 2026-08-15**:跟进 2026-08-14 服务端更新。🔴 **破坏性**:`quote day-kline --security all` 已失效(服务端停止支持),改用 `aShares` / `hkStocks` / `usStocks`,且市场关键字必须单独传(不能与证券代码或另一个关键字混填)——这两种写法 CLI 都会在发请求前报错并指出正确写法;`ai stock-summary` 同理不再接受市场关键字。`day-kline` 现覆盖 A股/港股/美股个股与交易所/概念/行业指数(可混查),三个旧命令 `day-kline-hk`/`day-kline-us`/`index-day-kline` 标记为已下线;`minute-kline` 支持指数。**另两处影响取数完整性的修复**:`quote fund-flow` 把市场关键字与证券代码混填时,此前会只返回那几个代码的数据且不报错("全市场 + 这只"静默变成"只有这只"),现改为本地拦截;`quote index-day-kline --security all` 跨 30 天以上时分片过宽会撞行数上限被截断(标 `partial` + 退出 3),现已调细粒度,同区间可完整取回。另:三大报表新增 `earliestAnncDate`(做时点对齐用它,不要用 `announcementDate`),EDE 报告期类指标改为必须显式传 `reportDate`,错误码提示按新行为更新。
|
|
12
13
|
- **v0.33.0 — 2026-08-09**:四处行为变更,都是把「静默给出看着正常的错结果」改成显式失败——分页端点返回异形首包(含 `data: null`)改退出码 3;`total` 被服务端封顶时探测并标 `totalCapped` + 退出 3(三个 opinion 端点的 `total` 恒为 10000 而实际远不止,全量导出此前会被静默截断);空结果不再在 stdout 留空行、`null` 不再被渲染成一条记录。另补多处帮助文案与 EDE 占位值(个别指标填 `0` 而非 `null`)的说明。
|
|
13
|
-
- **v0.32.0 — 2026-08-08**:新增帕米尔专家纪要列表与下载;跟进 2026-08-07 服务端更新——EDE 缺数据不再整列/整行消失(退出码 3 现在专指「代码没被识别」),条件选股重复指标的 `unreliable` 告警随之移除;`--search-type`/`--rank-type`/`--file-type` 等枚举改为本地拦截(此前传非法值不会报错,会拿到未过滤的全量)。
|
|
14
|
-
- **v0.31.0 — 2026-08-03**:修复 v0.30.1 的矩阵维度校验误杀「单指标 × 板块 ID」时序查询的回归;EDE 结果不完整或不可信时改以退出码 3 标记(`partial` / `unreliable`)。
|
|
15
|
-
- **v0.30.1 — 2026-08-02**:修复 `sDate` 吞掉查询日期导致的区间指标静默错数,让条件选股的文本筛选真正可用,并在服务端整行/整列丢数据时给出警告。
|
|
16
|
-
- **v0.30.0 — 2026-08-02**:适配 EDE 接口重构(`universe` 取代 `securityCodeList`、截面矩阵转置、指标元数据结构化),新增 `indicator screener` 条件选股,并修正复权参数名。
|
|
17
14
|
|
|
18
15
|
### 历史里程碑
|
|
19
16
|
|
|
@@ -263,7 +260,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
|
|
|
263
260
|
- **HTTP keep-alive**:所有请求复用同一个 `undici.Agent`(连接池 16),避免重复 TLS 握手。
|
|
264
261
|
- **流式下载**:指定 `--output` 时,二进制响应(PDF 等)直接 `pipeline` 到磁盘,不经过内存缓冲;50MB PDF 内存占用近乎为零。
|
|
265
262
|
- **流式输出**:`jsonl`/`csv` 格式且 `--output` 指定时,超过 1000 行自动切换为逐行写盘,避免一次性构建百 MB 字符串。
|
|
266
|
-
- **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**贵档端点例外**(one-pager 等生成/提交类 + `tool file-parse` 提交 + 50/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载,共 18 个):5xx
|
|
263
|
+
- **自动重试**:5xx / 429 / `ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `UND_ERR_*`(undici 连接/超时类)/ `999999` 系统错误自动指数退避重试 2 次。**贵档端点例外**(one-pager 等生成/提交类 + `tool file-parse` 提交 + 50/篇 的 summary / foreign-report / my-conference 下载 + 单价未公布但保守同档的 pamirs-summary 下载,共 18 个):5xx/超时不重放——**重放会重复扣分**:服务端可能已经执行并计费,重发按次计费的再扣一次,重发按篇/按条计费的会把已交付的行再计一次;仅连接失败、429 与 token 自愈重试。**`indicator`(EDE)端点对 `999999` 不重试**——重放一次已计费的查询没有意义(该码 2026-08-01 前还兼表「查询无数据」,现在无数据是保留行列的占位单元格(多数指标 `null`、个别如 `is_dnrpnp` 是 `0`),空表另表示整轴 code 未识别)。**终态码 `999011`(凭证无效)/ `140002`(异步生成失败)在任何 HTTP 状态下都不重试**——凭证错不会因重试而变,异步生成失败是终态。
|
|
267
264
|
- **Token 自愈**:调用返回 `0000001008` / `999002` 时自动强制刷新 Token 并重试一次。
|
|
268
265
|
- **K线/资金流向自动分片**:`quote day-kline --security aShares|hkStocks|usStocks`、`quote fund-flow --security aShares` 等全市场查询自动按日期切分(A股 K线/资金流向 1 天/片、美股 1 天/片、港股 2 天/片;已下线的 `day-kline-hk`/`day-kline-us`/`index-day-kline` 用 `all`,分别 2/1/15 天/片),并发执行后合并结果;按日分片自动跳过周六日。分片时如果用户未传 `--limit`,自动注入 `limit: 10000`(API 上限)避免默认 6000 截断。
|
|
269
266
|
- **Token 内存缓存**:Token 在进程内存中缓存,避免每次请求读盘。
|
|
@@ -707,11 +704,34 @@ CLI 会在本地校验常见数值参数,避免把明显非法的请求发到
|
|
|
707
704
|
- `--from`:非负整数
|
|
708
705
|
- `--size` / `--limit` / `--top`:正整数
|
|
709
706
|
- `--file-type` / `--resource-type` 以及数值型列表参数:有限数字
|
|
710
|
-
- 所有 date 参数(`--start-date`/`--end-date`/`--date`/`--report-date`,含 Quote/Fundamental/AI/Alternative/Indicator):`YYYY-MM-DD
|
|
711
|
-
- 所有 `--start-time` / `--end-time`(Insight/Vault/AI 透传、`quote minute-kline`,以及 A 股公告 / `knowledge-batch`
|
|
707
|
+
- 所有 date 参数(`--start-date`/`--end-date`/`--date`/`--report-date`,含 Quote/Fundamental/AI/Alternative/Indicator):`YYYY-MM-DD`、`YYYY/MM/DD` 或 `YYYYMMDD`,统一归一成 `YYYY-MM-DD` 发出(年在后等歧义写法在发请求前拒绝,见下节)
|
|
708
|
+
- 所有 `--start-time` / `--end-time`(Insight/Vault/AI 透传、`quote minute-kline`,以及 A 股公告 / `knowledge-batch` 两个转换端点):上述三种日期写法 + 可选的 `[ HH:mm[:ss]]`(秒可省、空格或 `T` 分隔),或 10/13 位 Unix 时间戳(同样归一日期部分、拒绝年在后写法)
|
|
712
709
|
|
|
713
710
|
校验失败会输出 `ValidationError: Invalid ...` 并以非 0 状态退出。
|
|
714
711
|
|
|
712
|
+
### 关于日期格式
|
|
713
|
+
|
|
714
|
+
**CLI 接受三种「年在前」写法,并统一归一成 `YYYY-MM-DD` 再发出:**
|
|
715
|
+
|
|
716
|
+
| 你写的 | 发出去的 |
|
|
717
|
+
|--------|---------|
|
|
718
|
+
| `2026-07-01` | `2026-07-01` |
|
|
719
|
+
| `2026/07/01` | `2026-07-01` |
|
|
720
|
+
| `20260701` | `2026-07-01` |
|
|
721
|
+
|
|
722
|
+
datetime 参数(`--start-time` / `--end-time`)同理,只归一日期部分:`2026/07/01 09:30:00` → `2026-07-01 09:30:00`;Unix 时间戳原样透传。
|
|
723
|
+
|
|
724
|
+
**「年在后」的写法(`07-01-2026`、`01/07/2026`)会被本地拒绝**,因为它对不同人意思不同:
|
|
725
|
+
|
|
726
|
+
| 写法 | 美式读法 | 欧洲读法 | 平台实际按 |
|
|
727
|
+
|------|---------|---------|-----------|
|
|
728
|
+
| `01-07-2026` | 1 月 7 日 | 7 月 1 日 | **1 月 7 日**(美式) |
|
|
729
|
+
| `07-01-2026` | 7 月 1 日 | 1 月 7 日 | **7 月 1 日**(美式) |
|
|
730
|
+
|
|
731
|
+
平台接口本身会解析年在后写法,**一律按美式「月在前」**。所以按欧洲/国际习惯用 `01-07-2026` 表示「7 月 1 日」的话,会拿到 1 月 7 日的数据——请求返回 200、行数看着也正常,**不会有任何报错提示**。CLI 在发请求前就拒掉这类写法(不发请求、不计费,报错直接给出可用的格式),所以经 CLI 调用不会踩到这个坑。
|
|
732
|
+
|
|
733
|
+
⚠️ **绕过 CLI 直接调 HTTP 接口时,请统一使用 `YYYY-MM-DD`。**
|
|
734
|
+
|
|
715
735
|
## 常见错误
|
|
716
736
|
|
|
717
737
|
| 错误/错误码 | 说明 |
|
package/dist/src/cli.js
CHANGED
|
@@ -710,7 +710,7 @@ indicator.command("time-series").option("--indicator <code>", "Indicator code, e
|
|
|
710
710
|
flagDropped(rows, data, options.security, options.indicator);
|
|
711
711
|
await printData(rows, format, options.output);
|
|
712
712
|
}));
|
|
713
|
-
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 (REQUIRED, repeat)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH, or a sector ID from 'gangtise reference sector-search' (REQUIRED, 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").requiredOption("--date <date>", "Data date (yyyy-MM-dd); sent as every indicator's tradeDate unless it already has one — omitting it leaves date-bearing indicators unfiltered and silently yields an empty screen. Report-period indicators (is_*) reject tradeDate: give them --indicator-param 'F1:reportDate=yyyy-MM-dd'", dateArg("--date")).option("--indicator-param <spec>", "Per-variable param 'F1:key=value', e.g. F1:scale=8 (repeat); read exact keys from 'indicator search'.
|
|
713
|
+
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 (REQUIRED, repeat)", collectList, []).option("--security <code>", "Security code, e.g. 600519.SH, or a sector ID from 'gangtise reference sector-search' (REQUIRED, 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").requiredOption("--date <date>", "Data date (yyyy-MM-dd); sent as every indicator's tradeDate unless it already has one — omitting it leaves date-bearing indicators unfiltered and silently yields an empty screen. Report-period indicators (is_*) reject tradeDate: give them --indicator-param 'F1:reportDate=yyyy-MM-dd'", dateArg("--date")).option("--indicator-param <spec>", "Per-variable param 'F1:key=value', e.g. F1:scale=8 (repeat); read exact keys from 'indicator search'. Bare 'F1:' (nothing after the colon) declares that the indicator takes NO date — required by any indicator whose parameterList has no date key at all, which otherwise rejects the tradeDate --date injects: the pty_* / scr_* static-attribute families (pty_op_scope, scr_exchg_sctr, scr_isin …), plus div_cash_paid_ratio / div_cash_yr (add 'F1:fiscalYear=YYYY' too) and pty_shr_reg. It composes with real params, so 'F1:' + 'F1:scale=8' keeps the scale", 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) => {
|
|
714
714
|
const format = parseOutputFormat(options.format);
|
|
715
715
|
requireIndicatorScope(options.indicator, options.security);
|
|
716
716
|
const bindings = parseScreenerIndicators(options.indicator, options.indicatorParam, options.expression);
|
package/dist/src/core/args.js
CHANGED
|
@@ -82,27 +82,39 @@ export function parseChoiceList(values, optionName, allowed) {
|
|
|
82
82
|
}
|
|
83
83
|
return maybeArray(values);
|
|
84
84
|
}
|
|
85
|
-
const
|
|
85
|
+
const YEAR_FIRST_DATE = /^(\d{4})([-/]?)(\d{2})\2(\d{2})$/;
|
|
86
|
+
function normalizeYearFirstDate(value) {
|
|
87
|
+
const parts = YEAR_FIRST_DATE.exec(value);
|
|
88
|
+
return parts ? `${parts[1]}-${parts[3]}-${parts[4]}` : undefined;
|
|
89
|
+
}
|
|
86
90
|
export function parseDateOption(value, optionName) {
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
const normalized = normalizeYearFirstDate(value);
|
|
92
|
+
if (normalized === undefined) {
|
|
93
|
+
throw new ValidationError(`Invalid ${optionName}: expected YYYY-MM-DD (YYYY/MM/DD and YYYYMMDD also accepted), got "${value}" — year-last layouts are refused because the API reads them month-first (US convention): "01-07-2026" means 7 January, not 1 July`);
|
|
89
94
|
}
|
|
90
|
-
const [year, month, day] =
|
|
91
|
-
const parsed = new Date(`${
|
|
95
|
+
const [year, month, day] = normalized.split("-").map(Number);
|
|
96
|
+
const parsed = new Date(`${normalized}T00:00:00Z`);
|
|
92
97
|
if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) {
|
|
93
98
|
throw new ValidationError(`Invalid ${optionName}: "${value}" is not a real calendar date`);
|
|
94
99
|
}
|
|
95
|
-
return
|
|
100
|
+
return normalized;
|
|
96
101
|
}
|
|
97
102
|
export function dateArg(optionName) {
|
|
98
103
|
return (value) => parseDateOption(value, optionName);
|
|
99
104
|
}
|
|
100
|
-
const LOCAL_DATETIME = /^(\d{4})
|
|
105
|
+
const LOCAL_DATETIME = /^(\d{4})([-/]?)(\d{2})\2(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?$/;
|
|
106
|
+
function normalizeDatetime(value) {
|
|
107
|
+
const parts = LOCAL_DATETIME.exec(value);
|
|
108
|
+
if (!parts)
|
|
109
|
+
return value;
|
|
110
|
+
const [, y, sep, mo, d] = parts;
|
|
111
|
+
return sep === "" ? `${y}-${mo}-${d}${value.slice(8)}` : `${y}-${mo}-${d}${value.slice(10)}`;
|
|
112
|
+
}
|
|
101
113
|
function datetimeFieldsValid(value) {
|
|
102
114
|
const parts = LOCAL_DATETIME.exec(value);
|
|
103
115
|
if (!parts)
|
|
104
116
|
return false;
|
|
105
|
-
const [, y, mo, d, hh = "0", mi = "0", ss = "0"] = parts;
|
|
117
|
+
const [, y, , mo, d, hh = "0", mi = "0", ss = "0"] = parts;
|
|
106
118
|
const year = Number(y), month = Number(mo), day = Number(d);
|
|
107
119
|
if (month < 1 || month > 12)
|
|
108
120
|
return false;
|
|
@@ -128,7 +140,7 @@ export function toTimestamp13(value) {
|
|
|
128
140
|
if (!datetimeFieldsValid(value))
|
|
129
141
|
return undefined;
|
|
130
142
|
const parts = LOCAL_DATETIME.exec(value);
|
|
131
|
-
const [, y, mo, d, hh = "0", mi = "0", ss = "0"] = parts;
|
|
143
|
+
const [, y, , mo, d, hh = "0", mi = "0", ss = "0"] = parts;
|
|
132
144
|
const year = Number(y);
|
|
133
145
|
const dt = new Date(year, Number(mo) - 1, Number(d), Number(hh), Number(mi), Number(ss));
|
|
134
146
|
const faithful = dt.getFullYear() === year && dt.getMonth() === Number(mo) - 1
|
|
@@ -141,15 +153,17 @@ export function parseTimestamp13(value, optionName) {
|
|
|
141
153
|
return undefined;
|
|
142
154
|
const parsed = toTimestamp13(value);
|
|
143
155
|
if (parsed === undefined) {
|
|
144
|
-
throw new ValidationError(`Invalid ${optionName}: expected a Unix timestamp or "YYYY-MM-DD" optionally with " HH:mm[:ss]" (space or T separator), got "${value}" — year-last
|
|
156
|
+
throw new ValidationError(`Invalid ${optionName}: expected a Unix timestamp or "YYYY-MM-DD" optionally with " HH:mm[:ss]" (space or T separator; YYYY/MM/DD and YYYYMMDD also accepted), got "${value}" — year-last layouts are refused because the API reads them month-first (US convention): "01-07-2026" means 7 January, not 1 July`);
|
|
145
157
|
}
|
|
146
158
|
return parsed;
|
|
147
159
|
}
|
|
148
160
|
export function parseDatetimeOption(value, optionName) {
|
|
149
|
-
if (epochMillis(value)
|
|
150
|
-
|
|
161
|
+
if (epochMillis(value) !== undefined)
|
|
162
|
+
return value;
|
|
163
|
+
if (!datetimeFieldsValid(value)) {
|
|
164
|
+
throw new ValidationError(`Invalid ${optionName}: expected a Unix timestamp or "YYYY-MM-DD" optionally with " HH:mm[:ss]" (space or T separator; YYYY/MM/DD and YYYYMMDD also accepted), got "${value}" — year-last layouts are refused because the API reads them month-first (US convention): "01-07-2026" means 7 January, not 1 July`);
|
|
151
165
|
}
|
|
152
|
-
return value;
|
|
166
|
+
return normalizeDatetime(value);
|
|
153
167
|
}
|
|
154
168
|
export function datetimeArg(optionName) {
|
|
155
169
|
return (value) => parseDatetimeOption(value, optionName);
|
|
@@ -287,10 +301,9 @@ export function parseScreenerIndicators(bindings, paramSpecs, expression) {
|
|
|
287
301
|
if (indicators.has(field)) {
|
|
288
302
|
throw new ValidationError(`Duplicate --indicator variable "${field}": each variable must bind exactly one indicator`);
|
|
289
303
|
}
|
|
290
|
-
indicators.set(field,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
throw new ValidationError(`--indicator-param "${[...noQueryDate][0]}:" (the no-date opt-out) is not supported by the screener: an indicator sent with no parameters does not take part in the screen, so it would run without that condition and answer 0 rows with no error. Read the value with 'indicator cross-section --indicator-param "<code>:"' and filter client-side.`);
|
|
304
|
+
indicators.set(field, noQueryDate.has(field)
|
|
305
|
+
? { field, indicatorCode, parameters: params.get(field) ?? [], noQueryDate: true }
|
|
306
|
+
: { field, indicatorCode, parameters: params.get(field) ?? [] });
|
|
294
307
|
}
|
|
295
308
|
for (const field of params.keys()) {
|
|
296
309
|
if (!indicators.has(field)) {
|
package/dist/src/core/client.js
CHANGED
|
@@ -237,7 +237,7 @@ export class GangtiseClient {
|
|
|
237
237
|
}
|
|
238
238
|
const short = collected.length < target;
|
|
239
239
|
let totalCapped = false;
|
|
240
|
-
if (
|
|
240
|
+
if (requestedSize === undefined && total > 0 && !short && !totalDrift && !truncatedByPageCap && failedPages.length === 0) {
|
|
241
241
|
try {
|
|
242
242
|
const probe = await this.requestJson(endpoint, { ...initialBody, from: total, size: 1 });
|
|
243
243
|
if (this.isPaginatedListResponse(probe) && probe.list.length > 0)
|
|
@@ -249,10 +249,6 @@ export class GangtiseClient {
|
|
|
249
249
|
if (totalCapped) {
|
|
250
250
|
process.stderr.write(`[gangtise] warning: ${endpoint.key} reported total=${total} but rows exist past that offset — 'total' is a server-side cap, not the real count. This export is TRUNCATED at ${collected.length} rows. Narrow the query (date range / filters) and fetch in slices.\n`);
|
|
251
251
|
}
|
|
252
|
-
if (endpoint.retry === "no-replay" && requestedSize === undefined && total > 0
|
|
253
|
-
&& !short && !totalDrift && !truncatedByPageCap && failedPages.length === 0 && isVerbose()) {
|
|
254
|
-
process.stderr.write(`[gangtise] total-cap probe skipped on ${endpoint.key} (per-call billing) — a capped 'total' would go undetected here\n`);
|
|
255
|
-
}
|
|
256
252
|
const out = {
|
|
257
253
|
...firstPage,
|
|
258
254
|
total,
|
|
@@ -80,8 +80,8 @@ export function buildIndicatorScreenerBody(options) {
|
|
|
80
80
|
return {
|
|
81
81
|
universe: maybeArray(options.security),
|
|
82
82
|
expression: options.expression,
|
|
83
|
-
indicatorList: indicators.map((indicator) => (indicator.parameters.some((param) => DATE_PARAM_KEYS.has(param.paramKey))
|
|
84
|
-
? indicator
|
|
85
|
-
: { ...indicator, parameters: [...indicator.parameters, { paramKey: "tradeDate", paramValue: options.date }] })),
|
|
83
|
+
indicatorList: indicators.map((indicator) => (indicator.noQueryDate || indicator.parameters.some((param) => DATE_PARAM_KEYS.has(param.paramKey))
|
|
84
|
+
? stripMarker(indicator)
|
|
85
|
+
: { ...stripMarker(indicator), parameters: [...indicator.parameters, { paramKey: "tradeDate", paramValue: options.date }] })),
|
|
86
86
|
};
|
|
87
87
|
}
|
package/dist/src/core/errors.js
CHANGED
|
@@ -124,7 +124,7 @@ const MESSAGE_HINTS = [
|
|
|
124
124
|
match: /不支持参数\s*(?:tradeDate|reportDate)/,
|
|
125
125
|
notMatch: /缺少必填参数[::]?\s*(?:reportDate|tradeDate)/,
|
|
126
126
|
when: (message) => distinctFailureShapes(message) <= 1,
|
|
127
|
-
hint: "服务端只说这个键不该出现,**没说该换成哪个**——别照着「改用 reportDate」做,那多半同样被拒。报错点名的指标,其 parameterList 里没有 --date 下发的那个日期键,给每个被点名的指标各加一条 --indicator-param \"<指标code>:\"(冒号后留空)声明「本指标不要查询日期」即可,它与真实参数可以共存。跑 `indicator search --keyword <指标code> --format json` 确认还缺什么:parameterList 为空就只加这一条;还列了 fiscalYear 之类的就连同 --indicator-param \"<指标code>:fiscalYear=2025\"
|
|
127
|
+
hint: "服务端只说这个键不该出现,**没说该换成哪个**——别照着「改用 reportDate」做,那多半同样被拒。报错点名的指标,其 parameterList 里没有 --date 下发的那个日期键,给每个被点名的指标各加一条 --indicator-param \"<指标code>:\"(冒号后留空)声明「本指标不要查询日期」即可,它与真实参数可以共存。跑 `indicator search --keyword <指标code> --format json` 确认还缺什么:parameterList 为空就只加这一条;还列了 fiscalYear 之类的就连同 --indicator-param \"<指标code>:fiscalYear=2025\" 一起给。screener 上是同一个写法,只是按变量写:--indicator-param \"F1:\"。",
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
codes: ["100001", "100003"],
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.
|
|
1
|
+
export const CLI_VERSION = "0.36.0";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gangtise-openapi
|
|
3
|
-
version: "0.
|
|
3
|
+
version: "0.36.0"
|
|
4
4
|
description: |-
|
|
5
5
|
通过 gangtise CLI 直接调用 Gangtise OpenAPI,拉取投研原始数据、批量导出、下载文件、调用 AI 能力。
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ description: |-
|
|
|
24
24
|
1. **`--format json`**:列表/数据类必加。AI 内容生成(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `*-check`)也加 json,但呈现时**直接取 `content` 字段**,不要展示 JSON 包装层。
|
|
25
25
|
2. **opaque ID**:先读 `references/lookup-ids.md`;找不到再按类型查:行业/区域/公告分类/城市 → `reference constant-list --category <分类>`(分类代码用 `reference constant-category` 查);题材 → `reference concept-search --keyword <名>`;板块 → `reference sector-search --keyword <名>`;申万 `--gts-code` 行业代码全量 → `sector-search --keyword 申万一级行业指数` 取指数数据板块层级的 sectorId 再 `sector-constituents`;券商/牵头/观点机构(按名称找 ID)→ `reference institution-search --keyword <名> [--category ...]`(服务端搜索,返回 `institutionId` + `usageScopes` 标明该 ID 用于哪个接口的哪个参数;覆盖 `--broker` / `--institution` 全部 5 类机构,含 `foreignOpinionInstitution`)——仅当要**全量枚举**时才用本地表 `gangtise lookup broker-org/meeting-org list`(institution-search 是搜索型:top≤10、非全量)。**绝不猜测**。
|
|
26
26
|
3. **公司名 → 证券代码**:先查下方速查表(5 只 mega-cap),其余一律 `gangtise reference securities-search --keyword <名> --category stock` 取 `list[0].gtsCode`。
|
|
27
|
-
4. **时间格式**:datetime `"YYYY-MM-DD HH:mm:ss"`(引号包裹),date `YYYY-MM-DD
|
|
27
|
+
4. **时间格式**:datetime `"YYYY-MM-DD HH:mm:ss"`(引号包裹),date `YYYY-MM-DD`(`YYYY/MM/DD`、`YYYYMMDD` 也收,会归一;**年在后写法一律拒绝**)。
|
|
28
28
|
5. **多值参数**:优先重复传(最稳、最明确):`--security 600519.SH --security 000858.SZ`。CLI 也支持半/全角逗号分隔(`args.ts` 为语音输入容错),但重复传不易被 shell 吞。
|
|
29
29
|
6. **K 线"最近 N 条"**:必须用 `--start-date`/`--end-date` 拉日期范围,从结果按 `tradeDate` 取尾部最近 N 条。**不要只用 `--limit N`**(截取的是窗口开头)。
|
|
30
30
|
6.1. **日 K 仅历史**:`day-kline` **不返回盘中实时数据**。当日数据入库时间:A 股 ~15:30 / 港股 ~16:30 / 美股 ~07:00(北京时间)。需要盘中快照请走 `quote realtime`。
|
|
@@ -74,9 +74,12 @@ description: |-
|
|
|
74
74
|
- **按条(观点/含详情类 list)**:independent-opinion list 与 `ai security-clue` 5;roadshow/site-visit/strategy/forum list 20;opinion / foreign-opinion list 30;`fundamental earning-forecast` 0.5;`ai stock-summary` 3(无看点的证券不返回也不扣);`alternative edb-data` 30
|
|
75
75
|
- **各 download(/篇)**:announcement / official-account / research 10;announcement-hk / announcement-us 20;independent-opinion 30;summary / foreign-report / my-conference 50;`performance-calendar download` A 股 10 / 港美股 20
|
|
76
76
|
- **按页**:`tool file-parse` 0.8/页,**提交(`--file`)时按实际页数一次性扣**,取结果(`file-parse-check`)免费——50 页 PDF = 40 积分,别重复提交同一文件
|
|
77
|
-
- 🔴 **按次贵**:`ai knowledge-batch` 10、`management-discuss-*` 10;AI Agent(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `earnings-review` / `viewpoint-debate` / `theme-tracking`)**50
|
|
77
|
+
- 🔴 **按次贵**:`ai knowledge-batch` 10、`management-discuss-*` 10;AI Agent(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `earnings-review` / `viewpoint-debate` / `theme-tracking`)**50/次**
|
|
78
|
+
- 🔴 **`ai hot-topic` 50/篇,按返回条数计**(不是按调用次数)。📌 **一「篇」= 一整份报告**(一份早报 / 午报 / 盘中快报 / 晚报),**不是报告里的一条话题**——一份报告通常包含多条热点话题。所以 `--size 20` 的一页 = 最多 20 份报告 = 1000 积分,**先用 `--start-date`/`--end-date` + `--category` 收窄再拉**,别省略 `--size` 直接全量。**可查的历史范围跟账号权限走**(试用档是滚动的「当前 −1 个月」,正式/定制档更长)——超出范围的日期返回空结果而不是报错,拿到空先想想是不是撞了权限窗口
|
|
78
79
|
- 🔴 **极贵**:`alternative concept-info` / `concept-securities` **500/次**
|
|
79
|
-
-
|
|
80
|
+
- ✅ **按篇 / 按条计费的接口,没查到内容就不扣分**(空结果 = 0 积分):各 download(`pamirs-summary` 除外,见 ②)、`ai hot-topic`(50/篇)、`ai stock-summary`(3/条,无看点总结的个股不进返回列表也不计费)。**所以「先小范围试探再放大」是安全的**——先用窄条件确认能查到东西,再扩范围。⚠️ **两类不适用**:① **按次计费的**——`ai knowledge-batch` / `management-discuss-*`、AI Agent 那批 50/次、`alternative concept-info` / `concept-securities` 500/次,不管有没有内容都扣,超时报错也可能已经扣过;② **单价未公布的**——`pamirs-summary`(见下),别据此假定
|
|
81
|
+
- ⚠️ **同参数重复调用不免费**:按次计费的那批无缓存命中豁免(2026-07-11 实测 `one-pager` 重复调用每次扣分,即使秒回缓存内容);**按篇/按条的也一样**——重拉同一批 `hot-topic` 就是按条数再计一次费。生成类与列表结果拿到后自行留存复用,别为「刷新」重调
|
|
82
|
+
- ⚠️ **贵档端点超时/5xx 不自动重放**(共 18 个:AI Agent 那批 + `ai knowledge-batch` / `management-discuss-*` / `hot-topic`、`alternative concept-info`·`concept-securities`、50/篇 的 `summary`·`foreign-report`·`my-conference` download 与同档处理的 `pamirs-summary` download、`tool file-parse` 提交)。**理由是重放会重复扣分**——服务端可能已经执行并计费,重发按次计费的会再扣一次,重发按篇/按条计费的会把**已交付的行**再计一次;两种都亏。仅连接失败、429 与 token 自愈会重试
|
|
80
83
|
- **按单元格**:`indicator cross-section` / `time-series` / `screener`(A股 0.05 / 港股 0.1 / 美股 0.2 积分每 100 单元格;screener 按**筛选前**范围计费,见 `indicator.md`);`ai knowledge-resource-download` 按下游资源计费
|
|
81
84
|
- **单价未公布**:`insight pamirs-summary list` / `download`——spec 只写了「需购买专家纪要数据库」这个准入门槛,没给单次价格。**别据此假定免费**;大批量拉取前先小量试,或向平台确认
|
|
82
85
|
|
|
@@ -263,9 +266,9 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
|
|
|
263
266
|
| 最新一期 / 最新报告期(财报) | — | — | 省略 `--fiscal-year`,传 `--period latest`(默认) |
|
|
264
267
|
| 最新观点 / 今日观点 | 1 天范围 + `--rank-type 2` | — | — |
|
|
265
268
|
|
|
266
|
-
日期参数**按参数名判断、不按命令组**(命令组会误导——AI 里既有 `--start-time` 又有 `--date`/`--report-date`;Insight 里 `performance-calendar` 是唯一用 `--start-date`/`--end-date` 的 list):名字带 `-date` 的(`--start-date`/`--end-date`/`--date`/`--report-date
|
|
269
|
+
日期参数**按参数名判断、不按命令组**(命令组会误导——AI 里既有 `--start-time` 又有 `--date`/`--report-date`;Insight 里 `performance-calendar` 是唯一用 `--start-date`/`--end-date` 的 list):名字带 `-date` 的(`--start-date`/`--end-date`/`--date`/`--report-date`)一律年在前日期(`YYYY-MM-DD` 首选,`YYYY/MM/DD`/`YYYYMMDD` 也收),覆盖 Quote/Fundamental、`insight performance-calendar`、AI 的 `theme-tracking`(`--date`)/`hot-topic`/`management-discuss-*`(`--report-date`)、Alternative `edb-data`、Indicator `cross-section`(`--date`)/`time-series`;名字带 `-time` 的(`--start-time`/`--end-time`)用年在前日期 + `[ HH:mm[:ss]]`(秒可省、空格或 `T` 分隔)或 10/13 位时间戳,覆盖 Insight/Vault 各 list、`quote minute-kline`、`ai security-clue`、`ai knowledge-batch`。其中 **A 股公告(`insight announcement list`)与 `knowledge-batch` 会把输入转成 13 位毫秒**(10 位秒自动 ×1000),其余 `-time` 命令(含 `announcement-hk`/`announcement-us`)原样透传字符串;CLI 输入统一接受 10/13 位纯数字或 `YYYY-MM-DD[ HH:mm[:ss]]`(同上:秒可省、空格或 `T` 分隔)。
|
|
267
270
|
|
|
268
|
-
支持排序切换的命令:opinion / summary / pamirs-summary / research / foreign-report / announcement / announcement-hk / announcement-us / foreign-opinion / independent-opinion / official-account。**要最新的加 `--rank-type 2`(严格按 `publishTime` 倒序);要最相关的用默认 `--rank-type 1` + `--keyword`(综合排序按相关度挑条目)**——两者从同一结果集里取的是**不同的子集**,不是同一批内容换个排法。🔴 **`--search-type` 不改变 `--rank-type 1`
|
|
271
|
+
支持排序切换的命令:opinion / summary / pamirs-summary / research / foreign-report / announcement / announcement-hk / announcement-us / foreign-opinion / independent-opinion / official-account。**要最新的加 `--rank-type 2`(严格按 `publishTime` 倒序);要最相关的用默认 `--rank-type 1` + `--keyword`(综合排序按相关度挑条目)**——两者从同一结果集里取的是**不同的子集**,不是同一批内容换个排法。🔴 **`--search-type` 不改变 `--rank-type 1` 取回哪些条目**(同一关键词下,两档的 `--rank-type 1` 结果相同——即使 `--search-type 2` 让 `total` 大出一个数量级),**要相关度不必加 `--search-type 2`**;它扩大的是命中总数和 `--rank-type 2` 的候选池。🔴 **两档差别有多大取决于关键词,别拿一个词判断参数有没有用**:有些关键词下两档返回同一批,有些交集接近 0,判据见 `insight.md` 开头。⚠️ 综合排序**挑完之后仍按时间倒序排列**,所以别用「结果是不是时间倒序」判断它有没有生效,要比条目 ID。没有 `--keyword` 时两者无差异,属正常。详见 `insight.md` 开头。其他 list 命令按 API 默认排序。
|
|
269
272
|
|
|
270
273
|
## 异常处理
|
|
271
274
|
|
|
@@ -305,7 +308,7 @@ gangtise reference securities-search --keyword <公司名> --category stock --to
|
|
|
305
308
|
| ✅ `140002` | 终态参数错:AI 异步生成失败,或 `indicator` 的指标必填参数缺失 / 枚举越界 / 表达式语法错(实测 2026-08-02) | **不重试**(终态码) | 按 msg 改参数重提;EDE 的参数名与枚举读 `indicator search --format json` 的 `parameterList` |
|
|
306
309
|
|
|
307
310
|
**⚠️ 几类"不报错"的坑(最难发现,逐条都关系到拿没拿到对的数)**
|
|
308
|
-
-
|
|
311
|
+
- **日期用「年在前」写法**:`YYYY-MM-DD`(首选)、`YYYY/MM/DD`、`YYYYMMDD` 三种 CLI 都收,并**统一归一成 `YYYY-MM-DD` 再发出**;时间加 `[ HH:mm[:ss]]`(秒可省、空格或 `T` 分隔)或用 10/13 位时间戳。🔴 **「年在后」写法(`01-07-2026`、`07/01/2026`)CLI 直接报 `ValidationError`,这是有意的**——接口本身会解析它,且**一律按美式「月在前」**(`01-07-2026` = 1 月 7 日,`07-01-2026` = 7 月 1 日)。按欧洲/国际习惯写 `01-07-2026` 想表达「7 月 1 日」的,会拿到差半年的数据且**不报错**;本地拒掉才有信号,还省一次请求与计费。绕过 CLI 直连接口时务必自己统一用 `YYYY-MM-DD`
|
|
309
312
|
- **财报接口的日期按「报告期末」过滤**,不是公告日:`fundamental balance-sheet` 等的 `--start-date`/`--end-date` 匹配的是 `endDate` 字段(如 `20200630`);公告日看 `earliestAnncDate`(首次公告日,做时点对齐用这个)而不是 `announcementDate`。**查某期财报要传季度末日期**(`2020-06-30` / `2020-03-31` / `2020-09-30` / `2020-12-31`);传 `2020-07-01` 这类非报告期日期会返回 0 行,属正常行为,不是没数据
|
|
310
313
|
- **非法证券代码**:`quote day-kline` / `realtime` / `minute-kline` / `fund-flow` 与 Fundamental 系都会报 `120001`,按报错核对后缀即可。⚠️ **三个已下线的旧端点 `quote day-kline-hk` / `day-kline-us` / `index-day-kline` 则返回 `total:0`**,与"该票该区间真无数据"无法区分——**用这三个拿到空结果时先回头核对代码后缀**。它们的能力已并入统一 `day-kline`(支持 A 股 / 港股 / 美股 / 交易所指数 / 概念指数 `.GT` / 申万行业指数 `.SWI`),新代码直接用 `day-kline`
|
|
311
314
|
- **枚举值拼错的后果按端点不同,且部分端点不报错**:纪要 `summary`、三个公告 list、路演 `roadshow`、调研 `site-visit` 的 `--search-type` / `--rank-type` / `--category` / `--market` 等传非法值会报 `100005`;而 `insight research`(内资研报)、`foreign-report`(外资研报)、`official-account`、`opinion` 上非法值被**静默忽略**,该筛选按未传处理。🔴 **最坏的一种**:`research` / `foreign-report` 上非法的 `--search-type` 会**连带吞掉 `--keyword`**——`research list --keyword 茅台 --search-type 99` 拿到的是**未经筛选的全库**,与不传 `--keyword` 的结果一致,而不是搜索结果。**v0.32.0 起 CLI 本地拦截**:全部 `--search-type` / `--rank-type`(19 处)、全部 `--file-type`(9 处下载,`foreign-report` 为 1–4、其余 1–2)、`pamirs-summary` 的 `--category` / `--market`、`--top` 上限、以及 `reference securities-search` / `institution-search` / `official-account-search` 的 `--category`。**仍未覆盖**:`insight research/summary --category`、`--market`、`--source`、`--llm-tag` 等仍是自由字符串,拼错不报错也不生效——这些要自己核对。**拼错的筛选条件会伪装成"结果正常",枚举拼写要自己保证**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
注意:`ai one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `viewpoint-debate-check` / `earnings-review-check` 返回 `{content: "markdown文本"}`;这类命令仍然加 `--format json`,但呈现给用户时直接取 `content` 字段,不要展示 JSON 包装层。
|
|
4
4
|
|
|
5
|
-
**⏱ 超时与重复扣分**:7 个 agent 类(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `theme-tracking` / `management-discuss-*`)CLI 已内置 120s 超时下限,**无需前缀**;`stock-summary` / `hot-topic` 首次生成也常 >30s,仍建议前置 `GANGTISE_TIMEOUT_MS=120000`。自 v0.26.0 起**贵档端点超时/5xx
|
|
5
|
+
**⏱ 超时与重复扣分**:7 个 agent 类(`one-pager` / `investment-logic` / `peer-comparison` / `research-outline` / `theme-tracking` / `management-discuss-*`)CLI 已内置 120s 超时下限,**无需前缀**;`stock-summary` / `hot-topic` 首次生成也常 >30s,仍建议前置 `GANGTISE_TIMEOUT_MS=120000`。自 v0.26.0 起**贵档端点超时/5xx 不再自动重试**(重放=重复扣分)——超时报错后内容可能已在服务端生成并扣费,同参数再调会**再扣一次**(`one-pager` 等生成类实测按次计费、无缓存命中豁免),所以一次调用给足超时比失败重跑省钱;拿到的生成内容自行留存复用,别为"刷新"重调。`earnings-review` / `viewpoint-debate` 是异步——用 `--wait`(工具超时 ≥360s)或 `*-check` 轮询,不吃这个超时。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -126,6 +126,8 @@ gangtise ai hot-topic [--start-date <date>] [--end-date <date>] [--category <nam
|
|
|
126
126
|
- `--category`:`morningBriefing` 早报 | `noonBriefing` 午报 | `afternoonFlash` 盘中快报 | `eveningBriefing` 晚报(可重复,默认全部)
|
|
127
127
|
- `--with-related-securities` / `--with-close-reading`:默认开启;`--no-with-related-securities` / `--no-with-close-reading` 显式排除(响应里相应字段置空)
|
|
128
128
|
- 自动翻页,单页最大 20
|
|
129
|
+
- 🔴 **计费 50/篇,按返回条数计**(不是按调用次数)。📌 **一「篇」= 一整份报告**(一份早报 / 午报 / 盘中快报 / 晚报),**不是报告里的一条话题**——一份报告通常包含多条热点话题,`--category` 选的也是报告类型而不是话题。所以一页 20 条 = 20 份报告 = 1000 积分:**先用 `--start-date`/`--end-date` + `--category` 把范围收窄,再考虑要不要全量**(省略 `--size` 即拉全量)
|
|
130
|
+
- **可查的历史范围跟账号权限走**:试用档是滚动的「当前 −1 个月」,正式/定制档更长。超出范围的日期**返回空结果、不报错**——拿到空先确认是不是撞了本账号的窗口,别当成「那天没有报告」
|
|
129
131
|
|
|
130
132
|
## 管理层讨论-财报 `ai management-discuss-announcement`
|
|
131
133
|
|
|
@@ -124,7 +124,7 @@ gangtise indicator screener --indicator <F1:code> [--indicator <F2:code2>] \
|
|
|
124
124
|
- 文本:`contains` / `notcontains`(不区分大小写,**仅对 `dataType: string` 的指标有效**)
|
|
125
125
|
- 逻辑:`&&` `||`,分组 `(` `)`
|
|
126
126
|
- `--date`(**必选**):下发为**每个**指标的 `tradeDate`(已带 `tradeDate`/`reportDate` 的不覆盖)。绝大多数指标吃 `tradeDate`,漏传就是一张空表且退出码 0,所以必填
|
|
127
|
-
- 🔴 **`parameterList` 里一个日期参数都没有的指标(`pty_*` / `scr_*` 静态属性两族、`div_cash_paid_ratio` / `div_cash_yr`、`pty_shr_reg
|
|
127
|
+
- 🔴 **`parameterList` 里一个日期参数都没有的指标(`pty_*` / `scr_*` 静态属性两族、`div_cash_paid_ratio` / `div_cash_yr`、`pty_shr_reg`)必须显式声明「不要日期」**:写 `--indicator-param "F1:"`(冒号后什么都不写)。不写的话 `--date` 会给它注入 `tradeDate`,而这些指标不接受该参数,**整条请求**报 `100003 指标 xxx 不支持参数 tradeDate`。`F1:` 与真参数可组合,`div_cash_*` 要同时给 `F1:` 和 `F1:fiscalYear=2025`。这与 `cross-section` 的 `"code:"` 是同一个写法。注意只有**没有任何日期参数**的指标需要,报告期类(`is_*` 等)给 `F1:reportDate=...` 即可
|
|
128
128
|
- `--indicator-param`:格式是 **`F1:key=value`(按变量,不是按 code)**。引用了没绑定的变量会直接报错,不会静默丢弃
|
|
129
129
|
- `--expression` 里引用未绑定的变量,CLI **本地就拦**(不发请求、不计费);服务端也会报 `100003`
|
|
130
130
|
- **输出(宽表)**:同 `cross-section`,每行一只**命中**的证券,列为 `security / name / <各指标名>…`;无命中返回空表
|
|
@@ -148,11 +148,12 @@ gangtise indicator screener --indicator F1:qte_close --indicator F2:qte_close \
|
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
150
|
# 文本筛选:白酒板块里经营范围含「酒」的公司
|
|
151
|
-
# ⚠️ pty_op_scope 的 parameterList
|
|
152
|
-
gangtise indicator
|
|
153
|
-
--indicator-param "
|
|
154
|
-
--security 1000000287 --
|
|
155
|
-
|
|
151
|
+
# ⚠️ pty_op_scope 的 parameterList 为空 → 必须带 "F1:" 声明它不吃日期,否则整条请求报 100003
|
|
152
|
+
gangtise indicator screener --indicator F1:pty_op_scope \
|
|
153
|
+
--indicator-param "F1:" \
|
|
154
|
+
--security 1000000287 --expression "F1 contains '酒'" \
|
|
155
|
+
--date 2026-08-13 --format table
|
|
156
|
+
# 有日期参数的字符串指标不需要 "F1:",直接筛:
|
|
156
157
|
gangtise indicator screener --indicator F1:mgn_flag \
|
|
157
158
|
--security 1000000287 --expression "F1 contains '是'" \
|
|
158
159
|
--date 2026-08-13 --format table # 融资融券标的
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
`新能源汽车` 那组里 `--rank-type 1` 前 50 有 47 条标题含「新能源」、22 条含完整词「新能源汽车」,`--rank-type 2` 分别是 28 条和 **0 条**——**综合排序确实在按相关度挑**。
|
|
36
36
|
|
|
37
|
-
⚠️ **`--search-type` 不影响 `--rank-type 1`
|
|
37
|
+
⚠️ **`--search-type` 不影响 `--rank-type 1` 取回哪些条目**:同一关键词下,`--search-type 1` 与 `2` 的 `--rank-type 1` 结果相同——即使 `--search-type 2` 把 `total` 放大一个数量级,取回的条目仍是同一批。`--search-type 2`(全文)扩大的是命中总数和 `--rank-type 2` 的候选池。**所以「要最相关」不需要加 `--search-type 2`。**
|
|
38
38
|
|
|
39
39
|
⚠️ **没有 `--keyword` 时两档结果一致**,这不是参数失效——没有关键词就无从计算相关度。
|
|
40
40
|
|