@dshtrading/connector-hithink 0.2.1 → 0.4.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
@@ -9,9 +9,11 @@ const ROUTER_PROVIDER = "hithink";
9
9
  const MARKET = "cn";
10
10
  function apply(ctx, config) {
11
11
  if (!config.enabled) return;
12
- const apiKey = process.env[config.apiKeyRef];
12
+ const apiKeyProvider = () => {
13
+ return (ctx.get?.("tradingMarketRouter", false))?.getCredential?.("hithink")?.apiKey || process.env[config.apiKeyRef];
14
+ };
13
15
  const registry = resolveMarketDataRegistry(ctx);
14
- const opts = apiKey ? { apiKey } : {};
16
+ const opts = { apiKeyProvider };
15
17
  if (registry === void 0) {
16
18
  new HiThinkMarketDataService(ctx, opts);
17
19
  return;
@@ -0,0 +1,7 @@
1
+ import { Config } from "./index.js";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/futures-dataplane.d.ts
4
+ declare const inject: string[];
5
+ declare function apply(ctx: Context, config: Config): void;
6
+ //#endregion
7
+ export { apply, inject };
@@ -0,0 +1,26 @@
1
+ import { HiThinkFuturesMarketDataService, TRADING_FUTURES_MARKET_DATA_KEY } from "./futures.js";
2
+ //#region src/futures-dataplane.ts
3
+ const inject = [];
4
+ function resolveMarketDataRegistry(ctx) {
5
+ const candidate = ctx.get?.("tradingMarketDataRegistry", false);
6
+ return candidate !== void 0 ? candidate : void 0;
7
+ }
8
+ const ROUTER_PROVIDER = "hithink";
9
+ const MARKET = "futures";
10
+ function apply(ctx, config) {
11
+ if (!config.enabled) return;
12
+ const apiKeyProvider = () => {
13
+ return (ctx.get?.("tradingMarketRouter", false))?.getCredential?.("hithink")?.apiKey || process.env[config.apiKeyRef];
14
+ };
15
+ const registry = resolveMarketDataRegistry(ctx);
16
+ const opts = { apiKeyProvider };
17
+ if (registry === void 0) {
18
+ new HiThinkFuturesMarketDataService(ctx, opts);
19
+ return;
20
+ }
21
+ const inner = ctx.isolate(TRADING_FUTURES_MARKET_DATA_KEY);
22
+ const service = new HiThinkFuturesMarketDataService(inner, opts);
23
+ ctx.effect(() => registry.register(MARKET, ROUTER_PROVIDER, service));
24
+ }
25
+ //#endregion
26
+ export { apply, inject };
@@ -0,0 +1,8 @@
1
+ import { Config } from "./index.js";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/futures-plugin.d.ts
4
+ declare const name = "dsh-trading-futures-connector-hithink";
5
+ declare const inject: string[];
6
+ declare function apply(ctx: Context, config: Config): void;
7
+ //#endregion
8
+ export { Config, apply, inject, name };
@@ -0,0 +1,14 @@
1
+ import { Config } from "./index.js";
2
+ import { HiThinkFuturesMarketDataService } from "./futures.js";
3
+ //#region src/futures-plugin.ts
4
+ const name = "dsh-trading-futures-connector-hithink";
5
+ const inject = [];
6
+ function apply(ctx, config) {
7
+ if (!config.enabled) return;
8
+ const apiKeyProvider = () => {
9
+ return (ctx.get?.("tradingMarketRouter", false))?.getCredential?.("hithink")?.apiKey || process.env[config.apiKeyRef];
10
+ };
11
+ new HiThinkFuturesMarketDataService(ctx, { apiKeyProvider });
12
+ }
13
+ //#endregion
14
+ export { Config, apply, inject, name };
@@ -0,0 +1,44 @@
1
+ import { HiThinkRestOptions } from "./rest.js";
2
+ import { Context, Service } from "@deepseek-ai/cordis";
3
+ import { Disposable, Interval, Kline, MarketDataService, Ticker } from "@dshtrading/api";
4
+ //#region src/futures.d.ts
5
+ declare const TRADING_FUTURES_MARKET_DATA_KEY = "tradingFuturesMarketData";
6
+ /** 规范化期货代码输入(大写、去空白;带/不带交易所后缀均透传,后缀歧义交由上游裁决)。 */
7
+ declare function normalizeFuturesCode(input: string): string;
8
+ /**
9
+ * 中金所(CFFEX) 8888「加权」连续码:上游代码表/检索可见,但行情端点不供数
10
+ * (daily 返回空 item、intraday 返回 code=5003;2026-09-14 实测 IF8888.CFE/
11
+ * IH8888.CFE/T8888.CFE)。其他交易所的 8888 加权码(如 RB8888.SHF)与中金所
12
+ * 主连 `…ZL.CFE` 均有完整数据,故只剔除 CFE 后缀的 8888。
13
+ * 用于列表/检索,避免用户加入无行情标的。
14
+ */
15
+ declare function isUnpricedCffexWeighted(symbol: string): boolean;
16
+ declare class HiThinkFuturesMarketDataService extends Service implements MarketDataService {
17
+ private readonly client;
18
+ constructor(ctx: Context, options?: HiThinkRestOptions, serviceName?: string);
19
+ /**
20
+ * 最新价:分时末点优先(盘中即时),回落日K最近根(收盘价)。
21
+ * 两者都没有数据时抛 TRADING_UNSUPPORTED_SYMBOL——上游目录里存在但行情端点
22
+ * 不供数的代码(实测中金所 8888 加权码)不能被伪造成 price=0 的假行情。
23
+ */
24
+ getTicker(symbol: string): Promise<Ticker>;
25
+ /**
26
+ * 期货 K 线:1d 直连日K;3d/1w/1M 由日K本地聚合;分钟周期由当日分时点聚合
27
+ * (上游仅提供当日分时,分钟线不覆盖历史交易日)。
28
+ */
29
+ getKlines(symbol: string, interval?: Interval, limit?: number): Promise<Kline[]>;
30
+ /**
31
+ * 期货标的名册/检索(GUI 添加自选对话框)。带 query 走跨资产检索(asset_type=futures),
32
+ * 无 query 返回全量代码表(过滤 last_trade_date 已过期的合约)。
33
+ * 两个分支都剔除上游无行情的中金所 8888 加权码(isUnpricedCffexWeighted)。
34
+ */
35
+ listInstruments(query?: string): Promise<Array<{
36
+ symbol: string;
37
+ name: string;
38
+ }>>;
39
+ subscribeTicker(symbol: string, cb: (ticker: Ticker) => void, options?: {
40
+ intervalMs?: number;
41
+ }): Disposable;
42
+ }
43
+ //#endregion
44
+ export { HiThinkFuturesMarketDataService, TRADING_FUTURES_MARKET_DATA_KEY, isUnpricedCffexWeighted, normalizeFuturesCode };
package/lib/futures.js ADDED
@@ -0,0 +1,117 @@
1
+ import { HiThinkRestClient, TradingServiceError } from "./rest.js";
2
+ import { aggregateDailyKlines, aggregateMinutePoints, dailyBarToKline } from "./kline.js";
3
+ import { Service } from "@deepseek-ai/cordis";
4
+ //#region src/futures.ts
5
+ const TRADING_FUTURES_MARKET_DATA_KEY = "tradingFuturesMarketData";
6
+ /** 期货分钟周期 → 分时点聚合步长(分钟)。上游仅提供当日分时,分钟线仅覆盖当日。 */
7
+ const MINUTE_INTERVAL_STEPS = {
8
+ "1m": 1,
9
+ "5m": 5,
10
+ "15m": 15,
11
+ "30m": 30,
12
+ "1h": 60,
13
+ "2h": 120,
14
+ "4h": 240,
15
+ "6h": 360,
16
+ "8h": 480,
17
+ "12h": 720
18
+ };
19
+ /** 规范化期货代码输入(大写、去空白;带/不带交易所后缀均透传,后缀歧义交由上游裁决)。 */
20
+ function normalizeFuturesCode(input) {
21
+ return input.trim().toUpperCase();
22
+ }
23
+ /**
24
+ * 中金所(CFFEX) 8888「加权」连续码:上游代码表/检索可见,但行情端点不供数
25
+ * (daily 返回空 item、intraday 返回 code=5003;2026-09-14 实测 IF8888.CFE/
26
+ * IH8888.CFE/T8888.CFE)。其他交易所的 8888 加权码(如 RB8888.SHF)与中金所
27
+ * 主连 `…ZL.CFE` 均有完整数据,故只剔除 CFE 后缀的 8888。
28
+ * 用于列表/检索,避免用户加入无行情标的。
29
+ */
30
+ function isUnpricedCffexWeighted(symbol) {
31
+ return /^[A-Z]+8888\.CFE$/i.test(symbol.trim());
32
+ }
33
+ var HiThinkFuturesMarketDataService = class extends Service {
34
+ client;
35
+ constructor(ctx, options = {}, serviceName = TRADING_FUTURES_MARKET_DATA_KEY) {
36
+ super(ctx, serviceName);
37
+ this.client = new HiThinkRestClient(options);
38
+ }
39
+ /**
40
+ * 最新价:分时末点优先(盘中即时),回落日K最近根(收盘价)。
41
+ * 两者都没有数据时抛 TRADING_UNSUPPORTED_SYMBOL——上游目录里存在但行情端点
42
+ * 不供数的代码(实测中金所 8888 加权码)不能被伪造成 price=0 的假行情。
43
+ */
44
+ async getTicker(symbol) {
45
+ const thscode = normalizeFuturesCode(symbol);
46
+ const dailyBars = await this.client.getFuturesDailyKlines(thscode, 2);
47
+ const lastDaily = dailyBars[dailyBars.length - 1];
48
+ const prevDaily = dailyBars.length >= 2 ? dailyBars[dailyBars.length - 2] : void 0;
49
+ let price = lastDaily?.close_price ?? void 0;
50
+ let timestamp = lastDaily?.timestamp ?? void 0;
51
+ try {
52
+ const points = ((await this.client.getFuturesIntraday(thscode))?.item ?? []).filter((p) => typeof p.timestamp === "number" && typeof p.price === "number");
53
+ const last = points[points.length - 1];
54
+ if (last) {
55
+ price = last.price;
56
+ timestamp = last.timestamp;
57
+ }
58
+ } catch {}
59
+ if (price === void 0 || timestamp === void 0) throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `HiThink: no futures quote for ${thscode}; daily bars and intraday are both empty (upstream has no data for this code)`);
60
+ return {
61
+ symbol: thscode,
62
+ price,
63
+ ...prevDaily?.close_price !== void 0 && prevDaily?.close_price !== null ? { prevClose: prevDaily.close_price } : {},
64
+ ...lastDaily?.volume !== void 0 && lastDaily?.volume !== null ? { volume: lastDaily.volume } : {},
65
+ timestamp
66
+ };
67
+ }
68
+ /**
69
+ * 期货 K 线:1d 直连日K;3d/1w/1M 由日K本地聚合;分钟周期由当日分时点聚合
70
+ * (上游仅提供当日分时,分钟线不覆盖历史交易日)。
71
+ */
72
+ async getKlines(symbol, interval = "1d", limit = 100) {
73
+ const thscode = normalizeFuturesCode(symbol);
74
+ const n = Math.max(1, Math.min(Number.isFinite(limit) ? Math.floor(limit) : 100, 1e3));
75
+ const minuteStep = MINUTE_INTERVAL_STEPS[interval];
76
+ if (minuteStep !== void 0) {
77
+ const points = ((await this.client.getFuturesIntraday(thscode))?.item ?? []).flatMap((p) => typeof p.timestamp === "number" && typeof p.price === "number" ? [{
78
+ timestamp: p.timestamp,
79
+ price: p.price,
80
+ volume: p.volume ?? 0
81
+ }] : []);
82
+ return aggregateMinutePoints(points, minuteStep).slice(-n);
83
+ }
84
+ const daily = (await this.client.getFuturesDailyKlines(thscode, interval === "1d" ? n : Math.min(n * 35, 1e3))).map((b) => dailyBarToKline(b.timestamp, b.open_price, b.high_price, b.low_price, b.close_price, b.volume ?? 0));
85
+ if (interval === "1d") return daily;
86
+ return aggregateDailyKlines(daily, interval).slice(-n);
87
+ }
88
+ /**
89
+ * 期货标的名册/检索(GUI 添加自选对话框)。带 query 走跨资产检索(asset_type=futures),
90
+ * 无 query 返回全量代码表(过滤 last_trade_date 已过期的合约)。
91
+ * 两个分支都剔除上游无行情的中金所 8888 加权码(isUnpricedCffexWeighted)。
92
+ */
93
+ async listInstruments(query) {
94
+ const trimmed = query?.trim();
95
+ if (trimmed) return (await this.client.searchFuturesTickers(trimmed)).filter((item) => !isUnpricedCffexWeighted(item.thscode)).map((item) => ({
96
+ symbol: item.thscode,
97
+ name: item.name
98
+ }));
99
+ const items = await this.client.listFuturesTickers();
100
+ const todayKey = new Date(Date.now() + 288e5).toISOString().slice(0, 10);
101
+ return items.filter((item) => item.last_trade_date === null || item.last_trade_date === void 0 || item.last_trade_date >= todayKey).filter((item) => !isUnpricedCffexWeighted(item.thscode)).map((item) => ({
102
+ symbol: item.thscode,
103
+ name: item.name
104
+ }));
105
+ }
106
+ subscribeTicker(symbol, cb, options) {
107
+ const ms = Math.max(options?.intervalMs ?? 1e4, 3e3);
108
+ const tick = () => {
109
+ this.getTicker(symbol).then(cb, () => {});
110
+ };
111
+ tick();
112
+ const timer = setInterval(tick, ms);
113
+ return { dispose: () => clearInterval(timer) };
114
+ }
115
+ };
116
+ //#endregion
117
+ export { HiThinkFuturesMarketDataService, TRADING_FUTURES_MARKET_DATA_KEY, isUnpricedCffexWeighted, normalizeFuturesCode };
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem } from "./types.js";
1
+ import { HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkFuturesDailyData, HiThinkFuturesDailyItem, HiThinkFuturesIntradayData, HiThinkFuturesIntradayItem, HiThinkHistoricalData, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkMetaTickerData, HiThinkMetaTickerItem, HiThinkPriceBarItem, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem } from "./types.js";
2
2
  import { HiThinkRestClient, HiThinkRestOptions, TradingServiceError, normalizeThsCode } from "./rest.js";
