@dshtrading/connector-bybit 0.5.0 → 0.6.1
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/README.md +1 -1
- package/lib/index.d.ts +11 -4
- package/lib/index.js +23 -9
- package/lib/rest.d.ts +37 -2
- package/lib/rest.js +167 -19
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
node scripts/new-connector.mjs --slug bybit --title Bybit --market crypto
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
生成后按 [docs/connector-playbook.md](../../docs/connector-playbook.md) 的填写顺序
|
|
10
|
+
生成后按 [docs/guides/connector-playbook.md](../../docs/guides/connector-playbook.md) 的填写顺序
|
|
11
11
|
(src/rest.ts 头部清单 7 项)实现 TODO。每个 TODO 的参照段都在 connector-okx
|
|
12
12
|
(packages/connector-okx/),它是本仓第一个真实 TradeService 连接器。
|
|
13
13
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AccountBalance, BybitRestClient, BybitRestOptions, INTERVAL_VOCABULARY, Interval, Kline, Order, Position, Ticker, TradingServiceError, normalizeCryptoSymbol, parseIntervalMs, toBybitInterval } from "./rest.js";
|
|
1
|
+
import { AccountBalance, BybitRestClient, BybitRestOptions, INTERVAL_VOCABULARY, Interval, Kline, Order, ParsedCryptoSymbol, Position, Ticker, TradingServiceError, bybitAssetClassOf, bybitCategory, normalizeCryptoSymbol, parseCryptoSymbol, parseIntervalMs, toBybitInterval } from "./rest.js";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import Schema from "@deepseek-ai/schemastery";
|
|
4
|
-
import { AccountBalance as AccountBalance$1, DerivativesData, DerivativesHistory, Disposable, Interval as Interval$1, Kline as Kline$1, MarketDataService, Order as Order$1, OrderRequest, Orderbook, Position as Position$1, Ticker as Ticker$1, TradeFill, TradeService, TradeTick } from "@dshtrading/api";
|
|
4
|
+
import { AccountBalance as AccountBalance$1, DerivativesData, DerivativesHistory, Disposable, InstrumentRef, Interval as Interval$1, Kline as Kline$1, MarketDataService, Order as Order$1, OrderRequest, Orderbook, Position as Position$1, Ticker as Ticker$1, TradeFill, TradeService, TradeTick } from "@dshtrading/api";
|
|
5
5
|
//#region src/index.d.ts
|
|
6
6
|
declare const name = "dsh-trading-crypto-connector-bybit";
|
|
7
7
|
interface Config {
|
|
@@ -20,6 +20,13 @@ declare class BybitMarketDataService extends Service implements MarketDataServic
|
|
|
20
20
|
constructor(ctx: Context, options?: BybitRestOptions, serviceName?: string);
|
|
21
21
|
getTicker(symbol: string): Promise<Ticker$1>;
|
|
22
22
|
getKlines(symbol: string, interval?: Interval$1, limit?: number): Promise<Kline$1[]>;
|
|
23
|
+
/**
|
|
24
|
+
* 标的名册(P3):现货 ∪ 线性永续;永续输出规范形 `BTCUSDT-SWAP`(form=perp)。
|
|
25
|
+
* assetClass 只由交易所 `symbolType` 字面量裁决(stock/ETF/xstocks→equity、
|
|
26
|
+
* commodity→commodity、innovation/adventure→crypto);交易所没给(空串)或未登记
|
|
27
|
+
* (forex/mstocks)即留空,不本地推断(见 rest.ts bybitAssetClassOf)。
|
|
28
|
+
*/
|
|
29
|
+
listInstruments(): Promise<InstrumentRef[]>;
|
|
23
30
|
subscribeTicker(symbol: string, cb: (ticker: Ticker$1) => void, options?: {
|
|
24
31
|
intervalMs?: number;
|
|
25
32
|
}): Disposable;
|
|
@@ -43,7 +50,7 @@ declare class BybitMarketDataService extends Service implements MarketDataServic
|
|
|
43
50
|
}
|
|
44
51
|
declare class BybitTradeService extends Service implements TradeService {
|
|
45
52
|
private readonly client;
|
|
46
|
-
/** 插件配置(服务缝闸门 P0:dryRun 强制模拟 / liveTrading
|
|
53
|
+
/** 插件配置(服务缝闸门 P0:dryRun 强制模拟 / liveTrading 非权威镜像——实盘授权由 @dshtrading/authority 的人工签署平面裁定)。 */
|
|
47
54
|
private readonly config;
|
|
48
55
|
constructor(ctx: Context, options: BybitRestOptions & {
|
|
49
56
|
config: Config;
|
|
@@ -61,4 +68,4 @@ declare const ROUTER_PROVIDER = "bybit";
|
|
|
61
68
|
declare function routeAllows(ctx: Context, config: Config, market: string): boolean;
|
|
62
69
|
declare function apply(ctx: Context, config: Config): void;
|
|
63
70
|
//#endregion
|
|
64
|
-
export { type AccountBalance, BybitMarketDataService, BybitRestClient, BybitRestOptions, BybitTradeService, Config, INTERVAL_VOCABULARY, type Interval, type Kline, type Order, type Position, ROUTER_PROVIDER, TRADING_CRYPTO_MARKET_DATA_KEY, TRADING_CRYPTO_TRADE_KEY, type Ticker, TradingServiceError, apply, name, normalizeCryptoSymbol, parseIntervalMs, routeAllows, toBybitInterval };
|
|
71
|
+
export { type AccountBalance, BybitMarketDataService, BybitRestClient, BybitRestOptions, BybitTradeService, Config, INTERVAL_VOCABULARY, type Interval, type Kline, type Order, ParsedCryptoSymbol, type Position, ROUTER_PROVIDER, TRADING_CRYPTO_MARKET_DATA_KEY, TRADING_CRYPTO_TRADE_KEY, type Ticker, TradingServiceError, apply, bybitAssetClassOf, bybitCategory, name, normalizeCryptoSymbol, parseCryptoSymbol, parseIntervalMs, routeAllows, toBybitInterval };
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { BybitRestClient, INTERVAL_VOCABULARY, TradingServiceError, normalizeCryptoSymbol, parseIntervalMs, toBybitInterval } from "./rest.js";
|
|
1
|
+
import { BybitRestClient, INTERVAL_VOCABULARY, TradingServiceError, bybitAssetClassOf, bybitCategory, normalizeCryptoSymbol, parseCryptoSymbol, parseIntervalMs, toBybitInterval } 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
|
//#region src/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* @dshtrading/connector-bybit
|
|
9
|
+
* Bybit 加密货币连接器插件(提供 tradingCryptoMarketData 与 crypto_* 工具)。
|
|
10
|
+
*/
|
|
6
11
|
const name = "dsh-trading-crypto-connector-bybit";
|
|
7
12
|
const Config = Schema.object({
|
|
8
13
|
enabled: Schema.boolean().default(true).description("是否激活 Bybit 连接器"),
|
|
@@ -26,6 +31,15 @@ var BybitMarketDataService = class extends Service {
|
|
|
26
31
|
async getKlines(symbol, interval = "1d", limit = 100) {
|
|
27
32
|
return this.client.getKlines(symbol, interval, limit);
|
|
28
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* 标的名册(P3):现货 ∪ 线性永续;永续输出规范形 `BTCUSDT-SWAP`(form=perp)。
|
|
36
|
+
* assetClass 只由交易所 `symbolType` 字面量裁决(stock/ETF/xstocks→equity、
|
|
37
|
+
* commodity→commodity、innovation/adventure→crypto);交易所没给(空串)或未登记
|
|
38
|
+
* (forex/mstocks)即留空,不本地推断(见 rest.ts bybitAssetClassOf)。
|
|
39
|
+
*/
|
|
40
|
+
async listInstruments() {
|
|
41
|
+
return this.client.listInstruments();
|
|
42
|
+
}
|
|
29
43
|
subscribeTicker(symbol, cb, options) {
|
|
30
44
|
const ms = Math.max(options?.intervalMs ?? 5e3, 1e3);
|
|
31
45
|
const tick = () => {
|
|
@@ -97,7 +111,7 @@ var BybitMarketDataService = class extends Service {
|
|
|
97
111
|
};
|
|
98
112
|
var BybitTradeService = class extends Service {
|
|
99
113
|
client;
|
|
100
|
-
/** 插件配置(服务缝闸门 P0:dryRun 强制模拟 / liveTrading
|
|
114
|
+
/** 插件配置(服务缝闸门 P0:dryRun 强制模拟 / liveTrading 非权威镜像——实盘授权由 @dshtrading/authority 的人工签署平面裁定)。 */
|
|
101
115
|
config;
|
|
102
116
|
constructor(ctx, options, serviceName = TRADING_CRYPTO_TRADE_KEY) {
|
|
103
117
|
super(ctx, serviceName);
|
|
@@ -116,7 +130,7 @@ var BybitTradeService = class extends Service {
|
|
|
116
130
|
}
|
|
117
131
|
async placeOrder(order) {
|
|
118
132
|
const requestedDryRun = order.dryRun ?? true;
|
|
119
|
-
if (!requestedDryRun && !this.config.liveTrading) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", `Bybit TradeService.placeOrder rejected: the request asks for real execution (dryRun=${String(order.dryRun)}) but
|
|
133
|
+
if (!requestedDryRun && !liveTradingEnabled(this.config.liveTrading)) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", `Bybit TradeService.placeOrder rejected: the request asks for real execution (dryRun=${String(order.dryRun)}) but the signed live-trading authority does not grant real execution (see @dshtrading/authority) — keep dryRun=true for a simulated fill.`);
|
|
120
134
|
if (requestedDryRun || this.config.dryRun) return {
|
|
121
135
|
id: `dry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
122
136
|
symbol: order.symbol,
|
|
@@ -131,7 +145,7 @@ var BybitTradeService = class extends Service {
|
|
|
131
145
|
return this.client.placeOrder(void 0, order);
|
|
132
146
|
}
|
|
133
147
|
async cancelOrder(orderId, _symbol) {
|
|
134
|
-
if (!this.config.liveTrading || this.config.dryRun) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", "Bybit TradeService.cancelOrder rejected at the service seam: cancel is a live action and requires
|
|
148
|
+
if (!liveTradingEnabled(this.config.liveTrading) || this.config.dryRun) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", "Bybit TradeService.cancelOrder rejected at the service seam: cancel is a live action and requires a signed live-trading grant (see @dshtrading/authority) with dryRun=false.");
|
|
135
149
|
return this.client.cancelOrder(void 0, orderId);
|
|
136
150
|
}
|
|
137
151
|
async getOrder(symbol, id) {
|
|
@@ -184,11 +198,11 @@ function apply(ctx, config) {
|
|
|
184
198
|
};
|
|
185
199
|
register(defineTool({
|
|
186
200
|
name: "crypto_get_ticker",
|
|
187
|
-
description: "Get the latest trade price and quote for a crypto pair via Bybit API.",
|
|
201
|
+
description: "Get the latest trade price and quote for a crypto pair via Bybit API (spot, or perpetual with the -SWAP suffix).",
|
|
188
202
|
parameters: { symbol: {
|
|
189
203
|
type: "string",
|
|
190
204
|
required: true,
|
|
191
|
-
description: "
|
|
205
|
+
description: "Pair symbol: BTCUSDT for spot, BTCUSDT-SWAP for the USDT perpetual"
|
|
192
206
|
} },
|
|
193
207
|
output: {
|
|
194
208
|
schema: { type: "string" },
|
|
@@ -203,12 +217,12 @@ function apply(ctx, config) {
|
|
|
203
217
|
}));
|
|
204
218
|
register(defineTool({
|
|
205
219
|
name: "crypto_get_klines",
|
|
206
|
-
description: "Get recent public klines for a crypto pair via Bybit API. Supports 1m/5m/15m/30m/1h/4h/1d/1w/1M.",
|
|
220
|
+
description: "Get recent public klines for a crypto pair via Bybit API (spot, or perpetual with the -SWAP suffix). Supports 1m/5m/15m/30m/1h/4h/1d/1w/1M.",
|
|
207
221
|
parameters: {
|
|
208
222
|
symbol: {
|
|
209
223
|
type: "string",
|
|
210
224
|
required: true,
|
|
211
|
-
description: "
|
|
225
|
+
description: "Pair symbol: BTCUSDT for spot, BTCUSDT-SWAP for the USDT perpetual"
|
|
212
226
|
},
|
|
213
227
|
interval: {
|
|
214
228
|
type: "string",
|
|
@@ -237,4 +251,4 @@ function apply(ctx, config) {
|
|
|
237
251
|
});
|
|
238
252
|
}
|
|
239
253
|
//#endregion
|
|
240
|
-
export { BybitMarketDataService, BybitRestClient, BybitTradeService, Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CRYPTO_MARKET_DATA_KEY, TRADING_CRYPTO_TRADE_KEY, TradingServiceError, apply, name, normalizeCryptoSymbol, parseIntervalMs, routeAllows, toBybitInterval };
|
|
254
|
+
export { BybitMarketDataService, BybitRestClient, BybitTradeService, Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CRYPTO_MARKET_DATA_KEY, TRADING_CRYPTO_TRADE_KEY, TradingServiceError, apply, bybitAssetClassOf, bybitCategory, name, normalizeCryptoSymbol, parseCryptoSymbol, parseIntervalMs, routeAllows, toBybitInterval };
|
package/lib/rest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccountBalance as AccountBalance$1, DerivativesPoint, Interval as Interval$1, Kline as Kline$1, Order as Order$1, OrderRequest, Orderbook, Position as Position$1, Ticker as Ticker$1, TradeTick, TradingErrorCode } from "@dshtrading/api";
|
|
1
|
+
import { AccountBalance as AccountBalance$1, DerivativesPoint, InstrumentAssetClass, InstrumentForm, InstrumentRef, Interval as Interval$1, Kline as Kline$1, Order as Order$1, OrderRequest, Orderbook, Position as Position$1, Ticker as Ticker$1, TradeTick, TradingErrorCode } from "@dshtrading/api";
|
|
2
2
|
//#region src/rest.d.ts
|
|
3
3
|
declare class TradingServiceError extends Error {
|
|
4
4
|
readonly code: TradingErrorCode;
|
|
@@ -7,7 +7,32 @@ declare class TradingServiceError extends Error {
|
|
|
7
7
|
declare const INTERVAL_VOCABULARY: readonly ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"];
|
|
8
8
|
declare function toBybitInterval(interval: Interval$1): string;
|
|
9
9
|
declare function parseIntervalMs(interval: Interval$1): number;
|
|
10
|
+
/** 入参符号的形态裁决结果(交易所侧符号 + 规范形)。 */
|
|
11
|
+
interface ParsedCryptoSymbol {
|
|
12
|
+
/** 交易所侧符号(无 `-SWAP` 后缀):Bybit 现货与线性永续同形,如 BTCUSDT。 */
|
|
13
|
+
readonly base: string;
|
|
14
|
+
readonly form: InstrumentForm;
|
|
15
|
+
/** 市场规范形:spot=BTCUSDT、perp=BTCUSDT-SWAP(输出纪律用)。 */
|
|
16
|
+
readonly canonical: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 解析入参符号:形态由规范后缀裁决(复用 @dshtrading/api 的 instrumentFormOf,
|
|
20
|
+
* 连接器不自写 endsWith('-SWAP'))。
|
|
21
|
+
* `-SWAP` 必须整段剥掉再做分隔符清理——否则 BTCUSDT-SWAP 会被揉成 BTCUSDTSWAP,
|
|
22
|
+
* Bybit 查无此标的(issue #54 评审 M2)。
|
|
23
|
+
*/
|
|
24
|
+
declare function parseCryptoSymbol(raw: string): ParsedCryptoSymbol;
|
|
25
|
+
/**
|
|
26
|
+
* 交易所侧符号(无形态后缀)。
|
|
27
|
+
* 只做归一化、不表达形态:**凡可能收到 `-SWAP` 的行情路径必须改用
|
|
28
|
+
* parseCryptoSymbol 并按 form 分 category**,否则永续会被静默打到现货端点
|
|
29
|
+
* (P3 硬不变量:任何路径不得把 -SWAP 落到 spot 端点)。
|
|
30
|
+
*/
|
|
10
31
|
declare function normalizeCryptoSymbol(raw: string): string;
|
|
32
|
+
/** Bybit v5 market 的 category:形态的唯一分岔(spot / linear),端点与响应形状一致。 */
|
|
33
|
+
declare function bybitCategory(form: InstrumentForm): 'spot' | 'linear';
|
|
34
|
+
/** symbolType → 资产类别;空串/未登记/缺失 = undefined(不推断)。 */
|
|
35
|
+
declare function bybitAssetClassOf(symbolType: unknown): InstrumentAssetClass | undefined;
|
|
11
36
|
interface BybitRestOptions {
|
|
12
37
|
baseUrl?: string;
|
|
13
38
|
apiKey?: string;
|
|
@@ -22,8 +47,18 @@ declare class BybitRestClient {
|
|
|
22
47
|
private readonly fetchImpl;
|
|
23
48
|
constructor(options?: BybitRestOptions);
|
|
24
49
|
private requestJson;
|
|
50
|
+
/**
|
|
51
|
+
* 公共 ticker:现货 → `category=spot`,永续(`-SWAP`)→ `category=linear`。
|
|
52
|
+
* 形态只由规范后缀裁决;永续请求绝不回落现货端点。输出 symbol 为规范形。
|
|
53
|
+
*/
|
|
25
54
|
getTicker(symbol: string): Promise<Ticker$1>;
|
|
55
|
+
/** 公共 K 线:与 getTicker 同一条形态分岔(spot / linear)。 */
|
|
26
56
|
getKlines(symbol: string, interval?: Interval$1, limit?: number): Promise<Kline$1[]>;
|
|
57
|
+
/**
|
|
58
|
+
* 标的名册:现货 ∪ 线性永续(只收 status=Trading;线性侧还要求 contractType=LinearPerpetual,
|
|
59
|
+
* instruments-info 会连已交割/预上市行一起返回)。输出规范形 + form + contract 原样透传。
|
|
60
|
+
*/
|
|
61
|
+
listInstruments(): Promise<InstrumentRef[]>;
|
|
27
62
|
/**
|
|
28
63
|
* 线性合约行情行:GET /v5/market/tickers?category=linear —— 单端点同时携带
|
|
29
64
|
* fundingRate(当前资金费率,小数)、openInterest(base 币计持仓量)、
|
|
@@ -67,4 +102,4 @@ declare class BybitRestClient {
|
|
|
67
102
|
}>;
|
|
68
103
|
}
|
|
69
104
|
//#endregion
|
|
70
|
-
export { type AccountBalance$1 as AccountBalance, BybitRestClient, BybitRestOptions, INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, type Order$1 as Order, type Position$1 as Position, type Ticker$1 as Ticker, TradingServiceError, normalizeCryptoSymbol, parseIntervalMs, toBybitInterval };
|
|
105
|
+
export { type AccountBalance$1 as AccountBalance, BybitRestClient, BybitRestOptions, INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, type Order$1 as Order, ParsedCryptoSymbol, type Position$1 as Position, type Ticker$1 as Ticker, TradingServiceError, bybitAssetClassOf, bybitCategory, normalizeCryptoSymbol, parseCryptoSymbol, parseIntervalMs, toBybitInterval };
|
package/lib/rest.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { SWAP_SYMBOL_SUFFIX, instrumentFormOf } from "@dshtrading/api";
|
|
1
2
|
//#region src/rest.ts
|
|
3
|
+
/**
|
|
4
|
+
* @dshtrading/connector-bybit/rest
|
|
5
|
+
* Bybit API v5 REST 客户端(支持公共行情与现货/衍生品交易)。
|
|
6
|
+
*/
|
|
2
7
|
var TradingServiceError = class extends Error {
|
|
3
8
|
code;
|
|
4
9
|
constructor(code, message, cause) {
|
|
@@ -45,10 +50,34 @@ function parseIntervalMs(interval) {
|
|
|
45
50
|
case "1M": return 2592e6;
|
|
46
51
|
}
|
|
47
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* 解析入参符号:形态由规范后缀裁决(复用 @dshtrading/api 的 instrumentFormOf,
|
|
55
|
+
* 连接器不自写 endsWith('-SWAP'))。
|
|
56
|
+
* `-SWAP` 必须整段剥掉再做分隔符清理——否则 BTCUSDT-SWAP 会被揉成 BTCUSDTSWAP,
|
|
57
|
+
* Bybit 查无此标的(issue #54 评审 M2)。
|
|
58
|
+
*/
|
|
59
|
+
function parseCryptoSymbol(raw) {
|
|
60
|
+
const form = instrumentFormOf(raw);
|
|
61
|
+
const base = raw.trim().toUpperCase().replace(/-SWAP$/, "").replace(/[-_/]/g, "");
|
|
62
|
+
if (!base) throw new TradingServiceError("TRADING_INVALID_ARGUMENT", "Symbol cannot be empty");
|
|
63
|
+
return {
|
|
64
|
+
base,
|
|
65
|
+
form,
|
|
66
|
+
canonical: form === "perp" ? `${base}${SWAP_SYMBOL_SUFFIX}` : base
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 交易所侧符号(无形态后缀)。
|
|
71
|
+
* 只做归一化、不表达形态:**凡可能收到 `-SWAP` 的行情路径必须改用
|
|
72
|
+
* parseCryptoSymbol 并按 form 分 category**,否则永续会被静默打到现货端点
|
|
73
|
+
* (P3 硬不变量:任何路径不得把 -SWAP 落到 spot 端点)。
|
|
74
|
+
*/
|
|
48
75
|
function normalizeCryptoSymbol(raw) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
return parseCryptoSymbol(raw).base;
|
|
77
|
+
}
|
|
78
|
+
/** Bybit v5 market 的 category:形态的唯一分岔(spot / linear),端点与响应形状一致。 */
|
|
79
|
+
function bybitCategory(form) {
|
|
80
|
+
return form === "perp" ? "linear" : "spot";
|
|
52
81
|
}
|
|
53
82
|
/** 宽松转 number(字符串/数字皆收,非有限值返回 undefined)。 */
|
|
54
83
|
function num(value) {
|
|
@@ -56,6 +85,78 @@ function num(value) {
|
|
|
56
85
|
const n = typeof value === "string" ? Number(value) : typeof value === "number" ? value : NaN;
|
|
57
86
|
return Number.isFinite(n) ? n : void 0;
|
|
58
87
|
}
|
|
88
|
+
function asRecord(value) {
|
|
89
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
90
|
+
}
|
|
91
|
+
/** 线性永续 instruments-info 行 → contract 元数据(字段缺失一律缺省,不本地推断)。 */
|
|
92
|
+
function parseContractMeta(row) {
|
|
93
|
+
const lot = asRecord(row.lotSizeFilter);
|
|
94
|
+
const price = asRecord(row.priceFilter);
|
|
95
|
+
const leverage = asRecord(row.leverageFilter);
|
|
96
|
+
const multiplier = num(row.contractSize);
|
|
97
|
+
const tickSize = num(price?.tickSize);
|
|
98
|
+
const lotSize = num(lot?.qtyStep);
|
|
99
|
+
const maxLeverage = num(leverage?.maxLeverage);
|
|
100
|
+
const settleCcy = typeof row.settleCoin === "string" && row.settleCoin !== "" ? row.settleCoin : void 0;
|
|
101
|
+
const contract = {
|
|
102
|
+
...multiplier !== void 0 ? { multiplier } : {},
|
|
103
|
+
...tickSize !== void 0 ? { tickSize } : {},
|
|
104
|
+
...lotSize !== void 0 ? { lotSize } : {},
|
|
105
|
+
...maxLeverage !== void 0 ? { maxLeverage } : {},
|
|
106
|
+
...settleCcy !== void 0 ? { settleCcy } : {}
|
|
107
|
+
};
|
|
108
|
+
return Object.keys(contract).length > 0 ? contract : void 0;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Bybit `symbolType` 字面量 → 资产类别标签。
|
|
112
|
+
*
|
|
113
|
+
* `symbolType` 是 instruments-info 行上的**可选**分类字段,官方枚举页列出的取值有
|
|
114
|
+
* innovation/adventure/xstocks/commodity/stock/forex/ETF/mstocks。2026-10-08 可达环境实测
|
|
115
|
+
* (线性 893 行 + 现货 528 行,见 spikes/impl-crypto-perp-tradfi/EVIDENCE-reverify-2026-10-08.md):
|
|
116
|
+
* 线性侧 stock 202 / innovation 128 / ETF 54 / commodity 4 / forex 3 / 空串 502;
|
|
117
|
+
* 现货侧 空串 514 / xstocks 11(NVDAXUSDT、AAPLXUSDT 等代币化股票)/ adventure 3。
|
|
118
|
+
*
|
|
119
|
+
* 只登记**实测过的**字面量:
|
|
120
|
+
* - 空串 = 交易所没给分类(标准加密品种)⇒ **留空而不是推断成 crypto**(不变量:取不到即留空,
|
|
121
|
+
* 漏标而非错标;空串不是分类值)。
|
|
122
|
+
* - `forex`(EURUSD/GBPUSD/USDJPY)与 `mstocks` 未登记:枚举无外汇成员,mstocks 未在实测中出现;
|
|
123
|
+
* 要不要扩枚举属契约层决定,不在本连接器内定。
|
|
124
|
+
*/
|
|
125
|
+
const BYBIT_SYMBOL_TYPE_ASSET_CLASS = {
|
|
126
|
+
innovation: "crypto",
|
|
127
|
+
adventure: "crypto",
|
|
128
|
+
stock: "equity",
|
|
129
|
+
ETF: "equity",
|
|
130
|
+
xstocks: "equity",
|
|
131
|
+
commodity: "commodity"
|
|
132
|
+
};
|
|
133
|
+
/** symbolType → 资产类别;空串/未登记/缺失 = undefined(不推断)。 */
|
|
134
|
+
function bybitAssetClassOf(symbolType) {
|
|
135
|
+
if (typeof symbolType !== "string") return void 0;
|
|
136
|
+
return BYBIT_SYMBOL_TYPE_ASSET_CLASS[symbolType.trim()];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* instruments-info 行 → InstrumentRef(输出规范形)。
|
|
140
|
+
* assetClass 只由交易所 `symbolType` 字面量裁决(现货与线性同一规则);交易所没给
|
|
141
|
+
* (空串/未登记)即留空,绝不按符号猜(硬不变量 #2:TradFi 归属只信交易所元数据)。
|
|
142
|
+
*/
|
|
143
|
+
function toInstrumentRef(row, form) {
|
|
144
|
+
const exchangeSymbol = typeof row.symbol === "string" ? row.symbol.trim().toUpperCase() : "";
|
|
145
|
+
if (exchangeSymbol === "") return void 0;
|
|
146
|
+
const assetClass = bybitAssetClassOf(row.symbolType);
|
|
147
|
+
if (form === "spot") return {
|
|
148
|
+
symbol: exchangeSymbol,
|
|
149
|
+
form,
|
|
150
|
+
...assetClass !== void 0 ? { assetClass } : {}
|
|
151
|
+
};
|
|
152
|
+
const contract = parseContractMeta(row);
|
|
153
|
+
return {
|
|
154
|
+
symbol: `${exchangeSymbol}${SWAP_SYMBOL_SUFFIX}`,
|
|
155
|
+
form,
|
|
156
|
+
...assetClass !== void 0 ? { assetClass } : {},
|
|
157
|
+
...contract !== void 0 ? { contract } : {}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
59
160
|
var BybitRestClient = class {
|
|
60
161
|
baseUrl;
|
|
61
162
|
apiKey;
|
|
@@ -78,10 +179,14 @@ var BybitRestClient = class {
|
|
|
78
179
|
throw new TradingServiceError("TRADING_NETWORK", `Bybit network error: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
79
180
|
}
|
|
80
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* 公共 ticker:现货 → `category=spot`,永续(`-SWAP`)→ `category=linear`。
|
|
184
|
+
* 形态只由规范后缀裁决;永续请求绝不回落现货端点。输出 symbol 为规范形。
|
|
185
|
+
*/
|
|
81
186
|
async getTicker(symbol) {
|
|
82
|
-
const sym =
|
|
83
|
-
const data = await this.requestJson(`/v5/market/tickers?category
|
|
84
|
-
if (data.retCode !== 0 || !data.result?.list || data.result.list.length === 0) throw new TradingServiceError("
|
|
187
|
+
const { base: sym, form, canonical } = parseCryptoSymbol(symbol);
|
|
188
|
+
const data = await this.requestJson(`/v5/market/tickers?category=${bybitCategory(form)}&symbol=${sym}`);
|
|
189
|
+
if (data.retCode !== 0 || !data.result?.list || data.result.list.length === 0) throw new TradingServiceError("TRADING_UNSUPPORTED_SYMBOL", `Bybit ticker not found for ${canonical} (category=${bybitCategory(form)})`);
|
|
85
190
|
const row = data.result.list[0];
|
|
86
191
|
const price = parseFloat(row.lastPrice);
|
|
87
192
|
const volume = parseFloat(row.volume24h);
|
|
@@ -89,7 +194,7 @@ var BybitRestClient = class {
|
|
|
89
194
|
const prevClose = row.prevPrice24h ? parseFloat(row.prevPrice24h) : void 0;
|
|
90
195
|
const changePercent = row.price24hPcnt ? parseFloat(row.price24hPcnt) * 100 : void 0;
|
|
91
196
|
return {
|
|
92
|
-
symbol:
|
|
197
|
+
symbol: canonical,
|
|
93
198
|
price,
|
|
94
199
|
volume,
|
|
95
200
|
timestamp,
|
|
@@ -97,11 +202,12 @@ var BybitRestClient = class {
|
|
|
97
202
|
...changePercent !== void 0 && Number.isFinite(changePercent) ? { changePercent } : {}
|
|
98
203
|
};
|
|
99
204
|
}
|
|
205
|
+
/** 公共 K 线:与 getTicker 同一条形态分岔(spot / linear)。 */
|
|
100
206
|
async getKlines(symbol, interval = "1d", limit = 100) {
|
|
101
|
-
const sym =
|
|
207
|
+
const { base: sym, form } = parseCryptoSymbol(symbol);
|
|
102
208
|
const bybitInt = toBybitInterval(interval);
|
|
103
209
|
const stepMs = parseIntervalMs(interval);
|
|
104
|
-
const data = await this.requestJson(`/v5/market/kline?category
|
|
210
|
+
const data = await this.requestJson(`/v5/market/kline?category=${bybitCategory(form)}&symbol=${sym}&interval=${bybitInt}&limit=${limit}`);
|
|
105
211
|
if (data.retCode !== 0 || !data.result?.list) return [];
|
|
106
212
|
return [...data.result.list].reverse().map((r) => {
|
|
107
213
|
const openTime = parseInt(r[0], 10);
|
|
@@ -116,6 +222,49 @@ var BybitRestClient = class {
|
|
|
116
222
|
};
|
|
117
223
|
});
|
|
118
224
|
}
|
|
225
|
+
/** instruments-info 单页(limit=1000;游标翻页由 #collectInstrumentRows 驱动)。 */
|
|
226
|
+
async #fetchInstrumentPage(category, cursor) {
|
|
227
|
+
const query = `/v5/market/instruments-info?category=${category}&limit=1000` + (cursor !== void 0 && cursor !== "" ? `&cursor=${encodeURIComponent(cursor)}` : "");
|
|
228
|
+
const data = await this.requestJson(query);
|
|
229
|
+
if (data.retCode !== 0 || !Array.isArray(data.result?.list)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Bybit instruments-info (${category}): ${data.retMsg}`);
|
|
230
|
+
const next = data.result?.nextPageCursor;
|
|
231
|
+
return {
|
|
232
|
+
list: data.result.list,
|
|
233
|
+
...typeof next === "string" && next !== "" ? { next } : {}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async #collectInstrumentRows(category) {
|
|
237
|
+
const rows = [];
|
|
238
|
+
const seen = /* @__PURE__ */ new Set();
|
|
239
|
+
let cursor;
|
|
240
|
+
for (;;) {
|
|
241
|
+
const page = await this.#fetchInstrumentPage(category, cursor);
|
|
242
|
+
rows.push(...page.list);
|
|
243
|
+
if (page.next === void 0) return rows;
|
|
244
|
+
if (seen.has(page.next)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Bybit instruments-info (${category}): upstream repeated page cursor ${page.next}`);
|
|
245
|
+
seen.add(page.next);
|
|
246
|
+
cursor = page.next;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 标的名册:现货 ∪ 线性永续(只收 status=Trading;线性侧还要求 contractType=LinearPerpetual,
|
|
251
|
+
* instruments-info 会连已交割/预上市行一起返回)。输出规范形 + form + contract 原样透传。
|
|
252
|
+
*/
|
|
253
|
+
async listInstruments() {
|
|
254
|
+
const [spotRows, linearRows] = await Promise.all([this.#collectInstrumentRows("spot"), this.#collectInstrumentRows("linear")]);
|
|
255
|
+
const out = [];
|
|
256
|
+
for (const row of spotRows) {
|
|
257
|
+
if (row.status !== "Trading") continue;
|
|
258
|
+
const ref = toInstrumentRef(row, "spot");
|
|
259
|
+
if (ref !== void 0) out.push(ref);
|
|
260
|
+
}
|
|
261
|
+
for (const row of linearRows) {
|
|
262
|
+
if (row.contractType !== "LinearPerpetual" || row.status !== "Trading") continue;
|
|
263
|
+
const ref = toInstrumentRef(row, "perp");
|
|
264
|
+
if (ref !== void 0) out.push(ref);
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
119
268
|
/**
|
|
120
269
|
* 线性合约行情行:GET /v5/market/tickers?category=linear —— 单端点同时携带
|
|
121
270
|
* fundingRate(当前资金费率,小数)、openInterest(base 币计持仓量)、
|
|
@@ -211,11 +360,11 @@ var BybitRestClient = class {
|
|
|
211
360
|
* bids/asks 全称(2026-09-02 真实响应实证,spikes/impl-orderbook-ticks/bybit-orderbook-raw.json)。
|
|
212
361
|
*/
|
|
213
362
|
async getOrderbook(symbol) {
|
|
214
|
-
const sym =
|
|
215
|
-
const data = await this.requestJson(`/v5/market/orderbook?category
|
|
216
|
-
if (data.retCode !== 0 || !Array.isArray(data.result?.b) || !Array.isArray(data.result?.a)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Bybit orderbook for ${
|
|
363
|
+
const { base: sym, form, canonical } = parseCryptoSymbol(symbol);
|
|
364
|
+
const data = await this.requestJson(`/v5/market/orderbook?category=${bybitCategory(form)}&symbol=${sym}&limit=25`);
|
|
365
|
+
if (data.retCode !== 0 || !Array.isArray(data.result?.b) || !Array.isArray(data.result?.a)) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Bybit orderbook for ${canonical}: unexpected response shape`);
|
|
217
366
|
return {
|
|
218
|
-
symbol:
|
|
367
|
+
symbol: canonical,
|
|
219
368
|
bids: data.result.b.map((row) => this.#parseBookRow(row)).filter((l) => l !== void 0),
|
|
220
369
|
asks: data.result.a.map((row) => this.#parseBookRow(row)).filter((l) => l !== void 0),
|
|
221
370
|
timestamp: num(data.result.ts) ?? Date.now()
|
|
@@ -239,12 +388,11 @@ var BybitRestClient = class {
|
|
|
239
388
|
}
|
|
240
389
|
/** 最近逐笔成交:GET /v5/market/recent-trade?category=spot(响应新→旧 → 反转升序)。 */
|
|
241
390
|
async getRecentTrades(symbol, limit = 50) {
|
|
242
|
-
const sym =
|
|
391
|
+
const { base: sym, form, canonical } = parseCryptoSymbol(symbol);
|
|
243
392
|
const capped = Math.max(1, Math.min(Math.floor(limit) || 50, 60));
|
|
244
|
-
const data = await this.requestJson(`/v5/market/recent-trade?category
|
|
245
|
-
if (data.retCode !== 0 || !data.result?.list) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Bybit trades for ${
|
|
246
|
-
|
|
247
|
-
return data.result.list.map((row) => this.#parseTradeRow(row, symbolOut)).reverse();
|
|
393
|
+
const data = await this.requestJson(`/v5/market/recent-trade?category=${bybitCategory(form)}&symbol=${sym}&limit=${capped}`);
|
|
394
|
+
if (data.retCode !== 0 || !data.result?.list) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", `Bybit trades for ${canonical}: unexpected response shape`);
|
|
395
|
+
return data.result.list.map((row) => this.#parseTradeRow(row, canonical)).reverse();
|
|
248
396
|
}
|
|
249
397
|
async getBalance() {
|
|
250
398
|
return {
|
|
@@ -275,4 +423,4 @@ var BybitRestClient = class {
|
|
|
275
423
|
}
|
|
276
424
|
};
|
|
277
425
|
//#endregion
|
|
278
|
-
export { BybitRestClient, INTERVAL_VOCABULARY, TradingServiceError, normalizeCryptoSymbol, parseIntervalMs, toBybitInterval };
|
|
426
|
+
export { BybitRestClient, INTERVAL_VOCABULARY, TradingServiceError, bybitAssetClassOf, bybitCategory, normalizeCryptoSymbol, parseCryptoSymbol, parseIntervalMs, toBybitInterval };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-bybit",
|
|
3
3
|
"description": "【模板】交易所连接器插件脚手架:Bybit(bybit)v2 插件骨架 —— 市场数据服务 + 交易服务 + 三态闸门 + BYOK 凭证 ref。生成器:node scripts/new-connector.mjs --slug <exchange>",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
],
|
|
22
22
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dshtrading/
|
|
24
|
+
"@dshtrading/authority": "^0.6.1",
|
|
25
|
+
"@dshtrading/api": "^0.6.1"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
28
|
"@deepseek-ai/cordis": ">=4.0.0",
|