@dshtrading/connector-binance 0.4.1 → 0.6.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/index.d.ts +9 -12
- package/lib/index.js +23 -7
- package/lib/rest.d.ts +45 -11
- package/lib/rest.js +157 -31
- package/package.json +5 -4
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BinanceRestClient, BinanceRestOptions, INTERVAL_VOCABULARY, TradingServiceError, normalizeBinanceFuturesSymbol } from "./rest.js";
|
|
1
|
+
import { BinanceRestClient, BinanceRestOptions, INTERVAL_VOCABULARY, TradingServiceError, binanceAssetClassOf, binanceContractOf, normalizeBinanceFuturesSymbol, resolveBinanceInstrument } from "./rest.js";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import Schema from "@deepseek-ai/schemastery";
|
|
4
|
-
import { DerivativesData, DerivativesHistory, Disposable, Interval, Kline, MarketDataService, Orderbook, Ticker, TradeTick } from "@dshtrading/api";
|
|
4
|
+
import { DerivativesData, DerivativesHistory, Disposable, InstrumentRef, Interval, Kline, MarketDataService, Orderbook, Ticker, TradeTick } from "@dshtrading/api";
|
|
5
5
|
//#region src/index.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Cordis 插件名 = patch 行 id(TEMPLATES §8):`dsh-trading-crypto-*` 市场命名空间,
|
|
@@ -13,7 +13,7 @@ interface Config {
|
|
|
13
13
|
enabled: boolean;
|
|
14
14
|
/** 交易安全闸门(铁律 #3):true 时下单类工具强制 dry-run。 */
|
|
15
15
|
dryRun: boolean;
|
|
16
|
-
/**
|
|
16
|
+
/** 实盘镜像:默认 false。它是非权威镜像——只能收紧、永不能授予;实盘授权由 @dshtrading/authority 的人工签署平面裁定 [S4]。 */
|
|
17
17
|
liveTrading: boolean;
|
|
18
18
|
}
|
|
19
19
|
declare const Config: Schema<Config>;
|
|
@@ -30,10 +30,7 @@ declare class BinanceMarketDataService extends Service implements MarketDataServ
|
|
|
30
30
|
constructor(ctx: Context, options?: BinanceRestOptions, name?: string);
|
|
31
31
|
getTicker(symbol: string): Promise<Ticker>;
|
|
32
32
|
getKlines(symbol: string, interval: Interval, limit?: number): Promise<Kline[]>;
|
|
33
|
-
listInstruments(): Promise<
|
|
34
|
-
symbol: string;
|
|
35
|
-
name?: string;
|
|
36
|
-
}>>;
|
|
33
|
+
listInstruments(): Promise<InstrumentRef[]>;
|
|
37
34
|
subscribeTicker(symbol: string, cb: (ticker: Ticker) => void, options?: SubscribeTickerOptions): Disposable;
|
|
38
35
|
/**
|
|
39
36
|
* 盘口快照(api 可选契约 getOrderbook,issue #39):现货 depth 20 档透传,
|
|
@@ -73,15 +70,15 @@ interface PlaceOrderArgs {
|
|
|
73
70
|
}
|
|
74
71
|
/**
|
|
75
72
|
* 闸门判定结果(三条路径,顺序即铁律 #3 修订版的裁决顺序):
|
|
76
|
-
* - `reject` —— ① 请求实盘(dryRun!==true
|
|
73
|
+
* - `reject` —— ① 请求实盘(dryRun!==true)而未获实盘授权:工具返回结构化
|
|
77
74
|
* 拒绝(不抛异常,模型可读到明确原因与出路);
|
|
78
75
|
* - `simulate` —— ② dryRun=true(显式、缺省,或被插件 config.dryRun 强制):
|
|
79
76
|
* 返回 DRY-RUN 模拟成交回执;
|
|
80
|
-
* - `live` —— ③ dryRun=false
|
|
77
|
+
* - `live` —— ③ dryRun=false 且实盘已获授权:本切片无签名下单能力,
|
|
81
78
|
* 工具抛 TRADING_NOT_IMPLEMENTED 结构化错误。
|
|
82
79
|
*
|
|
83
80
|
* 优先级说明:config.dryRun=true 是「强制模拟」开关,只影响 ②/③ 的归类;
|
|
84
|
-
* 显式 dryRun=false 的实盘意图在
|
|
81
|
+
* 显式 dryRun=false 的实盘意图在 未获实盘授权时仍走 ① 明确拒绝,
|
|
85
82
|
* 不做静默降级(拒绝语义优先于强制模拟,调用方必须知道实盘意图被拒)。
|
|
86
83
|
*/
|
|
87
84
|
type OrderGateVerdict = {
|
|
@@ -107,7 +104,7 @@ declare function buildDryRunReceipt(args: PlaceOrderArgs, marketData: Pick<Marke
|
|
|
107
104
|
interface PlaceOrderToolDeps {
|
|
108
105
|
/** 行情服务( dry-run 回执的市价参照),按接口取用,不直连 REST。 */
|
|
109
106
|
readonly marketData: Pick<MarketDataService, 'getTicker'>;
|
|
110
|
-
/** 插件配置(dryRun 强制模拟 / liveTrading
|
|
107
|
+
/** 插件配置(dryRun 强制模拟 / liveTrading 非权威镜像——实盘授权由 @dshtrading/authority 的人工签署平面裁定)。 */
|
|
111
108
|
readonly config: Config;
|
|
112
109
|
}
|
|
113
110
|
/**
|
|
@@ -132,4 +129,4 @@ interface MarketRouterLike {
|
|
|
132
129
|
declare function routeAllows(ctx: Context, config: Config, market: string): boolean;
|
|
133
130
|
declare function apply(ctx: Context, config: Config): void;
|
|
134
131
|
//#endregion
|
|
135
|
-
export { BinanceMarketDataService, BinanceRestClient, BinanceRestOptions, Config, DryRunReference, INTERVAL_VOCABULARY, MarketRouterLike, OrderGateVerdict, PlaceOrderArgs, PlaceOrderToolDeps, ROUTER_PROVIDER, SubscribeTickerOptions, TRADING_CRYPTO_MARKET_DATA_KEY, TradingServiceError, apply, buildDryRunReceipt, createPlaceOrderTool, evaluateOrderGate, inject, name, normalizeBinanceFuturesSymbol, routeAllows };
|
|
132
|
+
export { BinanceMarketDataService, BinanceRestClient, BinanceRestOptions, Config, DryRunReference, INTERVAL_VOCABULARY, MarketRouterLike, OrderGateVerdict, PlaceOrderArgs, PlaceOrderToolDeps, ROUTER_PROVIDER, SubscribeTickerOptions, TRADING_CRYPTO_MARKET_DATA_KEY, TradingServiceError, apply, binanceAssetClassOf, binanceContractOf, buildDryRunReceipt, createPlaceOrderTool, evaluateOrderGate, inject, name, normalizeBinanceFuturesSymbol, resolveBinanceInstrument, routeAllows };
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import { BinanceRestClient, INTERVAL_VOCABULARY, TradingServiceError, normalizeBinanceFuturesSymbol } from "./rest.js";
|
|
1
|
+
import { BinanceRestClient, INTERVAL_VOCABULARY, TradingServiceError, binanceAssetClassOf, binanceContractOf, normalizeBinanceFuturesSymbol, resolveBinanceInstrument } from "./rest.js";
|
|
2
|
+
import { liveTradingEnabled } from "@dshtrading/authority";
|
|
2
3
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
4
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
5
|
import Schema from "@deepseek-ai/schemastery";
|
|
5
6
|
import { createGetIndicatorsTool } from "@dshtrading/indicators/tool";
|
|
7
|
+
import { instrumentFormOf } from "@dshtrading/api";
|
|
6
8
|
//#region src/index.ts
|
|
7
9
|
/**
|
|
10
|
+
* Binance 连接器插件(dsh-trading crypto 切片):真实 MarketDataService。
|
|
11
|
+
*
|
|
12
|
+
* 能力三角色:
|
|
13
|
+
* - 声明:ctx 键 `tradingCryptoMarketData` 由 @dshtrading/api 的模块增强统一声明;
|
|
14
|
+
* - provide:BinanceMarketDataService(Service 基类,随插件 fiber 自动注销);
|
|
15
|
+
* - inject:工具经 ctx.inject 等待服务就绪后注册,一律经服务执行,不直连 REST。
|
|
16
|
+
*
|
|
17
|
+
* REST 数据面见 ./rest.ts(可独立单测/脚本消费);错误词汇映射 @dshtrading/api
|
|
18
|
+
* 的 TradingErrorCode(TradingServiceError.code)。
|
|
19
|
+
*
|
|
20
|
+
* @module @dshtrading/connector-binance
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
8
23
|
* 互斥激活的注册面(与 connector-okx 的 registerTool 同款,2026-08-31 补齐):
|
|
9
24
|
* dsh-tools 对同名重复注册直接抛错,互斥纪律下冲突降级为「先到先得 + warn」。
|
|
10
25
|
* 没有这层时,inject 回调就绪顺序不定会让同名工具(含 crypto_get_indicators)
|
|
@@ -135,18 +150,19 @@ var BinanceMarketDataService = class extends Service {
|
|
|
135
150
|
};
|
|
136
151
|
function evaluateOrderGate(config, args) {
|
|
137
152
|
const requestedDryRun = args.dryRun ?? true;
|
|
138
|
-
if (!requestedDryRun && !config.liveTrading) return {
|
|
153
|
+
if (!requestedDryRun && !liveTradingEnabled(config.liveTrading)) return {
|
|
139
154
|
action: "reject",
|
|
140
155
|
code: "TRADING_LIVE_TRADING_DISABLED",
|
|
141
|
-
message: `crypto_place_order rejected: the call requests real execution (dryRun=${String(args.dryRun)}) but live trading is disabled (
|
|
156
|
+
message: `crypto_place_order rejected: the call requests real execution (dryRun=${String(args.dryRun)}) but live trading is disabled (no signed live-trading grant from the authority plane). Ask the operator to sign a live-trading grant (packages/authority/bin/sign-live-trading.mjs) after confirmation, or keep dryRun=true for a simulated fill.`
|
|
142
157
|
};
|
|
143
158
|
if (requestedDryRun || config.dryRun) return { action: "simulate" };
|
|
144
159
|
return { action: "live" };
|
|
145
160
|
}
|
|
146
161
|
/** Binance 现货符号形如 BTCUSDT / ETHUSDT:大写字母数字(kit 同款词汇)。 */
|
|
147
162
|
const SPOT_SYMBOL_PATTERN = /^[A-Z0-9]{4,20}$/;
|
|
148
|
-
/**
|
|
163
|
+
/** 参数校验(模型调用错误词汇:合约输入必须显式拒绝,不能落到现货端点)。 */
|
|
149
164
|
function validatePlaceOrderArgs(args) {
|
|
165
|
+
if (instrumentFormOf(args.symbol) === "perp") throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `crypto_place_order: ${args.symbol} is a perpetual — Binance contract order placement is not implemented in this connector (market data only); use a spot symbol here or route crypto trades to the OKX connector. This path never falls back to the spot endpoint.`);
|
|
150
166
|
if (!SPOT_SYMBOL_PATTERN.test(args.symbol)) throw new Error(`crypto_place_order: invalid symbol ${JSON.stringify(args.symbol)} — expected an uppercase Binance symbol like BTCUSDT`);
|
|
151
167
|
if (args.side !== "BUY" && args.side !== "SELL") throw new Error(`crypto_place_order: invalid side ${JSON.stringify(args.side)} — expected BUY or SELL`);
|
|
152
168
|
if (args.type !== "MARKET" && args.type !== "LIMIT") throw new Error(`crypto_place_order: invalid type ${JSON.stringify(args.type)} — expected MARKET or LIMIT`);
|
|
@@ -202,7 +218,7 @@ async function buildDryRunReceipt(args, marketData) {
|
|
|
202
218
|
function createPlaceOrderTool(deps) {
|
|
203
219
|
return defineTool({
|
|
204
220
|
name: "crypto_place_order",
|
|
205
|
-
description: "Place a Binance
|
|
221
|
+
description: "Place a Binance SPOT order, or simulate one. dryRun defaults to true and returns a DRY-RUN simulated fill receipt with the current market price as reference. Real execution (dryRun=false) requires a signed live-trading grant plus user approval, and is not implemented yet in this slice. Perpetual (-SWAP) symbols are rejected with TRADING_UNSUPPORTED_SYMBOL — Binance contract order placement is not implemented; only contract market data is available.",
|
|
206
222
|
parameters: {
|
|
207
223
|
symbol: {
|
|
208
224
|
type: "string",
|
|
@@ -232,7 +248,7 @@ function createPlaceOrderTool(deps) {
|
|
|
232
248
|
},
|
|
233
249
|
dryRun: {
|
|
234
250
|
type: "boolean",
|
|
235
|
-
description: "true (default) = simulate only and return a DRY-RUN receipt; false = request real execution (gated by
|
|
251
|
+
description: "true (default) = simulate only and return a DRY-RUN receipt; false = request real execution (gated by the signed live-trading authority and user approval)",
|
|
236
252
|
default: true
|
|
237
253
|
}
|
|
238
254
|
},
|
|
@@ -356,4 +372,4 @@ function apply(ctx, config) {
|
|
|
356
372
|
});
|
|
357
373
|
}
|
|
358
374
|
//#endregion
|
|
359
|
-
export { BinanceMarketDataService, BinanceRestClient, Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CRYPTO_MARKET_DATA_KEY, TradingServiceError, apply, buildDryRunReceipt, createPlaceOrderTool, evaluateOrderGate, inject, name, normalizeBinanceFuturesSymbol, routeAllows };
|
|
375
|
+
export { BinanceMarketDataService, BinanceRestClient, Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CRYPTO_MARKET_DATA_KEY, TradingServiceError, apply, binanceAssetClassOf, binanceContractOf, buildDryRunReceipt, createPlaceOrderTool, evaluateOrderGate, inject, name, normalizeBinanceFuturesSymbol, resolveBinanceInstrument, routeAllows };
|
package/lib/rest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DerivativesPoint, Interval, Kline, Orderbook, Ticker, TradeTick, TradingErrorCode } from "@dshtrading/api";
|
|
1
|
+
import { DerivativesPoint, InstrumentAssetClass, InstrumentContract, InstrumentForm, InstrumentRef, Interval, Kline, Orderbook, Ticker, TradeTick, TradingErrorCode } from "@dshtrading/api";
|
|
2
2
|
//#region src/rest.d.ts
|
|
3
3
|
/** api 包 TradingError 契约的运行时 Error 实现。 */
|
|
4
4
|
declare class TradingServiceError extends Error {
|
|
@@ -22,25 +22,59 @@ declare const INTERVAL_VOCABULARY: readonly string[];
|
|
|
22
22
|
* OKX 原生 SWAP 形一律归一到 fapi 词汇——BTCUSDT / BTCUSDT-SWAP / BTC-USDT-SWAP → BTCUSDT。
|
|
23
23
|
*/
|
|
24
24
|
declare function normalizeBinanceFuturesSymbol(raw: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* 输入符号 → 形态 + 交易所词汇(2026-10-08 加密永续落地)。
|
|
27
|
+
*
|
|
28
|
+
* Binance 现货与永续在交易所侧**同形**(都叫 `BTCUSDT`),形态只能由规范后缀
|
|
29
|
+
* `-SWAP` 裁决——单一实现是 @dshtrading/api 的 `instrumentFormOf`,这里不另写
|
|
30
|
+
* 一份 endsWith。永续剥掉后缀后复用既有衍生品归一(同时容忍 OKX 原生形
|
|
31
|
+
* `BTC-USDT-SWAP`);现货保持原样交给交易所判存在性(未知符号仍报 -1121)。
|
|
32
|
+
*/
|
|
33
|
+
declare function resolveBinanceInstrument(symbol: string): {
|
|
34
|
+
form: InstrumentForm;
|
|
35
|
+
venueSymbol: string;
|
|
36
|
+
};
|
|
37
|
+
/** underlyingType(含 underlyingSubType 逐项兜底)→ 资产类别;未登记/缺失 = undefined。 */
|
|
38
|
+
declare function binanceAssetClassOf(underlyingType: unknown, underlyingSubType: unknown): InstrumentAssetClass | undefined;
|
|
39
|
+
/** USDT-M 合约规格 → InstrumentContract(交易所元数据原样透传;字段缺失即缺省)。 */
|
|
40
|
+
declare function binanceContractOf(row: {
|
|
41
|
+
readonly contractSize?: unknown;
|
|
42
|
+
readonly marginAsset?: unknown;
|
|
43
|
+
readonly filters?: unknown;
|
|
44
|
+
}): InstrumentContract | undefined;
|
|
25
45
|
declare class BinanceRestClient {
|
|
26
46
|
#private;
|
|
27
47
|
constructor(options?: BinanceRestOptions);
|
|
28
48
|
/**
|
|
29
|
-
*
|
|
49
|
+
* 最新行情(2026-10-08 形态分流):现货 /api/v3/ticker/24hr + bookTicker;
|
|
50
|
+
* 永续(`-SWAP` 后缀)走 USDT-M /fapi/v1/ticker/24hr + bookTicker。
|
|
51
|
+
* 两条路径各自报结构化错误——绝不用现货报价冒充合约价(降级纪律)。
|
|
30
52
|
*/
|
|
31
53
|
getTicker(symbol: string): Promise<Ticker>;
|
|
54
|
+
/** K 线(2026-10-08 形态分流):现货 /api/v3/klines;永续 /fapi/v1/klines,行结构同形。 */
|
|
32
55
|
getKlines(symbol: string, interval: Interval, limit?: number): Promise<Kline[]>;
|
|
33
56
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
57
|
+
* 全部可交易标的名册 = 现货 ∪ USDT-M 永续(2026-10-08 形态维度落地,Issue #15 扩展)。
|
|
58
|
+
*
|
|
59
|
+
* - 现货:GET /api/v3/exchangeInfo,`status=TRADING` → `BTCUSDT`(form=spot,
|
|
60
|
+
* assetClass=crypto:Binance 现货名册无 TradFi 条目)。
|
|
61
|
+
* - 永续:GET /fapi/v1/exchangeInfo,`contractType` ∈ {PERPETUAL, TRADIFI_PERPETUAL}
|
|
62
|
+
* 且 `status=TRADING` → `BTCUSDT-SWAP`(form=perp,与现货成对);assetClass 取
|
|
63
|
+
* underlyingType/underlyingSubType(取不到留空),contract 取 contractSize/
|
|
64
|
+
* filters/marginAsset。TradFi 永续的字面量是 TRADIFI_PERPETUAL(实测 217 行),
|
|
65
|
+
* 只认 PERPETUAL 会把股票/大宗/外汇合约整批漏掉。
|
|
66
|
+
*
|
|
67
|
+
* 两半都是名册契约的一部分:任一半失败即抛结构化错误,**不返回半份名册**
|
|
68
|
+
* (静默半份会让检索排序与可用性判断失真,调用方按错误决定自己的兜底目录)。
|
|
69
|
+
*/
|
|
70
|
+
listInstruments(): Promise<InstrumentRef[]>;
|
|
71
|
+
/**
|
|
72
|
+
* 盘口快照:GET /api/v3/depth(现货)或 /fapi/v1/depth(永续),limit=20 档;
|
|
73
|
+
* bids 降序 / asks 升序,Binance 原生序。永续不再回落现货 depth(合约盘口
|
|
74
|
+
* 落到现货端点等于把现货深度伪装成合约深度)。
|
|
36
75
|
*/
|
|
37
|
-
listInstruments(): Promise<Array<{
|
|
38
|
-
symbol: string;
|
|
39
|
-
name?: string;
|
|
40
|
-
}>>;
|
|
41
|
-
/** 盘口快照:GET /api/v3/depth(limit=20 档;bids 降序 / asks 升序,Binance 原生序)。 */
|
|
42
76
|
getOrderbook(symbol: string): Promise<Orderbook>;
|
|
43
|
-
/** 最近逐笔成交:GET /api/v3/trades
|
|
77
|
+
/** 最近逐笔成交:GET /api/v3/trades(现货)或 /fapi/v1/trades(永续),时间升序。 */
|
|
44
78
|
getRecentTrades(symbol: string, limit?: number): Promise<TradeTick[]>;
|
|
45
79
|
/** 未平仓合约量:GET /fapi/v1/openInterest(openInterest 以 base 币计,time=快照 ms)。 */
|
|
46
80
|
getFuturesOpenInterest(symbol: string): Promise<{
|
|
@@ -77,4 +111,4 @@ declare class BinanceRestClient {
|
|
|
77
111
|
getFuturesOpenInterestHistory(symbol: string, limit?: number): Promise<DerivativesPoint[]>;
|
|
78
112
|
}
|
|
79
113
|
//#endregion
|
|
80
|
-
export { BinanceRestClient, BinanceRestOptions, INTERVAL_VOCABULARY, TradingServiceError, normalizeBinanceFuturesSymbol };
|
|
114
|
+
export { BinanceRestClient, BinanceRestOptions, INTERVAL_VOCABULARY, TradingServiceError, binanceAssetClassOf, binanceContractOf, normalizeBinanceFuturesSymbol, resolveBinanceInstrument };
|
package/lib/rest.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SWAP_SYMBOL_SUFFIX, instrumentFormOf } from "@dshtrading/api";
|
|
1
2
|
//#region src/rest.ts
|
|
2
3
|
/** api 包 TradingError 契约的运行时 Error 实现。 */
|
|
3
4
|
var TradingServiceError = class extends Error {
|
|
@@ -48,6 +49,28 @@ function normalizeBinanceFuturesSymbol(raw) {
|
|
|
48
49
|
const clean = requireSymbol(raw).replace(/[-_]/g, "");
|
|
49
50
|
return clean.endsWith("SWAP") ? clean.slice(0, -4) : clean;
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 输入符号 → 形态 + 交易所词汇(2026-10-08 加密永续落地)。
|
|
54
|
+
*
|
|
55
|
+
* Binance 现货与永续在交易所侧**同形**(都叫 `BTCUSDT`),形态只能由规范后缀
|
|
56
|
+
* `-SWAP` 裁决——单一实现是 @dshtrading/api 的 `instrumentFormOf`,这里不另写
|
|
57
|
+
* 一份 endsWith。永续剥掉后缀后复用既有衍生品归一(同时容忍 OKX 原生形
|
|
58
|
+
* `BTC-USDT-SWAP`);现货保持原样交给交易所判存在性(未知符号仍报 -1121)。
|
|
59
|
+
*/
|
|
60
|
+
function resolveBinanceInstrument(symbol) {
|
|
61
|
+
const upper = requireSymbol(symbol);
|
|
62
|
+
const form = instrumentFormOf(upper);
|
|
63
|
+
if (form === "spot") return {
|
|
64
|
+
form,
|
|
65
|
+
venueSymbol: upper
|
|
66
|
+
};
|
|
67
|
+
const venueSymbol = normalizeBinanceFuturesSymbol(upper);
|
|
68
|
+
if (venueSymbol === "") throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `Binance: ${JSON.stringify(symbol)} carries the ${SWAP_SYMBOL_SUFFIX} suffix but has no base symbol`);
|
|
69
|
+
return {
|
|
70
|
+
form,
|
|
71
|
+
venueSymbol
|
|
72
|
+
};
|
|
73
|
+
}
|
|
51
74
|
/** Binance 返回数值均为字符串,宽松转 number(非有限值返回 undefined)。 */
|
|
52
75
|
function num(value) {
|
|
53
76
|
if (value === "") return void 0;
|
|
@@ -136,6 +159,63 @@ function parseTradeRow(row, symbol) {
|
|
|
136
159
|
timestamp: num(d.time) ?? Date.now()
|
|
137
160
|
};
|
|
138
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Binance 合约 underlying 元数据 → 资产类别标签。
|
|
164
|
+
*
|
|
165
|
+
* 只登记 Binance 公开字段的**实测取值**;未登记一律 undefined(留空),禁止按符号猜
|
|
166
|
+
* (硬不变量 #2:TradFi 归属只信交易所元数据)。
|
|
167
|
+
*
|
|
168
|
+
* 2026-10-08 可达环境实测取值(924 行 USDT-M exchangeInfo,见
|
|
169
|
+
* spikes/impl-crypto-perp-tradfi/EVIDENCE-reverify-2026-10-08.md):
|
|
170
|
+
* COIN 704 / EQUITY 179 / HK_EQUITY 15 / COMMODITY 8 / KR_EQUITY 8 / PREMARKET 4 /
|
|
171
|
+
* INDEX 3 / CN_EQUITY 2 / FX 1。
|
|
172
|
+
* - PREMARKET 是 Pre-IPO 股票合约(OPENAI/ANTHROPIC/MOONSHOT/OURA,underlyingSubType
|
|
173
|
+
* ["Pre-IPO","TradFi"])⇒ 归 equity;枚举里没有 premarket 成员,投影由字面量裁决而非符号。
|
|
174
|
+
* - FX(USDBRLUSDT)**故意不登记**:枚举没有外汇成员,硬塞进 equity/commodity 就是错标,
|
|
175
|
+
* 按「未登记即留空」兜底(漏标而非错标)。要不要加 FX 成员属契约层决定,不在本连接器内定。
|
|
176
|
+
*/
|
|
177
|
+
const BINANCE_UNDERLYING_ASSET_CLASS = {
|
|
178
|
+
COIN: "crypto",
|
|
179
|
+
CRYPTO: "crypto",
|
|
180
|
+
STOCK: "equity",
|
|
181
|
+
EQUITY: "equity",
|
|
182
|
+
HK_EQUITY: "equity",
|
|
183
|
+
KR_EQUITY: "equity",
|
|
184
|
+
CN_EQUITY: "equity",
|
|
185
|
+
PREMARKET: "equity",
|
|
186
|
+
INDEX: "index",
|
|
187
|
+
COMMODITY: "commodity"
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* USDT-M 名册里的永续字面量:加密永续 `PERPETUAL`、TradFi 永续 `TRADIFI_PERPETUAL`
|
|
191
|
+
* (2026-10-08 实测 703 / 217)。只按 `PERPETUAL` 过滤会把全部 TradFi 永续整批漏掉。
|
|
192
|
+
*/
|
|
193
|
+
const BINANCE_PERPETUAL_CONTRACT_TYPES = /* @__PURE__ */ new Set(["PERPETUAL", "TRADIFI_PERPETUAL"]);
|
|
194
|
+
/** underlyingType(含 underlyingSubType 逐项兜底)→ 资产类别;未登记/缺失 = undefined。 */
|
|
195
|
+
function binanceAssetClassOf(underlyingType, underlyingSubType) {
|
|
196
|
+
const candidates = [underlyingType];
|
|
197
|
+
if (Array.isArray(underlyingSubType)) candidates.push(...underlyingSubType);
|
|
198
|
+
for (const candidate of candidates) {
|
|
199
|
+
if (typeof candidate !== "string") continue;
|
|
200
|
+
const mapped = BINANCE_UNDERLYING_ASSET_CLASS[candidate.trim().toUpperCase()];
|
|
201
|
+
if (mapped !== void 0) return mapped;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** USDT-M 合约规格 → InstrumentContract(交易所元数据原样透传;字段缺失即缺省)。 */
|
|
205
|
+
function binanceContractOf(row) {
|
|
206
|
+
const filters = Array.isArray(row.filters) ? row.filters : [];
|
|
207
|
+
const multiplier = num(row.contractSize);
|
|
208
|
+
const tickSize = num(filters.find((f) => f.filterType === "PRICE_FILTER")?.tickSize);
|
|
209
|
+
const lotSize = num(filters.find((f) => f.filterType === "LOT_SIZE")?.stepSize);
|
|
210
|
+
const settleCcy = typeof row.marginAsset === "string" && row.marginAsset !== "" ? row.marginAsset : void 0;
|
|
211
|
+
if (multiplier === void 0 && tickSize === void 0 && lotSize === void 0 && settleCcy === void 0) return;
|
|
212
|
+
return {
|
|
213
|
+
...multiplier !== void 0 ? { multiplier } : {},
|
|
214
|
+
...tickSize !== void 0 ? { tickSize } : {},
|
|
215
|
+
...lotSize !== void 0 ? { lotSize } : {},
|
|
216
|
+
...settleCcy !== void 0 ? { settleCcy } : {}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
139
219
|
var BinanceRestClient = class {
|
|
140
220
|
#baseUrl;
|
|
141
221
|
#fapiBaseUrl;
|
|
@@ -169,22 +249,28 @@ var BinanceRestClient = class {
|
|
|
169
249
|
}
|
|
170
250
|
}
|
|
171
251
|
/**
|
|
172
|
-
*
|
|
252
|
+
* 最新行情(2026-10-08 形态分流):现货 /api/v3/ticker/24hr + bookTicker;
|
|
253
|
+
* 永续(`-SWAP` 后缀)走 USDT-M /fapi/v1/ticker/24hr + bookTicker。
|
|
254
|
+
* 两条路径各自报结构化错误——绝不用现货报价冒充合约价(降级纪律)。
|
|
173
255
|
*/
|
|
174
256
|
async getTicker(symbol) {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
257
|
+
const { form, venueSymbol } = resolveBinanceInstrument(symbol);
|
|
258
|
+
const perp = form === "perp";
|
|
259
|
+
const prefix = perp ? "/fapi/v1/ticker" : "/api/v3/ticker";
|
|
260
|
+
const base = perp ? this.#fapiBaseUrl : this.#baseUrl;
|
|
261
|
+
const [day, book] = await Promise.all([this.#request(`${prefix}/24hr`, { symbol: venueSymbol }, base), this.#request(`${prefix}/bookTicker`, { symbol: venueSymbol }, base)]);
|
|
177
262
|
const d = day;
|
|
178
263
|
const b = book;
|
|
179
264
|
const price = num(d.lastPrice);
|
|
180
|
-
if (price === void 0) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance ticker for ${
|
|
265
|
+
if (price === void 0) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance ticker for ${venueSymbol}: missing/invalid lastPrice`);
|
|
181
266
|
const bid = num(b.bidPrice);
|
|
182
267
|
const ask = num(b.askPrice);
|
|
183
268
|
const volume = num(d.volume);
|
|
184
269
|
const prevClose = num(d.prevClosePrice);
|
|
185
270
|
const changePercent = num(d.priceChangePercent);
|
|
271
|
+
const resolvedSymbol = typeof d.symbol === "string" && d.symbol ? d.symbol : venueSymbol;
|
|
186
272
|
return {
|
|
187
|
-
symbol:
|
|
273
|
+
symbol: perp ? `${resolvedSymbol}${SWAP_SYMBOL_SUFFIX}` : resolvedSymbol,
|
|
188
274
|
price,
|
|
189
275
|
timestamp: Date.now(),
|
|
190
276
|
...bid !== void 0 ? { bid } : {},
|
|
@@ -194,53 +280,93 @@ var BinanceRestClient = class {
|
|
|
194
280
|
...changePercent !== void 0 ? { changePercent } : {}
|
|
195
281
|
};
|
|
196
282
|
}
|
|
283
|
+
/** K 线(2026-10-08 形态分流):现货 /api/v3/klines;永续 /fapi/v1/klines,行结构同形。 */
|
|
197
284
|
async getKlines(symbol, interval, limit = 100) {
|
|
198
|
-
const
|
|
285
|
+
const { form, venueSymbol } = resolveBinanceInstrument(symbol);
|
|
286
|
+
const perp = form === "perp";
|
|
199
287
|
if (!isInterval(interval)) throw new TradingServiceError("TRADING_UNSUPPORTED_INTERVAL", `Binance klines: unsupported interval ${String(interval)}`);
|
|
200
288
|
if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance klines: limit must be an integer within 1..1000, got ${limit}`);
|
|
201
|
-
const body = await this.#request("/api/v3/klines", {
|
|
202
|
-
symbol:
|
|
289
|
+
const body = await this.#request(perp ? "/fapi/v1/klines" : "/api/v3/klines", {
|
|
290
|
+
symbol: venueSymbol,
|
|
203
291
|
interval,
|
|
204
292
|
limit: String(limit)
|
|
205
|
-
});
|
|
206
|
-
if (!Array.isArray(body)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance klines for ${
|
|
207
|
-
return body.map((row) => parseKlineRow(row,
|
|
293
|
+
}, perp ? this.#fapiBaseUrl : this.#baseUrl);
|
|
294
|
+
if (!Array.isArray(body)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance klines for ${venueSymbol}: unexpected response shape`);
|
|
295
|
+
return body.map((row) => parseKlineRow(row, venueSymbol));
|
|
208
296
|
}
|
|
209
297
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
298
|
+
* 全部可交易标的名册 = 现货 ∪ USDT-M 永续(2026-10-08 形态维度落地,Issue #15 扩展)。
|
|
299
|
+
*
|
|
300
|
+
* - 现货:GET /api/v3/exchangeInfo,`status=TRADING` → `BTCUSDT`(form=spot,
|
|
301
|
+
* assetClass=crypto:Binance 现货名册无 TradFi 条目)。
|
|
302
|
+
* - 永续:GET /fapi/v1/exchangeInfo,`contractType` ∈ {PERPETUAL, TRADIFI_PERPETUAL}
|
|
303
|
+
* 且 `status=TRADING` → `BTCUSDT-SWAP`(form=perp,与现货成对);assetClass 取
|
|
304
|
+
* underlyingType/underlyingSubType(取不到留空),contract 取 contractSize/
|
|
305
|
+
* filters/marginAsset。TradFi 永续的字面量是 TRADIFI_PERPETUAL(实测 217 行),
|
|
306
|
+
* 只认 PERPETUAL 会把股票/大宗/外汇合约整批漏掉。
|
|
307
|
+
*
|
|
308
|
+
* 两半都是名册契约的一部分:任一半失败即抛结构化错误,**不返回半份名册**
|
|
309
|
+
* (静默半份会让检索排序与可用性判断失真,调用方按错误决定自己的兜底目录)。
|
|
212
310
|
*/
|
|
213
311
|
async listInstruments() {
|
|
214
|
-
const
|
|
215
|
-
|
|
312
|
+
const [spotBody, futuresBody] = await Promise.all([this.#request("/api/v3/exchangeInfo", {}), this.#request("/fapi/v1/exchangeInfo", {}, this.#fapiBaseUrl)]);
|
|
313
|
+
const spotSymbols = spotBody.symbols;
|
|
314
|
+
if (!Array.isArray(spotSymbols)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", "Binance exchangeInfo: invalid response shape");
|
|
315
|
+
const futuresSymbols = futuresBody.symbols;
|
|
316
|
+
if (!Array.isArray(futuresSymbols)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", "Binance futures exchangeInfo: invalid response shape");
|
|
216
317
|
const result = [];
|
|
217
|
-
for (const
|
|
218
|
-
const
|
|
318
|
+
for (const raw of spotSymbols) {
|
|
319
|
+
const item = raw;
|
|
320
|
+
if (item?.status !== "TRADING" || typeof item.symbol !== "string" || item.symbol === "") continue;
|
|
321
|
+
const name = typeof item.baseAsset === "string" && item.baseAsset !== "" && typeof item.quoteAsset === "string" && item.quoteAsset !== "" ? `${item.baseAsset}/${item.quoteAsset}` : void 0;
|
|
219
322
|
result.push({
|
|
220
323
|
symbol: item.symbol,
|
|
221
|
-
...name ? { name } : {}
|
|
324
|
+
...name !== void 0 ? { name } : {},
|
|
325
|
+
form: "spot",
|
|
326
|
+
assetClass: "crypto"
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
for (const raw of futuresSymbols) {
|
|
330
|
+
const item = raw;
|
|
331
|
+
if (typeof item?.contractType !== "string" || !BINANCE_PERPETUAL_CONTRACT_TYPES.has(item.contractType) || item.status !== "TRADING" || typeof item.symbol !== "string" || item.symbol === "") continue;
|
|
332
|
+
const name = typeof item.baseAsset === "string" && item.baseAsset !== "" && typeof item.quoteAsset === "string" && item.quoteAsset !== "" ? `${item.baseAsset}/${item.quoteAsset}` : void 0;
|
|
333
|
+
const assetClass = binanceAssetClassOf(item.underlyingType, item.underlyingSubType);
|
|
334
|
+
const contract = binanceContractOf(item);
|
|
335
|
+
result.push({
|
|
336
|
+
symbol: `${item.symbol}${SWAP_SYMBOL_SUFFIX}`,
|
|
337
|
+
...name !== void 0 ? { name } : {},
|
|
338
|
+
form: "perp",
|
|
339
|
+
...assetClass !== void 0 ? { assetClass } : {},
|
|
340
|
+
...contract !== void 0 ? { contract } : {}
|
|
222
341
|
});
|
|
223
342
|
}
|
|
224
343
|
return result;
|
|
225
344
|
}
|
|
226
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* 盘口快照:GET /api/v3/depth(现货)或 /fapi/v1/depth(永续),limit=20 档;
|
|
347
|
+
* bids 降序 / asks 升序,Binance 原生序。永续不再回落现货 depth(合约盘口
|
|
348
|
+
* 落到现货端点等于把现货深度伪装成合约深度)。
|
|
349
|
+
*/
|
|
227
350
|
async getOrderbook(symbol) {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
351
|
+
const { form, venueSymbol } = resolveBinanceInstrument(symbol);
|
|
352
|
+
const perp = form === "perp";
|
|
353
|
+
return parseDepthBody(await this.#request(perp ? "/fapi/v1/depth" : "/api/v3/depth", {
|
|
354
|
+
symbol: venueSymbol,
|
|
231
355
|
limit: "20"
|
|
232
|
-
}),
|
|
356
|
+
}, perp ? this.#fapiBaseUrl : this.#baseUrl), perp ? `${venueSymbol}${SWAP_SYMBOL_SUFFIX}` : venueSymbol);
|
|
233
357
|
}
|
|
234
|
-
/** 最近逐笔成交:GET /api/v3/trades
|
|
358
|
+
/** 最近逐笔成交:GET /api/v3/trades(现货)或 /fapi/v1/trades(永续),时间升序。 */
|
|
235
359
|
async getRecentTrades(symbol, limit = 50) {
|
|
236
|
-
const
|
|
360
|
+
const { form, venueSymbol } = resolveBinanceInstrument(symbol);
|
|
361
|
+
const perp = form === "perp";
|
|
362
|
+
const outputSymbol = perp ? `${venueSymbol}${SWAP_SYMBOL_SUFFIX}` : venueSymbol;
|
|
237
363
|
const capped = Math.max(1, Math.min(Math.floor(limit) || 50, 100));
|
|
238
|
-
const body = await this.#request("/api/v3/trades", {
|
|
239
|
-
symbol:
|
|
364
|
+
const body = await this.#request(perp ? "/fapi/v1/trades" : "/api/v3/trades", {
|
|
365
|
+
symbol: venueSymbol,
|
|
240
366
|
limit: String(capped)
|
|
241
|
-
});
|
|
242
|
-
if (!Array.isArray(body)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance trades for ${
|
|
243
|
-
return body.map((row) => parseTradeRow(row,
|
|
367
|
+
}, perp ? this.#fapiBaseUrl : this.#baseUrl);
|
|
368
|
+
if (!Array.isArray(body)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Binance trades for ${outputSymbol}: unexpected response shape`);
|
|
369
|
+
return body.map((row) => parseTradeRow(row, outputSymbol));
|
|
244
370
|
}
|
|
245
371
|
/** 未平仓合约量:GET /fapi/v1/openInterest(openInterest 以 base 币计,time=快照 ms)。 */
|
|
246
372
|
async getFuturesOpenInterest(symbol) {
|
|
@@ -363,4 +489,4 @@ var BinanceRestClient = class {
|
|
|
363
489
|
}
|
|
364
490
|
};
|
|
365
491
|
//#endregion
|
|
366
|
-
export { BinanceRestClient, INTERVAL_VOCABULARY, TradingServiceError, normalizeBinanceFuturesSymbol };
|
|
492
|
+
export { BinanceRestClient, INTERVAL_VOCABULARY, TradingServiceError, binanceAssetClassOf, binanceContractOf, normalizeBinanceFuturesSymbol, resolveBinanceInstrument };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-binance",
|
|
3
3
|
"description": "Binance market connector plugin for dsh-trading: public market data over REST+WS implementing the @dshtrading/api contracts",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
],
|
|
22
22
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dshtrading/
|
|
25
|
-
"@dshtrading/indicators": "^0.
|
|
24
|
+
"@dshtrading/authority": "^0.6.0",
|
|
25
|
+
"@dshtrading/indicators": "^0.6.0",
|
|
26
|
+
"@dshtrading/api": "^0.6.0"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
28
29
|
"@deepseek-ai/cordis": ">=4.0.0",
|
|
29
|
-
"@deepseek-ai/dsh-tools": ">=0.
|
|
30
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2",
|
|
30
31
|
"@deepseek-ai/schemastery": ">=3.18.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|