gangtise-openapi-cli 0.14.3 → 0.15.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 +32 -0
- package/dist/src/cli.js +2 -0
- package/dist/src/core/endpoints.js +14 -0
- package/dist/src/core/quoteSharding.js +25 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
## Changelog
|
|
6
6
|
|
|
7
|
+
### v0.15.0 — 2026-05-29
|
|
8
|
+
|
|
9
|
+
**新增接口**
|
|
10
|
+
- `alternative concept-info` — 题材指数基本信息:返回题材整体画像(定义 / 投资逻辑 / 行业空间 / 竞争格局 / 催化事件)。按 `--concept-id` 查询,仅返回最新截面数据,不支持历史回溯
|
|
11
|
+
- `alternative concept-securities` — 题材指数成分股(题材深度 F8):按分组结构返回当前成分股,每只含是否重点个股 `isKey` 与纳入理由 `inclusionReason`。按 `--concept-id` 查询
|
|
12
|
+
|
|
13
|
+
**接口变更**
|
|
14
|
+
- `quote index-day-kline` 返回字段新增 `securityName`(指数名称,如"上证指数")
|
|
15
|
+
|
|
16
|
+
> `--concept-id` 与主题跟踪 `ai theme-tracking --theme-id` 共用同一套题材 ID 体系,可用 `gangtise lookup theme-id list` 按名称查询(如 机器人 → `121000130`)。
|
|
17
|
+
|
|
18
|
+
### v0.14.4 — 2026-05-29
|
|
19
|
+
|
|
20
|
+
**Bug fix(全市场 K 线分片容错)**
|
|
21
|
+
- `quote day-kline --security all` 等全市场查询的日期分片改为容错:部分分片失败时返回已成功分片的数据并标记 `partial: true` + `failedShards`(失败的日期区间),同时向 stderr 告警;只有全部分片失败才抛错。此前为 fail-fast,单片失败会让整次查询失败,或在异常路径上被误判为空结果。
|
|
22
|
+
|
|
23
|
+
### v0.14.3 — 2026-05-29
|
|
24
|
+
|
|
25
|
+
**性能 / 健壮性**
|
|
26
|
+
- 标题缓存按端点封顶(5000 条/端点)并清理过期项,修复 `title-cache.json` 无上限增长(曾达 ~58MB)拖慢启动的问题
|
|
27
|
+
- 下载接口遇鉴权失效(`8000014` / `8000015`)自动刷新 token 并重试一次(此前仅普通 JSON 调用具备 token 自愈)
|
|
28
|
+
- CLI handler 抽出 `emit` / `withClient` 公共封装去除重复样板;CSV 转义逻辑去重;翻页与 K 线分片统一走 `GANGTISE_PAGE_CONCURRENCY` 并发控制
|
|
29
|
+
- 补齐多个 core 模块的单元测试
|
|
30
|
+
|
|
7
31
|
### v0.14.2 — 2026-05-22
|
|
8
32
|
|
|
9
33
|
**Bug fix(A 股 / HK 全市场 K 线同源问题)**
|
|
@@ -240,6 +264,8 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
|
|
|
240
264
|
| | `stock-pool-list` / `stock-pool-stocks` | 自选股股票池列表与证券明细 |
|
|
241
265
|
| **Alternative** | `edb-search` | 行业指标搜索(按关键词匹配,返回 indicatorId 等元信息) |
|
|
242
266
|
| | `edb-data` | 行业指标时序数据(批量拉取,最多10个指标) |
|
|
267
|
+
| | `concept-info` | 题材指数基本信息(投资逻辑/行业空间/竞争格局/催化事件) |
|
|
268
|
+
| | `concept-securities` | 题材指数成分股(题材深度F8,按分组,标记重点个股) |
|
|
243
269
|
| **Raw** | `call` | 原始接口调用(可访问任意 endpoint) |
|
|
244
270
|
|
|
245
271
|
## 命令概览
|
|
@@ -549,6 +575,12 @@ gangtise alternative edb-data \
|
|
|
549
575
|
--end-date 2024-12-31 \
|
|
550
576
|
--format csv \
|
|
551
577
|
--output ./indicator.csv
|
|
578
|
+
|
|
579
|
+
# 题材指数:先查 conceptId(与 theme-id 共用 ID 体系),再拉画像 / 成分股
|
|
580
|
+
gangtise lookup theme-id list | grep 机器人 # → 121000130
|
|
581
|
+
gangtise alternative concept-info --concept-id 121000130 --format json
|
|
582
|
+
# 题材成分股(题材深度 F8,按分组返回,标记重点个股)
|
|
583
|
+
gangtise alternative concept-securities --concept-id 121000130 --format json
|
|
552
584
|
```
|
|
553
585
|
|
|
554
586
|
### Raw
|
package/dist/src/cli.js
CHANGED
|
@@ -412,6 +412,8 @@ alternative.command("edb-data").option("--indicator-id <id>", "Indicator ID (rep
|
|
|
412
412
|
}
|
|
413
413
|
await printData(data, parseOutputFormat(options.format), options.output);
|
|
414
414
|
}));
|
|
415
|
+
alternative.command("concept-info").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise lookup theme-id list'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-info", { conceptId: options.conceptId })));
|
|
416
|
+
alternative.command("concept-securities").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise lookup theme-id list'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-securities", { conceptId: options.conceptId })));
|
|
415
417
|
program.addCommand(alternative);
|
|
416
418
|
program.command("raw").description("Raw API calls").addCommand(new Command("call").argument("<endpointKey>").option("--body <json>").option("--query <key=value>", "Query string pair", collectKeyValue, {}).option("--format <format>", "Output format", "json").option("--output <path>").action(async (endpointKey, options) => {
|
|
417
419
|
const endpoint = ENDPOINTS[endpointKey];
|
|
@@ -537,4 +537,18 @@ export const ENDPOINTS = {
|
|
|
537
537
|
kind: "json",
|
|
538
538
|
description: "Get industry indicator time-series data by indicator ID list",
|
|
539
539
|
},
|
|
540
|
+
"alternative.concept-info": {
|
|
541
|
+
key: "alternative.concept-info",
|
|
542
|
+
method: "POST",
|
|
543
|
+
path: "/application/open-alternative/concept/info",
|
|
544
|
+
kind: "json",
|
|
545
|
+
description: "Query latest concept (theme index) profile by conceptId",
|
|
546
|
+
},
|
|
547
|
+
"alternative.concept-securities": {
|
|
548
|
+
key: "alternative.concept-securities",
|
|
549
|
+
method: "POST",
|
|
550
|
+
path: "/application/open-alternative/concept/securities",
|
|
551
|
+
kind: "json",
|
|
552
|
+
description: "Query concept (theme index) constituent securities, grouped",
|
|
553
|
+
},
|
|
540
554
|
};
|
|
@@ -65,8 +65,22 @@ export async function callKlineWithSharding(client, endpointKey, body, config) {
|
|
|
65
65
|
if (process.env.GANGTISE_VERBOSE === "1" || process.env.GANGTISE_VERBOSE === "true") {
|
|
66
66
|
process.stderr.write(`[gangtise] sharding ${endpointKey} into ${shards.length} requests (${config.shardDays} day(s) each)\n`);
|
|
67
67
|
}
|
|
68
|
+
// Per-shard fault tolerance: a failing shard is recorded and skipped (returns a
|
|
69
|
+
// null sentinel) instead of rejecting, so the surviving shards still complete.
|
|
70
|
+
// runWithConcurrency uses Promise.all under the hood, which would otherwise abort
|
|
71
|
+
// every shard on the first rejection.
|
|
72
|
+
const failedShards = [];
|
|
73
|
+
let firstError = null;
|
|
68
74
|
const results = await runWithConcurrency(shards, config.concurrency ?? SHARD_CONCURRENCY, async (shard) => {
|
|
69
|
-
|
|
75
|
+
try {
|
|
76
|
+
return await client.call(endpointKey, { ...allMarketBody, startDate: shard.startDate, endDate: shard.endDate });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!firstError)
|
|
80
|
+
firstError = error;
|
|
81
|
+
failedShards.push(shard);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
70
84
|
});
|
|
71
85
|
let fieldList;
|
|
72
86
|
let header = null;
|
|
@@ -82,10 +96,20 @@ export async function callKlineWithSharding(client, endpointKey, body, config) {
|
|
|
82
96
|
if (Array.isArray(rec.list))
|
|
83
97
|
merged.push(...rec.list);
|
|
84
98
|
}
|
|
99
|
+
// Every shard failed → surface the error loudly (non-zero exit) rather than
|
|
100
|
+
// masking a total outage as an empty success.
|
|
101
|
+
if (failedShards.length === shards.length) {
|
|
102
|
+
throw firstError ?? new Error(`All ${shards.length} kline shards failed`);
|
|
103
|
+
}
|
|
85
104
|
if (!header)
|
|
86
105
|
return { list: [] };
|
|
87
106
|
const out = { ...header, list: merged };
|
|
88
107
|
if (fieldList)
|
|
89
108
|
out.fieldList = fieldList;
|
|
109
|
+
if (failedShards.length > 0) {
|
|
110
|
+
out.partial = true;
|
|
111
|
+
out.failedShards = failedShards;
|
|
112
|
+
process.stderr.write(`[gangtise] warning: ${failedShards.length}/${shards.length} shards failed; results are partial (see failedShards)\n`);
|
|
113
|
+
}
|
|
90
114
|
return out;
|
|
91
115
|
}
|
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.15.0";
|