3
+ import { aggregateDailyKlines, aggregateMinutePoints, dailyBarToKline, shanghaiDateKey } from "./kline.js";
3
4
  import { Context, Service } from "@deepseek-ai/cordis";
4
5
  import Schema from "@deepseek-ai/schemastery";
5
6
  import { AuctionSnapshot, Disposable, Interval, Kline, LimitUpPoolItem, MarketDataService, StockFundamentals, Ticker } from "@dshtrading/api";
@@ -15,7 +16,12 @@ declare class HiThinkMarketDataService extends Service implements MarketDataServ
15
16
  private readonly client;
16
17
  constructor(ctx: Context, options?: HiThinkRestOptions, serviceName?: string);
17
18
  getTicker(symbol: string): Promise<Ticker>;
18
- getKlines(_symbol: string, _interval?: Interval, _limit?: number): Promise<Kline[]>;
19
+ /**
20
+ * A 股 K 线。上游仅开放日线(高频分钟模块未开放外部接入):
21
+ * 1d 直连 /api/a-share/prices/historical(前复权);3d/1w/1M 由日线本地聚合;
22
+ * 分钟级周期抛 TRADING_UNSUPPORTED_INTERVAL(需分钟 K 时切换 cn provider 至 tencent 等)。
23
+ */
24
+ getKlines(symbol: string, interval?: Interval, limit?: number): Promise<Kline[]>;
19
25
  getStockFundamentals(symbol: string): Promise<StockFundamentals>;
