@dshtrading/connector-eastmoney 0.1.5 → 0.2.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/lib/dataplane.js CHANGED
@@ -1,4 +1,4 @@
1
- import { EastmoneyMarketDataService, TRADING_CN_MARKET_DATA_KEY } from "./index.js";
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(TRADING_CN_MARKET_DATA_KEY);
18
- const service = new EastmoneyMarketDataService(inner);
19
- ctx.effect(() => registry.register(MARKET, ROUTER_PROVIDER, service));
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: string): boolean;
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({ enabled: Schema.boolean().default(true).description("是否激活东财连接器") });
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
- if (!routeAllows(ctx, config, "cn")) return;
44
- const marketData = new EastmoneyMarketDataService(ctx);
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: "cn_get_ticker",
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: "cn_get_klines",
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
- * 将标准代码(如 600519.SH / 000001.SZ / 600519 / 000001)转为东财 secid。
12
- * 上海(60/68/51等)= 1.xxxxxx
13
- * 深圳(00/30/15等)= 0.xxxxxx
14
- * 北京(83/87/43/92等)= 0.xxxxxx
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 toEastmoneySecid(symbol: string): {
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,13 +47,16 @@ function parseIntervalMs(interval) {
47
47
  * 上游分精度整数(×100,如昨收 f60=129740 → 1297.40)→ 数值;
48
48
  * '-'/‘−’ 停牌占位或缺失返回 undefined。
49
49
  */
50
- function parseScaledHundred(value) {
51
- if (typeof value === "number") return Number.isFinite(value) ? value / 100 : void 0;
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 / 100;
54
+ return Number.isNaN(parsed) ? void 0 : parsed / scale;
55
55
  }
56
56
  }
57
+ function parseScaledHundred(value) {
58
+ return parseScaled(value, 100);
59
+ }
57
60
  const KNOWN_SH_INDICES = /* @__PURE__ */ new Set([
58
61
  "000688",
59
62
  "000300",
@@ -61,45 +64,82 @@ const KNOWN_SH_INDICES = /* @__PURE__ */ new Set([
61
64
  "000905",
62
65
  "000852"
63
66
  ]);
67
+ /** CN/HK 均为永久 UTC+8(无夏令时)——墙钟时间用固定偏移锚定,与运行机器时区无关。 */
68
+ const UTC8_MS = 288e5;
64
69
  /**
65
- * 将标准代码(如 600519.SH / 000001.SZ / 600519 / 000001)转为东财 secid。
66
- * 上海(60/68/51等)= 1.xxxxxx
67
- * 深圳(00/30/15等)= 0.xxxxxx
68
- * 北京(83/87/43/92等)= 0.xxxxxx
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 序列相差数小时)。
69
74
  */
70
- function toEastmoneySecid(symbol) {
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") {
71
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)}`);
72
108
  let code = clean;
73
- let market = "";
109
+ let exchange = "";
74
110
  if (clean.includes(".")) {
75
111
  const parts = clean.split(".");
76
112
  code = parts[0];
77
- market = parts[1];
113
+ exchange = parts[1];
78
114
  } else if (/^\d{6}$/.test(clean)) {
79
115
  code = clean;
80
- if (KNOWN_SH_INDICES.has(code) || code.startsWith("6") || code.startsWith("5") || code.startsWith("9")) market = "SH";
81
- else if (code.startsWith("0") || code.startsWith("3") || code.startsWith("1")) market = "SZ";
82
- else if (code.startsWith("8") || code.startsWith("4") || code.startsWith("92")) market = "BJ";
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";
83
119
  }
84
- if (!market) {
85
- if (KNOWN_SH_INDICES.has(code) || code.startsWith("6") || code.startsWith("5")) market = "SH";
86
- else market = "SZ";
120
+ if (!exchange) {
121
+ if (KNOWN_SH_INDICES.has(code) || code.startsWith("6") || code.startsWith("5")) exchange = "SH";
122
+ else exchange = "SZ";
87
123
  }
88
124
  return {
89
- secid: `${market === "SH" ? "1" : "0"}.${code}`,
90
- canonical: `${code}.${market}`
125
+ secid: `${exchange === "SH" ? "1" : "0"}.${code}`,
126
+ canonical: `${code}.${exchange}`,
127
+ market: "cn"
91
128
  };
92
129
  }
93
130
  var EastmoneyRestClient = class {
94
131
  baseUrl;
95
132
  historyBaseUrl;
96
133
  searchBaseUrl;
134
+ /** 本实例的市场(cn/hk 单包双实例;形态解析必须与注册市场一致)。 */
135
+ market;
97
136
  fetchImpl;
98
137
  constructor(options = {}) {
99
138
  this.baseUrl = options.baseUrl ?? "https://push2.eastmoney.com";
100
139
  this.historyBaseUrl = options.historyBaseUrl ?? "https://push2his.eastmoney.com";
101
140
  this.searchBaseUrl = options.searchBaseUrl ?? "https://searchapi.eastmoney.com";
102
141
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
142
+ this.market = options.market ?? "cn";
103
143
  }
104
144
  async requestJson(url) {
105
145
  try {
@@ -115,21 +155,22 @@ var EastmoneyRestClient = class {
115
155
  }
116
156
  }
117
157
  async getTicker(symbol) {
118
- const { secid, canonical } = toEastmoneySecid(symbol);
158
+ const { secid, canonical, market } = toEastmoneySecid(symbol, this.market);
159
+ const scale = eastmoneyPriceScale(market);
119
160
  const url = `${this.baseUrl}/api/qt/stock/get?secid=${secid}&fields=f43,f44,f45,f46,f47,f48,f57,f58,f60,f86,f169,f170`;
120
161
  const res = await this.requestJson(url);
121
162
  if (!res.data) throw new TradingServiceError("TRADING_SYMBOL_NOT_FOUND", `Eastmoney symbol not found: ${symbol}`);
122
163
  const d = res.data;
123
164
  let rawPrice = 0;
124
- if (typeof d.f43 === "number") rawPrice = d.f43 / 100;
165
+ if (typeof d.f43 === "number") rawPrice = d.f43 / scale;
125
166
  else if (typeof d.f43 === "string" && d.f43 !== "-" && d.f43 !== "−") {
126
167
  const parsed = parseFloat(d.f43);
127
- rawPrice = Number.isNaN(parsed) ? 0 : parsed / 100;
168
+ rawPrice = Number.isNaN(parsed) ? 0 : parsed / scale;
128
169
  }
129
170
  const price = rawPrice > 0 ? rawPrice : 0;
130
171
  const volume = typeof d.f47 === "number" ? d.f47 : typeof d.f47 === "string" ? parseFloat(d.f47) : 0;
131
172
  const timestamp = typeof d.f86 === "number" ? d.f86 * 1e3 : Date.now();
132
- const prevClose = parseScaledHundred(d.f60);
173
+ const prevClose = market === "hk" ? parseScaled(d.f60, scale) : parseScaledHundred(d.f60);
133
174
  const changePercent = parseScaledHundred(d.f170);
134
175
  const name = typeof d.f58 === "string" && d.f58.trim() ? d.f58.trim() : void 0;
135
176
  return {
@@ -143,7 +184,8 @@ var EastmoneyRestClient = class {
143
184
  };
144
185
  }
145
186
  async getKlines(symbol, interval = "1d", limit = 100) {
146
- const { secid } = toEastmoneySecid(symbol);
187
+ const { secid, market } = toEastmoneySecid(symbol, this.market);
188
+ if (market === "hk" && interval === "1m") return this.getHkIntradayTrends(secid, limit);
147
189
  const klt = mapIntervalToKlt(interval);
148
190
  const stepMs = parseIntervalMs(interval);
149
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`;
@@ -159,8 +201,12 @@ var EastmoneyRestClient = class {
159
201
  const high = parseFloat(parts[3]);
160
202
  const low = parseFloat(parts[4]);
161
203
  const volume = parseFloat(parts[5]);
162
- const openTime = new Date(timeStr.replace(/-/g, "/")).getTime();
163
- if (Number.isNaN(openTime)) continue;
204
+ let openTime;
205
+ try {
206
+ openTime = utc8WallTimeToEpochMs(timeStr);
207
+ } catch {
208
+ continue;
209
+ }
164
210
  klines.push({
165
211
  openTime,
166
212
  open,
@@ -173,17 +219,48 @@ var EastmoneyRestClient = class {
173
219
  }
174
220
  return klines;
175
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
+ }
176
248
  async listInstruments(query) {
177
249
  if (!query) return [];
178
250
  const url = `${this.searchBaseUrl}/api/suggest/get?input=${encodeURIComponent(query)}&type=14`;
179
- return ((await this.requestJson(url)).QuotationCodeTable?.Data ?? []).map((item) => {
180
- const { canonical } = toEastmoneySecid(item.Code);
181
- return {
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({
182
256
  symbol: canonical,
183
257
  name: item.Name
184
- };
185
- });
258
+ });
259
+ } catch {
260
+ continue;
261
+ }
262
+ return out;
186
263
  }
187
264
  };
188
265
  //#endregion
189
- 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.5",
4
+ "version": "0.2.0",
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.5"
24
+ "@dshtrading/api": "^0.2.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@deepseek-ai/cordis": ">=4.0.0",