@dshtrading/connector-hithink 0.1.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/LICENSE +75 -0
- package/README.md +31 -0
- package/lib/dataplane.d.ts +7 -0
- package/lib/dataplane.js +24 -0
- package/lib/index.d.ts +37 -0
- package/lib/index.js +54 -0
- package/lib/rest.d.ts +46 -0
- package/lib/rest.js +154 -0
- package/lib/types.d.ts +123 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Copyright (c) 2026 zhu1090093659 (dsh-trading)
|
|
2
|
+
|
|
3
|
+
# PolyForm Noncommercial License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncommercial Purposes
|
|
34
|
+
|
|
35
|
+
Any noncommercial purpose is a permitted purpose.
|
|
36
|
+
|
|
37
|
+
## Personal Uses
|
|
38
|
+
|
|
39
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
40
|
+
|
|
41
|
+
## Noncommercial Organizations
|
|
42
|
+
|
|
43
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
44
|
+
|
|
45
|
+
## Fair Use
|
|
46
|
+
|
|
47
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
48
|
+
|
|
49
|
+
## No Other Rights
|
|
50
|
+
|
|
51
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
52
|
+
|
|
53
|
+
## Patent Defense
|
|
54
|
+
|
|
55
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
56
|
+
|
|
57
|
+
## Violations
|
|
58
|
+
|
|
59
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
60
|
+
|
|
61
|
+
## No Liability
|
|
62
|
+
|
|
63
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
64
|
+
|
|
65
|
+
## Definitions
|
|
66
|
+
|
|
67
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
68
|
+
|
|
69
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
70
|
+
|
|
71
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
72
|
+
|
|
73
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
74
|
+
|
|
75
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @dshtrading/connector-hithink
|
|
2
|
+
|
|
3
|
+
HiThink (同花顺/问财开放平台 OpenAPI) A 股连接器,提供基本面数据、估值快照、集合竞价与特色选股数据。
|
|
4
|
+
|
|
5
|
+
## 能力边界
|
|
6
|
+
|
|
7
|
+
- **数据服务 (`MarketDataService`)**:
|
|
8
|
+
- `getTicker(symbol)`:实时行情快照与最新量价;
|
|
9
|
+
- `getStockFundamentals(symbol)`:总市值、流通市值、市盈率 (PE-TTM/动/静)、市净率 (PB)、市销率 (PS)、股息率;
|
|
10
|
+
- `getAuctionSnapshot(symbol)`:早盘集合竞价匹配量、未匹配量与竞价强弱;
|
|
11
|
+
- `getLimitUpPool(date?)`:连板池、封单量与炸板分析;
|
|
12
|
+
- **交易服务 (`TradeService`)**:
|
|
13
|
+
- 不提供直接委托下单通道(A 股实盘执行由本地 MiniQMT 负责,遵循铁律 #3 统一安全闸门)。
|
|
14
|
+
|
|
15
|
+
## 配置项 (Config)
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"apiKey": "ref:env/HITHINK_FINANCE_API_KEY",
|
|
21
|
+
"baseUrl": "https://fuyao.aicubes.cn/openapi/v1"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `apiKey`:同花顺/问财开放平台认证 Token(遵循 BYOK 铁律 #5,仅支持 ref 引用或环境变量 `HITHINK_FINANCE_API_KEY`);
|
|
26
|
+
- `baseUrl`:开放平台端点网关。
|
|
27
|
+
|
|
28
|
+
## 合规与条款边界 (ToS Compliance)
|
|
29
|
+
|
|
30
|
+
- **个人使用**:仅供用户自主鉴权后获取个人看盘与研报所需数据;
|
|
31
|
+
- **严禁再分发 (铁律 #4)**:不缓存转售、不批量扒取、不落地再分发原始行情。
|
package/lib/dataplane.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { HiThinkMarketDataService, TRADING_CN_MARKET_DATA_KEY } from "./index.js";
|
|
2
|
+
//#region src/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 = "cn";
|
|
10
|
+
function apply(ctx, config) {
|
|
11
|
+
if (!config.enabled) return;
|
|
12
|
+
const apiKey = process.env[config.apiKeyRef];
|
|
13
|
+
const registry = resolveMarketDataRegistry(ctx);
|
|
14
|
+
const opts = apiKey ? { apiKey } : {};
|
|
15
|
+
if (registry === void 0) {
|
|
16
|
+
new HiThinkMarketDataService(ctx, opts);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const inner = ctx.isolate(TRADING_CN_MARKET_DATA_KEY);
|
|
20
|
+
const service = new HiThinkMarketDataService(inner, opts);
|
|
21
|
+
ctx.effect(() => registry.register(MARKET, ROUTER_PROVIDER, service));
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { apply, inject };
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem } from "./types.js";
|
|
2
|
+
import { HiThinkRestClient, HiThinkRestOptions, TradingServiceError, normalizeThsCode } from "./rest.js";
|
|
3
|
+
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
5
|
+
import { AuctionSnapshot, Disposable, Interval, Kline, LimitUpPoolItem, MarketDataService, StockFundamentals, Ticker } from "@dshtrading/api";
|
|
6
|
+
//#region src/index.d.ts
|
|
7
|
+
declare const name = "dsh-trading-cn-connector-hithink";
|
|
8
|
+
interface Config {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
apiKeyRef: string;
|
|
11
|
+
}
|
|
12
|
+
declare const Config: Schema<Config>;
|
|
13
|
+
declare const TRADING_CN_MARKET_DATA_KEY = "tradingCnMarketData";
|
|
14
|
+
declare class HiThinkMarketDataService extends Service implements MarketDataService {
|
|
15
|
+
private readonly client;
|
|
16
|
+
constructor(ctx: Context, options?: HiThinkRestOptions, serviceName?: string);
|
|
17
|
+
getTicker(symbol: string): Promise<Ticker>;
|
|
18
|
+
getKlines(_symbol: string, _interval?: Interval, _limit?: number): Promise<Kline[]>;
|
|
19
|
+
getStockFundamentals(symbol: string): Promise<StockFundamentals>;
|
|
20
|
+
getAuctionSnapshot(symbol: string): Promise<AuctionSnapshot | undefined>;
|
|
21
|
+
getLimitUpPool(options?: {
|
|
22
|
+
dateMs?: number;
|
|
23
|
+
page?: number;
|
|
24
|
+
size?: number;
|
|
25
|
+
}): Promise<LimitUpPoolItem[]>;
|
|
26
|
+
getLimitUpLadder(): Promise<HiThinkLadderData>;
|
|
27
|
+
searchTickers(query: string): Promise<{
|
|
28
|
+
symbol: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}[]>;
|
|
31
|
+
subscribeTicker(symbol: string, cb: (ticker: Ticker) => void, options?: {
|
|
32
|
+
intervalMs?: number;
|
|
33
|
+
}): Disposable;
|
|
34
|
+
}
|
|
35
|
+
declare function apply(ctx: Context, config: Config): void;
|
|
36
|
+
//#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 };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { HiThinkRestClient, TradingServiceError, normalizeThsCode } from "./rest.js";
|
|
2
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
const name = "dsh-trading-cn-connector-hithink";
|
|
6
|
+
const Config = Schema.object({
|
|
7
|
+
enabled: Schema.boolean().default(true).description("是否激活同花顺连接器"),
|
|
8
|
+
apiKeyRef: Schema.string().default("HITHINK_FINANCE_API_KEY").description("同花顺 API Key 环境变量名 (BYOK)")
|
|
9
|
+
});
|
|
10
|
+
const TRADING_CN_MARKET_DATA_KEY = "tradingCnMarketData";
|
|
11
|
+
var HiThinkMarketDataService = class extends Service {
|
|
12
|
+
client;
|
|
13
|
+
constructor(ctx, options = {}, serviceName = TRADING_CN_MARKET_DATA_KEY) {
|
|
14
|
+
super(ctx, serviceName);
|
|
15
|
+
this.client = new HiThinkRestClient(options);
|
|
16
|
+
}
|
|
17
|
+
async getTicker(symbol) {
|
|
18
|
+
return this.client.getTicker(symbol);
|
|
19
|
+
}
|
|
20
|
+
async getKlines(_symbol, _interval = "1d", _limit = 100) {
|
|
21
|
+
throw new TradingServiceError("TRADING_NOT_IMPLEMENTED", "HiThink K-lines not supported yet");
|
|
22
|
+
}
|
|
23
|
+
async getStockFundamentals(symbol) {
|
|
24
|
+
return this.client.getStockFundamentals(symbol);
|
|
25
|
+
}
|
|
26
|
+
async getAuctionSnapshot(symbol) {
|
|
27
|
+
return this.client.getAuctionSnapshot(symbol);
|
|
28
|
+
}
|
|
29
|
+
async getLimitUpPool(options) {
|
|
30
|
+
return this.client.getLimitUpPool(options);
|
|
31
|
+
}
|
|
32
|
+
async getLimitUpLadder() {
|
|
33
|
+
return this.client.getLimitUpLadder();
|
|
34
|
+
}
|
|
35
|
+
async searchTickers(query) {
|
|
36
|
+
return this.client.searchTickers(query);
|
|
37
|
+
}
|
|
38
|
+
subscribeTicker(symbol, cb, options) {
|
|
39
|
+
const ms = Math.max(options?.intervalMs ?? 5e3, 1e3);
|
|
40
|
+
const tick = () => {
|
|
41
|
+
this.getTicker(symbol).then(cb, () => {});
|
|
42
|
+
};
|
|
43
|
+
tick();
|
|
44
|
+
const timer = setInterval(tick, ms);
|
|
45
|
+
return { dispose: () => clearInterval(timer) };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function apply(ctx, config) {
|
|
49
|
+
if (!config.enabled) return;
|
|
50
|
+
const apiKey = process.env[config.apiKeyRef];
|
|
51
|
+
new HiThinkMarketDataService(ctx, apiKey ? { apiKey } : {});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { Config, HiThinkMarketDataService, HiThinkRestClient, TRADING_CN_MARKET_DATA_KEY, TradingServiceError, apply, name, normalizeThsCode };
|
package/lib/rest.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { HiThinkLadderData, HiThinkValuationData } from "./types.js";
|
|
2
|
+
import { AuctionSnapshot, LimitUpPoolItem, StockFundamentals, Ticker, TradingErrorCode } from "@dshtrading/api";
|
|
3
|
+
//#region src/rest.d.ts
|
|
4
|
+
declare class TradingServiceError extends Error {
|
|
5
|
+
readonly code: TradingErrorCode;
|
|
6
|
+
constructor(code: TradingErrorCode, message: string, cause?: unknown);
|
|
7
|
+
}
|
|
8
|
+
interface HiThinkRestOptions {
|
|
9
|
+
apiKey?: string | undefined;
|
|
10
|
+
baseUrl?: string | undefined;
|
|
11
|
+
fetchImpl?: typeof fetch | undefined;
|
|
12
|
+
timeoutMs?: number | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** 规范化输入为标准 thscode(如 600519 -> 600519.SH, sz000001 -> 000001.SZ)。 */
|
|
15
|
+
declare function normalizeThsCode(input: string): string;
|
|
16
|
+
declare class HiThinkRestClient {
|
|
17
|
+
private readonly apiKey?;
|
|
18
|
+
private readonly baseUrl;
|
|
19
|
+
private readonly fetchImpl;
|
|
20
|
+
private readonly timeoutMs;
|
|
21
|
+
constructor(options?: HiThinkRestOptions);
|
|
22
|
+
private request;
|
|
23
|
+
/** 获取最新行情快照。 */
|
|
24
|
+
getTicker(symbol: string): Promise<Ticker>;
|
|
25
|
+
/** 获取标的估值快照。 */
|
|
26
|
+
getValuation(symbol: string): Promise<HiThinkValuationData['item'][0] | undefined>;
|
|
27
|
+
/** 获取综合估值指标快照(StockFundamentals 适配)。 */
|
|
28
|
+
getStockFundamentals(symbol: string): Promise<StockFundamentals>;
|
|
29
|
+
/** 获取今日或指定日期涨跌停池。 */
|
|
30
|
+
getLimitUpPool(options?: {
|
|
31
|
+
dateMs?: number;
|
|
32
|
+
page?: number;
|
|
33
|
+
size?: number;
|
|
34
|
+
}): Promise<LimitUpPoolItem[]>;
|
|
35
|
+
/** 获取连板天梯数据。 */
|
|
36
|
+
getLimitUpLadder(): Promise<HiThinkLadderData>;
|
|
37
|
+
/** 获取集合竞价快照与强弱基准。 */
|
|
38
|
+
getAuctionSnapshot(symbol: string): Promise<AuctionSnapshot | undefined>;
|
|
39
|
+
/** 模糊搜索股票代码与全称(消歧)。 */
|
|
40
|
+
searchTickers(keyword: string): Promise<Array<{
|
|
41
|
+
symbol: string;
|
|
42
|
+
name: string;
|
|
43
|
+
}>>;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { HiThinkRestClient, HiThinkRestOptions, TradingServiceError, normalizeThsCode };
|
package/lib/rest.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
//#region src/rest.ts
|
|
2
|
+
var TradingServiceError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message, cause) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "TradingServiceError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
if (cause !== void 0) this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
/** 规范化输入为标准 thscode(如 600519 -> 600519.SH, sz000001 -> 000001.SZ)。 */
|
|
12
|
+
function normalizeThsCode(input) {
|
|
13
|
+
const raw = input.trim().toUpperCase();
|
|
14
|
+
if (/^\d{6}\.(SH|SZ|BJ)$/.test(raw)) return raw;
|
|
15
|
+
const prefixMatch = /^(SH|SZ|BJ)(\d{6})$/.exec(raw);
|
|
16
|
+
if (prefixMatch && prefixMatch[1] && prefixMatch[2]) return `${prefixMatch[2]}.${prefixMatch[1]}`;
|
|
17
|
+
const codeMatch = /^(\d{6})$/.exec(raw);
|
|
18
|
+
if (codeMatch && codeMatch[1]) {
|
|
19
|
+
const code = codeMatch[1];
|
|
20
|
+
return `${code}.${code.startsWith("920") || code.startsWith("8") || code.startsWith("4") ? "BJ" : code.startsWith("6") || code.startsWith("900") ? "SH" : "SZ"}`;
|
|
21
|
+
}
|
|
22
|
+
return raw;
|
|
23
|
+
}
|
|
24
|
+
var HiThinkRestClient = class {
|
|
25
|
+
apiKey;
|
|
26
|
+
baseUrl;
|
|
27
|
+
fetchImpl;
|
|
28
|
+
timeoutMs;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.apiKey = options.apiKey ?? process.env.HITHINK_FINANCE_API_KEY ?? process.env.HITHINK_API_KEY;
|
|
31
|
+
this.baseUrl = (options.baseUrl ?? "https://fuyao.aicubes.cn").replace(/\/+$/, "");
|
|
32
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
33
|
+
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
34
|
+
}
|
|
35
|
+
async request(path, params = {}) {
|
|
36
|
+
const url = new URL(path, this.baseUrl);
|
|
37
|
+
for (const [k, v] of Object.entries(params)) if (v !== void 0) url.searchParams.set(k, String(v));
|
|
38
|
+
const headers = {
|
|
39
|
+
Accept: "application/json",
|
|
40
|
+
"User-Agent": "dsh-trading/0.1.0"
|
|
41
|
+
};
|
|
42
|
+
if (this.apiKey) headers["X-api-key"] = this.apiKey;
|
|
43
|
+
let res;
|
|
44
|
+
try {
|
|
45
|
+
res = await this.fetchImpl(url.toString(), {
|
|
46
|
+
method: "GET",
|
|
47
|
+
headers,
|
|
48
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
49
|
+
});
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new TradingServiceError("TRADING_NETWORK", `HiThink request failed (${url.pathname}): ${err instanceof Error ? err.message : String(err)}`, err);
|
|
52
|
+
}
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
if (res.status === 401 || res.status === 403) throw new TradingServiceError("TRADING_AUTH_FAILED", `HiThink authentication failed (HTTP ${res.status}). Check HITHINK_FINANCE_API_KEY.`);
|
|
55
|
+
if (res.status === 429) throw new TradingServiceError("TRADING_RATE_LIMITED", `HiThink rate limited (HTTP 429). Retry later.`);
|
|
56
|
+
throw new TradingServiceError("TRADING_NETWORK", `HiThink upstream error (HTTP ${res.status})`);
|
|
57
|
+
}
|
|
58
|
+
const json = await res.json();
|
|
59
|
+
if (json.code !== 0) {
|
|
60
|
+
if (json.code === 2001 || json.code === 2003) throw new TradingServiceError("TRADING_AUTH_FAILED", `HiThink auth error (${json.code}): ${json.message}`);
|
|
61
|
+
if (json.code === 4001) throw new TradingServiceError("TRADING_RATE_LIMITED", `HiThink rate limited (${json.code}): ${json.message}`);
|
|
62
|
+
if (json.code === 3001) throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `HiThink ticker not found (${json.code}): ${json.message}`);
|
|
63
|
+
throw new TradingServiceError("TRADING_UNKNOWN", `HiThink API error (${json.code}): ${json.message}`);
|
|
64
|
+
}
|
|
65
|
+
return json.data;
|
|
66
|
+
}
|
|
67
|
+
/** 获取最新行情快照。 */
|
|
68
|
+
async getTicker(symbol) {
|
|
69
|
+
const thscode = normalizeThsCode(symbol);
|
|
70
|
+
const data = await this.request("/api/a-share/prices/snapshot", { thscodes: thscode });
|
|
71
|
+
const item = data?.item?.[0];
|
|
72
|
+
if (!item) throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `HiThink: no quote found for ${symbol} (${thscode})`);
|
|
73
|
+
return {
|
|
74
|
+
symbol: thscode,
|
|
75
|
+
price: item.last_price ?? item.prev_price ?? 0,
|
|
76
|
+
...item.prev_price !== void 0 ? { prevClose: item.prev_price } : {},
|
|
77
|
+
...item.price_change_ratio_pct !== void 0 ? { changePercent: item.price_change_ratio_pct } : {},
|
|
78
|
+
...item.volume !== void 0 ? { volume: item.volume } : {},
|
|
79
|
+
timestamp: data?.timestamp ?? Date.now()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** 获取标的估值快照。 */
|
|
83
|
+
async getValuation(symbol) {
|
|
84
|
+
const thscode = normalizeThsCode(symbol);
|
|
85
|
+
return (await this.request("/api/a-share/valuations/snapshot", { thscodes: thscode }))?.item?.[0];
|
|
86
|
+
}
|
|
87
|
+
/** 获取综合估值指标快照(StockFundamentals 适配)。 */
|
|
88
|
+
async getStockFundamentals(symbol) {
|
|
89
|
+
const thscode = normalizeThsCode(symbol);
|
|
90
|
+
const [ticker, val] = await Promise.allSettled([this.getTicker(thscode), this.getValuation(thscode)]);
|
|
91
|
+
const tickerVal = ticker.status === "fulfilled" ? ticker.value : void 0;
|
|
92
|
+
const valItem = val.status === "fulfilled" ? val.value : void 0;
|
|
93
|
+
return {
|
|
94
|
+
symbol: thscode,
|
|
95
|
+
...typeof valItem?.pe_ttm === "number" ? { peTtm: valItem.pe_ttm } : {},
|
|
96
|
+
...typeof valItem?.pe_mrq === "number" ? { peDynamic: valItem.pe_mrq } : {},
|
|
97
|
+
...typeof valItem?.pb_mrq === "number" ? { pb: valItem.pb_mrq } : {},
|
|
98
|
+
...typeof valItem?.ps_ttm === "number" ? { ps: valItem.ps_ttm } : {},
|
|
99
|
+
timestamp: tickerVal?.timestamp ?? Date.now()
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** 获取今日或指定日期涨跌停池。 */
|
|
103
|
+
async getLimitUpPool(options = {}) {
|
|
104
|
+
return ((await this.request("/api/a-share/special-data/limit-up-pool", {
|
|
105
|
+
...options.dateMs !== void 0 ? { date_ms: options.dateMs } : {},
|
|
106
|
+
page: options.page ?? 1,
|
|
107
|
+
size: options.size ?? 50,
|
|
108
|
+
sort_field: "limit_up_time",
|
|
109
|
+
sort_dir: "asc"
|
|
110
|
+
}))?.item ?? []).map((item) => ({
|
|
111
|
+
symbol: item.thscode,
|
|
112
|
+
name: item.name,
|
|
113
|
+
price: item.last_price,
|
|
114
|
+
changePercent: item.price_change_ratio_pct,
|
|
115
|
+
limitType: "up",
|
|
116
|
+
...item.limit_up_time !== void 0 ? { firstLimitTime: item.limit_up_time } : {},
|
|
117
|
+
...item.seal_money !== void 0 ? { limitOrderAmount: item.seal_money } : {},
|
|
118
|
+
...item.continue_day_cnt !== void 0 ? { consecutiveBoards: item.continue_day_cnt } : {},
|
|
119
|
+
...item.limit_up_reason !== void 0 ? { sectorConcept: item.limit_up_reason } : {}
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
/** 获取连板天梯数据。 */
|
|
123
|
+
async getLimitUpLadder() {
|
|
124
|
+
return this.request("/api/a-share/special-data/limit-up-ladder");
|
|
125
|
+
}
|
|
126
|
+
/** 获取集合竞价快照与强弱基准。 */
|
|
127
|
+
async getAuctionSnapshot(symbol) {
|
|
128
|
+
const thscode = normalizeThsCode(symbol);
|
|
129
|
+
const data = await this.request("/api/a-share/auction/snapshot", { thscodes: thscode });
|
|
130
|
+
const item = data?.item?.[0];
|
|
131
|
+
if (!item) return void 0;
|
|
132
|
+
const unmatchedSide = item.unmatched_side === "buy" || item.unmatched_side === "sell" ? item.unmatched_side : void 0;
|
|
133
|
+
const stage = item.stage === "call" || item.stage === "final" ? item.stage : void 0;
|
|
134
|
+
return {
|
|
135
|
+
symbol: thscode,
|
|
136
|
+
...item.match_price !== void 0 ? { matchPrice: item.match_price } : {},
|
|
137
|
+
...item.match_volume !== void 0 ? { matchVolume: item.match_volume } : {},
|
|
138
|
+
...item.unmatched_volume !== void 0 ? { unmatchedVolume: item.unmatched_volume } : {},
|
|
139
|
+
...unmatchedSide !== void 0 ? { unmatchedSide } : {},
|
|
140
|
+
...item.strength_index !== void 0 ? { strengthIndex: item.strength_index } : {},
|
|
141
|
+
...stage !== void 0 ? { stage } : {},
|
|
142
|
+
timestamp: data?.timestamp ?? Date.now()
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** 模糊搜索股票代码与全称(消歧)。 */
|
|
146
|
+
async searchTickers(keyword) {
|
|
147
|
+
return ((await this.request("/api/a-share/instruments/search", { keyword: keyword.trim() }))?.item ?? []).map((item) => ({
|
|
148
|
+
symbol: item.thscode,
|
|
149
|
+
name: item.name
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
export { HiThinkRestClient, TradingServiceError, normalizeThsCode };
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* @dshtrading/connector-hithink
|
|
4
|
+
* 同花顺官方金融数据服务 API 契约与内部数据类型。
|
|
5
|
+
*/
|
|
6
|
+
interface HiThinkEnvelope<T> {
|
|
7
|
+
code: number;
|
|
8
|
+
message: string;
|
|
9
|
+
request_id?: string;
|
|
10
|
+
data?: T;
|
|
11
|
+
}
|
|
12
|
+
interface HiThinkPriceSnapshotItem {
|
|
13
|
+
thscode: string;
|
|
14
|
+
ticker: string;
|
|
15
|
+
last_price?: number;
|
|
16
|
+
price_change?: number;
|
|
17
|
+
price_change_ratio_pct?: number;
|
|
18
|
+
open_price?: number;
|
|
19
|
+
high_price?: number;
|
|
20
|
+
low_price?: number;
|
|
21
|
+
prev_price?: number;
|
|
22
|
+
volume?: number;
|
|
23
|
+
turnover?: number;
|
|
24
|
+
}
|
|
25
|
+
interface HiThinkPriceSnapshotData {
|
|
26
|
+
timestamp: number | null;
|
|
27
|
+
total: number;
|
|
28
|
+
item: HiThinkPriceSnapshotItem[];
|
|
29
|
+
}
|
|
30
|
+
interface HiThinkValuationItem {
|
|
31
|
+
thscode: string;
|
|
32
|
+
ticker: string;
|
|
33
|
+
pe_ttm?: number | null;
|
|
34
|
+
pe_mrq?: number | null;
|
|
35
|
+
pb_mrq?: number | null;
|
|
36
|
+
ps_ttm?: number | null;
|
|
37
|
+
pcf_ttm?: number | null;
|
|
38
|
+
}
|
|
39
|
+
interface HiThinkValuationData {
|
|
40
|
+
timestamp: number | null;
|
|
41
|
+
total: number;
|
|
42
|
+
item: HiThinkValuationItem[];
|
|
43
|
+
}
|
|
44
|
+
interface HiThinkLimitUpItem {
|
|
45
|
+
thscode: string;
|
|
46
|
+
ticker: string;
|
|
47
|
+
name: string;
|
|
48
|
+
is_st?: boolean;
|
|
49
|
+
is_new?: boolean;
|
|
50
|
+
last_price: number;
|
|
51
|
+
price_change_ratio_pct: number;
|
|
52
|
+
limit_up_time?: string;
|
|
53
|
+
limit_up_reason?: string;
|
|
54
|
+
continue_day_text?: string;
|
|
55
|
+
continue_day_cnt?: number;
|
|
56
|
+
seal_money?: number;
|
|
57
|
+
max_seal_money?: number;
|
|
58
|
+
}
|
|
59
|
+
interface HiThinkLimitUpPoolData {
|
|
60
|
+
timestamp: number;
|
|
61
|
+
pagination?: {
|
|
62
|
+
total: number;
|
|
63
|
+
pages: number;
|
|
64
|
+
size: number;
|
|
65
|
+
page: number;
|
|
66
|
+
};
|
|
67
|
+
item: HiThinkLimitUpItem[];
|
|
68
|
+
}
|
|
69
|
+
interface HiThinkLadderBoardItem {
|
|
70
|
+
thscode: string;
|
|
71
|
+
ticker: string;
|
|
72
|
+
name: string;
|
|
73
|
+
board_num: number;
|
|
74
|
+
sign_level?: string;
|
|
75
|
+
seal_nextday?: string | null;
|
|
76
|
+
}
|
|
77
|
+
interface HiThinkLadderDayItem {
|
|
78
|
+
date: string;
|
|
79
|
+
boards: {
|
|
80
|
+
two_board?: HiThinkLadderBoardItem[];
|
|
81
|
+
three_board?: HiThinkLadderBoardItem[];
|
|
82
|
+
four_board?: HiThinkLadderBoardItem[];
|
|
83
|
+
five_board?: HiThinkLadderBoardItem[];
|
|
84
|
+
six_board?: HiThinkLadderBoardItem[];
|
|
85
|
+
seven_over?: HiThinkLadderBoardItem[];
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
interface HiThinkLadderData {
|
|
89
|
+
timestamp: number;
|
|
90
|
+
window?: {
|
|
91
|
+
length: number;
|
|
92
|
+
date_list: string[];
|
|
93
|
+
board_caps: Record<string, number>;
|
|
94
|
+
};
|
|
95
|
+
item: HiThinkLadderDayItem[];
|
|
96
|
+
}
|
|
97
|
+
interface HiThinkAuctionItem {
|
|
98
|
+
thscode: string;
|
|
99
|
+
ticker: string;
|
|
100
|
+
match_price?: number;
|
|
101
|
+
match_volume?: number;
|
|
102
|
+
unmatched_volume?: number;
|
|
103
|
+
unmatched_side?: 'buy' | 'sell' | string;
|
|
104
|
+
strength_index?: number;
|
|
105
|
+
stage?: 'call' | 'final' | string;
|
|
106
|
+
}
|
|
107
|
+
interface HiThinkAuctionData {
|
|
108
|
+
timestamp: number | null;
|
|
109
|
+
item: HiThinkAuctionItem[];
|
|
110
|
+
}
|
|
111
|
+
interface HiThinkTickerSearchItem {
|
|
112
|
+
thscode: string;
|
|
113
|
+
ticker: string;
|
|
114
|
+
name: string;
|
|
115
|
+
asset_type?: string;
|
|
116
|
+
exchange?: string;
|
|
117
|
+
}
|
|
118
|
+
interface HiThinkTickerSearchData {
|
|
119
|
+
total: number;
|
|
120
|
+
item: HiThinkTickerSearchItem[];
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { HiThinkAuctionData, HiThinkAuctionItem, HiThinkEnvelope, HiThinkLadderBoardItem, HiThinkLadderData, HiThinkLadderDayItem, HiThinkLimitUpItem, HiThinkLimitUpPoolData, HiThinkPriceSnapshotData, HiThinkPriceSnapshotItem, HiThinkTickerSearchData, HiThinkTickerSearchItem, HiThinkValuationData, HiThinkValuationItem };
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dshtrading/connector-hithink",
|
|
3
|
+
"description": "【模板】交易所连接器插件脚手架:HiThink(hithink)v2 插件骨架 —— 市场数据服务 + 交易服务 + 三态闸门 + BYOK 凭证 ref。生成器:node scripts/new-connector.mjs --slug <exchange>",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./dataplane": {
|
|
14
|
+
"types": "./lib/dataplane.d.ts",
|
|
15
|
+
"default": "./lib/dataplane.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib"
|
|
21
|
+
],
|
|
22
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@dshtrading/api": "^0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@deepseek-ai/cordis": ">=4.0.0",
|
|
28
|
+
"@deepseek-ai/dsh-tools": ">=0.1.2-alpha.1",
|
|
29
|
+
"@deepseek-ai/schemastery": ">=3.18.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsdown": "^0.22.0",
|
|
33
|
+
"vitest": "^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsdown",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
}
|
|
39
|
+
}
|