@dshtrading/connector-eastmoney 0.1.4 → 0.1.6
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/lib/dataplane.js +7 -6
- package/lib/index.d.ts +7 -3
- package/lib/index.js +19 -11
- package/lib/rest.d.ts +32 -6
- package/lib/rest.js +115 -31
- package/package.json +2 -2
package/lib/dataplane.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EastmoneyMarketDataService,
|
|
1
|
+
import { EastmoneyMarketDataService, marketDataKey } from "./index.js";
|
|
2
2
|
//#region src/dataplane.ts
|
|
3
3
|
const inject = [];
|
|
4
4
|
function resolveMarketDataRegistry(ctx) {
|
|
@@ -6,17 +6,18 @@ function resolveMarketDataRegistry(ctx) {
|
|
|
6
6
|
return candidate !== void 0 ? candidate : void 0;
|
|
7
7
|
}
|
|
8
8
|
const ROUTER_PROVIDER = "eastmoney";
|
|
9
|
-
const MARKET = "cn";
|
|
10
9
|
function apply(ctx, config) {
|
|
11
10
|
if (!config.enabled) return;
|
|
11
|
+
const market = config.market ?? "cn";
|
|
12
|
+
const key = marketDataKey(market);
|
|
12
13
|
const registry = resolveMarketDataRegistry(ctx);
|
|
13
14
|
if (registry === void 0) {
|
|
14
|
-
new EastmoneyMarketDataService(ctx);
|
|
15
|
+
new EastmoneyMarketDataService(ctx, { market }, key);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
|
-
const inner = ctx.isolate(
|
|
18
|
-
const service = new EastmoneyMarketDataService(inner);
|
|
19
|
-
ctx.effect(() => registry.register(
|
|
18
|
+
const inner = ctx.isolate(key);
|
|
19
|
+
const service = new EastmoneyMarketDataService(inner, { market }, key);
|
|
20
|
+
ctx.effect(() => registry.register(market, ROUTER_PROVIDER, service));
|
|
20
21
|
}
|
|
21
22
|
//#endregion
|
|
22
23
|
export { apply, inject };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EastmoneyRestClient, EastmoneyRestOptions, INTERVAL_VOCABULARY, Interval, Kline, Ticker, TradingServiceError, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid } from "./rest.js";
|
|
1
|
+
import { EastmoneyMarket, EastmoneyRestClient, EastmoneyRestOptions, INTERVAL_VOCABULARY, Interval, Kline, Ticker, TradingServiceError, eastmoneyPriceScale, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid, utc8WallTimeToEpochMs } 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,9 +6,13 @@ import { Disposable, Interval as Interval$1, Kline as Kline$1, MarketDataService
|
|
|
6
6
|
declare const name = "dsh-trading-cn-connector-eastmoney";
|
|
7
7
|
interface Config {
|
|
8
8
|
enabled: boolean;
|
|
9
|
+
/** 市场分流(单包双市场,照抄 connector-tencent 模式):cn=A 股(缺省),hk=港股(trends2 分钟线实证)。 */
|
|
10
|
+
market?: 'cn' | 'hk';
|
|
9
11
|
}
|
|
10
12
|
declare const Config: Schema<Config>;
|
|
11
13
|
declare const TRADING_CN_MARKET_DATA_KEY = "tradingCnMarketData";
|
|
14
|
+
declare const TRADING_HK_MARKET_DATA_KEY = "tradingHkMarketData";
|
|
15
|
+
declare function marketDataKey(market: 'cn' | 'hk'): string;
|
|
12
16
|
declare class EastmoneyMarketDataService extends Service implements MarketDataService {
|
|
13
17
|
private readonly client;
|
|
14
18
|
constructor(ctx: Context, options?: EastmoneyRestOptions, serviceName?: string);
|
|
@@ -23,7 +27,7 @@ declare class EastmoneyMarketDataService extends Service implements MarketDataSe
|
|
|
23
27
|
}): Disposable;
|
|
24
28
|
}
|
|
25
29
|
declare const ROUTER_PROVIDER = "eastmoney";
|
|
26
|
-
declare function routeAllows(ctx: Context, config: Config, market:
|
|
30
|
+
declare function routeAllows(ctx: Context, config: Config, market: 'cn' | 'hk'): boolean;
|
|
27
31
|
declare function apply(ctx: Context, config: Config): void;
|
|
28
32
|
//#endregion
|
|
29
|
-
export { Config, EastmoneyMarketDataService, EastmoneyRestClient, EastmoneyRestOptions, INTERVAL_VOCABULARY, type Interval, type Kline, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, type Ticker, TradingServiceError, apply, mapIntervalToKlt, name, parseIntervalMs, routeAllows, toEastmoneySecid };
|
|
33
|
+
export { Config, EastmoneyMarket, EastmoneyMarketDataService, EastmoneyRestClient, EastmoneyRestOptions, INTERVAL_VOCABULARY, type Interval, type Kline, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TRADING_HK_MARKET_DATA_KEY, type Ticker, TradingServiceError, apply, eastmoneyPriceScale, mapIntervalToKlt, marketDataKey, name, parseIntervalMs, routeAllows, toEastmoneySecid, utc8WallTimeToEpochMs };
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { EastmoneyRestClient, INTERVAL_VOCABULARY, TradingServiceError, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid } from "./rest.js";
|
|
1
|
+
import { EastmoneyRestClient, INTERVAL_VOCABULARY, TradingServiceError, eastmoneyPriceScale, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid, utc8WallTimeToEpochMs } 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-eastmoney";
|
|
7
|
-
const Config = Schema.object({
|
|
7
|
+
const Config = Schema.object({
|
|
8
|
+
enabled: Schema.boolean().default(true).description("是否激活东财连接器"),
|
|
9
|
+
market: Schema.union(["cn", "hk"]).default("cn").description("市场:cn=A 股,hk=港股")
|
|
10
|
+
});
|
|
8
11
|
const TRADING_CN_MARKET_DATA_KEY = "tradingCnMarketData";
|
|
12
|
+
const TRADING_HK_MARKET_DATA_KEY = "tradingHkMarketData";
|
|
13
|
+
function marketDataKey(market) {
|
|
14
|
+
return market === "hk" ? TRADING_HK_MARKET_DATA_KEY : TRADING_CN_MARKET_DATA_KEY;
|
|
15
|
+
}
|
|
9
16
|
var EastmoneyMarketDataService = class extends Service {
|
|
10
17
|
client;
|
|
11
18
|
constructor(ctx, options = {}, serviceName = TRADING_CN_MARKET_DATA_KEY) {
|
|
@@ -40,20 +47,21 @@ function routeAllows(ctx, config, market) {
|
|
|
40
47
|
}
|
|
41
48
|
function apply(ctx, config) {
|
|
42
49
|
if (!config.enabled) return;
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const market = config.market ?? "cn";
|
|
51
|
+
if (!routeAllows(ctx, config, market)) return;
|
|
52
|
+
const marketData = new EastmoneyMarketDataService(ctx, { market }, marketDataKey(market));
|
|
45
53
|
ctx.inject(["tools"], (ctx) => {
|
|
46
54
|
const tools = ctx.tools;
|
|
47
55
|
const register = (t) => {
|
|
48
56
|
if (tools.get(t.name) === void 0) tools.register(t);
|
|
49
57
|
};
|
|
50
58
|
register(defineTool({
|
|
51
|
-
name:
|
|
52
|
-
description: "Get the latest trade price and quote for an A-share stock via Eastmoney API.",
|
|
59
|
+
name: `${market}_get_ticker`,
|
|
60
|
+
description: market === "hk" ? "Get the latest trade price and quote for a HK stock via Eastmoney API." : "Get the latest trade price and quote for an A-share stock via Eastmoney API.",
|
|
53
61
|
parameters: { symbol: {
|
|
54
62
|
type: "string",
|
|
55
63
|
required: true,
|
|
56
|
-
description: "A-share stock symbol, e.g. 600519.SH"
|
|
64
|
+
description: market === "hk" ? "HK stock symbol, e.g. 00700.HK" : "A-share stock symbol, e.g. 600519.SH"
|
|
57
65
|
} },
|
|
58
66
|
output: {
|
|
59
67
|
schema: { type: "string" },
|
|
@@ -67,13 +75,13 @@ function apply(ctx, config) {
|
|
|
67
75
|
}
|
|
68
76
|
}));
|
|
69
77
|
register(defineTool({
|
|
70
|
-
name:
|
|
71
|
-
description: "Get recent public klines for an A-share stock via Eastmoney API. Supports 1m/5m/15m/30m/1h/1d/1w/1M.",
|
|
78
|
+
name: `${market}_get_klines`,
|
|
79
|
+
description: market === "hk" ? "Get recent public klines for a HK stock via Eastmoney API. 1m = 当日分时(trends2);5m/15m/30m/1h/1d/1w/1M 走 kline。" : "Get recent public klines for an A-share stock via Eastmoney API. Supports 1m/5m/15m/30m/1h/1d/1w/1M.",
|
|
72
80
|
parameters: {
|
|
73
81
|
symbol: {
|
|
74
82
|
type: "string",
|
|
75
83
|
required: true,
|
|
76
|
-
description: "A-share stock symbol, e.g. 600519.SH"
|
|
84
|
+
description: market === "hk" ? "HK stock symbol, e.g. 00700.HK" : "A-share stock symbol, e.g. 600519.SH"
|
|
77
85
|
},
|
|
78
86
|
interval: {
|
|
79
87
|
type: "string",
|
|
@@ -102,4 +110,4 @@ function apply(ctx, config) {
|
|
|
102
110
|
});
|
|
103
111
|
}
|
|
104
112
|
//#endregion
|
|
105
|
-
export { Config, EastmoneyMarketDataService, EastmoneyRestClient, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, apply, mapIntervalToKlt, name, parseIntervalMs, routeAllows, toEastmoneySecid };
|
|
113
|
+
export { Config, EastmoneyMarketDataService, EastmoneyRestClient, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TRADING_HK_MARKET_DATA_KEY, TradingServiceError, apply, eastmoneyPriceScale, mapIntervalToKlt, marketDataKey, name, parseIntervalMs, routeAllows, toEastmoneySecid, utc8WallTimeToEpochMs };
|
package/lib/rest.d.ts
CHANGED
|
@@ -7,35 +7,61 @@ declare class TradingServiceError extends Error {
|
|
|
7
7
|
declare const INTERVAL_VOCABULARY: readonly ["1m", "5m", "15m", "30m", "1h", "1d", "1w", "1M"];
|
|
8
8
|
declare function mapIntervalToKlt(interval: Interval$1): string;
|
|
9
9
|
declare function parseIntervalMs(interval: Interval$1): number;
|
|
10
|
+
/** 连接器服务的市场(cn=A 股;hk=港股,2026-09-08 扩展,实证见 spikes/impl-eastmoney-hk/)。 */
|
|
11
|
+
type EastmoneyMarket = 'cn' | 'hk';
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* `YYYY-MM-DD[ HH:MM[:SS]]`(UTC+8 墙钟;日 K 只有日期段)→ epoch ms。
|
|
14
|
+
* CN/HK 均为永久 UTC+8,故用固定偏移锚定,与运行机器时区无关(审查 M1:
|
|
15
|
+
* kline/get 此前用 `new Date('YYYY/MM/DD')` 按宿主本地时区解析,TZ≠UTC+8 时
|
|
16
|
+
* 与同市场 1m 序列相差数小时)。
|
|
15
17
|
*/
|
|
16
|
-
declare function
|
|
18
|
+
declare function utc8WallTimeToEpochMs(value: string): number;
|
|
19
|
+
/** 东财价格字段的分精度倍率:cn ×100(2 位小数),hk ×1000(3 位小数,响应 decimal=3)。 */
|
|
20
|
+
declare function eastmoneyPriceScale(market: EastmoneyMarket): number;
|
|
21
|
+
/**
|
|
22
|
+
* 将标准代码转为东财 secid。**按连接器市场分流**(2026-09-08 审查 H3/L1):同一个
|
|
23
|
+
* 东财服务实例可能服务 cn 或 hk,形态解析必须知道自己是哪个市场——否则港股宽容
|
|
24
|
+
* 形 `700` 会落进 CN 兜底(secid 0.700 / canonical 700.SZ),查询到另一个市场
|
|
25
|
+
* 的证券且回带错误规范形。
|
|
26
|
+
*
|
|
27
|
+
* - `market='hk'`:`00700.HK` / `HK.00700`(Futu 原生形)/ `HK00700` / 裸 1-5 位
|
|
28
|
+
* 数字 → secid 116.xxxxx;CN 形态(6 位数字 / .SH/.SZ/.BJ)显式拒绝。
|
|
29
|
+
* - `market='cn'`:6 位数字(可带 .SH/.SZ/.BJ)→ 1.xxxxxx(沪)/ 0.xxxxxx(深、
|
|
30
|
+
* 北);HK 形态显式拒绝。
|
|
31
|
+
*/
|
|
32
|
+
declare function toEastmoneySecid(symbol: string, market?: EastmoneyMarket): {
|
|
17
33
|
secid: string;
|
|
18
34
|
canonical: string;
|
|
35
|
+
market: EastmoneyMarket;
|
|
19
36
|
};
|
|
20
37
|
interface EastmoneyRestOptions {
|
|
21
38
|
baseUrl?: string;
|
|
22
39
|
historyBaseUrl?: string;
|
|
23
40
|
searchBaseUrl?: string;
|
|
24
41
|
fetchImpl?: typeof fetch;
|
|
42
|
+
/** 本实例服务的市场(形态解析与价格倍率都按它分流;缺省 cn)。 */
|
|
43
|
+
market?: EastmoneyMarket;
|
|
25
44
|
}
|
|
26
45
|
declare class EastmoneyRestClient {
|
|
27
46
|
readonly baseUrl: string;
|
|
28
47
|
readonly historyBaseUrl: string;
|
|
29
48
|
readonly searchBaseUrl: string;
|
|
49
|
+
/** 本实例的市场(cn/hk 单包双实例;形态解析必须与注册市场一致)。 */
|
|
50
|
+
readonly market: EastmoneyMarket;
|
|
30
51
|
private readonly fetchImpl;
|
|
31
52
|
constructor(options?: EastmoneyRestOptions);
|
|
32
53
|
private requestJson;
|
|
33
54
|
getTicker(symbol: string): Promise<Ticker$1>;
|
|
34
55
|
getKlines(symbol: string, interval?: Interval$1, limit?: number): Promise<Kline$1[]>;
|
|
56
|
+
/**
|
|
57
|
+
* 港股当日分时(trends2,ndays=1 = 最近一个交易日全天分钟序列,非交易日自然回落)。
|
|
58
|
+
* 行格式同 kline/get:`时间,开,收,高,低,量,额,均价`;时间 'YYYY-MM-DD HH:MM' 为 UTC+8 墙钟。
|
|
59
|
+
*/
|
|
60
|
+
private getHkIntradayTrends;
|
|
35
61
|
listInstruments(query?: string): Promise<Array<{
|
|
36
62
|
symbol: string;
|
|
37
63
|
name: string;
|
|
38
64
|
}>>;
|
|
39
65
|
}
|
|
40
66
|
//#endregion
|
|
41
|
-
export { EastmoneyRestClient, EastmoneyRestOptions, INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, type Ticker$1 as Ticker, TradingServiceError, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid };
|
|
67
|
+
export { EastmoneyMarket, EastmoneyRestClient, EastmoneyRestOptions, INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, type Ticker$1 as Ticker, TradingServiceError, eastmoneyPriceScale, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid, utc8WallTimeToEpochMs };
|
package/lib/rest.js
CHANGED
|
@@ -47,52 +47,99 @@ function parseIntervalMs(interval) {
|
|
|
47
47
|
* 上游分精度整数(×100,如昨收 f60=129740 → 1297.40)→ 数值;
|
|
48
48
|
* '-'/‘−’ 停牌占位或缺失返回 undefined。
|
|
49
49
|
*/
|
|
50
|
-
function
|
|
51
|
-
if (typeof value === "number") return Number.isFinite(value) ? value /
|
|
50
|
+
function parseScaled(value, scale) {
|
|
51
|
+
if (typeof value === "number") return Number.isFinite(value) ? value / scale : void 0;
|
|
52
52
|
if (typeof value === "string" && value !== "-" && value !== "−") {
|
|
53
53
|
const parsed = parseFloat(value);
|
|
54
|
-
return Number.isNaN(parsed) ? void 0 : parsed /
|
|
54
|
+
return Number.isNaN(parsed) ? void 0 : parsed / scale;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
function parseScaledHundred(value) {
|
|
58
|
+
return parseScaled(value, 100);
|
|
59
|
+
}
|
|
60
|
+
const KNOWN_SH_INDICES = /* @__PURE__ */ new Set([
|
|
61
|
+
"000688",
|
|
62
|
+
"000300",
|
|
63
|
+
"000016",
|
|
64
|
+
"000905",
|
|
65
|
+
"000852"
|
|
66
|
+
]);
|
|
67
|
+
/** CN/HK 均为永久 UTC+8(无夏令时)——墙钟时间用固定偏移锚定,与运行机器时区无关。 */
|
|
68
|
+
const UTC8_MS = 288e5;
|
|
57
69
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
70
|
+
* `YYYY-MM-DD[ HH:MM[:SS]]`(UTC+8 墙钟;日 K 只有日期段)→ epoch ms。
|
|
71
|
+
* CN/HK 均为永久 UTC+8,故用固定偏移锚定,与运行机器时区无关(审查 M1:
|
|
72
|
+
* kline/get 此前用 `new Date('YYYY/MM/DD')` 按宿主本地时区解析,TZ≠UTC+8 时
|
|
73
|
+
* 与同市场 1m 序列相差数小时)。
|
|
62
74
|
*/
|
|
63
|
-
function
|
|
75
|
+
function utc8WallTimeToEpochMs(value) {
|
|
76
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})(?: (\d{2}):(\d{2})(?::(\d{2}))?)?$/.exec(value);
|
|
77
|
+
if (!m) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `invalid eastmoney wall time ${JSON.stringify(value)}`);
|
|
78
|
+
return Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4] ?? 0), Number(m[5] ?? 0), Number(m[6] ?? 0)) - UTC8_MS;
|
|
79
|
+
}
|
|
80
|
+
/** 东财价格字段的分精度倍率:cn ×100(2 位小数),hk ×1000(3 位小数,响应 decimal=3)。 */
|
|
81
|
+
function eastmoneyPriceScale(market) {
|
|
82
|
+
return market === "hk" ? 1e3 : 100;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 将标准代码转为东财 secid。**按连接器市场分流**(2026-09-08 审查 H3/L1):同一个
|
|
86
|
+
* 东财服务实例可能服务 cn 或 hk,形态解析必须知道自己是哪个市场——否则港股宽容
|
|
87
|
+
* 形 `700` 会落进 CN 兜底(secid 0.700 / canonical 700.SZ),查询到另一个市场
|
|
88
|
+
* 的证券且回带错误规范形。
|
|
89
|
+
*
|
|
90
|
+
* - `market='hk'`:`00700.HK` / `HK.00700`(Futu 原生形)/ `HK00700` / 裸 1-5 位
|
|
91
|
+
* 数字 → secid 116.xxxxx;CN 形态(6 位数字 / .SH/.SZ/.BJ)显式拒绝。
|
|
92
|
+
* - `market='cn'`:6 位数字(可带 .SH/.SZ/.BJ)→ 1.xxxxxx(沪)/ 0.xxxxxx(深、
|
|
93
|
+
* 北);HK 形态显式拒绝。
|
|
94
|
+
*/
|
|
95
|
+
function toEastmoneySecid(symbol, market = "cn") {
|
|
64
96
|
const clean = symbol.trim().toUpperCase();
|
|
97
|
+
if (market === "hk") {
|
|
98
|
+
const hkMatch = /^(\d{1,5})\.HK$/.exec(clean) ?? /^HK\.?(\d{1,5})$/.exec(clean) ?? /^(\d{1,5})$/.exec(clean);
|
|
99
|
+
if (hkMatch === null) throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `Eastmoney hk: malformed HK symbol ${JSON.stringify(symbol)}`);
|
|
100
|
+
const hkCode = hkMatch[1].padStart(5, "0");
|
|
101
|
+
return {
|
|
102
|
+
secid: `116.${hkCode}`,
|
|
103
|
+
canonical: `${hkCode}.HK`,
|
|
104
|
+
market: "hk"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (/^\d{1,5}$/.test(clean) || /\.HK$/.test(clean) || /^HK/.test(clean)) throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `Eastmoney cn: not an A-share symbol ${JSON.stringify(symbol)}`);
|
|
65
108
|
let code = clean;
|
|
66
|
-
let
|
|
109
|
+
let exchange = "";
|
|
67
110
|
if (clean.includes(".")) {
|
|
68
111
|
const parts = clean.split(".");
|
|
69
112
|
code = parts[0];
|
|
70
|
-
|
|
113
|
+
exchange = parts[1];
|
|
71
114
|
} else if (/^\d{6}$/.test(clean)) {
|
|
72
115
|
code = clean;
|
|
73
|
-
if (code.startsWith("6") || code.startsWith("5") || code.startsWith("9"))
|
|
74
|
-
else if (code.startsWith("0") || code.startsWith("3") || code.startsWith("1"))
|
|
75
|
-
else if (code.startsWith("8") || code.startsWith("4") || code.startsWith("92"))
|
|
116
|
+
if (KNOWN_SH_INDICES.has(code) || code.startsWith("6") || code.startsWith("5") || code.startsWith("9")) exchange = "SH";
|
|
117
|
+
else if (code.startsWith("0") || code.startsWith("3") || code.startsWith("1")) exchange = "SZ";
|
|
118
|
+
else if (code.startsWith("8") || code.startsWith("4") || code.startsWith("92")) exchange = "BJ";
|
|
76
119
|
}
|
|
77
|
-
if (!
|
|
78
|
-
if (code.startsWith("6") || code.startsWith("5"))
|
|
79
|
-
else
|
|
120
|
+
if (!exchange) {
|
|
121
|
+
if (KNOWN_SH_INDICES.has(code) || code.startsWith("6") || code.startsWith("5")) exchange = "SH";
|
|
122
|
+
else exchange = "SZ";
|
|
80
123
|
}
|
|
81
124
|
return {
|
|
82
|
-
secid: `${
|
|
83
|
-
canonical: `${code}.${
|
|
125
|
+
secid: `${exchange === "SH" ? "1" : "0"}.${code}`,
|
|
126
|
+
canonical: `${code}.${exchange}`,
|
|
127
|
+
market: "cn"
|
|
84
128
|
};
|
|
85
129
|
}
|
|
86
130
|
var EastmoneyRestClient = class {
|
|
87
131
|
baseUrl;
|
|
88
132
|
historyBaseUrl;
|
|
89
133
|
searchBaseUrl;
|
|
134
|
+
/** 本实例的市场(cn/hk 单包双实例;形态解析必须与注册市场一致)。 */
|
|
135
|
+
market;
|
|
90
136
|
fetchImpl;
|
|
91
137
|
constructor(options = {}) {
|
|
92
138
|
this.baseUrl = options.baseUrl ?? "https://push2.eastmoney.com";
|
|
93
139
|
this.historyBaseUrl = options.historyBaseUrl ?? "https://push2his.eastmoney.com";
|
|
94
140
|
this.searchBaseUrl = options.searchBaseUrl ?? "https://searchapi.eastmoney.com";
|
|
95
141
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
142
|
+
this.market = options.market ?? "cn";
|
|
96
143
|
}
|
|
97
144
|
async requestJson(url) {
|
|
98
145
|
try {
|
|
@@ -108,21 +155,22 @@ var EastmoneyRestClient = class {
|
|
|
108
155
|
}
|
|
109
156
|
}
|
|
110
157
|
async getTicker(symbol) {
|
|
111
|
-
const { secid, canonical } = toEastmoneySecid(symbol);
|
|
158
|
+
const { secid, canonical, market } = toEastmoneySecid(symbol, this.market);
|
|
159
|
+
const scale = eastmoneyPriceScale(market);
|
|
112
160
|
const url = `${this.baseUrl}/api/qt/stock/get?secid=${secid}&fields=f43,f44,f45,f46,f47,f48,f57,f58,f60,f86,f169,f170`;
|
|
113
161
|
const res = await this.requestJson(url);
|
|
114
162
|
if (!res.data) throw new TradingServiceError("TRADING_SYMBOL_NOT_FOUND", `Eastmoney symbol not found: ${symbol}`);
|
|
115
163
|
const d = res.data;
|
|
116
164
|
let rawPrice = 0;
|
|
117
|
-
if (typeof d.f43 === "number") rawPrice = d.f43 /
|
|
165
|
+
if (typeof d.f43 === "number") rawPrice = d.f43 / scale;
|
|
118
166
|
else if (typeof d.f43 === "string" && d.f43 !== "-" && d.f43 !== "−") {
|
|
119
167
|
const parsed = parseFloat(d.f43);
|
|
120
|
-
rawPrice = Number.isNaN(parsed) ? 0 : parsed /
|
|
168
|
+
rawPrice = Number.isNaN(parsed) ? 0 : parsed / scale;
|
|
121
169
|
}
|
|
122
170
|
const price = rawPrice > 0 ? rawPrice : 0;
|
|
123
171
|
const volume = typeof d.f47 === "number" ? d.f47 : typeof d.f47 === "string" ? parseFloat(d.f47) : 0;
|
|
124
172
|
const timestamp = typeof d.f86 === "number" ? d.f86 * 1e3 : Date.now();
|
|
125
|
-
const prevClose = parseScaledHundred(d.f60);
|
|
173
|
+
const prevClose = market === "hk" ? parseScaled(d.f60, scale) : parseScaledHundred(d.f60);
|
|
126
174
|
const changePercent = parseScaledHundred(d.f170);
|
|
127
175
|
const name = typeof d.f58 === "string" && d.f58.trim() ? d.f58.trim() : void 0;
|
|
128
176
|
return {
|
|
@@ -136,7 +184,8 @@ var EastmoneyRestClient = class {
|
|
|
136
184
|
};
|
|
137
185
|
}
|
|
138
186
|
async getKlines(symbol, interval = "1d", limit = 100) {
|
|
139
|
-
const { secid } = toEastmoneySecid(symbol);
|
|
187
|
+
const { secid, market } = toEastmoneySecid(symbol, this.market);
|
|
188
|
+
if (market === "hk" && interval === "1m") return this.getHkIntradayTrends(secid, limit);
|
|
140
189
|
const klt = mapIntervalToKlt(interval);
|
|
141
190
|
const stepMs = parseIntervalMs(interval);
|
|
142
191
|
const url = `${this.historyBaseUrl}/api/qt/stock/kline/get?secid=${secid}&klt=${klt}&fqt=1&lmt=${limit}&end=20500101&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58`;
|
|
@@ -152,8 +201,12 @@ var EastmoneyRestClient = class {
|
|
|
152
201
|
const high = parseFloat(parts[3]);
|
|
153
202
|
const low = parseFloat(parts[4]);
|
|
154
203
|
const volume = parseFloat(parts[5]);
|
|
155
|
-
|
|
156
|
-
|
|
204
|
+
let openTime;
|
|
205
|
+
try {
|
|
206
|
+
openTime = utc8WallTimeToEpochMs(timeStr);
|
|
207
|
+
} catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
157
210
|
klines.push({
|
|
158
211
|
openTime,
|
|
159
212
|
open,
|
|
@@ -166,17 +219,48 @@ var EastmoneyRestClient = class {
|
|
|
166
219
|
}
|
|
167
220
|
return klines;
|
|
168
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* 港股当日分时(trends2,ndays=1 = 最近一个交易日全天分钟序列,非交易日自然回落)。
|
|
224
|
+
* 行格式同 kline/get:`时间,开,收,高,低,量,额,均价`;时间 'YYYY-MM-DD HH:MM' 为 UTC+8 墙钟。
|
|
225
|
+
*/
|
|
226
|
+
async getHkIntradayTrends(secid, limit) {
|
|
227
|
+
const url = `${this.historyBaseUrl}/api/qt/stock/trends2/get?secid=${secid}&fields1=f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11&fields2=f51,f52,f53,f54,f55,f56,f57,f58&ndays=1&iscr=0`;
|
|
228
|
+
const trends = (await this.requestJson(url)).data?.trends;
|
|
229
|
+
if (!Array.isArray(trends)) return [];
|
|
230
|
+
const stepMs = parseIntervalMs("1m");
|
|
231
|
+
const klines = [];
|
|
232
|
+
for (const line of trends) {
|
|
233
|
+
const parts = line.split(",");
|
|
234
|
+
if (parts.length < 6) continue;
|
|
235
|
+
const openTime = utc8WallTimeToEpochMs(parts[0] ?? "");
|
|
236
|
+
klines.push({
|
|
237
|
+
openTime,
|
|
238
|
+
open: parseFloat(parts[1] ?? ""),
|
|
239
|
+
high: parseFloat(parts[3] ?? ""),
|
|
240
|
+
low: parseFloat(parts[4] ?? ""),
|
|
241
|
+
close: parseFloat(parts[2] ?? ""),
|
|
242
|
+
volume: parseFloat(parts[5] ?? ""),
|
|
243
|
+
closeTime: openTime + stepMs - 1
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return klines.slice(-Math.max(limit, 1));
|
|
247
|
+
}
|
|
169
248
|
async listInstruments(query) {
|
|
170
249
|
if (!query) return [];
|
|
171
250
|
const url = `${this.searchBaseUrl}/api/suggest/get?input=${encodeURIComponent(query)}&type=14`;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
251
|
+
const items = (await this.requestJson(url)).QuotationCodeTable?.Data ?? [];
|
|
252
|
+
const out = [];
|
|
253
|
+
for (const item of items) try {
|
|
254
|
+
const { canonical } = toEastmoneySecid(item.Code, this.market);
|
|
255
|
+
out.push({
|
|
175
256
|
symbol: canonical,
|
|
176
257
|
name: item.Name
|
|
177
|
-
};
|
|
178
|
-
}
|
|
258
|
+
});
|
|
259
|
+
} catch {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
179
263
|
}
|
|
180
264
|
};
|
|
181
265
|
//#endregion
|
|
182
|
-
export { EastmoneyRestClient, INTERVAL_VOCABULARY, TradingServiceError, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid };
|
|
266
|
+
export { EastmoneyRestClient, INTERVAL_VOCABULARY, TradingServiceError, eastmoneyPriceScale, mapIntervalToKlt, parseIntervalMs, toEastmoneySecid, utc8WallTimeToEpochMs };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-eastmoney",
|
|
3
3
|
"description": "【模板】交易所连接器插件脚手架:Eastmoney(eastmoney)v2 插件骨架 —— 市场数据服务 + 交易服务 + 三态闸门 + BYOK 凭证 ref。生成器:node scripts/new-connector.mjs --slug <exchange>",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dshtrading/api": "^0.1.
|
|
24
|
+
"@dshtrading/api": "^0.1.6"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@deepseek-ai/cordis": ">=4.0.0",
|