20
26
  getAuctionSnapshot(symbol: string): Promise<AuctionSnapshot | undefined>;
21
27
  getLimitUpPool(options?: {
@@ -34,4 +40,4 @@ declare class HiThinkMarketDataService extends Service implements MarketDataServ
34
40
  }
35
41
  declare function apply(ctx: Context, config: Config): void;
36
42
  //#endregion
37
- export { Config, HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkMarketDataService, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkRestClient, HiThinkRestOptions, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, apply, name, normalizeThsCode };
43
+ export { Config, HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkFuturesDailyData, HiThinkFuturesDailyItem, HiThinkFuturesIntradayData, HiThinkFuturesIntradayItem, HiThinkHistoricalData, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkMarketDataService, HiThinkMetaTickerData, HiThinkMetaTickerItem, HiThinkPriceBarItem, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkRestClient, HiThinkRestOptions, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, aggregateDailyKlines, aggregateMinutePoints, apply, dailyBarToKline, name, normalizeThsCode, shanghaiDateKey };
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { HiThinkRestClient, TradingServiceError, normalizeThsCode } from "./rest.js";
2
+ import { aggregateDailyKlines, aggregateMinutePoints, dailyBarToKline, shanghaiDateKey } from "./kline.js";
2
3
  import { Service } from "@deepseek-ai/cordis";
3
4
  import Schema from "@deepseek-ai/schemastery";
4
5
  //#region src/index.ts
@@ -17,8 +18,17 @@ var HiThinkMarketDataService = class extends Service {
17
18
  async getTicker(symbol) {
18
19
  return this.client.getTicker(symbol);
19
20
  }
20
- async getKlines(_symbol, _interval = "1d", _limit = 100) {
21
- throw new TradingServiceError("TRADING_NOT_IMPLEMENTED", "HiThink K-lines not supported yet");
21
+ /**
22
+ * A 股 K 线。上游仅开放日线(高频分钟模块未开放外部接入):
23
+ * 1d 直连 /api/a-share/prices/historical(前复权);3d/1w/1M 由日线本地聚合;
24
+ * 分钟级周期抛 TRADING_UNSUPPORTED_INTERVAL(需分钟 K 时切换 cn provider 至 tencent 等)。
25
+ */
26
+ async getKlines(symbol, interval = "1d", limit = 100) {
27
+ const n = Math.max(1, Math.min(Number.isFinite(limit) ? Math.floor(limit) : 100, 1e3));
28
+ if (interval !== "1d" && interval !== "3d" && interval !== "1w" && interval !== "1M") throw new TradingServiceError("TRADING_UNSUPPORTED_INTERVAL", `HiThink A-share K-lines only support 1d/3d/1w/1M (upstream minute module not open); got ${interval}`);
29
+ const daily = (await this.client.getHistoricalDailyKlines(symbol, interval === "1d" ? n : Math.min(n * 35, 1e3))).map((b) => dailyBarToKline(b.date_ms, b.open_price, b.high_price, b.low_price, b.close_price, b.volume ?? 0));
30
+ if (interval === "1d") return daily;
31
+ return aggregateDailyKlines(daily, interval).slice(-n);
22
32
  }
23
33
  async getStockFundamentals(symbol) {
24
34
  return this.client.getStockFundamentals(symbol);
@@ -47,8 +57,10 @@ var HiThinkMarketDataService = class extends Service {
47
57
  };
48
58
  function apply(ctx, config) {
49
59
  if (!config.enabled) return;
50
- const apiKey = process.env[config.apiKeyRef];
51
- new HiThinkMarketDataService(ctx, apiKey ? { apiKey } : {});
60
+ const apiKeyProvider = () => {
61
+ return (ctx.get?.("tradingMarketRouter", false))?.getCredential?.("hithink")?.apiKey || process.env[config.apiKeyRef];
62
+ };
63
+ new HiThinkMarketDataService(ctx, { apiKeyProvider });
52
64
  }
53
65
  //#endregion
54
- export { Config, HiThinkMarketDataService, HiThinkRestClient, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, apply, name, normalizeThsCode };
66
+ export { Config, HiThinkMarketDataService, HiThinkRestClient, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, aggregateDailyKlines, aggregateMinutePoints, apply, dailyBarToKline, name, normalizeThsCode, shanghaiDateKey };
package/lib/kline.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Kline } from "@dshtrading/api";
2
+ //#region src/kline.d.ts
3
+ /** 东八区日期键(YYYY-MM-DD),月/周聚合与 last_trade_date 过滤用。 */
4
+ declare function shanghaiDateKey(ms: number): string;
5
+ /** 单位日K → 标准 Kline(closeTime = openTime + 1d - 1,与 eastmoney/tencent 约定一致)。 */
6
+ declare function dailyBarToKline(openTime: number, open: number, high: number, low: number, close: number, volume: number): Kline;
7
+ /** 日K序列按日历桶聚合('3d' 按东八区日序 3 天分桶;'1w' ISO 周;'1M' 自然月)。 */
8
+ declare function aggregateDailyKlines(daily: Kline[], interval: '3d' | '1w' | '1M'): Kline[];
9
+ /** 分钟分桶聚合(分时点 → N 分钟 OHLC 条;volume 求和)。 */
10
+ declare function aggregateMinutePoints(points: Array<{
11
+ timestamp: number;
12
+ price: number;
13
+ volume: number;
14
+ }>, stepMinutes: number): Kline[];
15
+ //#endregion
16
+ export { aggregateDailyKlines, aggregateMinutePoints, dailyBarToKline, shanghaiDateKey };
package/lib/kline.js ADDED
@@ -0,0 +1,92 @@
1
+ //#region src/kline.ts
2
+ const DAY_MS = 864e5;
3
+ const SHANGHAI_OFFSET_MS = 288e5;
4
+ /** 东八区日序号(交易日零点 ms → 天数)。 */
5
+ function shanghaiDay(ms) {
6
+ return Math.floor((ms + SHANGHAI_OFFSET_MS) / DAY_MS);
7
+ }
8
+ /** 东八区日期键(YYYY-MM-DD),月/周聚合与 last_trade_date 过滤用。 */
9
+ function shanghaiDateKey(ms) {
10
+ const d = new Date(ms + SHANGHAI_OFFSET_MS);
11
+ return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`;
12
+ }
13
+ /** ISO 周键(周一为一周起点,按东八区日界)。 */
14
+ function isoWeekKey(ms) {
15
+ const d = new Date(ms + SHANGHAI_OFFSET_MS);
16
+ const t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
17
+ const monday = t - (new Date(t).getUTCDay() + 6) % 7 * DAY_MS;
18
+ return String(monday);
19
+ }
20
+ /** 单位日K → 标准 Kline(closeTime = openTime + 1d - 1,与 eastmoney/tencent 约定一致)。 */
21
+ function dailyBarToKline(openTime, open, high, low, close, volume) {
22
+ return {
23
+ openTime,
24
+ open,
25
+ high,
26
+ low,
27
+ close,
28
+ volume,
29
+ closeTime: openTime + DAY_MS - 1
30
+ };
31
+ }
32
+ /** 日K序列按日历桶聚合('3d' 按东八区日序 3 天分桶;'1w' ISO 周;'1M' 自然月)。 */
33
+ function aggregateDailyKlines(daily, interval) {
34
+ const buckets = /* @__PURE__ */ new Map();
35
+ for (const bar of daily) {
36
+ const key = interval === "1w" ? isoWeekKey(bar.openTime) : interval === "1M" ? shanghaiDateKey(bar.openTime).slice(0, 7) : String(Math.floor(shanghaiDay(bar.openTime) / 3));
37
+ const existing = buckets.get(key);
38
+ if (!existing) {
39
+ buckets.set(key, {
40
+ first: bar,
41
+ last: bar,
42
+ high: bar.high,
43
+ low: bar.low,
44
+ volume: bar.volume
45
+ });
46
+ continue;
47
+ }
48
+ existing.last = bar;
49
+ existing.high = Math.max(existing.high, bar.high);
50
+ existing.low = Math.min(existing.low, bar.low);
51
+ existing.volume += bar.volume;
52
+ }
53
+ return [...buckets.values()].map((b) => ({
54
+ openTime: b.first.openTime,
55
+ open: b.first.open,
56
+ high: b.high,
57
+ low: b.low,
58
+ close: b.last.close,
59
+ volume: b.volume,
60
+ closeTime: b.last.closeTime
61
+ }));
62
+ }
63
+ /** 分钟分桶聚合(分时点 → N 分钟 OHLC 条;volume 求和)。 */
64
+ function aggregateMinutePoints(points, stepMinutes) {
65
+ const stepMs = stepMinutes * 6e4;
66
+ const buckets = /* @__PURE__ */ new Map();
67
+ for (const p of points) {
68
+ const openTime = Math.floor(p.timestamp / stepMs) * stepMs;
69
+ const existing = buckets.get(openTime);
70
+ if (!existing) {
71
+ buckets.set(openTime, {
72
+ openTime,
73
+ open: p.price,
74
+ high: p.price,
75
+ low: p.price,
76
+ close: p.price,
77
+ volume: p.volume
78
+ });
79
+ continue;
80
+ }
81
+ existing.high = Math.max(existing.high, p.price);
82
+ existing.low = Math.min(existing.low, p.price);
83
+ existing.close = p.price;
84
+ existing.volume += p.volume;
85
+ }
86
+ return [...buckets.values()].sort((a, b) => a.openTime - b.openTime).map((b) => ({
87
+ ...b,
88
+ closeTime: b.openTime + stepMs - 1
89
+ }));
90
+ }
91
+ //#endregion
92
+ export { aggregateDailyKlines, aggregateMinutePoints, dailyBarToKline, shanghaiDateKey };
package/lib/rest.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { HiThinkLadderData, HiThinkValuationData } from "./types.js";
1
+ import { HiThinkFuturesDailyData, HiThinkFuturesIntradayData, HiThinkHistoricalData, HiThinkLadderData, HiThinkMetaTickerData, HiThinkValuationData } from "./types.js";
2
2
  import { AuctionSnapshot, LimitUpPoolItem, StockFundamentals, Ticker, TradingErrorCode } from "@dshtrading/api";
3
3
  //#region src/rest.d.ts
4
4
  declare class TradingServiceError extends Error {
@@ -7,6 +7,8 @@ declare class TradingServiceError extends Error {
7
7
  }
8
8
  interface HiThinkRestOptions {
9
9
  apiKey?: string | undefined;
10
+ /** 惰性凭证源(每次请求解析):settings credentials 热切换与异步加载晚于插件 apply 时生效。 */
11
+ apiKeyProvider?: (() => string | undefined) | undefined;
10
12
  baseUrl?: string | undefined;
11
13
  fetchImpl?: typeof fetch | undefined;
12
14
  timeoutMs?: number | undefined;
@@ -15,10 +17,12 @@ interface HiThinkRestOptions {
15
17
  declare function normalizeThsCode(input: string): string;
16
18
  declare class HiThinkRestClient {
17
19
  private readonly apiKey?;
20
+ private readonly apiKeyProvider?;
18
21
  private readonly baseUrl;
19
22
  private readonly fetchImpl;
20
23
  private readonly timeoutMs;
21
24
  constructor(options?: HiThinkRestOptions);
25
+ private resolvedApiKey;
22
26
  private request;
23
27
  /** 获取最新行情快照。 */
24
28
  getTicker(symbol: string): Promise<Ticker>;
@@ -41,6 +45,19 @@ declare class HiThinkRestClient {
41
45
  symbol: string;
42
46
  name: string;
43
47
  }>>;
48
+ /**
49
+ * A 股历史日K(上游仅开放 1d,start/end 毫秒必填,窗口最长 10 年)。
50
+ * 以 end=now 反推覆盖窗口取数后截尾最近 limit 根(升序)。
51
+ */
52
+ getHistoricalDailyKlines(symbol: string, limit: number, adjust?: 'none' | 'forward' | 'backward'): Promise<HiThinkHistoricalData['item']>;
53
+ /** 期货日K(省略 start/end 返回最近 100 根;窗口模式可取更长序列)。 */
54
+ getFuturesDailyKlines(thscode: string, limit: number): Promise<HiThinkFuturesDailyData['item']>;
55
+ /** 期货当日分时点(session 缺省 intraday;非交易时段可能为空)。 */
56
+ getFuturesIntraday(thscode: string, session?: 'pre_market' | 'intraday' | 'post_market'): Promise<HiThinkFuturesIntradayData>;
57
+ /** 跨资产标的检索(期货用 asset_type=futures 过滤;q 支持 thscode/代码/中英文名称子串)。 */
58
+ searchFuturesTickers(keyword: string, limit?: number): Promise<HiThinkMetaTickerData['item']>;
59
+ /** 期货全量代码表(offset 分页取尽;由调用方过滤已到期合约)。 */
60
+ listFuturesTickers(): Promise<HiThinkMetaTickerData['item']>;
44
61
  }
45
62
  //#endregion
46
63
  export { HiThinkRestClient, HiThinkRestOptions, TradingServiceError, normalizeThsCode };
package/lib/rest.js CHANGED
@@ -23,15 +23,20 @@ function normalizeThsCode(input) {
23
23
  }
24
24
  var HiThinkRestClient = class {
25
25
  apiKey;
26
+ apiKeyProvider;
26
27
  baseUrl;
27
28
  fetchImpl;
28
29
  timeoutMs;
29
30
  constructor(options = {}) {
30
31
  this.apiKey = options.apiKey ?? process.env.HITHINK_FINANCE_API_KEY ?? process.env.HITHINK_API_KEY;
32
+ this.apiKeyProvider = options.apiKeyProvider;
31
33
  this.baseUrl = (options.baseUrl ?? "https://fuyao.aicubes.cn").replace(/\/+$/, "");
32
34
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
33
35
  this.timeoutMs = options.timeoutMs ?? 1e4;
34
36
  }
37
+ resolvedApiKey() {
38
+ return this.apiKeyProvider?.() ?? this.apiKey;
39
+ }
35
40
  async request(path, params = {}) {
36
41
  const url = new URL(path, this.baseUrl);
37
42
  for (const [k, v] of Object.entries(params)) if (v !== void 0) url.searchParams.set(k, String(v));
@@ -39,7 +44,8 @@ var HiThinkRestClient = class {
39
44
  Accept: "application/json",
40
45
  "User-Agent": "dsh-trading/0.1.0"
41
46
  };
42
- if (this.apiKey) headers["X-api-key"] = this.apiKey;
47
+ const apiKey = this.resolvedApiKey();
48
+ if (apiKey) headers["X-api-key"] = apiKey;
43
49
  let res;
44
50
  try {
45
51
  res = await this.fetchImpl(url.toString(), {
@@ -149,6 +155,64 @@ var HiThinkRestClient = class {
149
155
  name: item.name
150
156
  }));
151
157
  }
158
+ /**
159
+ * A 股历史日K(上游仅开放 1d,start/end 毫秒必填,窗口最长 10 年)。
160
+ * 以 end=now 反推覆盖窗口取数后截尾最近 limit 根(升序)。
161
+ */
162
+ async getHistoricalDailyKlines(symbol, limit, adjust = "forward") {
163
+ const thscode = normalizeThsCode(symbol);
164
+ const end = Date.now();
165
+ const windowDays = Math.min(Math.ceil(limit * 1.7) + 7, 3650);
166
+ return ((await this.request("/api/a-share/prices/historical", {
167
+ thscode,
168
+ interval: "1d",
169
+ start: end - windowDays * 864e5,
170
+ end,
171
+ adjust
172
+ }))?.item ?? []).filter((b) => typeof b.date_ms === "number" && typeof b.close_price === "number").slice(-limit);
173
+ }
174
+ /** 期货日K(省略 start/end 返回最近 100 根;窗口模式可取更长序列)。 */
175
+ async getFuturesDailyKlines(thscode, limit) {
176
+ const end = Date.now();
177
+ const windowDays = Math.min(Math.ceil(limit * 1.7) + 7, 3650);
178
+ return ((await this.request("/api/futures/prices/daily", {
179
+ thscode,
180
+ ...limit > 100 ? {
181
+ start: end - windowDays * 864e5,
182
+ end
183
+ } : {}
184
+ }))?.item ?? []).filter((b) => typeof b.timestamp === "number" && typeof b.close_price === "number").slice(-limit);
185
+ }
186
+ /** 期货当日分时点(session 缺省 intraday;非交易时段可能为空)。 */
187
+ async getFuturesIntraday(thscode, session) {
188
+ return this.request("/api/futures/prices/intraday", {
189
+ thscode,
190
+ ...session !== void 0 ? { session } : {}
191
+ });
192
+ }
193
+ /** 跨资产标的检索(期货用 asset_type=futures 过滤;q 支持 thscode/代码/中英文名称子串)。 */
194
+ async searchFuturesTickers(keyword, limit = 50) {
195
+ return (await this.request("/api/meta/tickers/search", {
196
+ q: keyword.trim(),
197
+ asset_type: "futures",
198
+ limit
199
+ }))?.item ?? [];
200
+ }
201
+ /** 期货全量代码表(offset 分页取尽;由调用方过滤已到期合约)。 */
202
+ async listFuturesTickers() {
203
+ const all = [];
204
+ const pageSize = 1e4;
205
+ for (let offset = 0; offset < 1e5; offset += pageSize) {
206
+ const page = (await this.request("/api/meta/tickers/list", {
207
+ asset_type: "futures",
208
+ limit: pageSize,
209
+ offset
210
+ }))?.item ?? [];
211
+ all.push(...page);
212
+ if (page.length < pageSize) break;
213
+ }
214
+ return all;
215
+ }
152
216
  };
153
217
  //#endregion
154
218
  export { HiThinkRestClient, TradingServiceError, normalizeThsCode };
package/lib/types.d.ts CHANGED
@@ -119,5 +119,66 @@ interface HiThinkTickerSearchData {
119
119
  total: number;
120
120
  item: HiThinkTickerSearchItem[];
121
121
  }
122
+ /** A 股历史日K条目(/api/a-share/prices/historical,date_ms 为东八区交易日零点)。 */
123
+ interface HiThinkPriceBarItem {
124
+ date_ms?: number | null;
125
+ open_price?: number | null;
126
+ high_price?: number | null;
127
+ low_price?: number | null;
128
+ close_price?: number | null;
129
+ volume?: number | null;
130
+ turnover?: number | null;
131
+ }
132
+ interface HiThinkHistoricalData {
133
+ timestamp: number | null;
134
+ item: HiThinkPriceBarItem[];
135
+ }
136
+ /** 期货日K条目(/api/futures/prices/daily,timestamp 为毫秒;与 A 股的 date_ms 字段名不同)。 */
137
+ interface HiThinkFuturesDailyItem {
138
+ timestamp?: number | null;
139
+ open_price?: number | null;
140
+ high_price?: number | null;
141
+ low_price?: number | null;
142
+ close_price?: number | null;
143
+ volume?: number | null;
144
+ turnover?: number | null;
145
+ }
146
+ interface HiThinkFuturesDailyData {
147
+ timestamp: number | null;
148
+ thscode: string;
149
+ interval: string;
150
+ item: HiThinkFuturesDailyItem[];
151
+ }
152
+ /** 期货当日分时点(/api/futures/prices/intraday;价格点序列,非 OHLC)。 */
153
+ interface HiThinkFuturesIntradayItem {
154
+ timestamp?: number | null;
155
+ price?: number | null;
156
+ volume?: number | null;
157
+ turnover?: number | null;
158
+ }
159
+ interface HiThinkFuturesIntradayData {
160
+ timestamp: number | null;
161
+ thscode: string;
162
+ date: string;
163
+ session: string;
164
+ item: HiThinkFuturesIntradayItem[];
165
+ }
166
+ /** 跨资产代码表条目(/api/meta/tickers/list|search,日期为 yyyy-MM-dd 或 null)。 */
167
+ interface HiThinkMetaTickerItem {
168
+ thscode: string;
169
+ ticker: string;
170
+ name: string;
171
+ exchange?: string | null;
172
+ asset_type: string;
173
+ currency?: string | null;
174
+ list_date?: string | null;
175
+ end_date?: string | null;
176
+ last_trade_date?: string | null;
177
+ last_delivery_date?: string | null;
178
+ }
179
+ interface HiThinkMetaTickerData {
180
+ timestamp: number | null;
181
+ item: HiThinkMetaTickerItem[];
182
+ }
122
183
  //#endregion
123
- export { HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem };
184
+ export { HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkFuturesDailyData, HiThinkFuturesDailyItem, HiThinkFuturesIntradayData, HiThinkFuturesIntradayItem, HiThinkHistoricalData, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkMetaTickerData, HiThinkMetaTickerItem, HiThinkPriceBarItem, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dshtrading/connector-hithink",
3
3
  "description": "【模板】交易所连接器插件脚手架:HiThink(hithink)v2 插件骨架 —— 市场数据服务 + 交易服务 + 三态闸门 + BYOK 凭证 ref。生成器:node scripts/new-connector.mjs --slug <exchange>",
4
- "version": "0.2.1",
4
+ "version": "0.4.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -14,6 +14,18 @@
14
14
  "types": "./lib/dataplane.d.ts",
15
15
  "default": "./lib/dataplane.js"
16
16
  },
17
+ "./futures": {
18
+ "types": "./lib/futures.d.ts",
19
+ "default": "./lib/futures.js"
20
+ },
21
+ "./futures-dataplane": {
22
+ "types": "./lib/futures-dataplane.d.ts",
23
+ "default": "./lib/futures-dataplane.js"
24
+ },
25
+ "./futures-plugin": {
26
+ "types": "./lib/futures-plugin.d.ts",
27
+ "default": "./lib/futures-plugin.js"
28
+ },
17
29
  "./package.json": "./package.json"
18
30
  },
19
31
  "files": [
@@ -21,7 +33,7 @@
21
33
  ],
22
34
  "license": "PolyForm-Noncommercial-1.0.0",
23
35
  "dependencies": {
24
- "@dshtrading/api": "^0.2.1"
36
+ "@dshtrading/api": "^0.4.0"
25
37
  },
26
38
  "peerDependencies": {
27
39
  "@deepseek-ai/cordis": ">=4.0.0",