@dshtrading/connector-akshare 0.6.0 → 0.6.2
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 +122 -25
- package/lib/dataplane.d.ts +2 -2
- package/lib/dataplane.js +7 -6
- package/lib/index.d.ts +2 -3
- package/lib/index.js +5 -8
- package/lib/rest.d.ts +12 -3
- package/lib/rest.js +94 -16
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,34 +1,131 @@
|
|
|
1
|
-
# @dshtrading/connector-
|
|
1
|
+
# @dshtrading/connector-akshare
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
A 股行情连接器:包名取 AkShare,**实现面是东方财富公开 REST 直连**(`push2` / `push2his` 两域)。
|
|
4
|
+
只提供 `tradingCnMarketData` 一个服务与 3 个 `cn_*` 工具,无交易通道(`src/index.ts` 无 TradeService、
|
|
5
|
+
无 `tradingCnTrade`)。配置只有 `enabled`;本包不连任何 AkShare / Python 本地服务,也没有需要填的凭证。
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
## 状态
|
|
8
|
+
|
|
9
|
+
**未实证(本出口当前不可用)**。
|
|
10
|
+
|
|
11
|
+
2026-10-14 本次会话实测,上游 push2 域不可达:
|
|
12
|
+
|
|
13
|
+
curl -m 15 'https://push2.eastmoney.com/api/qt/ulist.np/get?secids=1.600519&fields=f1,f2,f14'
|
|
14
|
+
# curl: (52) Empty reply from server
|
|
15
|
+
|
|
16
|
+
同刻连接器经 `RestClient.getSectorFundFlow()` 表现为 `TradingServiceError`,code `TRADING_NETWORK`
|
|
17
|
+
(`fetch failed`)。该现象与 `packages/connector-eastmoney/README.md`「已知边界」记录的 push2 整域空回一致。
|
|
18
|
+
|
|
19
|
+
2026-10-15 复测 push2his 同样空回(`kline/get` 各 klt 一律 `curl: (52)`),故本文「周期与契约」里
|
|
20
|
+
补上的 `1w/1M` 映射本次**未做真网络复验**,取值沿用同端点的 connector-eastmoney 映射表。
|
|
21
|
+
|
|
22
|
+
本仓唯一一份 akshare 真实网络记录是 [spikes/verify-connectors-suite/EVIDENCE.md](../../spikes/verify-connectors-suite/EVIDENCE.md)
|
|
23
|
+
(2026-08-31,针对 commit `b80000e`,import `lib/index.js` 直调各包 Rest 客户端):
|
|
24
|
+
板块资金流返回 10 条,但当时 `changePercent` 少除 100(现 `src/rest.ts:205` 已除 100);
|
|
25
|
+
北向资金接口当时恒返回 0 条(该工具与该能力现已从源码移除,当前 `src/` 无 `getNorthboundFlow`)。
|
|
26
|
+
报价与日 K 走同一组东财端点,该组端点的 2026-10-14 实测记录在
|
|
27
|
+
[spikes/impl-eastmoney-quote-endpoint/](../../spikes/impl-eastmoney-quote-endpoint/)(由 `src/rest.ts:144-146` 引用)。
|
|
28
|
+
|
|
29
|
+
## 配置
|
|
30
|
+
|
|
31
|
+
| 字段 | 默认 | 说明 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `enabled` | `true` | 是否激活本连接器(`src/index.ts:29-35`) |
|
|
34
|
+
|
|
35
|
+
## 接线
|
|
36
|
+
|
|
37
|
+
- **数据面行**:`packages/cn/cordis.patch.yml:35-38` → 行 id `dsh-trading-cn-dataplane-akshare`
|
|
38
|
+
(name `@dshtrading/connector-akshare/dataplane`,行 config 只写 `enabled: true`)。
|
|
39
|
+
注册 `(cn, akshare)` 进 `tradingMarketDataRegistry`(`src/dataplane.ts:36`),不占根市场键。
|
|
40
|
+
- **preset 行**:`packages/cn/assets/preset/cn-trader/agent.cordis.yml:62-67` → 行 id
|
|
41
|
+
`dsh-trading-cn-connector-akshare`(`config: {enabled: true, dryRun: true, liveTrading: false}`)。
|
|
42
|
+
- **bundle 依赖**:`packages/cn/package.json:31` 的 `"@dshtrading/connector-akshare": "workspace:*"`。
|
|
43
|
+
- **provider 词汇**:`packages/router/src/index.ts:77`(`PROVIDER_VOCABULARY` 成员)与
|
|
44
|
+
`src/index.ts:74` 的 `ROUTER_PROVIDER = 'akshare'` 同名。
|
|
45
|
+
- **服务键**:`tradingCnMarketData`(`src/index.ts:37`;dataplane 侧 isolate 同名,`src/dataplane.ts:34`)。
|
|
46
|
+
- **激活判定**:`routeAllows`(`src/index.ts:76-81`)——`enabled` 为真、且 router 的 `cn` 路由选中
|
|
47
|
+
`akshare` 才 provide;router 缺席时视为允许。
|
|
48
|
+
- **设置页登记**:显示名 `packages/client-ui-settings/src/client/locales.ts` 的 `'provider.akshare'` 条目
|
|
49
|
+
(zh `AkShare (东方财富公开端点)` / en `AkShare (Eastmoney public endpoints)`);
|
|
50
|
+
候选行 `packages/client-ui-settings/src/client/trading-settings-controller.ts:60` 只标
|
|
51
|
+
`markets: ['cn']`(= 本包 dataplane 的真实注册面),**无 `env`、无 `configField`、无凭据字段**——
|
|
52
|
+
本包没有本地服务或凭证可填。
|
|
9
53
|
|
|
10
|
-
|
|
11
|
-
(src/rest.ts 头部清单 7 项)实现 TODO。每个 TODO 的参照段都在 connector-okx
|
|
12
|
-
(packages/connector-okx/),它是本仓第一个真实 TradeService 连接器。
|
|
54
|
+
## 上游端点
|
|
13
55
|
|
|
14
|
-
|
|
56
|
+
| 用途 | 端点 | 源码 |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| 报价(单只与批量同端点) | `https://push2.eastmoney.com/api/qt/ulist.np/get?secids=<secid>&fields=f1,f2,f3,f5,f12,f13,f14,f18,f124` | `src/rest.ts:81,147` |
|
|
59
|
+
| K 线 | `https://push2his.eastmoney.com/api/qt/stock/kline/get?secid=<secid>&klt=<klt>&fqt=1&lmt=<limit>&fields2=f51,f52,f53,f54,f55,f56` | `src/rest.ts:180` |
|
|
60
|
+
| 板块资金流(东财行业板块 `fs=m:90+t:2`,固定取前 10) | `https://push2.eastmoney.com/api/qt/clist/get?pn=1&pz=10&po=1&np=1&fields=f12,f14,f3,f62&fs=m:90+t:2` | `src/rest.ts:200` |
|
|
15
61
|
|
|
16
|
-
|
|
17
|
-
模拟盘头/注入 fetch)直接可用;签名与端点 TODO。
|
|
18
|
-
- `src/index.ts` —— Config(enabled/env/dryRun/liveTrading + 三 ref 凭证组)、
|
|
19
|
-
闸门三态判定、凭证解析(BYOK + 环境变量回落)、MarketDataService/TradeService 骨架、
|
|
20
|
-
7 个市场前缀工具注册(duplicate-safe)、apply(enabled=false 静默退出)。
|
|
21
|
-
- `test/template.test.ts` —— 结构冒烟 7 用例。
|
|
62
|
+
## 周期与契约
|
|
22
63
|
|
|
23
|
-
|
|
64
|
+
- **周期词汇**:`INTERVAL_VOCABULARY = ['1m','5m','15m','30m','1h','1d','1w','1M']`(`src/rest.ts:24`),
|
|
65
|
+
同一数组进 `cn_get_klines` 的 `interval` 枚举(`src/index.ts:110`)。
|
|
66
|
+
- **真实可服务子集**:`SUPPORTED_KLINES_INTERVALS = ['5m','15m','30m','1h','1d','1w','1M']`(`src/rest.ts:44`,
|
|
67
|
+
由 `KLINES_INTERVAL_SPEC` 的键导出,`src/rest.ts:33-41`);工具描述里如实标注该子集(`src/index.ts:114`)。
|
|
68
|
+
- **klt 映射与拒绝**:`5m→5`、`15m→15`、`30m→30`、`1h→60`、`1d→101`、`1w→102`、`1M→103`;
|
|
69
|
+
表外周期(含 `1m`)在**发请求前**由 `resolveKlinesInterval` 抛 `TRADING_UNSUPPORTED_INTERVAL`
|
|
70
|
+
(`src/rest.ts:51-60`),不静默按日线返回。`1m` 被拒是因为 `kline/get` 的 `klt=1` 只回当日、
|
|
71
|
+
`lmt` 被上游忽略(connector-eastmoney 实测),本包没有 trends2 分时通道。
|
|
72
|
+
- **`closeTime` 按周期**:`closeTime = openTime + stepMs - 1`(`src/rest.ts:194`),
|
|
73
|
+
stepMs 与 klt 同表(1d/1w/1M = 24h/7d/30d)——周/月 K 跨度 ≥ 2 天,`packages/indicators` 的
|
|
74
|
+
`klineTradingDay` 才会按周期末对齐;旧的一律 `+86400000-1` 会把周 K 当单日 K。
|
|
75
|
+
- **符号形态**(`toEastmoneySecid`,`src/rest.ts:62-75`):含 `.` 时 `.<市场>` 原样取(`600519.SH`);
|
|
76
|
+
裸 6 位不以 `0`/`3` 开头视为 `SH`、以 `0`/`3` 开头视为 `SZ`;输出规范形 `<code>.<MARKET>`,
|
|
77
|
+
东财 `secid` = `<SH→1,其余→0>.<code>`。不接受 `sh600519` 前缀形(会拼成 `1.SH600519`),
|
|
78
|
+
也不做市场白名单或显式拒绝。
|
|
79
|
+
- **取价按行**:`price = f2 / 10^f1`;`f1` 缺失或不在 `0..6` 时回落市场级 `×100`(`src/rest.ts:99-108,156-158`)。
|
|
80
|
+
- **错误映射**(`src/rest.ts:123-140,153`):非 2xx → `TRADING_UPSTREAM_ERROR`;
|
|
81
|
+
不可达与响应非 JSON 均 → `TRADING_NETWORK`;报价端点少返回行(`data.diff` 空或 `data` 为 `null`)
|
|
82
|
+
→ `TRADING_SYMBOL_NOT_FOUND`;周期表外 → `TRADING_UNSUPPORTED_INTERVAL`(本地判定,不出网);
|
|
83
|
+
`getSectorFundFlow` 对空结果返回 `[]`,不报错。
|
|
24
84
|
|
|
25
|
-
|
|
26
|
-
`crypto`(工具前缀)/ `Crypto`(服务键 infix)。token 未展开也可编译;
|
|
27
|
-
生成器保证全部替换。
|
|
85
|
+
## 能力面
|
|
28
86
|
|
|
29
|
-
|
|
87
|
+
| 服务方法 | 本包实现 | 工具暴露 |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `getTicker` | 有(`src/index.ts:51`) | `cn_get_ticker`(`src/index.ts:96`) |
|
|
90
|
+
| `getKlines` | 有(`src/index.ts:55`) | `cn_get_klines`(`src/index.ts:106`) |
|
|
91
|
+
| `getSectorFundFlow` | 有(`src/index.ts:59`) | `cn_get_sector_fund_flow`(`src/index.ts:126`) |
|
|
92
|
+
| `subscribeTicker`(快照轮询,最小 1000 ms、默认 5000 ms) | 有(`src/index.ts:63-71`) | 缺席 |
|
|
93
|
+
| `listInstruments` / 盘口 / 基本面 / 宏观与另类数据 | 未实现 | 未实现 |
|
|
94
|
+
| TradeService(下单 / 撤单 / 持仓 / 资金) | 未实现(本包无 `tradingCnTrade`) | 未实现 |
|
|
95
|
+
|
|
96
|
+
## 已知边界
|
|
97
|
+
|
|
98
|
+
- **`openTime` 按本地时区解析**:`new Date(parts[0].replace(/-/g, '/'))`(`src/rest.ts:186`)。
|
|
99
|
+
- **`1m` 无实现路径**:见「周期与契约」——上层按 `TRADING_UNSUPPORTED_INTERVAL` 自适应更粗粒度
|
|
100
|
+
(`packages/client-ui-trading/src/client/intraday-series.ts`)。
|
|
101
|
+
- **`1w/1M`(klt 102/103)未真网络复验**:2026-10-15 push2his 空回,映射取自同端点的
|
|
102
|
+
connector-eastmoney。
|
|
103
|
+
- 板块资金流固定 `pz=10`(只回前 10 个板块,`src/rest.ts:200`);`changePercent` 由 `f3/100` 得到
|
|
104
|
+
(`src/rest.ts:205`),`mainNetInflow` 直接取 `f62`,单位未换算(`src/rest.ts:206`)。
|
|
105
|
+
- 无 `listInstruments`、盘口、基本面、主动买盘能力(同市场的 eastmoney、xysz 各有对照实现)。
|
|
106
|
+
- 2026-10-14 起本出口对 `push2.eastmoney.com` 不可达(`curl: (52) Empty reply from server`),
|
|
107
|
+
故「状态」段按未实证处理;2026-10-15 复测 push2his 同样空回。
|
|
108
|
+
|
|
109
|
+
## 合规记录
|
|
110
|
+
|
|
111
|
+
**东方财富公开端点**:无 key、无官方授权,个人使用边界自负,以东方财富服务条款为准。
|
|
112
|
+
本次会话实测该端点不可达。本仓不缓存、不再分发行情数据。
|
|
113
|
+
|
|
114
|
+
## 出网验证
|
|
115
|
+
|
|
116
|
+
本包暂无已提交的独立出网验证脚本(`spikes/` 下无 akshare 专属探针;上文引的两份是跨连接器随测与东财端点实测)。
|
|
117
|
+
最小复跑方式(与仓内探针纪律一致:`tsx` 直跑 `src/*.ts`,不依赖 `lib/` 构建产物):
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
# 板块资金流(单次请求;上游空结果返回 [],不报错)
|
|
121
|
+
./node_modules/.bin/tsx -e "import('./packages/connector-akshare/src/rest.ts').then(async m => console.log(JSON.stringify(await new m.AkshareRestClient().getSectorFundFlow())))"
|
|
122
|
+
|
|
123
|
+
# 报价(走 push2 ulist.np/get,单只)
|
|
124
|
+
./node_modules/.bin/tsx -e "import('./packages/connector-akshare/src/rest.ts').then(async m => console.log(JSON.stringify(await new m.AkshareRestClient().getTicker('600519.SH'))))"
|
|
125
|
+
|
|
126
|
+
# 周期映射(不出网):1w 应得 klt=102;1m 应抛 TRADING_UNSUPPORTED_INTERVAL
|
|
127
|
+
./node_modules/.bin/tsx -e "import('./packages/connector-akshare/src/rest.ts').then(m => { console.log(m.resolveKlinesInterval('1w')); try { m.resolveKlinesInterval('1m') } catch (e) { console.log(e.code) } })"
|
|
128
|
+
```
|
|
30
129
|
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
- 行 id = `dsh-trading-<market>-connector-<slug>`,全仓唯一(insert-only 铁律 #1)。
|
|
34
|
-
- 凭证只进 ref,绝不内置/落日志(铁律 #3/#5)。
|
|
130
|
+
2026-10-14 本次会话下,第一条落在 `TRADING_NETWORK: fetch failed`;2026-10-15 复测 push2his 空回,
|
|
131
|
+
第二、三条未取得上游响应(第三条不出网,本地可判)。
|
package/lib/dataplane.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import { Config } from "./index.js";
|
|
|
2
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
3
3
|
//#region src/dataplane.d.ts
|
|
4
4
|
declare const inject: string[];
|
|
5
|
-
declare function apply(ctx: Context, config
|
|
5
|
+
declare function apply(ctx: Context, config?: Config): void;
|
|
6
6
|
//#endregion
|
|
7
|
-
export { apply, inject };
|
|
7
|
+
export { Config, apply, inject };
|
package/lib/dataplane.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { AkshareMarketDataService, TRADING_CN_MARKET_DATA_KEY } from "./index.js";
|
|
1
|
+
import { AkshareMarketDataService, Config, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY } from "./index.js";
|
|
2
2
|
//#region src/dataplane.ts
|
|
3
3
|
const inject = [];
|
|
4
|
+
/** schema 的运行时可调用形态(校验即补默认值);手工构造 ctx 不传 config 时按 schema 默认值处理。 */
|
|
5
|
+
const withDefaults = (value) => Config(value);
|
|
4
6
|
function resolveMarketDataRegistry(ctx) {
|
|
5
7
|
const candidate = ctx.get?.("tradingMarketDataRegistry", false);
|
|
6
8
|
return candidate !== void 0 ? candidate : void 0;
|
|
7
9
|
}
|
|
8
|
-
const ROUTER_PROVIDER = "akshare";
|
|
9
10
|
const MARKET = "cn";
|
|
10
|
-
function apply(ctx, config) {
|
|
11
|
+
function apply(ctx, config = withDefaults(void 0)) {
|
|
11
12
|
if (!config.enabled) return;
|
|
12
13
|
const registry = resolveMarketDataRegistry(ctx);
|
|
13
14
|
if (registry === void 0) {
|
|
14
|
-
new AkshareMarketDataService(ctx
|
|
15
|
+
new AkshareMarketDataService(ctx);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
const inner = ctx.isolate(TRADING_CN_MARKET_DATA_KEY);
|
|
18
|
-
const service = new AkshareMarketDataService(inner
|
|
19
|
+
const service = new AkshareMarketDataService(inner);
|
|
19
20
|
ctx.effect(() => registry.register(MARKET, ROUTER_PROVIDER, service));
|
|
20
21
|
}
|
|
21
22
|
//#endregion
|
|
22
|
-
export { apply, inject };
|
|
23
|
+
export { Config, apply, inject };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AkshareRestClient, AkshareRestOptions, INTERVAL_VOCABULARY, Interval, Kline, Ticker, TradingServiceError, toEastmoneySecid } from "./rest.js";
|
|
1
|
+
import { AkshareRestClient, AkshareRestOptions, INTERVAL_VOCABULARY, Interval, Kline, SUPPORTED_KLINES_INTERVALS, Ticker, TradingServiceError, resolveKlinesInterval, toEastmoneySecid } from "./rest.js";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import Schema from "@deepseek-ai/schemastery";
|
|
4
4
|
import { Disposable, Interval as Interval$1, Kline as Kline$1, MarketDataService, Ticker as Ticker$1 } from "@dshtrading/api";
|
|
@@ -6,7 +6,6 @@ import { Disposable, Interval as Interval$1, Kline as Kline$1, MarketDataService
|
|
|
6
6
|
declare const name = "dsh-trading-cn-connector-akshare";
|
|
7
7
|
interface Config {
|
|
8
8
|
enabled: boolean;
|
|
9
|
-
apiUrl?: string;
|
|
10
9
|
}
|
|
11
10
|
declare const Config: Schema<Config>;
|
|
12
11
|
declare const TRADING_CN_MARKET_DATA_KEY = "tradingCnMarketData";
|
|
@@ -28,4 +27,4 @@ declare const ROUTER_PROVIDER = "akshare";
|
|
|
28
27
|
declare function routeAllows(ctx: Context, config: Config, market: string): boolean;
|
|
29
28
|
declare function apply(ctx: Context, config: Config): void;
|
|
30
29
|
//#endregion
|
|
31
|
-
export { AkshareMarketDataService, AkshareRestClient, AkshareRestOptions, Config, INTERVAL_VOCABULARY, type Interval, type Kline, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, type Ticker, TradingServiceError, apply, name, routeAllows, toEastmoneySecid };
|
|
30
|
+
export { AkshareMarketDataService, AkshareRestClient, AkshareRestOptions, Config, INTERVAL_VOCABULARY, type Interval, type Kline, ROUTER_PROVIDER, SUPPORTED_KLINES_INTERVALS, TRADING_CN_MARKET_DATA_KEY, type Ticker, TradingServiceError, apply, name, resolveKlinesInterval, routeAllows, toEastmoneySecid };
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { AkshareRestClient, INTERVAL_VOCABULARY, TradingServiceError, toEastmoneySecid } from "./rest.js";
|
|
1
|
+
import { AkshareRestClient, INTERVAL_VOCABULARY, SUPPORTED_KLINES_INTERVALS, TradingServiceError, resolveKlinesInterval, toEastmoneySecid } from "./rest.js";
|
|
2
2
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
4
|
import Schema from "@deepseek-ai/schemastery";
|
|
5
5
|
//#region src/index.ts
|
|
6
6
|
const name = "dsh-trading-cn-connector-akshare";
|
|
7
|
-
const Config = Schema.object({
|
|
8
|
-
enabled: Schema.boolean().default(true).description("是否激活 AkShare 连接器"),
|
|
9
|
-
apiUrl: Schema.string().default("http://127.0.0.1:8080").description("AkShare 本地 RPC/HTTP 服务地址")
|
|
10
|
-
});
|
|
7
|
+
const Config = Schema.object({ enabled: Schema.boolean().default(true).description("是否激活 AkShare 连接器") });
|
|
11
8
|
const TRADING_CN_MARKET_DATA_KEY = "tradingCnMarketData";
|
|
12
9
|
var AkshareMarketDataService = class extends Service {
|
|
13
10
|
client;
|
|
@@ -44,7 +41,7 @@ function routeAllows(ctx, config, market) {
|
|
|
44
41
|
function apply(ctx, config) {
|
|
45
42
|
if (!config.enabled) return;
|
|
46
43
|
if (!routeAllows(ctx, config, "cn")) return;
|
|
47
|
-
const marketData = new AkshareMarketDataService(ctx
|
|
44
|
+
const marketData = new AkshareMarketDataService(ctx);
|
|
48
45
|
ctx.inject(["tools"], (ctx) => {
|
|
49
46
|
const tools = ctx.tools;
|
|
50
47
|
const register = (t) => {
|
|
@@ -82,7 +79,7 @@ function apply(ctx, config) {
|
|
|
82
79
|
type: "string",
|
|
83
80
|
enum: INTERVAL_VOCABULARY,
|
|
84
81
|
default: "1d",
|
|
85
|
-
description:
|
|
82
|
+
description: `Interval (Eastmoney kline/get serves ${SUPPORTED_KLINES_INTERVALS.join("/")}; 1m and other values are rejected with TRADING_UNSUPPORTED_INTERVAL)`
|
|
86
83
|
},
|
|
87
84
|
limit: {
|
|
88
85
|
type: "integer",
|
|
@@ -121,4 +118,4 @@ function apply(ctx, config) {
|
|
|
121
118
|
});
|
|
122
119
|
}
|
|
123
120
|
//#endregion
|
|
124
|
-
export { AkshareMarketDataService, AkshareRestClient, Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, apply, name, routeAllows, toEastmoneySecid };
|
|
121
|
+
export { AkshareMarketDataService, AkshareRestClient, Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, SUPPORTED_KLINES_INTERVALS, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, apply, name, resolveKlinesInterval, routeAllows, toEastmoneySecid };
|
package/lib/rest.d.ts
CHANGED
|
@@ -5,16 +5,25 @@ declare class TradingServiceError extends Error {
|
|
|
5
5
|
constructor(code: TradingErrorCode, message: string, cause?: unknown);
|
|
6
6
|
}
|
|
7
7
|
declare const INTERVAL_VOCABULARY: readonly ["1m", "5m", "15m", "30m", "1h", "1d", "1w", "1M"];
|
|
8
|
+
/** 本包 K 线真实可服务的周期(`INTERVAL_VOCABULARY` 的严格子集:1m 不在其中)。 */
|
|
9
|
+
declare const SUPPORTED_KLINES_INTERVALS: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* 周期 → klt 与时长;映射表外的周期在发请求前显式拒绝(`TRADING_UNSUPPORTED_INTERVAL`),
|
|
12
|
+
* 不静默按日线返回。`1m` 也在拒绝之列:kline/get 的 klt=1 只回当日(`lmt` 被上游忽略,
|
|
13
|
+
* connector-eastmoney/src/rest.ts 实测),本包没有 trends2 分时通道,取不到多日 1m 序列。
|
|
14
|
+
*/
|
|
15
|
+
declare function resolveKlinesInterval(interval: Interval$1): {
|
|
16
|
+
readonly klt: string;
|
|
17
|
+
readonly stepMs: number;
|
|
18
|
+
};
|
|
8
19
|
declare function toEastmoneySecid(symbol: string): {
|
|
9
20
|
secid: string;
|
|
10
21
|
canonical: string;
|
|
11
22
|
};
|
|
12
23
|
interface AkshareRestOptions {
|
|
13
|
-
apiUrl?: string;
|
|
14
24
|
fetchImpl?: typeof fetch;
|
|
15
25
|
}
|
|
16
26
|
declare class AkshareRestClient {
|
|
17
|
-
readonly apiUrl: string;
|
|
18
27
|
private readonly fetchImpl;
|
|
19
28
|
constructor(options?: AkshareRestOptions);
|
|
20
29
|
private requestJson;
|
|
@@ -27,4 +36,4 @@ declare class AkshareRestClient {
|
|
|
27
36
|
}>>;
|
|
28
37
|
}
|
|
29
38
|
//#endregion
|
|
30
|
-
export { AkshareRestClient, AkshareRestOptions, INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, type Ticker$1 as Ticker, TradingServiceError, toEastmoneySecid };
|
|
39
|
+
export { AkshareRestClient, AkshareRestOptions, INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, SUPPORTED_KLINES_INTERVALS, type Ticker$1 as Ticker, TradingServiceError, resolveKlinesInterval, toEastmoneySecid };
|
package/lib/rest.js
CHANGED
|
@@ -18,6 +18,55 @@ const INTERVAL_VOCABULARY = [
|
|
|
18
18
|
"1w",
|
|
19
19
|
"1M"
|
|
20
20
|
];
|
|
21
|
+
/**
|
|
22
|
+
* K 线周期 → 东财 kline/get 的 klt 周期码 + 单根时长。
|
|
23
|
+
*
|
|
24
|
+
* klt 与同打 push2his kline/get 的 connector-eastmoney 同一张表(该包 `mapIntervalToKlt`);
|
|
25
|
+
* stepMs 是 K 线覆盖时长,用于 closeTime = openTime + stepMs - 1——分钟线不再按日线加满一天,
|
|
26
|
+
* 周/月线也必须长于 2 天,否则 packages/indicators 的 klineTradingDay 会把周 K 当成单日 K。
|
|
27
|
+
*/
|
|
28
|
+
const KLINES_INTERVAL_SPEC = {
|
|
29
|
+
"5m": {
|
|
30
|
+
klt: "5",
|
|
31
|
+
stepMs: 3e5
|
|
32
|
+
},
|
|
33
|
+
"15m": {
|
|
34
|
+
klt: "15",
|
|
35
|
+
stepMs: 9e5
|
|
36
|
+
},
|
|
37
|
+
"30m": {
|
|
38
|
+
klt: "30",
|
|
39
|
+
stepMs: 18e5
|
|
40
|
+
},
|
|
41
|
+
"1h": {
|
|
42
|
+
klt: "60",
|
|
43
|
+
stepMs: 36e5
|
|
44
|
+
},
|
|
45
|
+
"1d": {
|
|
46
|
+
klt: "101",
|
|
47
|
+
stepMs: 864e5
|
|
48
|
+
},
|
|
49
|
+
"1w": {
|
|
50
|
+
klt: "102",
|
|
51
|
+
stepMs: 6048e5
|
|
52
|
+
},
|
|
53
|
+
"1M": {
|
|
54
|
+
klt: "103",
|
|
55
|
+
stepMs: 2592e6
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/** 本包 K 线真实可服务的周期(`INTERVAL_VOCABULARY` 的严格子集:1m 不在其中)。 */
|
|
59
|
+
const SUPPORTED_KLINES_INTERVALS = Object.keys(KLINES_INTERVAL_SPEC);
|
|
60
|
+
/**
|
|
61
|
+
* 周期 → klt 与时长;映射表外的周期在发请求前显式拒绝(`TRADING_UNSUPPORTED_INTERVAL`),
|
|
62
|
+
* 不静默按日线返回。`1m` 也在拒绝之列:kline/get 的 klt=1 只回当日(`lmt` 被上游忽略,
|
|
63
|
+
* connector-eastmoney/src/rest.ts 实测),本包没有 trends2 分时通道,取不到多日 1m 序列。
|
|
64
|
+
*/
|
|
65
|
+
function resolveKlinesInterval(interval) {
|
|
66
|
+
const spec = KLINES_INTERVAL_SPEC[interval];
|
|
67
|
+
if (spec === void 0) throw new TradingServiceError("TRADING_UNSUPPORTED_INTERVAL", `AkShare/Eastmoney klines do not serve interval "${String(interval)}"; supported: ${SUPPORTED_KLINES_INTERVALS.join(", ")}`);
|
|
68
|
+
return spec;
|
|
69
|
+
}
|
|
21
70
|
function toEastmoneySecid(symbol) {
|
|
22
71
|
const clean = symbol.trim().toUpperCase();
|
|
23
72
|
let code = clean;
|
|
@@ -32,11 +81,34 @@ function toEastmoneySecid(symbol) {
|
|
|
32
81
|
canonical: `${code}.${market}`
|
|
33
82
|
};
|
|
34
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* ulist.np/get 报价行字段:f1=小数位数 f2=最新价 f3=涨跌幅(%) f5=成交量(手) f12=代码
|
|
86
|
+
* f13=市场 f14=名称 f18=昨收 f124=行情时间(秒)。价格类字段是 ×10^f1 的整数。
|
|
87
|
+
*/
|
|
88
|
+
const QUOTE_FIELDS = "f1,f2,f3,f5,f12,f13,f14,f18,f124";
|
|
89
|
+
/** 报价字段 → 有限数;null/空串/停牌占位返回 undefined,由调用方决定兜底值。 */
|
|
90
|
+
function toFiniteNumber(value) {
|
|
91
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
92
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
93
|
+
const parsed = Number(value);
|
|
94
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** cn 市场的分精度兜底倍率:×100(2 位小数),只在报价行没给出可用的 f1 时使用。 */
|
|
98
|
+
const CN_PRICE_SCALE = 100;
|
|
99
|
+
/**
|
|
100
|
+
* 报价行的价格除数 = 10^f1(f1 = 上游给该标的声明的小数位数):A 股股票 f1=2、A 股 ETF
|
|
101
|
+
* f1=3(2026-10-14 实测),故取价一律按行——市场级 ×100 会把 ETF 读成 10 倍。
|
|
102
|
+
* f1 缺失或落在 0..6 之外时回落到市场级常数。
|
|
103
|
+
*/
|
|
104
|
+
function quotePriceScale(decimalDigits) {
|
|
105
|
+
const digits = toFiniteNumber(decimalDigits);
|
|
106
|
+
if (digits === void 0 || !Number.isInteger(digits) || digits < 0 || digits > 6) return CN_PRICE_SCALE;
|
|
107
|
+
return 10 ** digits;
|
|
108
|
+
}
|
|
35
109
|
var AkshareRestClient = class {
|
|
36
|
-
apiUrl;
|
|
37
110
|
fetchImpl;
|
|
38
111
|
constructor(options = {}) {
|
|
39
|
-
this.apiUrl = options.apiUrl ?? (process.env.AKSHARE_API_URL || "http://127.0.0.1:8080");
|
|
40
112
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
41
113
|
}
|
|
42
114
|
async requestJson(url) {
|
|
@@ -51,24 +123,30 @@ var AkshareRestClient = class {
|
|
|
51
123
|
}
|
|
52
124
|
async getTicker(symbol) {
|
|
53
125
|
const { secid, canonical } = toEastmoneySecid(symbol);
|
|
54
|
-
const url = `https://push2.eastmoney.com/api/qt/
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
126
|
+
const url = `https://push2.eastmoney.com/api/qt/ulist.np/get?secids=${secid}&fields=${QUOTE_FIELDS}`;
|
|
127
|
+
const row = (await this.requestJson(url)).data?.diff?.[0];
|
|
128
|
+
if (row === void 0) throw new TradingServiceError("TRADING_SYMBOL_NOT_FOUND", `AkShare/Eastmoney quote not found: ${symbol}`);
|
|
129
|
+
const scale = quotePriceScale(row.f1);
|
|
130
|
+
const rawPrice = toFiniteNumber(row.f2);
|
|
131
|
+
const price = rawPrice !== void 0 && rawPrice > 0 ? rawPrice / scale : 0;
|
|
132
|
+
const prevClose = toFiniteNumber(row.f18);
|
|
133
|
+
const changePercent = toFiniteNumber(row.f3);
|
|
134
|
+
const quoteTime = toFiniteNumber(row.f124);
|
|
135
|
+
const name = typeof row.f14 === "string" && row.f14.trim() ? row.f14.trim() : void 0;
|
|
60
136
|
return {
|
|
61
137
|
symbol: canonical,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
...
|
|
138
|
+
...name ? { name } : {},
|
|
139
|
+
price,
|
|
140
|
+
volume: toFiniteNumber(row.f5) ?? 0,
|
|
141
|
+
timestamp: quoteTime !== void 0 && quoteTime > 0 ? quoteTime * 1e3 : Date.now(),
|
|
142
|
+
...prevClose !== void 0 && prevClose > 0 ? { prevClose: prevClose / scale } : {},
|
|
143
|
+
...changePercent !== void 0 ? { changePercent: changePercent / 100 } : {}
|
|
67
144
|
};
|
|
68
145
|
}
|
|
69
146
|
async getKlines(symbol, interval = "1d", limit = 100) {
|
|
147
|
+
const { klt, stepMs } = resolveKlinesInterval(interval);
|
|
70
148
|
const { secid } = toEastmoneySecid(symbol);
|
|
71
|
-
const url = `https://push2his.eastmoney.com/api/qt/stock/kline/get?secid=${secid}&klt=${
|
|
149
|
+
const url = `https://push2his.eastmoney.com/api/qt/stock/kline/get?secid=${secid}&klt=${klt}&fqt=1&lmt=${limit}&fields2=f51,f52,f53,f54,f55,f56`;
|
|
72
150
|
const res = await this.requestJson(url);
|
|
73
151
|
if (!res.data?.klines) return [];
|
|
74
152
|
return res.data.klines.map((line) => {
|
|
@@ -81,7 +159,7 @@ var AkshareRestClient = class {
|
|
|
81
159
|
high: parseFloat(parts[3]),
|
|
82
160
|
low: parseFloat(parts[4]),
|
|
83
161
|
volume: parseFloat(parts[5]),
|
|
84
|
-
closeTime: openTime +
|
|
162
|
+
closeTime: openTime + stepMs - 1
|
|
85
163
|
};
|
|
86
164
|
});
|
|
87
165
|
}
|
|
@@ -94,4 +172,4 @@ var AkshareRestClient = class {
|
|
|
94
172
|
}
|
|
95
173
|
};
|
|
96
174
|
//#endregion
|
|
97
|
-
export { AkshareRestClient, INTERVAL_VOCABULARY, TradingServiceError, toEastmoneySecid };
|
|
175
|
+
export { AkshareRestClient, INTERVAL_VOCABULARY, SUPPORTED_KLINES_INTERVALS, TradingServiceError, resolveKlinesInterval, toEastmoneySecid };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-akshare",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.6.
|
|
3
|
+
"description": "AkShare 名义的 A 股宏观/另类数据连接器:实现面为东方财富公开 REST 直连(push2/push2his),提供 tradingCnMarketData 行情服务与 cn_get_ticker / cn_get_klines / cn_get_sector_fund_flow 三个工具,无交易通道。",
|
|
4
|
+
"version": "0.6.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
],
|
|
22
22
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dshtrading/api": "^0.6.
|
|
24
|
+
"@dshtrading/api": "^0.6.2"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@deepseek-ai/cordis": ">=4.0.0",
|
|
28
|
-
"@deepseek-ai/dsh-tools": ">=0.2.
|
|
28
|
+
"@deepseek-ai/dsh-tools": ">=0.2.1-rc.2",
|
|
29
29
|
"@deepseek-ai/schemastery": ">=3.18.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|