@imbingox/acex 0.3.1-beta.0 → 0.4.0-beta.10
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 +11 -10
- package/docs/api.md +502 -1030
- package/package.json +1 -1
- package/src/adapters/binance/adapter.ts +19 -1
- package/src/adapters/binance/market-catalog.ts +93 -22
- package/src/adapters/binance/private-adapter.ts +302 -59
- package/src/adapters/binance/rate-limit.ts +47 -0
- package/src/adapters/binance/server-time.ts +106 -0
- package/src/adapters/juplend/private-adapter.ts +97 -68
- package/src/adapters/types.ts +25 -1
- package/src/client/context.ts +26 -9
- package/src/client/private-subscription-coordinator.ts +898 -63
- package/src/client/runtime.ts +49 -11
- package/src/client/venue-capabilities.ts +1 -0
- package/src/errors.ts +156 -2
- package/src/index.ts +8 -1
- package/src/internal/decimal.ts +19 -0
- package/src/internal/http-client.ts +608 -0
- package/src/internal/rate-limiter.ts +181 -0
- package/src/internal/watermark.ts +83 -0
- package/src/managers/account-manager.ts +267 -55
- package/src/managers/market-manager.ts +261 -60
- package/src/managers/order-manager.ts +798 -84
- package/src/types/account.ts +27 -28
- package/src/types/client.ts +1 -0
- package/src/types/market.ts +37 -12
- package/src/types/order.ts +7 -7
- package/src/types/shared.ts +66 -0
package/docs/api.md
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
# acex 使用手册
|
|
1
|
+
# @imbingox/acex API 使用手册
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本文面向 SDK 下游调用方:策略服务、风控面板、交易执行器和数据采集进程。目标是说明如何正确持有 `AcexClient`、查询当前 runtime 能力、订阅状态型数据、执行订单命令,以及在接入时处理错误和限制。
|
|
4
4
|
|
|
5
5
|
## 目录
|
|
6
6
|
|
|
7
|
-
1.
|
|
8
|
-
2.
|
|
9
|
-
3.
|
|
10
|
-
4.
|
|
11
|
-
5.
|
|
12
|
-
6.
|
|
13
|
-
7.
|
|
14
|
-
8.
|
|
15
|
-
9.
|
|
16
|
-
10.
|
|
17
|
-
11.
|
|
7
|
+
- [1. 当前能力](#1-当前能力)
|
|
8
|
+
- [2. 快速接入](#2-快速接入)
|
|
9
|
+
- [3. 核心概念](#3-核心概念)
|
|
10
|
+
- [4. Client 生命周期](#4-client-生命周期)
|
|
11
|
+
- [5. MarketManager](#5-marketmanager)
|
|
12
|
+
- [6. AccountManager](#6-accountmanager)
|
|
13
|
+
- [7. OrderManager](#7-ordermanager)
|
|
14
|
+
- [8. 健康与错误事件](#8-健康与错误事件)
|
|
15
|
+
- [9. 数据类型速查](#9-数据类型速查)
|
|
16
|
+
- [10. 错误处理](#10-错误处理)
|
|
17
|
+
- [11. 当前限制](#11-当前限制)
|
|
18
18
|
|
|
19
|
-
## 1.
|
|
19
|
+
## 1. 当前能力
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
`@imbingox/acex` 是状态型多 venue SDK。调用方创建一个 `AcexClient`,通过 `market` / `account` / `order` 三个 manager 读取最新快照、消费事件流、执行命令;SDK 内部维护本地缓存、ready barrier、WebSocket 生命周期、自动重连、REST timeout / retry / 错误脱敏和 reactive rate limiter。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
当前 runtime 落地:
|
|
24
24
|
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
25
|
+
| Venue | Market | Account | Order |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `binance` | Spot / USDⓈ-M / COIN-M catalog(含 TradFi Perps);L1 Book;永续 funding rate;USDM server time | PAPI UM 私有账户流 + REST risk refresh | PAPI UM `limit` / `market` 下单、撤单、按 symbol 全撤 |
|
|
28
|
+
| `juplend` | 不支持 | Jupiter Lend 只读账户 polling | 不支持,read-only |
|
|
29
|
+
| `okx` / `bybit` / `gate` | 类型占位 | 类型占位 | 类型占位 |
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
## 2. 快速接入
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
### 2.1 安装和初始化
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
bun add @imbingox/acex
|
|
@@ -40,6 +40,17 @@ bun add @imbingox/acex
|
|
|
40
40
|
import { createClient } from "@imbingox/acex";
|
|
41
41
|
|
|
42
42
|
const client = createClient();
|
|
43
|
+
|
|
44
|
+
await client.start();
|
|
45
|
+
// ... use client.market / client.account / client.order
|
|
46
|
+
await client.stop();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`createClient()` 不建立网络连接。`start()` 后才能调用订阅类方法;`loadMarkets()`、`reloadMarkets()`、`fetchServerTime()` 和 capability 查询不要求 client 已 start。
|
|
50
|
+
|
|
51
|
+
### 2.2 订阅 Binance L1 Book
|
|
52
|
+
|
|
53
|
+
```ts
|
|
43
54
|
await client.start();
|
|
44
55
|
|
|
45
56
|
await client.market.subscribeL1Book({
|
|
@@ -51,27 +62,51 @@ const book = client.market.getL1Book({
|
|
|
51
62
|
venue: "binance",
|
|
52
63
|
symbol: "BTC/USDT:USDT",
|
|
53
64
|
});
|
|
54
|
-
|
|
65
|
+
|
|
66
|
+
console.log(book?.bidPrice, book?.askPrice, book?.status.freshness);
|
|
55
67
|
|
|
56
68
|
for await (const event of client.market.events.l1BookUpdates({
|
|
57
69
|
venue: "binance",
|
|
58
70
|
symbol: "BTC/USDT:USDT",
|
|
59
71
|
})) {
|
|
60
|
-
console.log(event.snapshot.bidPrice
|
|
72
|
+
console.log(event.snapshot.bidPrice);
|
|
61
73
|
break;
|
|
62
74
|
}
|
|
75
|
+
```
|
|
63
76
|
|
|
64
|
-
|
|
77
|
+
`subscribeL1Book()` 会等待该 logical subscription 的首条有效数据到达后才 resolve。首条数据超时会抛 `MARKET_STREAM_TIMEOUT`。
|
|
78
|
+
|
|
79
|
+
### 2.3 注册 Binance 交易账户
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await client.registerAccount({
|
|
83
|
+
accountId: "main-binance",
|
|
84
|
+
venue: "binance",
|
|
85
|
+
credentials: {
|
|
86
|
+
apiKey: process.env.BINANCE_PAPI_API_KEY,
|
|
87
|
+
secret: process.env.BINANCE_PAPI_SECRET,
|
|
88
|
+
},
|
|
89
|
+
options: {
|
|
90
|
+
recvWindow: 5_000,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await client.start();
|
|
95
|
+
|
|
96
|
+
await client.account.subscribeAccount({ accountId: "main-binance" });
|
|
97
|
+
await client.order.subscribeOrders({ accountId: "main-binance" });
|
|
98
|
+
|
|
99
|
+
const risk = client.account.getRiskSnapshot("main-binance");
|
|
100
|
+
const openOrders = client.order.getOpenOrders("main-binance");
|
|
65
101
|
```
|
|
66
102
|
|
|
67
|
-
|
|
103
|
+
Binance 账户能力当前面向 PAPI UM。账户风险字段会由私有 WS 事件和 `/papi/v1/account` + `/papi/v1/um/positionRisk` REST refresh 共同维护;默认每 60s 还会用 `/papi/v1/balance`、`/papi/v1/account`、`/papi/v1/um/positionRisk` 和订单 REST 接口做 private reconcile。Binance 全账户 `/papi/v1/um/openOrders` 不带 symbol 时 request weight 较高,默认 60s 是保守值。
|
|
104
|
+
|
|
105
|
+
### 2.4 注册 Juplend 只读账户
|
|
68
106
|
|
|
69
107
|
```ts
|
|
70
108
|
const client = createClient({
|
|
71
109
|
account: {
|
|
72
|
-
binance: {
|
|
73
|
-
riskPollIntervalMs: 5_000,
|
|
74
|
-
},
|
|
75
110
|
juplend: {
|
|
76
111
|
pollIntervalMs: 30_000,
|
|
77
112
|
rpcUrl: process.env.SOL_HELIUS_RPC,
|
|
@@ -79,16 +114,6 @@ const client = createClient({
|
|
|
79
114
|
},
|
|
80
115
|
},
|
|
81
116
|
});
|
|
82
|
-
await client.start();
|
|
83
|
-
|
|
84
|
-
await client.registerAccount({
|
|
85
|
-
accountId: "main-binance",
|
|
86
|
-
venue: "binance",
|
|
87
|
-
credentials: {
|
|
88
|
-
apiKey: process.env.BINANCE_PAPI_API_KEY,
|
|
89
|
-
secret: process.env.BINANCE_PAPI_SECRET,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
117
|
|
|
93
118
|
await client.registerAccount({
|
|
94
119
|
accountId: "jup-loop-a",
|
|
@@ -99,6 +124,16 @@ await client.registerAccount({
|
|
|
99
124
|
},
|
|
100
125
|
});
|
|
101
126
|
|
|
127
|
+
await client.start();
|
|
128
|
+
await client.account.subscribeAccount({ accountId: "jup-loop-a" });
|
|
129
|
+
|
|
130
|
+
const balances = client.account.getBalances("jup-loop-a");
|
|
131
|
+
const risk = client.account.getRiskSnapshot("jup-loop-a");
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
也可以用已知 vault + position 直接读取单仓,不扫全钱包:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
102
137
|
await client.registerAccount({
|
|
103
138
|
accountId: "jup-loop-direct",
|
|
104
139
|
venue: "juplend",
|
|
@@ -107,109 +142,92 @@ await client.registerAccount({
|
|
|
107
142
|
positionId: "<nft-position-id>",
|
|
108
143
|
},
|
|
109
144
|
});
|
|
110
|
-
|
|
111
|
-
await client.account.subscribeAccount({ accountId: "main-binance" });
|
|
112
|
-
await client.account.subscribeAccount({ accountId: "jup-loop-a" });
|
|
113
|
-
|
|
114
|
-
const binanceRisk = client.account.getRiskSnapshot("main-binance");
|
|
115
|
-
const juplendRisk = client.account.getRiskSnapshot("jup-loop-a");
|
|
116
|
-
|
|
117
|
-
console.log(binanceRisk?.riskRatio?.toFixed());
|
|
118
|
-
console.log(juplendRisk?.riskRatio?.toFixed());
|
|
119
145
|
```
|
|
120
146
|
|
|
121
|
-
|
|
147
|
+
Juplend 不需要私钥,不支持 supply / borrow / repay / withdraw。`accountId` 是 SDK 内的逻辑账户名,不是钱包地址。
|
|
122
148
|
|
|
123
|
-
|
|
124
|
-
const client = createClient();
|
|
125
|
-
await client.start();
|
|
149
|
+
### 2.5 下单和撤单
|
|
126
150
|
|
|
127
|
-
|
|
151
|
+
```ts
|
|
152
|
+
const order = await client.order.createOrder({
|
|
128
153
|
accountId: "main-binance",
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
symbol: "BTC/USDT:USDT",
|
|
155
|
+
side: "buy",
|
|
156
|
+
type: "limit",
|
|
157
|
+
price: "60000",
|
|
158
|
+
amount: "0.001",
|
|
159
|
+
postOnly: true,
|
|
160
|
+
clientOrderId: "strategy-001",
|
|
161
|
+
positionSide: "long",
|
|
134
162
|
});
|
|
135
163
|
|
|
136
|
-
await client.
|
|
137
|
-
|
|
164
|
+
const canceled = await client.order.cancelOrder({
|
|
165
|
+
accountId: "main-binance",
|
|
166
|
+
symbol: "BTC/USDT:USDT",
|
|
167
|
+
clientOrderId: "strategy-001",
|
|
168
|
+
});
|
|
138
169
|
|
|
139
|
-
await client.
|
|
170
|
+
const batch = await client.order.cancelAllOrders({
|
|
171
|
+
accountId: "main-binance",
|
|
172
|
+
symbol: "BTC/USDT:USDT",
|
|
173
|
+
});
|
|
140
174
|
```
|
|
141
175
|
|
|
176
|
+
下单命令由 `accountId` 对应的 venue 决定,不在 order input 里再传 venue。Juplend 和 type-only venue 会被 runtime 拒绝。
|
|
177
|
+
|
|
142
178
|
## 3. 核心概念
|
|
143
179
|
|
|
144
|
-
### 3.1
|
|
180
|
+
### 3.1 Stateful client
|
|
145
181
|
|
|
146
|
-
|
|
182
|
+
`AcexClient` 是长生命周期对象。manager 内部持有快照、状态、事件总线和订阅句柄。下游服务应复用同一个 client,而不是每次读取都重新创建。
|
|
147
183
|
|
|
148
|
-
|
|
149
|
-
- 跨 symbol 做决策(套利、对冲)时,在事件回调里用 `get*()` 拿各 symbol 最新值,比读 `event.snapshot` 更一致
|
|
184
|
+
### 3.2 Ready barrier
|
|
150
185
|
|
|
151
|
-
|
|
186
|
+
订阅方法 resolve 之后,相关 getter 应已有第一份可读快照:
|
|
152
187
|
|
|
153
188
|
```ts
|
|
154
|
-
await client.market.subscribeL1Book({ venue, symbol });
|
|
155
|
-
|
|
189
|
+
await client.market.subscribeL1Book({ venue: "binance", symbol });
|
|
190
|
+
const snapshot = client.market.getL1Book({ venue: "binance", symbol });
|
|
156
191
|
```
|
|
157
192
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
### 3.2.1 subscribe / unsubscribe 行为
|
|
161
|
-
|
|
162
|
-
- 同一个 `(venue, symbol)` 反复调用 `subscribe*()` 是幂等的:已存在的流不会重复创建,只会继续等待原有流进入 ready
|
|
163
|
-
- `unsubscribe*()` 可以和 `subscribe*()` 动态交替调用;退订后该 stream 停止维护,但 `get*()` 仍可读到最后一份快照
|
|
164
|
-
- `unsubscribe*()` 对未订阅目标是安全的 no-op
|
|
165
|
-
- 退订后再次 `subscribe*()` 会重新恢复该 stream 的维护与更新
|
|
166
|
-
- `subscribeL1Book()` 和 `subscribeFundingRate()` 彼此独立;退订其中一个不会影响另一个
|
|
167
|
-
|
|
168
|
-
### 3.3 event vs snapshot
|
|
193
|
+
如果首条数据迟迟不到,订阅 promise 会 reject。稳态期间断线不会清空旧快照;快照上的 `status.freshness` 会转为 `stale`。
|
|
169
194
|
|
|
170
|
-
|
|
195
|
+
### 3.3 Decimal string
|
|
171
196
|
|
|
172
|
-
|
|
173
|
-
- 跨 symbol 决策场景,把事件当触发器,用 `get*()` 读所有 symbol 的当下值
|
|
174
|
-
|
|
175
|
-
### 3.4 activity vs freshness vs runtime status
|
|
176
|
-
|
|
177
|
-
| 字段 | 语义 | 出现在 |
|
|
178
|
-
|---|---|---|
|
|
179
|
-
| `activity` | `"active"` 表示 SDK 仍在维护;`"inactive"` 表示已退订或未订阅 | 所有 `*DataStatus` |
|
|
180
|
-
| `freshness` | market 数据的新鲜度:`"fresh"` / `"stale"` / `"reconciling"` | `MarketDataStatus` |
|
|
181
|
-
| `runtimeStatus` | 私有链路运行态:`"bootstrap_pending"` / `"healthy"` / `"degraded"` / `"reconnecting"` / `"reconciling"` / `"stopped"` | `AccountDataStatus`、`OrderDataStatus` |
|
|
182
|
-
|
|
183
|
-
退订后 `activity` 变为 `"inactive"`,但最后一份快照仍可读——不要把它当实时值。
|
|
184
|
-
|
|
185
|
-
### 3.5 BigNumber 约定
|
|
186
|
-
|
|
187
|
-
输出侧的价格、数量、金额统一是 `BigNumber`(来自 [bignumber.js](https://github.com/MikeMcl/bignumber.js),SDK 已 re-export):
|
|
197
|
+
所有 public snapshot / market 数值字段都是 canonical decimal string:无损、无科学计数法、不补尾零。SDK 仍 re-export `BigNumber` 作为下游计算工具:
|
|
188
198
|
|
|
189
199
|
```ts
|
|
190
200
|
import { BigNumber } from "@imbingox/acex";
|
|
191
201
|
|
|
192
|
-
const book = client.market.getL1Book({ venue, symbol });
|
|
193
|
-
const spread = book!.askPrice.minus(book!.bidPrice);
|
|
194
|
-
console.log(spread.toFixed());
|
|
202
|
+
const book = client.market.getL1Book({ venue: "binance", symbol });
|
|
203
|
+
const spread = new BigNumber(book!.askPrice).minus(book!.bidPrice);
|
|
195
204
|
```
|
|
196
205
|
|
|
197
|
-
|
|
206
|
+
不要用 `parseFloat()` 处理金额、数量、价格和比率。`createOrder()` 的 `price` / `amount` 必须传 decimal string;`normalizeOrderInput()` 的 `DecimalInput` 可接受 string / number / `BigNumber`。
|
|
198
207
|
|
|
199
|
-
|
|
208
|
+
### 3.4 状态字段
|
|
200
209
|
|
|
201
|
-
|
|
210
|
+
常见状态字段:
|
|
202
211
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
212
|
+
| 字段 | 语义 |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `activity` | `"active"` 表示当前订阅活跃;`"inactive"` 表示已退订或停止 |
|
|
215
|
+
| `ready` | 是否已有首份可读数据 |
|
|
216
|
+
| `freshness` | market stream 新鲜度:`"fresh"` / `"stale"` / `"reconciling"` |
|
|
217
|
+
| `runtimeStatus` | private stream 状态:`"bootstrap_pending"` / `"healthy"` / `"degraded"` / `"reconnecting"` / `"reconciling"` / `"stopped"` |
|
|
218
|
+
| `reason` | 状态原因,如 `credentials_missing`、`http_failed`、`rate_limited`、`ws_disconnected` |
|
|
206
219
|
|
|
207
|
-
|
|
220
|
+
退订后旧快照仍可读,但不再代表实时值。
|
|
221
|
+
|
|
222
|
+
## 4. Client 生命周期
|
|
208
223
|
|
|
209
|
-
|
|
224
|
+
### 4.1 `createClient(options?)`
|
|
210
225
|
|
|
211
226
|
```ts
|
|
212
227
|
const client = createClient({
|
|
228
|
+
clock: {
|
|
229
|
+
now: () => Date.now(),
|
|
230
|
+
},
|
|
213
231
|
market: {
|
|
214
232
|
l1InitialMessageTimeoutMs: 15_000,
|
|
215
233
|
l1StaleAfterMs: 15_000,
|
|
@@ -223,120 +241,62 @@ const client = createClient({
|
|
|
223
241
|
listenKeyKeepAliveMs: 30 * 60_000,
|
|
224
242
|
binance: {
|
|
225
243
|
riskPollIntervalMs: 5_000,
|
|
244
|
+
privateReconcileIntervalMs: 60_000,
|
|
226
245
|
},
|
|
227
246
|
juplend: {
|
|
228
247
|
pollIntervalMs: 30_000,
|
|
229
248
|
rpcUrl: process.env.SOL_HELIUS_RPC,
|
|
249
|
+
jupApiKey: process.env.JUP_API,
|
|
230
250
|
},
|
|
231
251
|
},
|
|
232
252
|
});
|
|
233
253
|
```
|
|
234
254
|
|
|
235
|
-
`sandbox`、`logger`、`logLevel`
|
|
255
|
+
`clock` 只用于 outbound request / signing timestamp,不驱动 WebSocket freshness 的 received-at 时钟。需要自定义 REST 限流行为时可传 `rateLimiter`,否则使用默认 reactive limiter。Binance `riskPollIntervalMs` 默认 5s,用于风险和 mark-to-market 仓位刷新;`privateReconcileIntervalMs` 默认 60s,用于账户余额、仓位和订单状态 REST 对账,显式传 `0` 可关闭 private reconcile,但不关闭 risk polling。`sandbox`、`logger`、`logLevel` 目前是预留位。
|
|
236
256
|
|
|
237
257
|
### 4.2 `start()` / `stop()`
|
|
238
258
|
|
|
239
259
|
```ts
|
|
240
260
|
await client.start();
|
|
241
|
-
// ...
|
|
242
261
|
await client.stop();
|
|
243
|
-
await client.stop({ graceful: true, timeoutMs: 5_000 });
|
|
244
262
|
```
|
|
245
263
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
在 `start()` 之前调 `subscribe*()` 会直接失败,抛 `CLIENT_NOT_STARTED`。
|
|
264
|
+
状态机是 `idle → starting → running → stopping → stopped`。`start()` 和 `stop()` 幂等。`stop(options?)` 的 `graceful` / `timeoutMs` 当前是预留参数,不要依赖它们提供额外 drain 语义。
|
|
249
265
|
|
|
250
266
|
### 4.3 Venue capabilities
|
|
251
267
|
|
|
252
268
|
```ts
|
|
253
269
|
const binance = client.getVenueCapabilities("binance");
|
|
254
|
-
const
|
|
270
|
+
const all = client.listVenueCapabilities();
|
|
255
271
|
```
|
|
256
272
|
|
|
257
|
-
|
|
273
|
+
Capability 查询不访问网络,不要求 `start()`。返回值表达当前 SDK runtime 已实现能力,不代表交易所官网完整能力,也不检查 API key 权限。
|
|
258
274
|
|
|
259
|
-
|
|
275
|
+
当前摘要:
|
|
276
|
+
|
|
277
|
+
| Venue | runtimeStatus | readOnly | 关键能力 |
|
|
278
|
+
|---|---|---:|---|
|
|
279
|
+
| `binance` | `available` | false | market catalog / server time / L1;funding rate 为 `market_dependent`;order supported |
|
|
280
|
+
| `juplend` | `available` | true | account polling + lending;order reason 为 `read_only` |
|
|
281
|
+
| `okx` / `bybit` / `gate` | `type_only` | false | runtime 未接入,order reason 为 `not_implemented` |
|
|
282
|
+
|
|
283
|
+
下游应先查 capability 再展示或启用功能:
|
|
260
284
|
|
|
261
285
|
```ts
|
|
262
286
|
if (!client.getVenueCapabilities("juplend").order.supported) {
|
|
263
|
-
//
|
|
287
|
+
// 不展示下单按钮
|
|
264
288
|
}
|
|
265
289
|
```
|
|
266
290
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
| Venue | runtimeStatus | readOnly | Market | Account | Order |
|
|
270
|
-
|---|---|---:|---|---|---|
|
|
271
|
-
| `binance` | `available` | false | catalog / L1 支持;funding rate 为 `market_dependent` | WebSocket 私有账户流 | 支持 `limit` / `market` 下单、撤单、按 symbol 全撤 |
|
|
272
|
-
| `juplend` | `available` | true | 不支持 | polling 只读 lending 账户 | 不支持,`reason = "read_only"` |
|
|
273
|
-
| `okx` / `bybit` / `gate` | `type_only` | false | 当前未实现 | 当前未实现 | 当前未实现,`reason = "not_implemented"` |
|
|
274
|
-
|
|
275
|
-
第一版只提供 venue 级查询,不提供 symbol/market 级 capability。像 funding rate 这类依赖 market type 的能力会用 `market_dependent` 表达,具体 symbol 仍以实际 `subscribeFundingRate()` / `MARKET_FUNDING_RATE_UNSUPPORTED` 为准。
|
|
276
|
-
|
|
277
|
-
Binance 的 `order.supported = true` 只表示当前 SDK 有 Binance 订单命令链路;第一版命令固定走 Binance PAPI UM,主要面向 USDⓈ-M symbol,不代表 Binance spot、COIN-M 或交割合约都可通过 `OrderManager` 下单。需要按具体 symbol 预检时,应等后续 market-level capability。
|
|
278
|
-
|
|
279
|
-
### 4.4 账户注册
|
|
291
|
+
### 4.4 账户管理
|
|
280
292
|
|
|
281
293
|
```ts
|
|
282
|
-
await client.registerAccount(
|
|
283
|
-
accountId: "main-binance",
|
|
284
|
-
venue: "binance",
|
|
285
|
-
credentials: { apiKey, secret },
|
|
286
|
-
});
|
|
287
|
-
|
|
294
|
+
await client.registerAccount(input);
|
|
288
295
|
await client.updateAccountCredentials("main-binance", { apiKey, secret });
|
|
289
|
-
|
|
290
296
|
await client.removeAccount("main-binance");
|
|
291
297
|
```
|
|
292
298
|
|
|
293
|
-
`RegisterAccountInput`
|
|
294
|
-
|
|
295
|
-
```ts
|
|
296
|
-
await client.registerAccount({
|
|
297
|
-
accountId: "main-binance",
|
|
298
|
-
venue: "binance",
|
|
299
|
-
credentials: { apiKey, secret },
|
|
300
|
-
options: {
|
|
301
|
-
recvWindow: 5000,
|
|
302
|
-
timestamp: Date.now(),
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
await client.registerAccount({
|
|
307
|
-
accountId: "jup-loop-a",
|
|
308
|
-
venue: "juplend",
|
|
309
|
-
options: {
|
|
310
|
-
walletAddress,
|
|
311
|
-
positionId: "101", // 可选;不传则聚合该钱包全部 Juplend positions
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
await client.registerAccount({
|
|
316
|
-
accountId: "jup-loop-direct",
|
|
317
|
-
venue: "juplend",
|
|
318
|
-
options: {
|
|
319
|
-
vaultId: "1",
|
|
320
|
-
positionId: "101", // 直接读取单个 vault 内的 NFT position
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
约束:
|
|
326
|
-
|
|
327
|
-
- `accountId` 在单个 `AcexClient` 实例内全局唯一。重复注册抛 `ACCOUNT_ALREADY_EXISTS`
|
|
328
|
-
- 凭证校验发生在 `subscribeAccount()` / `subscribeOrders()` 时,不是注册时
|
|
329
|
-
- `updateAccountCredentials()` 可以在私有订阅活跃时调用,SDK 会按需重建私有链路
|
|
330
|
-
- `removeAccount()` 比 `unsubscribeAccount()` 更彻底:账户配置、凭证、账户级缓存都会清理
|
|
331
|
-
- Juplend 的 `accountId` 是自定义逻辑账户名;可传 `options.walletAddress` 聚合钱包全部仓位,或传 `options.vaultId + options.positionId` 直接读取单个仓位
|
|
332
|
-
- Juplend 不要求 `credentials`;原生 read 默认读取 `SOL_HELIUS_RPC`,也可通过 `account.juplend.rpcUrl` 显式覆盖;token metadata / price 优先读取 `account.juplend.jupApiKey` 或环境变量 `JUP_API`
|
|
333
|
-
|
|
334
|
-
### 4.5 `getStatus()` / `getHealth()`
|
|
335
|
-
|
|
336
|
-
```ts
|
|
337
|
-
client.getStatus(); // ClientStatus
|
|
338
|
-
client.getHealth(); // ClientHealthSnapshot(聚合所有 market/account/order 状态)
|
|
339
|
-
```
|
|
299
|
+
`RegisterAccountInput` 按 venue 区分。CEX venue 使用 `AccountCredentials`;Juplend 必须显式提供 `walletAddress` 或 `vaultId + positionId`。虽然 public `Venue` 包含 type-only venue,但注册成功不代表该 venue runtime 能订阅或下单,仍以 capability 和实际调用结果为准。
|
|
340
300
|
|
|
341
301
|
## 5. MarketManager
|
|
342
302
|
|
|
@@ -345,6 +305,9 @@ interface MarketManager {
|
|
|
345
305
|
readonly events: MarketEventStreams;
|
|
346
306
|
|
|
347
307
|
loadMarkets(): Promise<void>;
|
|
308
|
+
reloadMarkets(venue?: Venue): Promise<MarketCatalogReloadSummary[]>;
|
|
309
|
+
fetchServerTime(venue: Venue): Promise<VenueServerTime>;
|
|
310
|
+
|
|
348
311
|
listMarkets(venue?: Venue): MarketDefinition[];
|
|
349
312
|
getMarket(venue: Venue, symbol: string): MarketDefinition | undefined;
|
|
350
313
|
getMarkets(symbol: string): MarketDefinition[];
|
|
@@ -366,732 +329,369 @@ interface MarketManager {
|
|
|
366
329
|
|
|
367
330
|
### 5.1 Market catalog
|
|
368
331
|
|
|
332
|
+
`loadMarkets()` 懒加载所有已实现 market runtime 的 venue 目录;`reloadMarkets(venue?)` 主动刷新目录,返回新增/移除/总数/错误摘要。订阅方法会自动确保对应 venue 的 catalog 已加载。
|
|
333
|
+
|
|
369
334
|
```ts
|
|
370
335
|
await client.market.loadMarkets();
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
const binanceOnly = client.market.listMarkets("binance");
|
|
374
|
-
|
|
375
|
-
const btcPerp = client.market.getMarket("binance", "BTC/USDT:USDT");
|
|
376
|
-
const allBtcPerp = client.market.getMarkets("BTC/USDT:USDT");
|
|
336
|
+
const markets = client.market.listMarkets("binance");
|
|
337
|
+
const market = client.market.getMarket("binance", "BTC/USDT:USDT");
|
|
377
338
|
```
|
|
378
339
|
|
|
379
|
-
`
|
|
340
|
+
`MarketDefinition` 里的 `priceStep`、`amountStep`、`contractSize`、`minAmount`、`minNotional` 都是 decimal string。
|
|
380
341
|
|
|
381
|
-
`
|
|
342
|
+
Binance TradFi Perps 会按 USDⓈ-M 永续合约暴露,例如 `AAPLUSDT` 归一为 `AAPL/USDT:USDT`,可使用同一套 L1 Book 与 Funding Rate 订阅接口。
|
|
382
343
|
|
|
383
|
-
|
|
344
|
+
### 5.2 订单输入归一
|
|
384
345
|
|
|
385
346
|
```ts
|
|
386
|
-
await client.market.loadMarkets();
|
|
387
|
-
|
|
388
347
|
const normalized = client.market.normalizeOrderInput({
|
|
389
348
|
venue: "binance",
|
|
390
349
|
symbol: "BTC/USDT:USDT",
|
|
391
|
-
price: "
|
|
392
|
-
amount: "0.
|
|
350
|
+
price: "60000.123",
|
|
351
|
+
amount: "0.001234",
|
|
393
352
|
});
|
|
394
353
|
|
|
395
|
-
if (normalized.accepted) {
|
|
396
|
-
|
|
397
|
-
accountId: "main-binance",
|
|
398
|
-
symbol: "BTC/USDT:USDT",
|
|
399
|
-
side: "buy",
|
|
400
|
-
type: "limit",
|
|
401
|
-
price: normalized.price,
|
|
402
|
-
amount: normalized.amount,
|
|
403
|
-
});
|
|
354
|
+
if (!normalized.accepted) {
|
|
355
|
+
console.log(normalized.rejectReason);
|
|
404
356
|
}
|
|
405
357
|
```
|
|
406
358
|
|
|
407
|
-
`normalizeOrderInput()`
|
|
359
|
+
`normalizeOrderInput()` 会按 `priceStep` / `amountStep` 向下取整,并检查 `minAmount` / `minNotional`。它不会自动帮你下单,调用方需要把归一后的 string 放入 `createOrder()`。
|
|
408
360
|
|
|
409
|
-
|
|
361
|
+
### 5.3 Server time
|
|
410
362
|
|
|
411
363
|
```ts
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
symbol: "BTC/USDT:USDT",
|
|
415
|
-
price: "1000.09",
|
|
416
|
-
amount: "0.0049",
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// 示例返回:
|
|
420
|
-
// {
|
|
421
|
-
// price: "1000",
|
|
422
|
-
// amount: "0.004",
|
|
423
|
-
// rawPrice: "1000.09",
|
|
424
|
-
// rawAmount: "0.0049",
|
|
425
|
-
// adjusted: true,
|
|
426
|
-
// accepted: false,
|
|
427
|
-
// rejectReason: "notional_below_min",
|
|
428
|
-
// priceStep: "0.1",
|
|
429
|
-
// amountStep: "0.001",
|
|
430
|
-
// minAmount: "0.001",
|
|
431
|
-
// minNotional: "5"
|
|
432
|
-
// }
|
|
364
|
+
const time = await client.market.fetchServerTime("binance");
|
|
365
|
+
console.log(time.serverTime, time.roundTripMs, time.estimatedOffsetMs);
|
|
433
366
|
```
|
|
434
367
|
|
|
435
|
-
`
|
|
368
|
+
当前 Binance server time 测量源固定为 USDⓈ-M REST `/fapi/v1/time`。失败会包装为 `MARKET_SERVER_TIME_FETCH_FAILED`。
|
|
436
369
|
|
|
437
|
-
|
|
370
|
+
### 5.4 Funding rate
|
|
438
371
|
|
|
439
|
-
|
|
440
|
-
|---|---|---|
|
|
441
|
-
| `BASE/QUOTE` | 现货 | `BTC/USDT` |
|
|
442
|
-
| `BASE/QUOTE:SETTLE` | USDⓈ-M 永续 | `BTC/USDT:USDT` |
|
|
443
|
-
| `BASE/USD:BASE` | COIN-M 永续 | `BTC/USD:BTC` |
|
|
444
|
-
| `BASE/USD:BASE-YYYYMMDD` | COIN-M 交割 | `BTC/USD:BTC-20250627` |
|
|
372
|
+
Funding Rate 当前通过 Binance mark price websocket 更新,仅支持永续合约(`MarketDefinition.type === "swap"`,包括 Binance TradFi Perps)。spot 或 future 订阅会抛 `MARKET_FUNDING_RATE_UNSUPPORTED`。
|
|
445
373
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
### 5.2 L1 Book
|
|
374
|
+
## 6. AccountManager
|
|
449
375
|
|
|
450
376
|
```ts
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
symbol: "BTC/USDT:USDT",
|
|
454
|
-
});
|
|
377
|
+
interface AccountManager {
|
|
378
|
+
readonly events: AccountEventStreams;
|
|
455
379
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
symbol: "BTC/USDT:USDT",
|
|
459
|
-
});
|
|
380
|
+
subscribeAccount(input: SubscribeAccountInput): Promise<void>;
|
|
381
|
+
unsubscribeAccount(input: UnsubscribeAccountInput): Promise<void>;
|
|
460
382
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
383
|
+
getAccountSnapshot(accountId: string): AccountSnapshot | undefined;
|
|
384
|
+
getBalances(accountId: string): BalanceSnapshot[];
|
|
385
|
+
getBalance(accountId: string, asset: string): BalanceSnapshot | undefined;
|
|
386
|
+
getPositions(accountId: string, symbol?: string): PositionSnapshot[];
|
|
387
|
+
getPosition(input: PositionKeyInput): PositionSnapshot | undefined;
|
|
388
|
+
getRiskSnapshot(accountId: string): RiskSnapshot | undefined;
|
|
389
|
+
getAccountStatus(accountId: string): AccountDataStatus | undefined;
|
|
464
390
|
}
|
|
465
391
|
```
|
|
466
392
|
|
|
467
|
-
|
|
393
|
+
`AccountSnapshot.balances` 是 `Record<string, BalanceSnapshot>`,数组视图用 `getBalances()`。
|
|
468
394
|
|
|
469
|
-
|
|
470
|
-
for await (const event of client.market.events.l1BookUpdates({
|
|
471
|
-
venue: "binance",
|
|
472
|
-
symbol: "BTC/USDT:USDT",
|
|
473
|
-
})) {
|
|
474
|
-
console.log(event.snapshot.bidPrice.toFixed());
|
|
475
|
-
}
|
|
476
|
-
```
|
|
395
|
+
Binance account update 是 REST bootstrap + WS 增量 + REST risk refresh + private reconcile 的组合。risk refresh 是增量语义,不会因 REST 缺失项删除本地 position;private reconcile 是全量校准语义,会清理 REST 全量余额/仓位中缺失或归零的本地记录。Juplend 每次 poll 都是全量快照,成功 poll 会替换 balances / positions / risk,用于清理已关闭或不再匹配的 position。
|
|
477
396
|
|
|
478
|
-
|
|
397
|
+
Account 事件用于消费余额、仓位、风险或全量快照替换:
|
|
479
398
|
|
|
480
399
|
```ts
|
|
481
|
-
for await (const event of client.
|
|
482
|
-
|
|
400
|
+
for await (const event of client.account.events.updates({
|
|
401
|
+
accountId: "main-binance",
|
|
402
|
+
})) {
|
|
403
|
+
if (event.type === "risk.updated") {
|
|
404
|
+
console.log(event.snapshot.riskRatio);
|
|
405
|
+
}
|
|
406
|
+
break;
|
|
483
407
|
}
|
|
484
408
|
```
|
|
485
409
|
|
|
486
|
-
|
|
410
|
+
## 7. OrderManager
|
|
487
411
|
|
|
488
412
|
```ts
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
{ venue: "binance", symbol: "BTC/USD:BTC" },
|
|
492
|
-
];
|
|
493
|
-
|
|
494
|
-
for (const pair of pairs) await client.market.subscribeL1Book(pair);
|
|
413
|
+
interface OrderManager {
|
|
414
|
+
readonly events: OrderEventStreams;
|
|
495
415
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (books.some((b) => !b.book)) continue;
|
|
499
|
-
// 用 books 里的最新值做决策
|
|
500
|
-
}
|
|
501
|
-
```
|
|
416
|
+
subscribeOrders(input: SubscribeOrdersInput): Promise<void>;
|
|
417
|
+
unsubscribeOrders(input: UnsubscribeOrdersInput): Promise<void>;
|
|
502
418
|
|
|
503
|
-
|
|
419
|
+
createOrder(input: CreateOrderInput): Promise<OrderSnapshot>;
|
|
420
|
+
cancelOrder(input: CancelOrderInput): Promise<OrderSnapshot>;
|
|
421
|
+
cancelAllOrders(input: CancelAllOrdersInput): Promise<OrderSnapshot[]>;
|
|
504
422
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
});
|
|
423
|
+
getOrder(input: GetOrderInput): OrderSnapshot | undefined;
|
|
424
|
+
getOpenOrders(accountId: string, symbol?: string): OrderSnapshot[];
|
|
425
|
+
getOrderStatus(accountId: string): OrderDataStatus | undefined;
|
|
426
|
+
}
|
|
510
427
|
```
|
|
511
428
|
|
|
512
|
-
|
|
429
|
+
### 7.1 支持范围
|
|
513
430
|
|
|
514
|
-
|
|
431
|
+
- `createOrder()` 支持 `limit` / `market`
|
|
432
|
+
- `limit` 可传 `postOnly: true`,Binance PAPI UM 映射为 `timeInForce=GTX`
|
|
433
|
+
- `cancelOrder()` 必须传 `orderId` 或 `clientOrderId`
|
|
434
|
+
- `cancelAllOrders()` 必须传 `symbol`,不支持账户级全撤
|
|
435
|
+
- hedge mode 下必须显式传 `positionSide: "long" | "short"`
|
|
515
436
|
|
|
516
|
-
###
|
|
437
|
+
### 7.2 精度限制
|
|
517
438
|
|
|
518
|
-
|
|
439
|
+
`createOrder()` 不会自动纠偏。调用方应先用 `MarketDefinition.priceStep`、`amountStep`、`minAmount`、`minNotional` 和 `normalizeOrderInput()` 处理输入。交易所拒单会包装成 `ORDER_CREATE_FAILED`。
|
|
519
440
|
|
|
520
|
-
|
|
521
|
-
await client.market.subscribeFundingRate({
|
|
522
|
-
venue: "binance",
|
|
523
|
-
symbol: "BTC/USDT:USDT",
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
const funding = client.market.getFundingRate({
|
|
527
|
-
venue: "binance",
|
|
528
|
-
symbol: "BTC/USDT:USDT",
|
|
529
|
-
});
|
|
441
|
+
### 7.3 本地缓存与查询
|
|
530
442
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
console.log(funding.status.freshness);
|
|
537
|
-
}
|
|
538
|
-
```
|
|
443
|
+
- OrderManager 内部按 open / closed 分层缓存订单。**closed(filled / canceled / rejected / expired)订单按 symbol 各保留最近 N 个**,`N = CreateClientOptions.order.maxClosedOrdersPerSymbol`(默认 500,非正或非整数回退默认),超限按 FIFO 裁剪最旧;**open 订单不受此上限限制**。`getOpenOrders()` 查询复杂度与历史终态订单数量无关。
|
|
444
|
+
- `getOrder(input)` 需带 `orderId` 或 `clientOrderId`(否则返回 `undefined`),`symbol` 可选:
|
|
445
|
+
- 仅 `clientOrderId` 查询可命中 open 与未被裁剪的 closed;同一 `clientOrderId` 命中多笔时返回**最新一笔**(精确定位历史某一笔请用 `orderId`)。
|
|
446
|
+
- 同时给 `orderId` 与 `clientOrderId` 时,两者都匹配才命中。
|
|
447
|
+
- 已超出保留上限被裁剪的 closed 订单将查不到(返回 `undefined`)。
|
|
539
448
|
|
|
540
|
-
|
|
449
|
+
Order 事件用于消费订单状态变化和 open orders 快照校准。Binance private reconcile 会先用 `/papi/v1/um/openOrders` 校验当前 open set;本地 open order 从 open set 消失时,SDK 会优先查询单笔订单终态并发布 `order.filled` / `order.canceled` 等事件。若 Binance retention / not found 导致无法证明终态,SDK 不会合成 filled/canceled,而是保留原 snapshot 并把 order status 标记为 `degraded`,下一轮继续尝试。
|
|
541
450
|
|
|
542
451
|
```ts
|
|
543
|
-
for await (const event of client.
|
|
544
|
-
|
|
452
|
+
for await (const event of client.order.events.updates({
|
|
453
|
+
accountId: "main-binance",
|
|
545
454
|
symbol: "BTC/USDT:USDT",
|
|
546
455
|
})) {
|
|
547
|
-
|
|
456
|
+
if (event.type === "order.filled") {
|
|
457
|
+
console.log(event.snapshot.filled);
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
548
460
|
}
|
|
549
461
|
```
|
|
550
462
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
Funding Rate 也支持动态重复 `subscribe` / `unsubscribe`;对同一个 market 重复 `subscribeFundingRate()` 不会重复开流,退订后再次订阅会恢复维护。
|
|
554
|
-
|
|
555
|
-
### 5.4 订阅状态
|
|
463
|
+
## 8. 健康与错误事件
|
|
556
464
|
|
|
557
465
|
```ts
|
|
558
|
-
const
|
|
559
|
-
venue: "binance",
|
|
560
|
-
symbol: "BTC/USDT:USDT",
|
|
561
|
-
});
|
|
466
|
+
const health = client.getHealth();
|
|
562
467
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
status.freshness; // "fresh" | "stale" | "reconciling"
|
|
567
|
-
status.lastReceivedAt; // 最后收到数据的时间
|
|
568
|
-
status.reason; // "ws_disconnected" | "heartbeat_timeout" | "reconciling"
|
|
468
|
+
for await (const event of client.events.health({ venue: "binance" })) {
|
|
469
|
+
console.log(event.type);
|
|
470
|
+
break;
|
|
569
471
|
}
|
|
570
|
-
```
|
|
571
472
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
book?.status.freshness; // 只代表 L1 Book stream
|
|
577
|
-
|
|
578
|
-
const funding = client.market.getFundingRate({ venue, symbol });
|
|
579
|
-
funding?.status.freshness; // 只代表 Funding Rate stream
|
|
473
|
+
for await (const error of client.events.errors()) {
|
|
474
|
+
console.error(error.source, error.error);
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
580
477
|
```
|
|
581
478
|
|
|
582
|
-
`
|
|
583
|
-
|
|
584
|
-
- `ws_disconnected`:底层连接已断
|
|
585
|
-
- `heartbeat_timeout`:连接仍在但长时间没收到消息
|
|
479
|
+
`getHealth()` 聚合 client、market、account、order 的当前状态。`events.health(filter)` 只返回满足 filter 的事件;如果事件没有 filter 请求的字段,会被过滤掉。
|
|
586
480
|
|
|
587
|
-
|
|
481
|
+
## 9. 数据类型速查
|
|
588
482
|
|
|
589
|
-
|
|
483
|
+
以下类型均从 `@imbingox/acex` 根入口导出;以 package public types 为准。这里列常用形状,完整字段可由 TypeScript 自动补全。
|
|
590
484
|
|
|
591
485
|
```ts
|
|
592
|
-
|
|
593
|
-
|
|
486
|
+
type Venue = "binance" | "okx" | "bybit" | "gate" | "juplend";
|
|
487
|
+
type ClientStatus = "idle" | "starting" | "running" | "stopping" | "stopped";
|
|
488
|
+
type MarketType = "spot" | "swap" | "future";
|
|
489
|
+
type PositionSide = "long" | "short" | "net";
|
|
490
|
+
type CreateOrderType = "limit" | "market";
|
|
491
|
+
type OrderSide = "buy" | "sell";
|
|
492
|
+
type OrderStatus =
|
|
493
|
+
| "open"
|
|
494
|
+
| "partially_filled"
|
|
495
|
+
| "filled"
|
|
496
|
+
| "canceled"
|
|
497
|
+
| "rejected"
|
|
498
|
+
| "expired";
|
|
594
499
|
|
|
595
|
-
|
|
596
|
-
|
|
500
|
+
type PrivateRuntimeReason =
|
|
501
|
+
| "credentials_missing"
|
|
502
|
+
| "auth_failed"
|
|
503
|
+
| "http_failed"
|
|
504
|
+
| "rate_limited"
|
|
505
|
+
| "ws_disconnected"
|
|
506
|
+
| "heartbeat_timeout"
|
|
507
|
+
| "reconciling";
|
|
597
508
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
### 6.1 订阅与退订
|
|
609
|
-
|
|
610
|
-
```ts
|
|
611
|
-
await client.account.subscribeAccount({ accountId: "main-binance" });
|
|
612
|
-
await client.account.unsubscribeAccount({ accountId: "main-binance" });
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
- 调用前需要先 `registerAccount()`
|
|
616
|
-
- 凭证不足抛 `CREDENTIALS_MISSING`
|
|
617
|
-
- bootstrap 失败抛 `ACCOUNT_BOOTSTRAP_FAILED`
|
|
618
|
-
|
|
619
|
-
### 6.2 读快照
|
|
620
|
-
|
|
621
|
-
```ts
|
|
622
|
-
const snapshot = client.account.getAccountSnapshot("main-binance");
|
|
623
|
-
// AccountSnapshot.balances 是 Record<string, BalanceSnapshot>(按 asset 索引)
|
|
624
|
-
|
|
625
|
-
const balances = client.account.getBalances("main-binance");
|
|
626
|
-
// BalanceSnapshot[](数组视图)
|
|
627
|
-
|
|
628
|
-
const usdt = client.account.getBalance("main-binance", "USDT");
|
|
629
|
-
// BalanceSnapshot | undefined
|
|
630
|
-
|
|
631
|
-
const positions = client.account.getPositions("main-binance");
|
|
632
|
-
const btcPosition = client.account.getPosition({
|
|
633
|
-
accountId: "main-binance",
|
|
634
|
-
symbol: "BTC/USDT:USDT",
|
|
635
|
-
side: "long", // 双向持仓时必传;单向持仓可省略
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
const risk = client.account.getRiskSnapshot("main-binance");
|
|
509
|
+
type SubscriptionActivity = "active" | "inactive";
|
|
510
|
+
type MarketFreshness = "fresh" | "stale" | "reconciling";
|
|
511
|
+
type PrivateRuntimeStatus =
|
|
512
|
+
| "bootstrap_pending"
|
|
513
|
+
| "healthy"
|
|
514
|
+
| "degraded"
|
|
515
|
+
| "reconnecting"
|
|
516
|
+
| "reconciling"
|
|
517
|
+
| "stopped";
|
|
639
518
|
```
|
|
640
519
|
|
|
641
|
-
所有数量字段(`free` / `used` / `total` / `size` / `entryPrice` / `netEquity` / `riskEquity` / ...)都是 `BigNumber`。
|
|
642
|
-
|
|
643
|
-
`RiskSnapshot.netEquity` 表示不含风控折算的净资产价值;`riskEquity` 表示抵押系数或清算阈值折算后的风控净权益。Binance 使用 `actualEquity` / `accountEquity` 映射这两个字段;Juplend 使用 `totalCollateralUsd - totalDebtUsd` / `Σ(suppliedValue × liquidationThreshold) - totalDebtUsd`。
|
|
644
|
-
|
|
645
|
-
> **注意**:`AccountSnapshot.balances` 是 `Record<string, BalanceSnapshot>`,不是数组;需要数组视图用 `getBalances()`。
|
|
646
|
-
|
|
647
|
-
### 6.3 事件
|
|
648
|
-
|
|
649
520
|
```ts
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
521
|
+
interface VenueCapabilities {
|
|
522
|
+
venue: Venue;
|
|
523
|
+
runtimeStatus: "available" | "type_only" | "reserved";
|
|
524
|
+
readOnly: boolean;
|
|
525
|
+
notes: string[];
|
|
526
|
+
market: {
|
|
527
|
+
catalog: "supported" | "unsupported";
|
|
528
|
+
serverTime: "supported" | "unsupported";
|
|
529
|
+
l1Book: "supported" | "unsupported";
|
|
530
|
+
fundingRate: "supported" | "unsupported" | "market_dependent";
|
|
531
|
+
marketTypes: MarketType[];
|
|
532
|
+
};
|
|
533
|
+
account: {
|
|
534
|
+
register: "supported" | "unsupported";
|
|
535
|
+
snapshot: "supported" | "unsupported";
|
|
536
|
+
updates: "websocket" | "polling" | "unsupported";
|
|
537
|
+
balances: "supported" | "unsupported";
|
|
538
|
+
positions: "supported" | "unsupported";
|
|
539
|
+
risk: "supported" | "unsupported";
|
|
540
|
+
lending: "supported" | "unsupported";
|
|
541
|
+
credentialsRequired: boolean;
|
|
542
|
+
};
|
|
543
|
+
order: {
|
|
544
|
+
supported: boolean;
|
|
545
|
+
openOrders: "supported" | "unsupported";
|
|
546
|
+
updates: "websocket" | "polling" | "unsupported";
|
|
547
|
+
create: "supported" | "unsupported";
|
|
548
|
+
cancel: "supported" | "unsupported";
|
|
549
|
+
cancelAll: "symbol" | "account" | "unsupported";
|
|
550
|
+
orderTypes: CreateOrderType[];
|
|
551
|
+
timeInForce: Array<"gtc" | "post_only">;
|
|
552
|
+
postOnly: boolean;
|
|
553
|
+
reduceOnly: boolean;
|
|
554
|
+
positionSide: "optional" | "required_for_hedge" | "unsupported";
|
|
555
|
+
clientOrderId: boolean;
|
|
556
|
+
reason?:
|
|
557
|
+
| "not_implemented"
|
|
558
|
+
| "read_only"
|
|
559
|
+
| "market_type_unsupported"
|
|
560
|
+
| "sdk_reserved";
|
|
561
|
+
};
|
|
667
562
|
}
|
|
668
563
|
```
|
|
669
564
|
|
|
670
|
-
### 6.4 订阅状态
|
|
671
|
-
|
|
672
565
|
```ts
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
566
|
+
interface CreateClientOptions {
|
|
567
|
+
sandbox?: boolean;
|
|
568
|
+
clock?: { now(): number };
|
|
569
|
+
rateLimiter?: RateLimiter;
|
|
570
|
+
logger?: Logger;
|
|
571
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
572
|
+
market?: {
|
|
573
|
+
l1InitialMessageTimeoutMs?: number;
|
|
574
|
+
l1StaleAfterMs?: number;
|
|
575
|
+
l1ReconnectDelayMs?: number;
|
|
576
|
+
l1ReconnectMaxDelayMs?: number;
|
|
577
|
+
};
|
|
578
|
+
account?: {
|
|
579
|
+
streamOpenTimeoutMs?: number;
|
|
580
|
+
streamReconnectDelayMs?: number;
|
|
581
|
+
streamReconnectMaxDelayMs?: number;
|
|
582
|
+
listenKeyKeepAliveMs?: number;
|
|
583
|
+
binance?: {
|
|
584
|
+
riskPollIntervalMs?: number;
|
|
585
|
+
privateReconcileIntervalMs?: number;
|
|
586
|
+
};
|
|
587
|
+
juplend?: {
|
|
588
|
+
pollIntervalMs?: number;
|
|
589
|
+
rpcUrl?: string;
|
|
590
|
+
jupApiKey?: string;
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
order?: {
|
|
594
|
+
maxClosedOrdersPerSymbol?: number;
|
|
595
|
+
};
|
|
696
596
|
}
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
### 7.1 订阅订单流
|
|
700
|
-
|
|
701
|
-
```ts
|
|
702
|
-
await client.order.subscribeOrders({ accountId: "main-binance" });
|
|
703
|
-
await client.order.unsubscribeOrders({ accountId: "main-binance" });
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
- 需要先 `registerAccount()`
|
|
707
|
-
- 凭证不足抛 `CREDENTIALS_MISSING`
|
|
708
|
-
- bootstrap(open orders 拉取)失败抛 `ORDER_BOOTSTRAP_FAILED`
|
|
709
|
-
|
|
710
|
-
### 7.2 读快照
|
|
711
|
-
|
|
712
|
-
```ts
|
|
713
|
-
const openOrders = client.order.getOpenOrders("main-binance");
|
|
714
|
-
const btcOrders = client.order.getOpenOrders("main-binance", "BTC/USDT:USDT");
|
|
715
|
-
|
|
716
|
-
const order = client.order.getOrder({
|
|
717
|
-
accountId: "main-binance",
|
|
718
|
-
orderId: "12345",
|
|
719
|
-
// 或 clientOrderId: "my-order-1"
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
const status = client.order.getOrderStatus("main-binance");
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### 7.3 下单
|
|
726
|
-
|
|
727
|
-
`createOrder()` 第一版支持 `limit` / `market` 两种类型。`price` / `amount` 是 decimal string。
|
|
728
|
-
|
|
729
|
-
```ts
|
|
730
|
-
const limit = await client.order.createOrder({
|
|
731
|
-
accountId: "main-binance",
|
|
732
|
-
symbol: "BTC/USDT:USDT",
|
|
733
|
-
side: "buy",
|
|
734
|
-
type: "limit",
|
|
735
|
-
price: "71830.6",
|
|
736
|
-
amount: "0.001",
|
|
737
|
-
clientOrderId: "my-order-1", // 可选
|
|
738
|
-
reduceOnly: false, // 可选
|
|
739
|
-
postOnly: true, // 可选:仅限 limit,Binance 映射为 GTX
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
const market = await client.order.createOrder({
|
|
743
|
-
accountId: "main-binance",
|
|
744
|
-
symbol: "BTC/USDT:USDT",
|
|
745
|
-
side: "sell",
|
|
746
|
-
type: "market",
|
|
747
|
-
amount: "0.001",
|
|
748
|
-
});
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
**双向持仓模式(hedge mode)必须显式传 `positionSide`**:
|
|
752
|
-
|
|
753
|
-
```ts
|
|
754
|
-
const hedge = await client.order.createOrder({
|
|
755
|
-
accountId: "main-binance",
|
|
756
|
-
symbol: "BTC/USDT:USDT",
|
|
757
|
-
side: "buy",
|
|
758
|
-
type: "limit",
|
|
759
|
-
price: "71900.9",
|
|
760
|
-
amount: "0.001",
|
|
761
|
-
positionSide: "long", // "long" | "short"
|
|
762
|
-
});
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
单向持仓模式可以省略 `positionSide`,返回的 snapshot 通常归一成 `"net"`。
|
|
766
|
-
|
|
767
|
-
`postOnly` 仅对 `limit` 单有效;当前 Binance PAPI UM adapter 会把普通 limit 单映射为 `timeInForce=GTC`,把 `postOnly: true` 映射为 `timeInForce=GTX`。
|
|
768
|
-
|
|
769
|
-
失败时抛 `ORDER_CREATE_FAILED`;输入本身不合法(比如 limit 单缺 price)抛 `ORDER_INPUT_INVALID`。
|
|
770
|
-
|
|
771
|
-
### 7.4 撤单
|
|
772
|
-
|
|
773
|
-
```ts
|
|
774
|
-
const canceled = await client.order.cancelOrder({
|
|
775
|
-
accountId: "main-binance",
|
|
776
|
-
symbol: "BTC/USDT:USDT",
|
|
777
|
-
orderId: "12345",
|
|
778
|
-
// 或 clientOrderId: "my-order-1"
|
|
779
|
-
});
|
|
780
597
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
`cancelOrder()` 要求 `orderId` / `clientOrderId` 至少一个,否则本地校验失败抛 `ORDER_INPUT_INVALID`。命令失败抛 `ORDER_CANCEL_FAILED` / `ORDER_CANCEL_ALL_FAILED`。
|
|
788
|
-
|
|
789
|
-
### 7.5 命令结果 vs 事件流
|
|
790
|
-
|
|
791
|
-
- `createOrder()` / `cancelOrder()` resolve 的是 **REST 成功后标准化的 `OrderSnapshot`**
|
|
792
|
-
- `events.updates()` 是订单的 **后续生命周期变化流**,不是唯一 ack 来源
|
|
793
|
-
|
|
794
|
-
也就是说,命令 resolve 不代表订单已终结。想追踪完整生命周期(部分成交 → 完全成交 / 撤销 / 拒绝)要同时消费事件。
|
|
795
|
-
|
|
796
|
-
### 7.6 事件
|
|
797
|
-
|
|
798
|
-
```ts
|
|
799
|
-
for await (const event of client.order.events.updates({
|
|
800
|
-
accountId: "main-binance",
|
|
801
|
-
})) {
|
|
802
|
-
switch (event.type) {
|
|
803
|
-
case "order.updated":
|
|
804
|
-
console.log("更新", event.snapshot.status, event.snapshot.filled.toFixed());
|
|
805
|
-
break;
|
|
806
|
-
case "order.filled":
|
|
807
|
-
console.log("全部成交", event.snapshot.avgFillPrice?.toFixed());
|
|
808
|
-
break;
|
|
809
|
-
case "order.canceled":
|
|
810
|
-
console.log("已撤单");
|
|
811
|
-
break;
|
|
812
|
-
case "order.rejected":
|
|
813
|
-
console.log("被拒绝");
|
|
814
|
-
break;
|
|
815
|
-
case "order.snapshot_replaced":
|
|
816
|
-
// 私有链路重连/重对账后的全量订单集合替换
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
598
|
+
interface RateLimitScope {
|
|
599
|
+
venue: Venue;
|
|
600
|
+
accountId?: string;
|
|
601
|
+
endpointKey: string;
|
|
819
602
|
}
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
### 7.7 Binance PAPI UM 精度约束
|
|
823
603
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
### 8.1 `getHealth()`
|
|
829
|
-
|
|
830
|
-
```ts
|
|
831
|
-
const health = client.getHealth();
|
|
832
|
-
// {
|
|
833
|
-
// clientStatus: "running",
|
|
834
|
-
// markets: MarketDataStatus[],
|
|
835
|
-
// accounts: AccountDataStatus[],
|
|
836
|
-
// orders: OrderDataStatus[],
|
|
837
|
-
// updatedAt: 1710000000000,
|
|
838
|
-
// }
|
|
839
|
-
```
|
|
840
|
-
|
|
841
|
-
### 8.2 `events.health()`
|
|
842
|
-
|
|
843
|
-
```ts
|
|
844
|
-
for await (const event of client.events.health()) {
|
|
845
|
-
switch (event.type) {
|
|
846
|
-
case "client.status_changed":
|
|
847
|
-
console.log("client", event.status);
|
|
848
|
-
break;
|
|
849
|
-
case "market.status_changed":
|
|
850
|
-
console.log("market", event.venue, event.symbol, event.status.activity);
|
|
851
|
-
break;
|
|
852
|
-
case "account.status_changed":
|
|
853
|
-
console.log("account", event.accountId, event.status.runtimeStatus);
|
|
854
|
-
break;
|
|
855
|
-
case "order.status_changed":
|
|
856
|
-
console.log("order", event.accountId, event.status.runtimeStatus);
|
|
857
|
-
break;
|
|
858
|
-
}
|
|
604
|
+
interface RateLimitUsage {
|
|
605
|
+
weight?: Record<string, number>;
|
|
606
|
+
orderCount?: Record<string, number>;
|
|
859
607
|
}
|
|
860
|
-
```
|
|
861
608
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
```ts
|
|
865
|
-
// 只看 market 范围
|
|
866
|
-
for await (const e of client.events.health({ scope: "market" })) { /* ... */ }
|
|
867
|
-
|
|
868
|
-
// 只看某个 venue
|
|
869
|
-
for await (const e of client.events.health({ venue: "binance" })) { /* ... */ }
|
|
870
|
-
|
|
871
|
-
// 只看某个 account
|
|
872
|
-
for await (const e of client.events.health({ accountId: "main-binance" })) { /* ... */ }
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
### 8.3 `events.errors()`
|
|
876
|
-
|
|
877
|
-
```ts
|
|
878
|
-
for await (const err of client.events.errors()) {
|
|
879
|
-
console.error(`[${err.source}] ${err.error.message}`, {
|
|
880
|
-
venue: err.venue,
|
|
881
|
-
accountId: err.accountId,
|
|
882
|
-
symbol: err.symbol,
|
|
883
|
-
});
|
|
609
|
+
interface RateLimitRequestContext {
|
|
610
|
+
scope: RateLimitScope;
|
|
884
611
|
}
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
`AcexInternalError.source` 枚举:`"client" | "market" | "account" | "order" | "adapter" | "runtime"`。适合桥接到日志或告警系统。
|
|
888
612
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
```ts
|
|
894
|
-
import type {
|
|
895
|
-
Venue, ClientStatus, CreateClientOptions, VenueCapabilities,
|
|
896
|
-
VenueRuntimeStatus, VenueCapabilitySupport, VenueCapabilityReason,
|
|
897
|
-
FundingRateCapability, PrivateUpdateCapability,
|
|
898
|
-
CancelAllOrdersCapability, PositionSideCapability,
|
|
899
|
-
OrderTimeInForceCapability,
|
|
900
|
-
AccountCredentials,
|
|
901
|
-
MarketDefinition, L1Book, FundingRateSnapshot, MarketDataStatus,
|
|
902
|
-
MarketDataStreamStatus,
|
|
903
|
-
BalanceSnapshot, PositionSnapshot, RiskSnapshot, AccountSnapshot,
|
|
904
|
-
AccountDataStatus, CreateOrderInput, CancelOrderInput, CancelAllOrdersInput,
|
|
905
|
-
OrderSnapshot, OrderDataStatus, OrderSide, OrderStatus, PositionSide,
|
|
906
|
-
MarketEvent, AccountEvent, OrderEvent, HealthEvent,
|
|
907
|
-
AcexInternalError,
|
|
908
|
-
} from "@imbingox/acex";
|
|
909
|
-
import { BigNumber, AcexError } from "@imbingox/acex";
|
|
910
|
-
```
|
|
911
|
-
|
|
912
|
-
### 9.1 基础
|
|
913
|
-
|
|
914
|
-
```ts
|
|
915
|
-
const SUPPORTED_VENUES = ["binance", "okx", "bybit", "gate", "juplend"] as const;
|
|
916
|
-
type Venue = (typeof SUPPORTED_VENUES)[number];
|
|
917
|
-
|
|
918
|
-
type ClientStatus = "idle" | "starting" | "running" | "stopping" | "stopped";
|
|
919
|
-
type VenueRuntimeStatus = "available" | "type_only" | "reserved";
|
|
920
|
-
type VenueCapabilitySupport = "supported" | "unsupported";
|
|
921
|
-
type VenueCapabilityReason =
|
|
922
|
-
| "not_implemented" | "read_only"
|
|
923
|
-
| "market_type_unsupported" | "sdk_reserved";
|
|
924
|
-
|
|
925
|
-
type SubscriptionActivity = "active" | "inactive";
|
|
926
|
-
type MarketFreshness = "fresh" | "stale" | "reconciling";
|
|
927
|
-
type PrivateRuntimeStatus =
|
|
928
|
-
| "bootstrap_pending" | "healthy" | "degraded"
|
|
929
|
-
| "reconnecting" | "reconciling" | "stopped";
|
|
930
|
-
type PrivateRuntimeReason =
|
|
931
|
-
| "credentials_missing" | "auth_failed" | "http_failed" | "rate_limited"
|
|
932
|
-
| "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
933
|
-
|
|
934
|
-
type OrderSide = "buy" | "sell";
|
|
935
|
-
type OrderStatus =
|
|
936
|
-
| "open" | "partially_filled" | "filled"
|
|
937
|
-
| "canceled" | "rejected" | "expired";
|
|
938
|
-
type PositionSide = "long" | "short" | "net";
|
|
939
|
-
type CreateOrderType = "limit" | "market";
|
|
940
|
-
type MarketType = "spot" | "swap" | "future";
|
|
941
|
-
```
|
|
942
|
-
|
|
943
|
-
### 9.2 Client 配置
|
|
944
|
-
|
|
945
|
-
```ts
|
|
946
|
-
interface MarketRuntimeOptions {
|
|
947
|
-
l1InitialMessageTimeoutMs?: number; // 默认 15_000
|
|
948
|
-
l1StaleAfterMs?: number; // 默认 15_000
|
|
949
|
-
l1ReconnectDelayMs?: number; // 默认 1_000
|
|
950
|
-
l1ReconnectMaxDelayMs?: number; // 默认 10_000
|
|
613
|
+
interface RateLimitResponseContext {
|
|
614
|
+
status: number;
|
|
615
|
+
headers?: Headers;
|
|
616
|
+
usage?: RateLimitUsage;
|
|
951
617
|
}
|
|
952
618
|
|
|
953
|
-
interface
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
binance?: {
|
|
959
|
-
riskPollIntervalMs?: number; // 默认 5_000
|
|
960
|
-
};
|
|
961
|
-
juplend?: {
|
|
962
|
-
pollIntervalMs?: number;
|
|
963
|
-
rpcUrl?: string;
|
|
964
|
-
jupApiKey?: string;
|
|
965
|
-
};
|
|
619
|
+
interface RateLimitTransportErrorContext {
|
|
620
|
+
status?: number;
|
|
621
|
+
headers?: Headers;
|
|
622
|
+
retryAfterMs?: number;
|
|
623
|
+
usage?: RateLimitUsage;
|
|
966
624
|
}
|
|
967
625
|
|
|
968
|
-
interface
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
626
|
+
interface RateLimitSnapshot {
|
|
627
|
+
scope: RateLimitScope;
|
|
628
|
+
usage?: RateLimitUsage;
|
|
629
|
+
blockedUntil?: number;
|
|
630
|
+
retryAfterMs?: number;
|
|
631
|
+
state: "ok" | "rate_limited" | "banned";
|
|
632
|
+
updatedAt?: number;
|
|
974
633
|
}
|
|
975
634
|
|
|
976
|
-
interface
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
635
|
+
interface RateLimiter {
|
|
636
|
+
beforeRequest(ctx: RateLimitRequestContext): Promise<void> | void;
|
|
637
|
+
afterResponse(
|
|
638
|
+
ctx: RateLimitRequestContext,
|
|
639
|
+
response: RateLimitResponseContext,
|
|
640
|
+
): Promise<void> | void;
|
|
641
|
+
onTransportError(
|
|
642
|
+
ctx: RateLimitRequestContext,
|
|
643
|
+
error: RateLimitTransportErrorContext,
|
|
644
|
+
): Promise<void> | void;
|
|
645
|
+
getSnapshot(scope: RateLimitScope): RateLimitSnapshot | undefined;
|
|
981
646
|
}
|
|
982
647
|
|
|
983
|
-
interface
|
|
984
|
-
|
|
985
|
-
|
|
648
|
+
interface Logger {
|
|
649
|
+
debug(msg: string, context?: Record<string, unknown>): void;
|
|
650
|
+
info(msg: string, context?: Record<string, unknown>): void;
|
|
651
|
+
warn(msg: string, context?: Record<string, unknown>): void;
|
|
652
|
+
error(msg: string, context?: Record<string, unknown>): void;
|
|
986
653
|
}
|
|
987
654
|
|
|
988
|
-
type JuplendAccountOptions =
|
|
989
|
-
| {
|
|
990
|
-
walletAddress: string;
|
|
991
|
-
vaultId?: string;
|
|
992
|
-
positionId?: string;
|
|
993
|
-
}
|
|
994
|
-
| {
|
|
995
|
-
walletAddress?: string;
|
|
996
|
-
vaultId: string;
|
|
997
|
-
positionId: string;
|
|
998
|
-
};
|
|
999
|
-
|
|
1000
655
|
type RegisterAccountInput =
|
|
1001
656
|
| {
|
|
1002
657
|
accountId: string;
|
|
1003
658
|
venue: "binance" | "okx" | "bybit" | "gate";
|
|
1004
659
|
credentials?: AccountCredentials;
|
|
1005
|
-
options?:
|
|
660
|
+
options?: {
|
|
661
|
+
timestamp?: number;
|
|
662
|
+
recvWindow?: number;
|
|
663
|
+
};
|
|
1006
664
|
}
|
|
1007
665
|
| {
|
|
1008
666
|
accountId: string;
|
|
1009
667
|
venue: "juplend";
|
|
1010
668
|
credentials?: AccountCredentials;
|
|
1011
|
-
options:
|
|
669
|
+
options:
|
|
670
|
+
| {
|
|
671
|
+
walletAddress: string;
|
|
672
|
+
vaultId?: string;
|
|
673
|
+
positionId?: string;
|
|
674
|
+
}
|
|
675
|
+
| {
|
|
676
|
+
walletAddress?: string;
|
|
677
|
+
vaultId: string;
|
|
678
|
+
positionId: string;
|
|
679
|
+
};
|
|
1012
680
|
};
|
|
1013
681
|
|
|
1014
|
-
interface
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
interface StopOptions {
|
|
1020
|
-
graceful?: boolean;
|
|
1021
|
-
timeoutMs?: number;
|
|
1022
|
-
}
|
|
1023
|
-
```
|
|
1024
|
-
|
|
1025
|
-
### 9.2.1 Venue Capabilities
|
|
1026
|
-
|
|
1027
|
-
```ts
|
|
1028
|
-
type FundingRateCapability =
|
|
1029
|
-
| VenueCapabilitySupport
|
|
1030
|
-
| "market_dependent";
|
|
1031
|
-
|
|
1032
|
-
type PrivateUpdateCapability =
|
|
1033
|
-
| "websocket"
|
|
1034
|
-
| "polling"
|
|
1035
|
-
| "unsupported";
|
|
1036
|
-
|
|
1037
|
-
type CancelAllOrdersCapability =
|
|
1038
|
-
| "symbol"
|
|
1039
|
-
| "account"
|
|
1040
|
-
| "unsupported";
|
|
1041
|
-
|
|
1042
|
-
type PositionSideCapability =
|
|
1043
|
-
| "optional"
|
|
1044
|
-
| "required_for_hedge"
|
|
1045
|
-
| "unsupported";
|
|
1046
|
-
|
|
1047
|
-
type OrderTimeInForceCapability = "gtc" | "post_only";
|
|
1048
|
-
|
|
1049
|
-
interface VenueCapabilities {
|
|
1050
|
-
venue: Venue;
|
|
1051
|
-
runtimeStatus: VenueRuntimeStatus;
|
|
1052
|
-
readOnly: boolean;
|
|
1053
|
-
notes: string[];
|
|
1054
|
-
market: {
|
|
1055
|
-
catalog: VenueCapabilitySupport;
|
|
1056
|
-
l1Book: VenueCapabilitySupport;
|
|
1057
|
-
fundingRate: FundingRateCapability;
|
|
1058
|
-
marketTypes: MarketType[];
|
|
1059
|
-
};
|
|
1060
|
-
account: {
|
|
1061
|
-
register: VenueCapabilitySupport;
|
|
1062
|
-
snapshot: VenueCapabilitySupport;
|
|
1063
|
-
updates: PrivateUpdateCapability;
|
|
1064
|
-
balances: VenueCapabilitySupport;
|
|
1065
|
-
positions: VenueCapabilitySupport;
|
|
1066
|
-
risk: VenueCapabilitySupport;
|
|
1067
|
-
lending: VenueCapabilitySupport;
|
|
1068
|
-
credentialsRequired: boolean;
|
|
1069
|
-
};
|
|
1070
|
-
order: {
|
|
1071
|
-
supported: boolean;
|
|
1072
|
-
openOrders: VenueCapabilitySupport;
|
|
1073
|
-
updates: PrivateUpdateCapability;
|
|
1074
|
-
create: VenueCapabilitySupport;
|
|
1075
|
-
cancel: VenueCapabilitySupport;
|
|
1076
|
-
cancelAll: CancelAllOrdersCapability;
|
|
1077
|
-
orderTypes: CreateOrderType[];
|
|
1078
|
-
timeInForce: OrderTimeInForceCapability[];
|
|
1079
|
-
postOnly: boolean;
|
|
1080
|
-
reduceOnly: boolean;
|
|
1081
|
-
positionSide: PositionSideCapability;
|
|
1082
|
-
clientOrderId: boolean;
|
|
1083
|
-
reason?: VenueCapabilityReason;
|
|
1084
|
-
};
|
|
682
|
+
interface AccountCredentials {
|
|
683
|
+
apiKey?: string;
|
|
684
|
+
secret?: string;
|
|
685
|
+
password?: string;
|
|
686
|
+
extra?: Record<string, string>;
|
|
1085
687
|
}
|
|
1086
688
|
```
|
|
1087
689
|
|
|
1088
|
-
### 9.3 Market
|
|
1089
|
-
|
|
1090
690
|
```ts
|
|
1091
691
|
interface MarketDefinition {
|
|
1092
692
|
venue: Venue;
|
|
1093
|
-
symbol: string;
|
|
1094
|
-
id: string;
|
|
693
|
+
symbol: string;
|
|
694
|
+
id: string;
|
|
1095
695
|
type: MarketType;
|
|
1096
696
|
base: string;
|
|
1097
697
|
quote: string;
|
|
@@ -1100,80 +700,32 @@ interface MarketDefinition {
|
|
|
1100
700
|
contract: boolean;
|
|
1101
701
|
linear?: boolean;
|
|
1102
702
|
inverse?: boolean;
|
|
1103
|
-
contractSize?:
|
|
703
|
+
contractSize?: string;
|
|
1104
704
|
pricePrecision: number;
|
|
1105
705
|
amountPrecision: number;
|
|
1106
|
-
priceStep: BigNumber;
|
|
1107
|
-
amountStep: BigNumber;
|
|
1108
|
-
minAmount?: BigNumber;
|
|
1109
|
-
minNotional?: BigNumber;
|
|
1110
|
-
expiry?: number;
|
|
1111
|
-
raw: Record<string, unknown>;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
interface MarketKeyInput {
|
|
1115
|
-
venue: Venue;
|
|
1116
|
-
symbol: string;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
type DecimalInput = string | number | BigNumber;
|
|
1120
|
-
|
|
1121
|
-
type NormalizeOrderInputRejectReason =
|
|
1122
|
-
| "price_not_positive"
|
|
1123
|
-
| "amount_not_positive"
|
|
1124
|
-
| "amount_below_min"
|
|
1125
|
-
| "notional_below_min";
|
|
1126
|
-
|
|
1127
|
-
interface NormalizeOrderInputInput extends MarketKeyInput {
|
|
1128
|
-
price: DecimalInput;
|
|
1129
|
-
amount: DecimalInput;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
interface NormalizedOrderInput {
|
|
1133
|
-
price: string;
|
|
1134
|
-
amount: string;
|
|
1135
|
-
rawPrice: string;
|
|
1136
|
-
rawAmount: string;
|
|
1137
|
-
adjusted: boolean;
|
|
1138
|
-
accepted: boolean;
|
|
1139
|
-
rejectReason?: NormalizeOrderInputRejectReason;
|
|
1140
706
|
priceStep: string;
|
|
1141
707
|
amountStep: string;
|
|
1142
708
|
minAmount?: string;
|
|
1143
709
|
minNotional?: string;
|
|
710
|
+
expiry?: number;
|
|
711
|
+
raw: Record<string, unknown>;
|
|
1144
712
|
}
|
|
1145
713
|
|
|
1146
|
-
interface
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
symbol?: string;
|
|
714
|
+
interface VenueServerTime {
|
|
715
|
+
serverTime: number;
|
|
716
|
+
requestSentAt: number;
|
|
717
|
+
responseReceivedAt: number;
|
|
718
|
+
roundTripMs: number;
|
|
719
|
+
estimatedOffsetMs: number;
|
|
1153
720
|
}
|
|
1154
721
|
|
|
1155
722
|
interface L1Book {
|
|
1156
723
|
venue: Venue;
|
|
1157
724
|
symbol: string;
|
|
1158
|
-
bidPrice:
|
|
1159
|
-
bidSize:
|
|
1160
|
-
askPrice:
|
|
1161
|
-
askSize:
|
|
1162
|
-
exchangeTs?: number;
|
|
1163
|
-
receivedAt: number;
|
|
1164
|
-
updatedAt: number;
|
|
1165
|
-
version: number;
|
|
1166
|
-
status: MarketDataStreamStatus;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
interface FundingRateSnapshot {
|
|
1170
|
-
venue: Venue;
|
|
1171
|
-
symbol: string;
|
|
1172
|
-
fundingRate: BigNumber;
|
|
1173
|
-
nextFundingTime?: number;
|
|
1174
|
-
markPrice?: BigNumber;
|
|
1175
|
-
indexPrice?: BigNumber;
|
|
1176
|
-
exchangeTs?: number;
|
|
725
|
+
bidPrice: string;
|
|
726
|
+
bidSize: string;
|
|
727
|
+
askPrice: string;
|
|
728
|
+
askSize: string;
|
|
1177
729
|
receivedAt: number;
|
|
1178
730
|
updatedAt: number;
|
|
1179
731
|
version: number;
|
|
@@ -1190,43 +742,39 @@ interface MarketDataStreamStatus {
|
|
|
1190
742
|
reason?: "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
1191
743
|
}
|
|
1192
744
|
|
|
1193
|
-
interface MarketDataStatus {
|
|
745
|
+
interface MarketDataStatus extends MarketDataStreamStatus {
|
|
1194
746
|
venue: Venue;
|
|
1195
747
|
symbol: string;
|
|
1196
|
-
activity: SubscriptionActivity;
|
|
1197
|
-
ready: boolean;
|
|
1198
|
-
freshness?: MarketFreshness;
|
|
1199
|
-
lastReceivedAt?: number;
|
|
1200
|
-
lastReadyAt?: number;
|
|
1201
|
-
inactiveSince?: number;
|
|
1202
|
-
reason?: "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
1203
748
|
}
|
|
1204
|
-
```
|
|
1205
|
-
|
|
1206
|
-
### 9.4 Account
|
|
1207
749
|
|
|
1208
|
-
|
|
1209
|
-
interface BalanceSnapshot {
|
|
1210
|
-
accountId: string;
|
|
750
|
+
interface FundingRateSnapshot {
|
|
1211
751
|
venue: Venue;
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
752
|
+
symbol: string;
|
|
753
|
+
fundingRate: string;
|
|
754
|
+
nextFundingTime?: number;
|
|
755
|
+
markPrice?: string;
|
|
756
|
+
indexPrice?: string;
|
|
1217
757
|
receivedAt: number;
|
|
1218
758
|
updatedAt: number;
|
|
1219
|
-
|
|
1220
|
-
|
|
759
|
+
version: number;
|
|
760
|
+
status: MarketDataStreamStatus;
|
|
1221
761
|
}
|
|
1222
762
|
|
|
1223
|
-
interface
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
763
|
+
interface BalanceSnapshot {
|
|
764
|
+
accountId: string;
|
|
765
|
+
venue: Venue;
|
|
766
|
+
asset: string;
|
|
767
|
+
free: string;
|
|
768
|
+
used: string;
|
|
769
|
+
total: string;
|
|
770
|
+
lending?: {
|
|
771
|
+
supplied: string;
|
|
772
|
+
borrowed: string;
|
|
773
|
+
interest: string;
|
|
774
|
+
netAsset: string;
|
|
775
|
+
supplyAPY?: string;
|
|
776
|
+
borrowAPY?: string;
|
|
777
|
+
};
|
|
1230
778
|
}
|
|
1231
779
|
|
|
1232
780
|
interface PositionSnapshot {
|
|
@@ -1234,52 +782,37 @@ interface PositionSnapshot {
|
|
|
1234
782
|
venue: Venue;
|
|
1235
783
|
symbol: string;
|
|
1236
784
|
side: PositionSide;
|
|
1237
|
-
size:
|
|
1238
|
-
entryPrice?:
|
|
1239
|
-
markPrice?:
|
|
1240
|
-
unrealizedPnl?:
|
|
1241
|
-
leverage?:
|
|
1242
|
-
liquidationPrice?:
|
|
1243
|
-
exchangeTs?: number;
|
|
1244
|
-
receivedAt: number;
|
|
1245
|
-
updatedAt: number;
|
|
1246
|
-
seq: number;
|
|
785
|
+
size: string;
|
|
786
|
+
entryPrice?: string;
|
|
787
|
+
markPrice?: string;
|
|
788
|
+
unrealizedPnl?: string;
|
|
789
|
+
leverage?: string;
|
|
790
|
+
liquidationPrice?: string;
|
|
1247
791
|
}
|
|
1248
792
|
|
|
1249
793
|
interface RiskSnapshot {
|
|
1250
794
|
accountId: string;
|
|
1251
795
|
venue: Venue;
|
|
1252
|
-
netEquity?:
|
|
1253
|
-
riskEquity?:
|
|
1254
|
-
riskRatio?:
|
|
1255
|
-
riskLeverage?:
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
interface LendingRiskFacet {
|
|
1266
|
-
marginLevel?: BigNumber;
|
|
1267
|
-
healthFactor?: BigNumber;
|
|
1268
|
-
ltv?: BigNumber;
|
|
1269
|
-
liquidationThreshold?: BigNumber;
|
|
1270
|
-
totalCollateralUSD?: BigNumber;
|
|
1271
|
-
totalDebtUSD?: BigNumber;
|
|
796
|
+
netEquity?: string;
|
|
797
|
+
riskEquity?: string;
|
|
798
|
+
riskRatio?: string;
|
|
799
|
+
riskLeverage?: string;
|
|
800
|
+
lending?: {
|
|
801
|
+
marginLevel?: string;
|
|
802
|
+
healthFactor?: string;
|
|
803
|
+
ltv?: string;
|
|
804
|
+
liquidationThreshold?: string;
|
|
805
|
+
totalCollateralUSD?: string;
|
|
806
|
+
totalDebtUSD?: string;
|
|
807
|
+
};
|
|
1272
808
|
}
|
|
1273
809
|
|
|
1274
810
|
interface AccountSnapshot {
|
|
1275
811
|
accountId: string;
|
|
1276
812
|
venue: Venue;
|
|
1277
|
-
balances: Record<string, BalanceSnapshot>;
|
|
813
|
+
balances: Record<string, BalanceSnapshot>;
|
|
1278
814
|
positions: PositionSnapshot[];
|
|
1279
815
|
risk?: RiskSnapshot;
|
|
1280
|
-
exchangeTs?: number;
|
|
1281
|
-
receivedAt: number;
|
|
1282
|
-
updatedAt: number;
|
|
1283
816
|
}
|
|
1284
817
|
|
|
1285
818
|
interface AccountDataStatus {
|
|
@@ -1288,25 +821,19 @@ interface AccountDataStatus {
|
|
|
1288
821
|
activity: SubscriptionActivity;
|
|
1289
822
|
ready: boolean;
|
|
1290
823
|
runtimeStatus?: PrivateRuntimeStatus;
|
|
1291
|
-
lastReceivedAt?: number;
|
|
1292
|
-
lastReadyAt?: number;
|
|
1293
|
-
inactiveSince?: number;
|
|
1294
824
|
reason?: PrivateRuntimeReason;
|
|
1295
825
|
}
|
|
1296
826
|
```
|
|
1297
827
|
|
|
1298
|
-
### 9.5 Order
|
|
1299
|
-
|
|
1300
828
|
```ts
|
|
1301
|
-
// limit / market 两个 variant
|
|
1302
829
|
type CreateOrderInput =
|
|
1303
830
|
| {
|
|
1304
831
|
accountId: string;
|
|
1305
832
|
symbol: string;
|
|
1306
833
|
side: OrderSide;
|
|
1307
834
|
type: "limit";
|
|
1308
|
-
price: string;
|
|
1309
|
-
amount: string;
|
|
835
|
+
price: string;
|
|
836
|
+
amount: string;
|
|
1310
837
|
postOnly?: boolean;
|
|
1311
838
|
clientOrderId?: string;
|
|
1312
839
|
reduceOnly?: boolean;
|
|
@@ -1317,7 +844,7 @@ type CreateOrderInput =
|
|
|
1317
844
|
symbol: string;
|
|
1318
845
|
side: OrderSide;
|
|
1319
846
|
type: "market";
|
|
1320
|
-
amount: string;
|
|
847
|
+
amount: string;
|
|
1321
848
|
clientOrderId?: string;
|
|
1322
849
|
reduceOnly?: boolean;
|
|
1323
850
|
positionSide?: PositionSide;
|
|
@@ -1335,12 +862,6 @@ interface CancelAllOrdersInput {
|
|
|
1335
862
|
symbol: string;
|
|
1336
863
|
}
|
|
1337
864
|
|
|
1338
|
-
interface GetOrderInput {
|
|
1339
|
-
accountId: string;
|
|
1340
|
-
orderId?: string;
|
|
1341
|
-
clientOrderId?: string;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
865
|
interface OrderSnapshot {
|
|
1345
866
|
accountId: string;
|
|
1346
867
|
venue: Venue;
|
|
@@ -1348,20 +869,13 @@ interface OrderSnapshot {
|
|
|
1348
869
|
clientOrderId?: string;
|
|
1349
870
|
symbol: string;
|
|
1350
871
|
side: OrderSide;
|
|
1351
|
-
type: string;
|
|
872
|
+
type: string;
|
|
1352
873
|
status: OrderStatus;
|
|
1353
|
-
price?:
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
remaining?: BigNumber;
|
|
1358
|
-
reduceOnly?: boolean;
|
|
874
|
+
price?: string;
|
|
875
|
+
amount: string;
|
|
876
|
+
filled: string;
|
|
877
|
+
remaining?: string;
|
|
1359
878
|
positionSide?: PositionSide;
|
|
1360
|
-
avgFillPrice?: BigNumber;
|
|
1361
|
-
exchangeTs?: number;
|
|
1362
|
-
receivedAt: number;
|
|
1363
|
-
updatedAt: number;
|
|
1364
|
-
seq: number;
|
|
1365
879
|
}
|
|
1366
880
|
|
|
1367
881
|
interface OrderDataStatus {
|
|
@@ -1370,123 +884,81 @@ interface OrderDataStatus {
|
|
|
1370
884
|
activity: SubscriptionActivity;
|
|
1371
885
|
ready: boolean;
|
|
1372
886
|
runtimeStatus?: PrivateRuntimeStatus;
|
|
1373
|
-
lastReceivedAt?: number;
|
|
1374
|
-
lastReadyAt?: number;
|
|
1375
|
-
inactiveSince?: number;
|
|
1376
887
|
reason?: PrivateRuntimeReason;
|
|
1377
888
|
}
|
|
1378
889
|
```
|
|
1379
890
|
|
|
1380
|
-
### 9.6 事件
|
|
1381
|
-
|
|
1382
891
|
```ts
|
|
1383
|
-
// Market
|
|
1384
892
|
type MarketEvent =
|
|
1385
893
|
| { type: "l1_book.updated"; venue: Venue; symbol: string; snapshot: L1Book; ts: number }
|
|
1386
894
|
| { type: "funding_rate.updated"; venue: Venue; symbol: string; snapshot: FundingRateSnapshot; ts: number }
|
|
1387
895
|
| { type: "market.status_changed"; venue: Venue; symbol: string; status: MarketDataStatus; ts: number };
|
|
1388
896
|
|
|
1389
|
-
// Account
|
|
1390
897
|
type AccountEvent =
|
|
1391
|
-
| { type: "balance.updated"; accountId: string; venue: Venue;
|
|
1392
|
-
| { type: "position.updated"; accountId: string; venue: Venue;
|
|
1393
|
-
| { type: "risk.updated"; accountId: string; venue: Venue;
|
|
1394
|
-
| { type: "account.snapshot_replaced"; accountId: string; venue: Venue;
|
|
898
|
+
| { type: "balance.updated"; accountId: string; venue: Venue; asset: string; snapshot: BalanceSnapshot; ts: number }
|
|
899
|
+
| { type: "position.updated"; accountId: string; venue: Venue; symbol: string; snapshot: PositionSnapshot; ts: number }
|
|
900
|
+
| { type: "risk.updated"; accountId: string; venue: Venue; snapshot: RiskSnapshot; ts: number }
|
|
901
|
+
| { type: "account.snapshot_replaced"; accountId: string; venue: Venue; snapshot: AccountSnapshot; ts: number };
|
|
1395
902
|
|
|
1396
|
-
// Order
|
|
1397
903
|
type OrderEvent =
|
|
1398
|
-
| { type: "order.updated"; accountId: string; venue: Venue; symbol: string;
|
|
1399
|
-
| { type: "order.filled"; accountId: string; venue: Venue; symbol: string;
|
|
1400
|
-
| { type: "order.canceled"; accountId: string; venue: Venue; symbol: string;
|
|
1401
|
-
| { type: "order.rejected"; accountId: string; venue: Venue; symbol: string;
|
|
1402
|
-
| { type: "order.snapshot_replaced"; accountId: string; venue: Venue;
|
|
1403
|
-
|
|
1404
|
-
// Health
|
|
1405
|
-
type HealthEvent =
|
|
1406
|
-
| { type: "client.status_changed"; status: ClientStatus; ts: number }
|
|
1407
|
-
| { type: "market.status_changed"; venue: Venue; symbol: string; status: MarketDataStatus; ts: number }
|
|
1408
|
-
| { type: "account.status_changed"; accountId: string; venue: Venue; status: AccountDataStatus; ts: number }
|
|
1409
|
-
| { type: "order.status_changed"; accountId: string; venue: Venue; status: OrderDataStatus; ts: number };
|
|
1410
|
-
```
|
|
1411
|
-
|
|
1412
|
-
过滤器:
|
|
1413
|
-
|
|
1414
|
-
```ts
|
|
1415
|
-
interface MarketEventFilter { venue?: Venue; symbol?: string; }
|
|
1416
|
-
interface AccountEventFilter { accountId?: string; venue?: Venue; symbol?: string; }
|
|
1417
|
-
interface OrderEventFilter { accountId?: string; venue?: Venue; symbol?: string; }
|
|
1418
|
-
interface HealthEventFilter {
|
|
1419
|
-
scope?: "client" | "market" | "account" | "order";
|
|
1420
|
-
venue?: Venue;
|
|
1421
|
-
accountId?: string;
|
|
1422
|
-
symbol?: string;
|
|
1423
|
-
}
|
|
1424
|
-
```
|
|
1425
|
-
|
|
1426
|
-
### 9.7 错误
|
|
1427
|
-
|
|
1428
|
-
```ts
|
|
1429
|
-
interface AcexInternalError {
|
|
1430
|
-
source: "client" | "market" | "account" | "order" | "adapter" | "runtime";
|
|
1431
|
-
venue?: Venue;
|
|
1432
|
-
accountId?: string;
|
|
1433
|
-
symbol?: string;
|
|
1434
|
-
error: Error;
|
|
1435
|
-
ts: number;
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
class AcexError extends Error {
|
|
1439
|
-
readonly code: AcexErrorCode;
|
|
1440
|
-
}
|
|
904
|
+
| { type: "order.updated"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
905
|
+
| { type: "order.filled"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
906
|
+
| { type: "order.canceled"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
907
|
+
| { type: "order.rejected"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
908
|
+
| { type: "order.snapshot_replaced"; accountId: string; venue: Venue; snapshot: OrderSnapshot[]; ts: number };
|
|
1441
909
|
```
|
|
1442
910
|
|
|
1443
911
|
## 10. 错误处理
|
|
1444
912
|
|
|
1445
|
-
|
|
913
|
+
可预期错误统一抛 `AcexError`:
|
|
1446
914
|
|
|
1447
915
|
```ts
|
|
1448
916
|
import { AcexError } from "@imbingox/acex";
|
|
1449
917
|
|
|
1450
918
|
try {
|
|
1451
919
|
await client.market.subscribeL1Book({ venue: "binance", symbol: "X/Y:Z" });
|
|
1452
|
-
} catch (
|
|
1453
|
-
if (
|
|
1454
|
-
console.log(
|
|
920
|
+
} catch (error) {
|
|
921
|
+
if (error instanceof AcexError) {
|
|
922
|
+
console.log(error.code);
|
|
923
|
+
console.log(error.details?.venueError?.code);
|
|
924
|
+
console.log(error.details?.transport?.status);
|
|
1455
925
|
}
|
|
1456
926
|
}
|
|
1457
927
|
```
|
|
1458
928
|
|
|
1459
|
-
|
|
929
|
+
`details.venueError` 是读取交易所结构化拒绝原因的首选字段;`details.transport` 保存已脱敏的 HTTP / transport 诊断信息;`cause` 保留底层错误链。
|
|
930
|
+
|
|
931
|
+
完整错误码:
|
|
1460
932
|
|
|
1461
933
|
| Code | 典型场景 |
|
|
1462
934
|
|---|---|
|
|
1463
|
-
| `CLIENT_NOT_STARTED` | 未
|
|
1464
|
-
| `VENUE_NOT_SUPPORTED` | venue
|
|
1465
|
-
| `MARKET_CATALOG_LOAD_FAILED` |
|
|
935
|
+
| `CLIENT_NOT_STARTED` | 未 start 就调用订阅方法 |
|
|
936
|
+
| `VENUE_NOT_SUPPORTED` | venue runtime 未实现,或 read-only venue 被用于下单 |
|
|
937
|
+
| `MARKET_CATALOG_LOAD_FAILED` | market catalog 拉取失败 |
|
|
938
|
+
| `MARKET_SERVER_TIME_FETCH_FAILED` | server time 请求失败或响应结构不合法 |
|
|
939
|
+
| `MARKET_INACTIVE` | catalog 中 market 不活跃 |
|
|
940
|
+
| `MARKET_FUNDING_RATE_UNSUPPORTED` | 指定 market 不支持 funding rate |
|
|
1466
941
|
| `MARKET_NOT_FOUND` | 指定 symbol 不存在 |
|
|
1467
|
-
| `MARKET_INACTIVE` | 指定 symbol 在 catalog 中但不可交易 |
|
|
1468
|
-
| `MARKET_FUNDING_RATE_UNSUPPORTED` | 指定 market 不支持资金费率订阅 |
|
|
1469
942
|
| `MARKET_STREAM_TIMEOUT` | market stream 首条消息超时 |
|
|
1470
|
-
| `ACCOUNT_ALREADY_EXISTS` |
|
|
1471
|
-
| `
|
|
1472
|
-
| `
|
|
1473
|
-
| `CREDENTIALS_MISSING` |
|
|
1474
|
-
| `ORDER_BOOTSTRAP_FAILED` |
|
|
1475
|
-
| `ORDER_INPUT_INVALID` |
|
|
1476
|
-
| `ORDER_CREATE_FAILED` |
|
|
943
|
+
| `ACCOUNT_ALREADY_EXISTS` | 重复注册 accountId |
|
|
944
|
+
| `ACCOUNT_BOOTSTRAP_FAILED` | account bootstrap 失败 |
|
|
945
|
+
| `ACCOUNT_NOT_FOUND` | accountId 未注册或已移除 |
|
|
946
|
+
| `CREDENTIALS_MISSING` | private 订阅或下单缺凭证 |
|
|
947
|
+
| `ORDER_BOOTSTRAP_FAILED` | open orders bootstrap 失败 |
|
|
948
|
+
| `ORDER_INPUT_INVALID` | 本地订单输入校验失败 |
|
|
949
|
+
| `ORDER_CREATE_FAILED` | 下单 REST 失败或交易所拒单 |
|
|
1477
950
|
| `ORDER_CANCEL_FAILED` | 撤单失败 |
|
|
1478
951
|
| `ORDER_CANCEL_ALL_FAILED` | 批量撤单失败 |
|
|
1479
952
|
|
|
1480
953
|
## 11. 当前限制
|
|
1481
954
|
|
|
1482
|
-
-
|
|
1483
|
-
-
|
|
1484
|
-
-
|
|
1485
|
-
-
|
|
1486
|
-
-
|
|
1487
|
-
-
|
|
1488
|
-
-
|
|
1489
|
-
-
|
|
1490
|
-
-
|
|
1491
|
-
-
|
|
1492
|
-
- **Client options**:`sandbox` / `logger` / `logLevel` 是预留位,当前不生效
|
|
955
|
+
- market/order runtime 当前只支持 `binance`
|
|
956
|
+
- account runtime 支持 `binance` 和只读 `juplend`
|
|
957
|
+
- `okx` / `bybit` / `gate` 只在 `Venue` 类型中声明
|
|
958
|
+
- Funding Rate 仅支持 Binance 永续合约,包括 Binance TradFi Perps
|
|
959
|
+
- Binance order 命令固定走 PAPI UM,venue 级 `order.supported = true` 不代表 spot、COIN-M 或交割合约都能下单
|
|
960
|
+
- `cancelAllOrders()` 必须带 `symbol`,不支持账户级全撤
|
|
961
|
+
- `createOrder()` 不支持条件单、改单
|
|
962
|
+
- SDK 不自动纠偏订单精度;下游应使用 `normalizeOrderInput()`
|
|
963
|
+
- Juplend 只读,不支持链上写操作和 `OrderManager`
|
|
964
|
+
- `sandbox`、`logger`、`logLevel` 为预留位
|