gangtise-openapi-cli 0.18.0 → 0.19.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 +61 -1
- package/dist/src/cli.js +20 -2
- package/dist/src/core/args.js +26 -0
- package/dist/src/core/auth.js +6 -1
- package/dist/src/core/commandBodies.js +28 -1
- package/dist/src/core/endpoints.js +22 -0
- package/dist/src/core/indicatorMatrix.js +95 -0
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
## Changelog
|
|
6
6
|
|
|
7
|
+
### v0.19.0 — 2026-06-24
|
|
8
|
+
|
|
9
|
+
**新增接口(Indicator · 证券级数据指标 EDE)**
|
|
10
|
+
- `indicator search` — 按名称搜索证券级数据指标,返回 `indicatorCode` 及可传参数 `parameterList`(含 `required` 必填标记与枚举);取数前必先 search 拿 code,绝不猜编码
|
|
11
|
+
- `indicator cross-section` — 指标截面数据(多指标 × 多证券,单日快照):`--indicator` / `--security`(均可重复)/ `--date` / `--currency` / `--scale` / `--indicator-param`
|
|
12
|
+
- `indicator time-series` — 指标时间序列(多指标 × 单证券 或 单指标 × 多证券,按区间):另有 `--start-date` / `--end-date` / `--calendar-type`(`ND`/`TD`/`WD`)
|
|
13
|
+
- 复权等指标专属参数用 `--indicator-param "code:key=value"`,参数 key 与取值以 search 的 `parameterList` 为准(行情复权键为 `adjustmentType`:`1` 不复权 / `2` 前复权 / `3` 后复权)
|
|
14
|
+
- 很多指标有必填参数,默认调用会报 `410106`(缺必填参数):N 期统计补 `periodNum`、区间/周期类补 `startDate`、年度/分红类补 `fiscalYear`;`999999` 多为「该证券公司类型/报告期无数据」而非系统故障。详见 `gangtise-openapi/references/commands/indicator.md`
|
|
15
|
+
|
|
16
|
+
**修复**
|
|
17
|
+
- `vault stock-pool-stocks --pool-id <id>` 过滤失效:此前因选项默认值 `["all"]` 泄漏,传具体 pool id 仍返回全部股票池证券;现已修复——传 id 精确过滤,省略则默认全量
|
|
18
|
+
- `auth` 缺凭证报错补充跨 shell(bash/zsh/fish)的 `export` 提示
|
|
19
|
+
|
|
20
|
+
**文档**
|
|
21
|
+
- README / SKILL 补充 indicator 命令组与取数最佳实践;`official-account` 命令文档补全
|
|
22
|
+
|
|
23
|
+
### v0.18.0 — 2026-06-17
|
|
24
|
+
|
|
25
|
+
**新增接口(Insight · 产业公众号资讯)**
|
|
26
|
+
- `insight official-account list` — 查询公众号资讯列表:支持 `--keyword`(需用数据中的具体词,非整句白话)/ `--account-id`(公众号 ID)/ `--security` / `--category`(文章类型枚举:`news`/`law`/`report`/`view`/`data`/`event`/`meeting`/`notice`/`recruit`/`investEdu`/`brand`/`notes`/`other`)/ `--industry`(`citicIndustry`/`swIndustry` 行业 ID)/ `--search-type`(`1` 标题 / `2` 全文)/ `--rank-type`(`1` 综合 / `2` 时间倒序);返回含模型生成摘要 `summary` 及关联行业/题材/证券列表
|
|
27
|
+
- `insight official-account download --article-id <id>` — 下载公众号文章:`--file-type 1` txt(默认)/ `2` HTML
|
|
28
|
+
|
|
7
29
|
### v0.17.0 — 2026-06-15
|
|
8
30
|
|
|
9
31
|
**接口变更(Breaking)**
|
|
@@ -217,6 +239,7 @@ gangtise-openapi/
|
|
|
217
239
|
│ ├── ai.md # AI 能力命令(one-pager / earnings-review / viewpoint-debate 等)
|
|
218
240
|
│ ├── alternative.md # 行业指标数据库(EDB search / EDB data)
|
|
219
241
|
│ ├── fundamental.md # 财务数据命令(A股/港股三大报表 / 估值 / 盈利预测 / 股东)
|
|
242
|
+
│ ├── indicator.md # 证券级数据指标 EDE(search / 截面 / 时序)
|
|
220
243
|
│ ├── insight.md # 投研内容命令(研报 / 观点 / 纪要 / 公告 / 外资)
|
|
221
244
|
│ ├── quote.md # 行情命令(A股/港股/指数 K 线)
|
|
222
245
|
│ ├── reference-and-lookup.md # GTS Code 搜索与枚举速查
|
|
@@ -272,6 +295,7 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
|
|
|
272
295
|
| | `announcement-hk list` / `download` | 港股公告(含下载) |
|
|
273
296
|
| | `foreign-opinion list` | 外资机构观点 |
|
|
274
297
|
| | `independent-opinion list` / `download` | 外资独立分析师观点(含原文/翻译HTML下载) |
|
|
298
|
+
| | `official-account list` / `download` | 产业公众号资讯(含 txt/HTML 下载) |
|
|
275
299
|
| **Reference** | `securities-search` | GTS Code 搜索(按名称/代码/拼音匹配) |
|
|
276
300
|
| | `constant-category` | 常量分类列表(含各分类适用的接口与参数) |
|
|
277
301
|
| | `constant-list` | 按分类导出常量值全量列表(行业/城市/公告分类/区域等) |
|
|
@@ -307,6 +331,9 @@ cp -r gangtise-openapi ~/.hermes/skills/gangtise-openapi
|
|
|
307
331
|
| | `my-conference-list` / `my-conference-download` | 我的会议列表与下载 |
|
|
308
332
|
| | `wechat-message-list` / `wechat-chatroom-list` | 群消息列表与群ID查询 |
|
|
309
333
|
| | `stock-pool-list` / `stock-pool-stocks` | 自选股股票池列表与证券明细 |
|
|
334
|
+
| **Indicator** | `search` | 证券级数据指标搜索(按名称匹配,返回 indicatorCode 及可传参数 parameterList) |
|
|
335
|
+
| | `cross-section` | 指标截面数据(多指标 × 多证券,单日快照;前置 `search` 拿 code) |
|
|
336
|
+
| | `time-series` | 指标时间序列(多指标 × 单证券 或 单指标 × 多证券,按区间) |
|
|
310
337
|
| **Alternative** | `edb-search` | 行业指标搜索(按关键词匹配,返回 indicatorId 等元信息) |
|
|
311
338
|
| | `edb-data` | 行业指标时序数据(批量拉取,最多10个指标) |
|
|
312
339
|
| | `concept-info` | 题材指数基本信息(投资逻辑/行业空间/竞争格局/催化事件) |
|
|
@@ -378,6 +405,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
|
|
|
378
405
|
- `insight announcement-hk list`
|
|
379
406
|
- `insight foreign-opinion list`
|
|
380
407
|
- `insight independent-opinion list`
|
|
408
|
+
- `insight official-account list`
|
|
381
409
|
- `ai security-clue`
|
|
382
410
|
- `vault drive-list`
|
|
383
411
|
- `vault record-list`
|
|
@@ -395,7 +423,7 @@ gangtise ai knowledge-batch --query 比亚迪 --query 最近热门概念
|
|
|
395
423
|
|
|
396
424
|
## 智能文件命名
|
|
397
425
|
|
|
398
|
-
下载命令(`summary download`、`research download`、`foreign-report download`、`announcement download`、`vault drive-download`、`vault record-download`、`vault my-conference-download`)省略 `--output` 时,自动使用真实标题作为文件名:
|
|
426
|
+
下载命令(`summary download`、`research download`、`foreign-report download`、`announcement download`、`official-account download`、`vault drive-download`、`vault record-download`、`vault my-conference-download`)省略 `--output` 时,自动使用真实标题作为文件名:
|
|
399
427
|
|
|
400
428
|
1. **缓存优先** — 如果之前执行过对应的 `list` 命令,标题已缓存在 `~/.config/gangtise/title-cache.json`,直接使用,无额外 API 调用
|
|
401
429
|
2. **API 回查** — 缓存未命中时,自动查询最近 200 条记录匹配标题
|
|
@@ -455,6 +483,10 @@ gangtise insight foreign-opinion list --security APP.O --rating buy --format jso
|
|
|
455
483
|
gangtise insight independent-opinion list --keyword "肿瘤" --industry 100800118 --format json
|
|
456
484
|
gangtise insight independent-opinion download --independent-opinion-id 207051900018372 --file-type 2
|
|
457
485
|
|
|
486
|
+
# 产业公众号资讯
|
|
487
|
+
gangtise insight official-account list --keyword 泡泡玛特 --rank-type 2 --size 20 --format json
|
|
488
|
+
gangtise insight official-account download --article-id 7286248 --file-type 2
|
|
489
|
+
|
|
458
490
|
# 纪要下载(会议平台来源可选 HTML 格式)
|
|
459
491
|
gangtise insight summary download --summary-id 4906813 --file-type 2
|
|
460
492
|
```
|
|
@@ -613,6 +645,34 @@ gangtise vault stock-pool-stocks --pool-id 808477293
|
|
|
613
645
|
gangtise vault stock-pool-stocks
|
|
614
646
|
```
|
|
615
647
|
|
|
648
|
+
### Indicator(证券级数据指标 EDE)
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
# Step 1:按名称搜索,拿 indicatorCode(绝不猜编码);--format json 看可传参数 parameterList 及 required
|
|
652
|
+
gangtise indicator search --keyword 收盘价 --format table # → qte_close
|
|
653
|
+
gangtise indicator search --keyword 平均ROE --limit 5 --format json # 看 parameterList
|
|
654
|
+
|
|
655
|
+
# 截面:多指标 × 多证券,单日快照(行情类用交易日;财务类用报告期末,如 2026-03-31)
|
|
656
|
+
gangtise indicator cross-section \
|
|
657
|
+
--indicator qte_close --indicator qte_vol --indicator qte_mkt_cptl \
|
|
658
|
+
--security 600519.SH --security 09992.HK \
|
|
659
|
+
--date 2026-05-18 --format table
|
|
660
|
+
|
|
661
|
+
# 时间序列:多指标 × 单证券 或 单指标 × 多证券(不能多 × 多,否则报 410001)
|
|
662
|
+
gangtise indicator time-series --indicator qte_close \
|
|
663
|
+
--security 600519.SH --security 09992.HK \
|
|
664
|
+
--start-date 2026-05-12 --end-date 2026-05-18 --format table
|
|
665
|
+
|
|
666
|
+
# 复权 / 指标专属参数用 --indicator-param "code:key=value",参数 key 以 search 的 parameterList 为准
|
|
667
|
+
gangtise indicator cross-section --indicator qte_close --security 600519.SH \
|
|
668
|
+
--date 2026-05-18 --indicator-param "qte_close:adjustmentType=3" # 1不复权/2前复权/3后复权
|
|
669
|
+
|
|
670
|
+
# 必填参数:很多指标默认调用报 410106(缺必填参数),按 parameterList 的 required 补齐再取:
|
|
671
|
+
# N 期统计补 periodNum、区间/周期类(如 qte_amp_mo 月振幅)补 startDate、年度/分红类补 fiscalYear
|
|
672
|
+
gangtise indicator cross-section --indicator finc_roe_avg_avg --security 600519.SH \
|
|
673
|
+
--date 2026-03-31 --indicator-param "finc_roe_avg_avg:periodNum=4"
|
|
674
|
+
```
|
|
675
|
+
|
|
616
676
|
### Alternative(行业指标数据库 EDB)
|
|
617
677
|
|
|
618
678
|
```bash
|
package/dist/src/cli.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Command, Option } from "commander";
|
|
|
3
3
|
import { checkAsyncContent, pollAsyncContent, POLL_MAX_ATTEMPTS } from "./core/asyncContent.js";
|
|
4
4
|
import { readTokenCache } from "./core/auth.js";
|
|
5
5
|
import { collectKeyValue, collectList, collectNumberList, maybeArray, parseFrom, parseNumberOption, parseOptionalNumberOption, parseSize, parseTimestamp13 } from "./core/args.js";
|
|
6
|
-
import { buildQuoteKlineBody, buildWechatChatroomListBody, buildWechatMessageListBody } from "./core/commandBodies.js";
|
|
6
|
+
import { buildIndicatorCrossSectionBody, buildIndicatorTimeSeriesBody, buildQuoteKlineBody, buildStockPoolStocksBody, buildWechatChatroomListBody, buildWechatMessageListBody } from "./core/commandBodies.js";
|
|
7
|
+
import { flattenCrossSection, flattenTimeSeries, unwrapIndicatorData } from "./core/indicatorMatrix.js";
|
|
7
8
|
import { callKlineWithSharding } from "./core/quoteSharding.js";
|
|
8
9
|
import { loadConfig } from "./core/config.js";
|
|
9
10
|
import { resolveTitle, saveDownloadResult } from "./core/download.js";
|
|
@@ -433,7 +434,7 @@ addDownloadCommand(vault, { endpointKey: "vault.my-conference.download", name: "
|
|
|
433
434
|
vault.command("wechat-message-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Total rows to return; omit to fetch all").option("--start-time <datetime>").option("--end-time <datetime>").option("--keyword <text>").option("--security <code>", "Security code (e.g. 000001.SZ)", collectList, []).option("--wechat-group-id <id>", "WeChat group ID", collectList, []).option("--industry <id>", "Industry ID", collectList, []).option("--category <name>", "Message type: text/image/documents/url", collectList, []).option("--tag <name>", "Tag: roadShow/research/strategyMeeting/meetingSummary/industryComment/companyComment/earningsReview", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.wechat-message.list", buildWechatMessageListBody(options))));
|
|
434
435
|
vault.command("wechat-chatroom-list").option("--from <number>", "Starting offset", "0").option("--size <number>", "Rows to return", "20").option("--room-name <name>", "WeChat group name; repeat or comma-separate for multiple names", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.wechat-chatroom.list", buildWechatChatroomListBody(options))));
|
|
435
436
|
vault.command("stock-pool-list").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.stock-pool.list", {})));
|
|
436
|
-
vault.command("stock-pool-stocks").option("--pool-id <id>", "Pool ID; repeat for multiple;
|
|
437
|
+
vault.command("stock-pool-stocks").option("--pool-id <id>", "Pool ID; repeat for multiple; omit (or 'all') for all pools", collectList).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => emit(options, (client) => client.call("vault.stock-pool.stocks", buildStockPoolStocksBody(options))));
|
|
437
438
|
program.addCommand(vault);
|
|
438
439
|
program.addCommand(ai);
|
|
439
440
|
const alternative = new Command("alternative").description("Alternative data APIs");
|
|
@@ -460,6 +461,23 @@ alternative.command("edb-data").option("--indicator-id <id>", "Indicator ID (rep
|
|
|
460
461
|
alternative.command("concept-info").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise reference concept-search'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-info", { conceptId: options.conceptId })));
|
|
461
462
|
alternative.command("concept-securities").requiredOption("--concept-id <id>", "Concept (theme index) ID, e.g. 121000130 机器人; discover via 'gangtise reference concept-search'").option("--format <format>", "Output format", "json").option("--output <path>").action((options) => emit(options, (client) => client.call("alternative.concept-securities", { conceptId: options.conceptId })));
|
|
462
463
|
program.addCommand(alternative);
|
|
464
|
+
const indicator = new Command("indicator").description("Data indicator (EDE) APIs: search codes, cross-section, time-series");
|
|
465
|
+
indicator.command("search").requiredOption("--keyword <text>", "Search keyword, e.g. '收盘价' '成交量' '营业收入' (not free-form questions)").option("--limit <number>", "Max results (default: 50, max: 100)", "50").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
466
|
+
const raw = await client.call("indicator.search", {
|
|
467
|
+
keyword: options.keyword,
|
|
468
|
+
limit: parseNumberOption(options.limit, "--limit", { integer: true, min: 1 }),
|
|
469
|
+
});
|
|
470
|
+
await printData(unwrapIndicatorData(raw), parseOutputFormat(options.format), options.output);
|
|
471
|
+
}));
|
|
472
|
+
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 for multiple)", collectList, []).requiredOption("--date <date>", "Data date (yyyy-MM-dd)").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:adjustmentType=2 for 前复权 (repeat)", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
473
|
+
const raw = await client.call("indicator.cross-section", buildIndicatorCrossSectionBody(options));
|
|
474
|
+
await printData(flattenCrossSection(unwrapIndicatorData(raw)), parseOutputFormat(options.format), options.output);
|
|
475
|
+
}));
|
|
476
|
+
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 for multiple)", collectList, []).requiredOption("--start-date <date>", "Start date (yyyy-MM-dd)").requiredOption("--end-date <date>", "End date (yyyy-MM-dd)").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:adjustmentType=2 for 前复权 (repeat)", collectList, []).option("--format <format>", "Output format", "table").option("--output <path>").action((options) => withClient(async (client) => {
|
|
477
|
+
const raw = await client.call("indicator.time-series", buildIndicatorTimeSeriesBody(options));
|
|
478
|
+
await printData(flattenTimeSeries(unwrapIndicatorData(raw)), parseOutputFormat(options.format), options.output);
|
|
479
|
+
}));
|
|
480
|
+
program.addCommand(indicator);
|
|
463
481
|
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) => {
|
|
464
482
|
const endpoint = ENDPOINTS[endpointKey];
|
|
465
483
|
if (!endpoint) {
|
package/dist/src/core/args.js
CHANGED
|
@@ -79,3 +79,29 @@ export function parseTimestamp13(value, optionName) {
|
|
|
79
79
|
}
|
|
80
80
|
return parsed;
|
|
81
81
|
}
|
|
82
|
+
// Parse repeatable `--indicator-param "code:key=value"` specs into the nested
|
|
83
|
+
// indicatorParamList the EDE cross-section / time-series endpoints expect.
|
|
84
|
+
// Multiple specs for the same code accumulate into one group, first-seen order.
|
|
85
|
+
export function parseIndicatorParams(specs) {
|
|
86
|
+
if (specs.length === 0)
|
|
87
|
+
return undefined;
|
|
88
|
+
const groups = new Map();
|
|
89
|
+
for (const spec of specs) {
|
|
90
|
+
const colon = spec.indexOf(":");
|
|
91
|
+
const rest = colon === -1 ? "" : spec.slice(colon + 1);
|
|
92
|
+
const eq = rest.indexOf("=");
|
|
93
|
+
const code = colon === -1 ? "" : spec.slice(0, colon).trim();
|
|
94
|
+
const paramKey = eq === -1 ? "" : rest.slice(0, eq).trim();
|
|
95
|
+
const paramValue = eq === -1 ? "" : rest.slice(eq + 1).trim();
|
|
96
|
+
if (!code || !paramKey) {
|
|
97
|
+
throw new ValidationError(`Invalid --indicator-param: expected "code:key=value", got "${spec}"`);
|
|
98
|
+
}
|
|
99
|
+
let group = groups.get(code);
|
|
100
|
+
if (!group) {
|
|
101
|
+
group = { indicatorCode: code, parameters: [] };
|
|
102
|
+
groups.set(code, group);
|
|
103
|
+
}
|
|
104
|
+
group.parameters.push({ paramKey, paramValue });
|
|
105
|
+
}
|
|
106
|
+
return [...groups.values()];
|
|
107
|
+
}
|
package/dist/src/core/auth.js
CHANGED
|
@@ -30,7 +30,12 @@ export function normalizeToken(token) {
|
|
|
30
30
|
}
|
|
31
31
|
export function requireAccessCredentials(accessKey, secretKey) {
|
|
32
32
|
if (!accessKey || !secretKey) {
|
|
33
|
-
|
|
33
|
+
const missing = [!accessKey && "GANGTISE_ACCESS_KEY", !secretKey && "GANGTISE_SECRET_KEY"].filter(Boolean).join(", ");
|
|
34
|
+
throw new ConfigError(`缺少环境变量: ${missing}(未导出到当前进程环境)\n`
|
|
35
|
+
+ `注意:在 shell 里赋值还不够,必须"导出",子进程才读得到:\n`
|
|
36
|
+
+ ` bash/zsh: export GANGTISE_ACCESS_KEY=... GANGTISE_SECRET_KEY=...\n`
|
|
37
|
+
+ ` fish: set -gx GANGTISE_ACCESS_KEY ...; set -gx GANGTISE_SECRET_KEY ...\n`
|
|
38
|
+
+ `验证:env | grep GANGTISE(能列出对应行才算导出成功)`);
|
|
34
39
|
}
|
|
35
40
|
return { accessKey, secretKey };
|
|
36
41
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { maybeArray, parseFrom, parseOptionalNumberOption, parseSize } from "./args.js";
|
|
1
|
+
import { maybeArray, parseFrom, parseIndicatorParams, parseOptionalNumberOption, parseSize } from "./args.js";
|
|
2
2
|
export function buildQuoteKlineBody(options) {
|
|
3
3
|
return {
|
|
4
4
|
securityList: maybeArray(options.security),
|
|
@@ -29,3 +29,30 @@ export function buildWechatChatroomListBody(options) {
|
|
|
29
29
|
roomName: options.roomName.length > 0 ? options.roomName.join(",") : undefined,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
export function buildStockPoolStocksBody(options) {
|
|
33
|
+
return {
|
|
34
|
+
poolIdList: options.poolId?.length ? options.poolId : ["all"],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function buildIndicatorCrossSectionBody(options) {
|
|
38
|
+
return {
|
|
39
|
+
indicatorCodeList: maybeArray(options.indicator),
|
|
40
|
+
securityCodeList: maybeArray(options.security),
|
|
41
|
+
date: options.date,
|
|
42
|
+
currency: options.currency,
|
|
43
|
+
scale: options.scale,
|
|
44
|
+
indicatorParamList: parseIndicatorParams(options.indicatorParam),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function buildIndicatorTimeSeriesBody(options) {
|
|
48
|
+
return {
|
|
49
|
+
indicatorCodeList: maybeArray(options.indicator),
|
|
50
|
+
securityCodeList: maybeArray(options.security),
|
|
51
|
+
startDate: options.startDate,
|
|
52
|
+
endDate: options.endDate,
|
|
53
|
+
calendarType: options.calendarType,
|
|
54
|
+
currency: options.currency,
|
|
55
|
+
scale: options.scale,
|
|
56
|
+
indicatorParamList: parseIndicatorParams(options.indicatorParam),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -559,4 +559,26 @@ export const ENDPOINTS = {
|
|
|
559
559
|
kind: "json",
|
|
560
560
|
description: "Query concept (theme index) constituent securities, grouped",
|
|
561
561
|
},
|
|
562
|
+
// ─── indicator (EDE: security-level data indicators) ───
|
|
563
|
+
"indicator.search": {
|
|
564
|
+
key: "indicator.search",
|
|
565
|
+
method: "POST",
|
|
566
|
+
path: "/application/open-indicator/EDE/search",
|
|
567
|
+
kind: "json",
|
|
568
|
+
description: "Search data indicators by keyword (returns indicatorCode + params)",
|
|
569
|
+
},
|
|
570
|
+
"indicator.cross-section": {
|
|
571
|
+
key: "indicator.cross-section",
|
|
572
|
+
method: "POST",
|
|
573
|
+
path: "/application/open-indicator/EDE/cross-section",
|
|
574
|
+
kind: "json",
|
|
575
|
+
description: "Get cross-section data (multi-indicator x multi-security, single date)",
|
|
576
|
+
},
|
|
577
|
+
"indicator.time-series": {
|
|
578
|
+
key: "indicator.time-series",
|
|
579
|
+
method: "POST",
|
|
580
|
+
path: "/application/open-indicator/EDE/time-series",
|
|
581
|
+
kind: "json",
|
|
582
|
+
description: "Get time-series data (multi-indicator x single-security OR single-indicator x multi-security)",
|
|
583
|
+
},
|
|
562
584
|
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ApiError } from "./errors.js";
|
|
2
|
+
// The EDE endpoints double-wrap on success: the shared client strips the outer
|
|
3
|
+
// envelope but leaves an inner { code, status, data } around the real payload.
|
|
4
|
+
// Peel that inner envelope so the list (search) / matrix (cross-section,
|
|
5
|
+
// time-series) is reachable. Observed errors arrive single-enveloped (the
|
|
6
|
+
// client throws on those), but a failure code carried only by the inner
|
|
7
|
+
// envelope must still surface instead of rendering its null payload as success.
|
|
8
|
+
export function unwrapIndicatorData(raw) {
|
|
9
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
10
|
+
const record = raw;
|
|
11
|
+
if ("data" in record && ("code" in record || "status" in record)) {
|
|
12
|
+
const code = record.code === undefined ? undefined : String(record.code);
|
|
13
|
+
const ok = record.status === true || code === "000000" || code === "0";
|
|
14
|
+
if (!ok) {
|
|
15
|
+
throw new ApiError(typeof record.msg === "string" && record.msg ? record.msg : "Indicator API request failed", code);
|
|
16
|
+
}
|
|
17
|
+
return record.data;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return raw;
|
|
21
|
+
}
|
|
22
|
+
function asStringArray(value) {
|
|
23
|
+
return Array.isArray(value) ? value.map((item) => String(item)) : undefined;
|
|
24
|
+
}
|
|
25
|
+
function rowOf(values, index) {
|
|
26
|
+
const row = values[index];
|
|
27
|
+
return Array.isArray(row) ? row : undefined;
|
|
28
|
+
}
|
|
29
|
+
// Build one column header per series. Prefer the human-readable name; on a
|
|
30
|
+
// duplicate name append the code so a column is never silently overwritten.
|
|
31
|
+
function buildHeaders(names, codes, count) {
|
|
32
|
+
const used = new Set();
|
|
33
|
+
const headers = [];
|
|
34
|
+
for (let i = 0; i < count; i++) {
|
|
35
|
+
const base = String(names?.[i] ?? codes?.[i] ?? `col${i}`);
|
|
36
|
+
let header = base;
|
|
37
|
+
let attempt = 1;
|
|
38
|
+
while (used.has(header)) {
|
|
39
|
+
const suffix = codes?.[i] ?? i;
|
|
40
|
+
header = attempt === 1 ? `${base} (${suffix})` : `${base} (${suffix})_${attempt}`;
|
|
41
|
+
attempt++;
|
|
42
|
+
}
|
|
43
|
+
used.add(header);
|
|
44
|
+
headers.push(header);
|
|
45
|
+
}
|
|
46
|
+
return headers;
|
|
47
|
+
}
|
|
48
|
+
// Cross-section: one row per security, one column per indicator. The live
|
|
49
|
+
// `values` is a flat [numIndicators * numSecurities][1] array in
|
|
50
|
+
// indicator-major order, so indicator i on security j is values[i*numSec+j][0].
|
|
51
|
+
export function flattenCrossSection(data) {
|
|
52
|
+
if (!data || typeof data !== "object")
|
|
53
|
+
return data;
|
|
54
|
+
const d = data;
|
|
55
|
+
const securityCode = asStringArray(d.securityCode);
|
|
56
|
+
const indicators = asStringArray(d.indicators);
|
|
57
|
+
if (!Array.isArray(d.values) || !securityCode || !indicators)
|
|
58
|
+
return data;
|
|
59
|
+
const securityName = asStringArray(d.securityName);
|
|
60
|
+
const headers = buildHeaders(asStringArray(d.indicatorName), indicators, indicators.length);
|
|
61
|
+
const numSec = securityCode.length;
|
|
62
|
+
const list = securityCode.map((code, j) => {
|
|
63
|
+
const row = { date: d.date, security: code, name: securityName?.[j] };
|
|
64
|
+
for (let i = 0; i < indicators.length; i++) {
|
|
65
|
+
row[headers[i]] = rowOf(d.values, i * numSec + j)?.[0];
|
|
66
|
+
}
|
|
67
|
+
return row;
|
|
68
|
+
});
|
|
69
|
+
return { list, total: list.length };
|
|
70
|
+
}
|
|
71
|
+
// Time-series: one row per date. Columns are the indicators (single-security
|
|
72
|
+
// case) or the securities (single-indicator case) — exactly one dimension
|
|
73
|
+
// varies, per the API contract. `values` is a 2D [series][date] matrix.
|
|
74
|
+
export function flattenTimeSeries(data) {
|
|
75
|
+
if (!data || typeof data !== "object")
|
|
76
|
+
return data;
|
|
77
|
+
const d = data;
|
|
78
|
+
const dates = asStringArray(d.dates);
|
|
79
|
+
const securityCode = asStringArray(d.securityCode);
|
|
80
|
+
const indicators = asStringArray(d.indicators);
|
|
81
|
+
if (!Array.isArray(d.values) || !dates || !securityCode || !indicators)
|
|
82
|
+
return data;
|
|
83
|
+
const seriesAreIndicators = securityCode.length <= 1;
|
|
84
|
+
const headers = seriesAreIndicators
|
|
85
|
+
? buildHeaders(asStringArray(d.indicatorName), indicators, indicators.length)
|
|
86
|
+
: buildHeaders(asStringArray(d.securityName), securityCode, securityCode.length);
|
|
87
|
+
const list = dates.map((date, k) => {
|
|
88
|
+
const row = { date };
|
|
89
|
+
for (let i = 0; i < headers.length; i++) {
|
|
90
|
+
row[headers[i]] = rowOf(d.values, i)?.[k];
|
|
91
|
+
}
|
|
92
|
+
return row;
|
|
93
|
+
});
|
|
94
|
+
return { list, total: list.length };
|
|
95
|
+
}
|
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.19.0";
|