@imbingox/acex 0.4.0-beta.8 → 0.4.0-beta.9
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 +2 -2
- package/docs/api.md +460 -1059
- package/package.json +1 -1
- package/src/adapters/binance/market-catalog.ts +3 -1
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,7 +62,8 @@ 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",
|
|
@@ -60,18 +72,41 @@ for await (const event of client.market.events.l1BookUpdates({
|
|
|
60
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 共同维护。
|
|
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);
|
|
118
|
-
console.log(juplendRisk?.riskRatio);
|
|
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()` 彼此独立;退订其中一个不会影响另一个
|
|
193
|
+
如果首条数据迟迟不到,订阅 promise 会 reject。稳态期间断线不会清空旧快照;快照上的 `status.freshness` 会转为 `stale`。
|
|
167
194
|
|
|
168
|
-
### 3.3
|
|
195
|
+
### 3.3 Decimal string
|
|
169
196
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- 单 symbol 场景,直接用 `event.snapshot` 即可
|
|
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 Decimal string 约定
|
|
186
|
-
|
|
187
|
-
输出侧的价格、数量、金额统一是 canonical 十进制 string:无损、无科学计数法、不补尾零。SDK 仍 re-export `BigNumber`(来自 [bignumber.js](https://github.com/MikeMcl/bignumber.js))作为可选工具;需要运算时由调用方显式解析:
|
|
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 = new BigNumber(book!.askPrice).minus(
|
|
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
|
-
不要用 `parseFloat()`
|
|
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
|
+
退订后旧快照仍可读,但不再代表实时值。
|
|
208
221
|
|
|
209
|
-
|
|
222
|
+
## 4. Client 生命周期
|
|
223
|
+
|
|
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,
|
|
@@ -227,116 +245,57 @@ const client = createClient({
|
|
|
227
245
|
juplend: {
|
|
228
246
|
pollIntervalMs: 30_000,
|
|
229
247
|
rpcUrl: process.env.SOL_HELIUS_RPC,
|
|
248
|
+
jupApiKey: process.env.JUP_API,
|
|
230
249
|
},
|
|
231
250
|
},
|
|
232
251
|
});
|
|
233
252
|
```
|
|
234
253
|
|
|
235
|
-
`sandbox`、`logger`、`logLevel`
|
|
254
|
+
`clock` 只用于 outbound request / signing timestamp,不驱动 WebSocket freshness 的 received-at 时钟。需要自定义 REST 限流行为时可传 `rateLimiter`,否则使用默认 reactive limiter。`sandbox`、`logger`、`logLevel` 目前是预留位。
|
|
236
255
|
|
|
237
256
|
### 4.2 `start()` / `stop()`
|
|
238
257
|
|
|
239
258
|
```ts
|
|
240
259
|
await client.start();
|
|
241
|
-
// ...
|
|
242
260
|
await client.stop();
|
|
243
|
-
await client.stop({ graceful: true, timeoutMs: 5_000 });
|
|
244
261
|
```
|
|
245
262
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
在 `start()` 之前调 `subscribe*()` 会直接失败,抛 `CLIENT_NOT_STARTED`。
|
|
263
|
+
状态机是 `idle → starting → running → stopping → stopped`。`start()` 和 `stop()` 幂等。`stop(options?)` 的 `graceful` / `timeoutMs` 当前是预留参数,不要依赖它们提供额外 drain 语义。
|
|
249
264
|
|
|
250
265
|
### 4.3 Venue capabilities
|
|
251
266
|
|
|
252
267
|
```ts
|
|
253
268
|
const binance = client.getVenueCapabilities("binance");
|
|
254
|
-
const
|
|
269
|
+
const all = client.listVenueCapabilities();
|
|
255
270
|
```
|
|
256
271
|
|
|
257
|
-
|
|
272
|
+
Capability 查询不访问网络,不要求 `start()`。返回值表达当前 SDK runtime 已实现能力,不代表交易所官网完整能力,也不检查 API key 权限。
|
|
258
273
|
|
|
259
|
-
|
|
274
|
+
当前摘要:
|
|
275
|
+
|
|
276
|
+
| Venue | runtimeStatus | readOnly | 关键能力 |
|
|
277
|
+
|---|---|---:|---|
|
|
278
|
+
| `binance` | `available` | false | market catalog / server time / L1;funding rate 为 `market_dependent`;order supported |
|
|
279
|
+
| `juplend` | `available` | true | account polling + lending;order reason 为 `read_only` |
|
|
280
|
+
| `okx` / `bybit` / `gate` | `type_only` | false | runtime 未接入,order reason 为 `not_implemented` |
|
|
281
|
+
|
|
282
|
+
下游应先查 capability 再展示或启用功能:
|
|
260
283
|
|
|
261
284
|
```ts
|
|
262
285
|
if (!client.getVenueCapabilities("juplend").order.supported) {
|
|
263
|
-
//
|
|
286
|
+
// 不展示下单按钮
|
|
264
287
|
}
|
|
265
288
|
```
|
|
266
289
|
|
|
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 账户注册
|
|
290
|
+
### 4.4 账户管理
|
|
280
291
|
|
|
281
292
|
```ts
|
|
282
|
-
await client.registerAccount(
|
|
283
|
-
accountId: "main-binance",
|
|
284
|
-
venue: "binance",
|
|
285
|
-
credentials: { apiKey, secret },
|
|
286
|
-
});
|
|
287
|
-
|
|
293
|
+
await client.registerAccount(input);
|
|
288
294
|
await client.updateAccountCredentials("main-binance", { apiKey, secret });
|
|
289
|
-
|
|
290
295
|
await client.removeAccount("main-binance");
|
|
291
296
|
```
|
|
292
297
|
|
|
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
|
-
```
|
|
298
|
+
`RegisterAccountInput` 按 venue 区分。CEX venue 使用 `AccountCredentials`;Juplend 必须显式提供 `walletAddress` 或 `vaultId + positionId`。虽然 public `Venue` 包含 type-only venue,但注册成功不代表该 venue runtime 能订阅或下单,仍以 capability 和实际调用结果为准。
|
|
340
299
|
|
|
341
300
|
## 5. MarketManager
|
|
342
301
|
|
|
@@ -346,6 +305,8 @@ interface MarketManager {
|
|
|
346
305
|
|
|
347
306
|
loadMarkets(): Promise<void>;
|
|
348
307
|
reloadMarkets(venue?: Venue): Promise<MarketCatalogReloadSummary[]>;
|
|
308
|
+
fetchServerTime(venue: Venue): Promise<VenueServerTime>;
|
|
309
|
+
|
|
349
310
|
listMarkets(venue?: Venue): MarketDefinition[];
|
|
350
311
|
getMarket(venue: Venue, symbol: string): MarketDefinition | undefined;
|
|
351
312
|
getMarkets(symbol: string): MarketDefinition[];
|
|
@@ -367,245 +328,47 @@ interface MarketManager {
|
|
|
367
328
|
|
|
368
329
|
### 5.1 Market catalog
|
|
369
330
|
|
|
370
|
-
|
|
371
|
-
await client.market.loadMarkets();
|
|
372
|
-
const reloadSummaries = await client.market.reloadMarkets("binance");
|
|
373
|
-
|
|
374
|
-
const all = client.market.listMarkets();
|
|
375
|
-
const binanceOnly = client.market.listMarkets("binance");
|
|
376
|
-
|
|
377
|
-
const btcPerp = client.market.getMarket("binance", "BTC/USDT:USDT");
|
|
378
|
-
const allBtcPerp = client.market.getMarkets("BTC/USDT:USDT");
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
`getMarkets(symbol)` 严格按完整统一 symbol 匹配。
|
|
382
|
-
|
|
383
|
-
`loadMarkets()` 会懒加载并缓存当前已注册 venue 的市场目录;已加载过的 venue 不会重复拉取。`reloadMarkets(venue?)` 用于主动刷新市场目录:传入 `venue` 时只刷新该 venue,省略时刷新所有已注册 market adapter。它和 `loadMarkets()` 一样不要求 client 已 `start()`,因此可在 `start()` 前或 `stop()` 后调用。
|
|
384
|
-
|
|
385
|
-
`reloadMarkets()` 返回每个 venue 的刷新摘要:
|
|
386
|
-
|
|
387
|
-
```ts
|
|
388
|
-
type MarketCatalogReloadSummary = {
|
|
389
|
-
venue: Venue;
|
|
390
|
-
added: string[];
|
|
391
|
-
removed: string[];
|
|
392
|
-
total: number;
|
|
393
|
-
ok: boolean;
|
|
394
|
-
error?: AcexError;
|
|
395
|
-
};
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
`added` / `removed` 是本次刷新相对旧目录变化的 symbol 列表,`total` 是刷新后该 venue 的目录数量。catalog 拉取失败时,对应 summary 为 `ok: false`,`error.code = "MARKET_CATALOG_LOAD_FAILED"`,旧目录会保留,方法不会因为该 venue 的 catalog 失败而 reject;未注册 runtime adapter 的合法 venue(例如当前 market adapter 未接入的 `bybit`)仍会抛 `VENUE_NOT_SUPPORTED`。
|
|
399
|
-
|
|
400
|
-
如果刷新会新增 symbol,调用方应先 `await client.market.reloadMarkets(venue)`,再按 summary 订阅新增 symbol。已加载 venue 上的后台 reload 不会阻塞并发 `subscribe*()`;reload 完成前订阅新增 symbol 仍可能按旧目录返回 `MARKET_NOT_FOUND`。
|
|
401
|
-
|
|
402
|
-
`MarketDefinition` 见 [§9](#9-数据类型参考)。价格/数量相关字段(`priceStep`、`amountStep`、`contractSize`、`minAmount`、`minNotional`)都是 canonical decimal string;需要运算时用 `new BigNumber(field)` 自行解析。
|
|
403
|
-
|
|
404
|
-
归一化下单价格和数量:
|
|
331
|
+
`loadMarkets()` 懒加载所有已实现 market runtime 的 venue 目录;`reloadMarkets(venue?)` 主动刷新目录,返回新增/移除/总数/错误摘要。订阅方法会自动确保对应 venue 的 catalog 已加载。
|
|
405
332
|
|
|
406
333
|
```ts
|
|
407
334
|
await client.market.loadMarkets();
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
venue: "binance",
|
|
411
|
-
symbol: "BTC/USDT:USDT",
|
|
412
|
-
price: "101000.123456789",
|
|
413
|
-
amount: "0.010987654321",
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
if (normalized.accepted) {
|
|
417
|
-
await client.order.createOrder({
|
|
418
|
-
accountId: "main-binance",
|
|
419
|
-
symbol: "BTC/USDT:USDT",
|
|
420
|
-
side: "buy",
|
|
421
|
-
type: "limit",
|
|
422
|
-
price: normalized.price,
|
|
423
|
-
amount: normalized.amount,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
335
|
+
const markets = client.market.listMarkets("binance");
|
|
336
|
+
const market = client.market.getMarket("binance", "BTC/USDT:USDT");
|
|
426
337
|
```
|
|
427
338
|
|
|
428
|
-
`
|
|
339
|
+
`MarketDefinition` 里的 `priceStep`、`amountStep`、`contractSize`、`minAmount`、`minNotional` 都是 decimal string。
|
|
429
340
|
|
|
430
|
-
|
|
341
|
+
Binance TradFi Perps 会按 USDⓈ-M 永续合约暴露,例如 `AAPLUSDT` 归一为 `AAPL/USDT:USDT`,可使用同一套 L1 Book 与 Funding Rate 订阅接口。
|
|
431
342
|
|
|
432
|
-
|
|
433
|
-
const normalized = client.market.normalizeOrderInput({
|
|
434
|
-
venue: "binance",
|
|
435
|
-
symbol: "BTC/USDT:USDT",
|
|
436
|
-
price: "1000.09",
|
|
437
|
-
amount: "0.0049",
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
// 示例返回:
|
|
441
|
-
// {
|
|
442
|
-
// price: "1000",
|
|
443
|
-
// amount: "0.004",
|
|
444
|
-
// rawPrice: "1000.09",
|
|
445
|
-
// rawAmount: "0.0049",
|
|
446
|
-
// adjusted: true,
|
|
447
|
-
// accepted: false,
|
|
448
|
-
// rejectReason: "notional_below_min",
|
|
449
|
-
// priceStep: "0.1",
|
|
450
|
-
// amountStep: "0.001",
|
|
451
|
-
// minAmount: "0.001",
|
|
452
|
-
// minNotional: "5"
|
|
453
|
-
// }
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
`rejectReason` 当前可能是:`price_not_positive`、`amount_not_positive`、`amount_below_min`、`notional_below_min`。
|
|
457
|
-
|
|
458
|
-
**统一 symbol 约定:**
|
|
459
|
-
|
|
460
|
-
| 格式 | 含义 | 示例 |
|
|
461
|
-
|---|---|---|
|
|
462
|
-
| `BASE/QUOTE` | 现货 | `BTC/USDT` |
|
|
463
|
-
| `BASE/QUOTE:SETTLE` | USDⓈ-M 永续 | `BTC/USDT:USDT` |
|
|
464
|
-
| `BASE/USD:BASE` | COIN-M 永续 | `BTC/USD:BTC` |
|
|
465
|
-
| `BASE/USD:BASE-YYYYMMDD` | COIN-M 交割 | `BTC/USD:BTC-20250627` |
|
|
466
|
-
|
|
467
|
-
`subscribeL1Book()` 内部会自动确保 catalog 已加载,所以不必手动先 `loadMarkets()`;只在需要枚举或读取精度字段时主动调用。
|
|
468
|
-
|
|
469
|
-
### 5.2 L1 Book
|
|
470
|
-
|
|
471
|
-
```ts
|
|
472
|
-
await client.market.subscribeL1Book({
|
|
473
|
-
venue: "binance",
|
|
474
|
-
symbol: "BTC/USDT:USDT",
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const book = client.market.getL1Book({
|
|
478
|
-
venue: "binance",
|
|
479
|
-
symbol: "BTC/USDT:USDT",
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
if (book) {
|
|
483
|
-
const spread = new BigNumber(book.askPrice).minus(new BigNumber(book.bidPrice));
|
|
484
|
-
console.log(`spread=${spread.toFixed()}`);
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
消费增量事件:
|
|
489
|
-
|
|
490
|
-
```ts
|
|
491
|
-
for await (const event of client.market.events.l1BookUpdates({
|
|
492
|
-
venue: "binance",
|
|
493
|
-
symbol: "BTC/USDT:USDT",
|
|
494
|
-
})) {
|
|
495
|
-
console.log(event.snapshot.bidPrice);
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
不传 filter 会拿到所有 symbol 的更新:
|
|
500
|
-
|
|
501
|
-
```ts
|
|
502
|
-
for await (const event of client.market.events.l1BookUpdates()) {
|
|
503
|
-
console.log(event.venue, event.symbol);
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
**事件当触发器模式**(跨 symbol 决策推荐):
|
|
508
|
-
|
|
509
|
-
```ts
|
|
510
|
-
const pairs = [
|
|
511
|
-
{ venue: "binance", symbol: "BTC/USDT:USDT" },
|
|
512
|
-
{ venue: "binance", symbol: "BTC/USD:BTC" },
|
|
513
|
-
];
|
|
514
|
-
|
|
515
|
-
for (const pair of pairs) await client.market.subscribeL1Book(pair);
|
|
516
|
-
|
|
517
|
-
for await (const _ of client.market.events.l1BookUpdates()) {
|
|
518
|
-
const books = pairs.map((p) => ({ ...p, book: client.market.getL1Book(p) }));
|
|
519
|
-
if (books.some((b) => !b.book)) continue;
|
|
520
|
-
// 用 books 里的最新值做决策
|
|
521
|
-
}
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
退订:
|
|
343
|
+
### 5.2 订单输入归一
|
|
525
344
|
|
|
526
345
|
```ts
|
|
527
|
-
|
|
528
|
-
venue: "binance",
|
|
529
|
-
symbol: "BTC/USDT:USDT",
|
|
530
|
-
});
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
退订后最后一份快照仍可读,但 `getMarketStatus().activity` 变为 `"inactive"`。
|
|
534
|
-
|
|
535
|
-
L1 Book 支持动态重复 `subscribe` / `unsubscribe`;对同一个 market 重复 `subscribeL1Book()` 不会重复开流,退订后再次订阅会恢复维护。
|
|
536
|
-
|
|
537
|
-
### 5.3 Funding Rate
|
|
538
|
-
|
|
539
|
-
Funding Rate 当前通过 Binance mark price websocket 实时更新,仅支持永续合约(`MarketDefinition.type === "swap"`)。订阅 spot 或交割合约会抛出 `MARKET_FUNDING_RATE_UNSUPPORTED`。
|
|
540
|
-
|
|
541
|
-
```ts
|
|
542
|
-
await client.market.subscribeFundingRate({
|
|
543
|
-
venue: "binance",
|
|
544
|
-
symbol: "BTC/USDT:USDT",
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const funding = client.market.getFundingRate({
|
|
346
|
+
const normalized = client.market.normalizeOrderInput({
|
|
548
347
|
venue: "binance",
|
|
549
348
|
symbol: "BTC/USDT:USDT",
|
|
349
|
+
price: "60000.123",
|
|
350
|
+
amount: "0.001234",
|
|
550
351
|
});
|
|
551
352
|
|
|
552
|
-
if (
|
|
553
|
-
console.log(
|
|
554
|
-
console.log(funding.markPrice);
|
|
555
|
-
console.log(funding.indexPrice);
|
|
556
|
-
console.log(funding.nextFundingTime);
|
|
557
|
-
console.log(funding.status.freshness);
|
|
353
|
+
if (!normalized.accepted) {
|
|
354
|
+
console.log(normalized.rejectReason);
|
|
558
355
|
}
|
|
559
356
|
```
|
|
560
357
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
```ts
|
|
564
|
-
for await (const event of client.market.events.fundingRateUpdates({
|
|
565
|
-
venue: "binance",
|
|
566
|
-
symbol: "BTC/USDT:USDT",
|
|
567
|
-
})) {
|
|
568
|
-
console.log(event.snapshot.fundingRate);
|
|
569
|
-
}
|
|
570
|
-
```
|
|
358
|
+
`normalizeOrderInput()` 会按 `priceStep` / `amountStep` 向下取整,并检查 `minAmount` / `minNotional`。它不会自动帮你下单,调用方需要把归一后的 string 放入 `createOrder()`。
|
|
571
359
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
Funding Rate 也支持动态重复 `subscribe` / `unsubscribe`;对同一个 market 重复 `subscribeFundingRate()` 不会重复开流,退订后再次订阅会恢复维护。
|
|
575
|
-
|
|
576
|
-
### 5.4 订阅状态
|
|
360
|
+
### 5.3 Server time
|
|
577
361
|
|
|
578
362
|
```ts
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
symbol: "BTC/USDT:USDT",
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
if (status) {
|
|
585
|
-
status.activity; // "active" | "inactive"
|
|
586
|
-
status.ready; // 首次 ready 是否完成
|
|
587
|
-
status.freshness; // "fresh" | "stale" | "reconciling"
|
|
588
|
-
status.lastReceivedAt; // 最后收到数据的时间
|
|
589
|
-
status.reason; // "ws_disconnected" | "heartbeat_timeout" | "reconciling"
|
|
590
|
-
}
|
|
363
|
+
const time = await client.market.fetchServerTime("binance");
|
|
364
|
+
console.log(time.serverTime, time.roundTripMs, time.estimatedOffsetMs);
|
|
591
365
|
```
|
|
592
366
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
```ts
|
|
596
|
-
const book = client.market.getL1Book({ venue, symbol });
|
|
597
|
-
book?.status.freshness; // 只代表 L1 Book stream
|
|
598
|
-
|
|
599
|
-
const funding = client.market.getFundingRate({ venue, symbol });
|
|
600
|
-
funding?.status.freshness; // 只代表 Funding Rate stream
|
|
601
|
-
```
|
|
367
|
+
当前 Binance server time 测量源固定为 USDⓈ-M REST `/fapi/v1/time`。失败会包装为 `MARKET_SERVER_TIME_FETCH_FAILED`。
|
|
602
368
|
|
|
603
|
-
|
|
369
|
+
### 5.4 Funding rate
|
|
604
370
|
|
|
605
|
-
|
|
606
|
-
- `heartbeat_timeout`:连接仍在但长时间没收到消息
|
|
607
|
-
|
|
608
|
-
自动重连由 SDK 负责,调用方不需要手工处理。
|
|
371
|
+
Funding Rate 当前通过 Binance mark price websocket 更新,仅支持永续合约(`MarketDefinition.type === "swap"`,包括 Binance TradFi Perps)。spot 或 future 订阅会抛 `MARKET_FUNDING_RATE_UNSUPPORTED`。
|
|
609
372
|
|
|
610
373
|
## 6. AccountManager
|
|
611
374
|
|
|
@@ -623,498 +386,299 @@ interface AccountManager {
|
|
|
623
386
|
getPosition(input: PositionKeyInput): PositionSnapshot | undefined;
|
|
624
387
|
getRiskSnapshot(accountId: string): RiskSnapshot | undefined;
|
|
625
388
|
getAccountStatus(accountId: string): AccountDataStatus | undefined;
|
|
626
|
-
}
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
### 6.1 订阅与退订
|
|
630
|
-
|
|
631
|
-
```ts
|
|
632
|
-
await client.account.subscribeAccount({ accountId: "main-binance" });
|
|
633
|
-
await client.account.unsubscribeAccount({ accountId: "main-binance" });
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
- 调用前需要先 `registerAccount()`
|
|
637
|
-
- 凭证不足抛 `CREDENTIALS_MISSING`
|
|
638
|
-
- bootstrap 失败抛 `ACCOUNT_BOOTSTRAP_FAILED`
|
|
639
|
-
|
|
640
|
-
### 6.2 读快照
|
|
641
|
-
|
|
642
|
-
```ts
|
|
643
|
-
const snapshot = client.account.getAccountSnapshot("main-binance");
|
|
644
|
-
// AccountSnapshot.balances 是 Record<string, BalanceSnapshot>(按 asset 索引)
|
|
645
|
-
|
|
646
|
-
const balances = client.account.getBalances("main-binance");
|
|
647
|
-
// BalanceSnapshot[](数组视图)
|
|
648
|
-
|
|
649
|
-
const usdt = client.account.getBalance("main-binance", "USDT");
|
|
650
|
-
// BalanceSnapshot | undefined
|
|
651
|
-
|
|
652
|
-
const positions = client.account.getPositions("main-binance");
|
|
653
|
-
const btcPosition = client.account.getPosition({
|
|
654
|
-
accountId: "main-binance",
|
|
655
|
-
symbol: "BTC/USDT:USDT",
|
|
656
|
-
side: "long", // 双向持仓时必传;单向持仓可省略
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
const risk = client.account.getRiskSnapshot("main-binance");
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
所有数量字段(`free` / `used` / `total` / `size` / `entryPrice` / `netEquity` / `riskEquity` / ...)都是 canonical decimal string;需要运算时用 `new BigNumber(field)` 自行解析。
|
|
663
|
-
|
|
664
|
-
`RiskSnapshot.netEquity` 表示不含风控折算的净资产价值;`riskEquity` 表示抵押系数或清算阈值折算后的风控净权益。Binance 使用 `actualEquity` / `accountEquity` 映射这两个字段;Juplend 使用 `totalCollateralUsd - totalDebtUsd` / `Σ(suppliedValue × liquidationThreshold) - totalDebtUsd`。
|
|
665
|
-
|
|
666
|
-
> **注意**:`AccountSnapshot.balances` 是 `Record<string, BalanceSnapshot>`,不是数组;需要数组视图用 `getBalances()`。
|
|
667
|
-
|
|
668
|
-
### 6.3 事件
|
|
669
|
-
|
|
670
|
-
```ts
|
|
671
|
-
for await (const event of client.account.events.updates({
|
|
672
|
-
accountId: "main-binance",
|
|
673
|
-
})) {
|
|
674
|
-
switch (event.type) {
|
|
675
|
-
case "balance.updated":
|
|
676
|
-
console.log(event.asset, event.snapshot.free);
|
|
677
|
-
break;
|
|
678
|
-
case "position.updated":
|
|
679
|
-
console.log(event.symbol, event.snapshot.size);
|
|
680
|
-
break;
|
|
681
|
-
case "risk.updated":
|
|
682
|
-
console.log(event.snapshot.riskRatio);
|
|
683
|
-
break;
|
|
684
|
-
case "account.snapshot_replaced":
|
|
685
|
-
// 私有链路重连/重对账后的全量替换
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
### 6.4 订阅状态
|
|
692
|
-
|
|
693
|
-
```ts
|
|
694
|
-
const status = client.account.getAccountStatus("main-binance");
|
|
695
|
-
status?.runtimeStatus;
|
|
696
|
-
// "bootstrap_pending" | "healthy" | "degraded" | "reconnecting" | "reconciling" | "stopped"
|
|
697
|
-
status?.reason;
|
|
698
|
-
// "credentials_missing" | "auth_failed" | "http_failed" | "rate_limited" | "ws_disconnected" | "heartbeat_timeout" | "reconciling"
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
## 7. OrderManager
|
|
702
|
-
|
|
703
|
-
```ts
|
|
704
|
-
interface OrderManager {
|
|
705
|
-
readonly events: OrderEventStreams;
|
|
706
|
-
|
|
707
|
-
subscribeOrders(input: SubscribeOrdersInput): Promise<void>;
|
|
708
|
-
unsubscribeOrders(input: UnsubscribeOrdersInput): Promise<void>;
|
|
709
|
-
|
|
710
|
-
createOrder(input: CreateOrderInput): Promise<OrderSnapshot>;
|
|
711
|
-
cancelOrder(input: CancelOrderInput): Promise<OrderSnapshot>;
|
|
712
|
-
cancelAllOrders(input: CancelAllOrdersInput): Promise<OrderSnapshot[]>;
|
|
713
|
-
|
|
714
|
-
getOrder(input: GetOrderInput): OrderSnapshot | undefined;
|
|
715
|
-
getOpenOrders(accountId: string, symbol?: string): OrderSnapshot[];
|
|
716
|
-
getOrderStatus(accountId: string): OrderDataStatus | undefined;
|
|
717
|
-
}
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
### 7.1 订阅订单流
|
|
721
|
-
|
|
722
|
-
```ts
|
|
723
|
-
await client.order.subscribeOrders({ accountId: "main-binance" });
|
|
724
|
-
await client.order.unsubscribeOrders({ accountId: "main-binance" });
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
- 需要先 `registerAccount()`
|
|
728
|
-
- 凭证不足抛 `CREDENTIALS_MISSING`
|
|
729
|
-
- bootstrap(open orders 拉取)失败抛 `ORDER_BOOTSTRAP_FAILED`
|
|
730
|
-
|
|
731
|
-
### 7.2 读快照
|
|
732
|
-
|
|
733
|
-
```ts
|
|
734
|
-
const openOrders = client.order.getOpenOrders("main-binance");
|
|
735
|
-
const btcOrders = client.order.getOpenOrders("main-binance", "BTC/USDT:USDT");
|
|
736
|
-
|
|
737
|
-
const order = client.order.getOrder({
|
|
738
|
-
accountId: "main-binance",
|
|
739
|
-
orderId: "12345",
|
|
740
|
-
// 或 clientOrderId: "my-order-1"
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
const status = client.order.getOrderStatus("main-binance");
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
### 7.3 下单
|
|
747
|
-
|
|
748
|
-
`createOrder()` 第一版支持 `limit` / `market` 两种类型。`price` / `amount` 是 decimal string。
|
|
749
|
-
|
|
750
|
-
```ts
|
|
751
|
-
const limit = await client.order.createOrder({
|
|
752
|
-
accountId: "main-binance",
|
|
753
|
-
symbol: "BTC/USDT:USDT",
|
|
754
|
-
side: "buy",
|
|
755
|
-
type: "limit",
|
|
756
|
-
price: "71830.6",
|
|
757
|
-
amount: "0.001",
|
|
758
|
-
clientOrderId: "my-order-1", // 可选
|
|
759
|
-
reduceOnly: false, // 可选
|
|
760
|
-
postOnly: true, // 可选:仅限 limit,Binance 映射为 GTX
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
const market = await client.order.createOrder({
|
|
764
|
-
accountId: "main-binance",
|
|
765
|
-
symbol: "BTC/USDT:USDT",
|
|
766
|
-
side: "sell",
|
|
767
|
-
type: "market",
|
|
768
|
-
amount: "0.001",
|
|
769
|
-
});
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
**双向持仓模式(hedge mode)必须显式传 `positionSide`**:
|
|
773
|
-
|
|
774
|
-
```ts
|
|
775
|
-
const hedge = await client.order.createOrder({
|
|
776
|
-
accountId: "main-binance",
|
|
777
|
-
symbol: "BTC/USDT:USDT",
|
|
778
|
-
side: "buy",
|
|
779
|
-
type: "limit",
|
|
780
|
-
price: "71900.9",
|
|
781
|
-
amount: "0.001",
|
|
782
|
-
positionSide: "long", // "long" | "short"
|
|
783
|
-
});
|
|
389
|
+
}
|
|
784
390
|
```
|
|
785
391
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
`postOnly` 仅对 `limit` 单有效;当前 Binance PAPI UM adapter 会把普通 limit 单映射为 `timeInForce=GTC`,把 `postOnly: true` 映射为 `timeInForce=GTX`。
|
|
392
|
+
`AccountSnapshot.balances` 是 `Record<string, BalanceSnapshot>`,数组视图用 `getBalances()`。
|
|
789
393
|
|
|
790
|
-
|
|
394
|
+
Binance account update 是 REST bootstrap + WS 增量 + REST risk refresh 的组合。Juplend 每次 poll 都是全量快照,成功 poll 会替换 balances / positions / risk,用于清理已关闭或不再匹配的 position。
|
|
791
395
|
|
|
792
|
-
|
|
396
|
+
Account 事件用于消费余额、仓位、风险或全量快照替换:
|
|
793
397
|
|
|
794
398
|
```ts
|
|
795
|
-
const
|
|
796
|
-
accountId: "main-binance",
|
|
797
|
-
symbol: "BTC/USDT:USDT",
|
|
798
|
-
orderId: "12345",
|
|
799
|
-
// 或 clientOrderId: "my-order-1"
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
const batch = await client.order.cancelAllOrders({
|
|
399
|
+
for await (const event of client.account.events.updates({
|
|
803
400
|
accountId: "main-binance",
|
|
804
|
-
|
|
805
|
-
|
|
401
|
+
})) {
|
|
402
|
+
if (event.type === "risk.updated") {
|
|
403
|
+
console.log(event.snapshot.riskRatio);
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
806
407
|
```
|
|
807
408
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
### 7.5 命令结果 vs 事件流
|
|
409
|
+
## 7. OrderManager
|
|
811
410
|
|
|
812
|
-
|
|
813
|
-
|
|
411
|
+
```ts
|
|
412
|
+
interface OrderManager {
|
|
413
|
+
readonly events: OrderEventStreams;
|
|
814
414
|
|
|
815
|
-
|
|
415
|
+
subscribeOrders(input: SubscribeOrdersInput): Promise<void>;
|
|
416
|
+
unsubscribeOrders(input: UnsubscribeOrdersInput): Promise<void>;
|
|
816
417
|
|
|
817
|
-
|
|
418
|
+
createOrder(input: CreateOrderInput): Promise<OrderSnapshot>;
|
|
419
|
+
cancelOrder(input: CancelOrderInput): Promise<OrderSnapshot>;
|
|
420
|
+
cancelAllOrders(input: CancelAllOrdersInput): Promise<OrderSnapshot[]>;
|
|
818
421
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
accountId:
|
|
822
|
-
})) {
|
|
823
|
-
switch (event.type) {
|
|
824
|
-
case "order.updated":
|
|
825
|
-
console.log("更新", event.snapshot.status, event.snapshot.filled);
|
|
826
|
-
break;
|
|
827
|
-
case "order.filled":
|
|
828
|
-
console.log("全部成交", event.snapshot.avgFillPrice);
|
|
829
|
-
break;
|
|
830
|
-
case "order.canceled":
|
|
831
|
-
console.log("已撤单");
|
|
832
|
-
break;
|
|
833
|
-
case "order.rejected":
|
|
834
|
-
console.log("被拒绝");
|
|
835
|
-
break;
|
|
836
|
-
case "order.snapshot_replaced":
|
|
837
|
-
// 私有链路重连/重对账后的全量订单集合替换
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
422
|
+
getOrder(input: GetOrderInput): OrderSnapshot | undefined;
|
|
423
|
+
getOpenOrders(accountId: string, symbol?: string): OrderSnapshot[];
|
|
424
|
+
getOrderStatus(accountId: string): OrderDataStatus | undefined;
|
|
840
425
|
}
|
|
841
426
|
```
|
|
842
427
|
|
|
843
|
-
### 7.
|
|
428
|
+
### 7.1 支持范围
|
|
844
429
|
|
|
845
|
-
`
|
|
430
|
+
- `createOrder()` 支持 `limit` / `market`
|
|
431
|
+
- `limit` 可传 `postOnly: true`,Binance PAPI UM 映射为 `timeInForce=GTX`
|
|
432
|
+
- `cancelOrder()` 必须传 `orderId` 或 `clientOrderId`
|
|
433
|
+
- `cancelAllOrders()` 必须传 `symbol`,不支持账户级全撤
|
|
434
|
+
- hedge mode 下必须显式传 `positionSide: "long" | "short"`
|
|
846
435
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
### 8.1 `getHealth()`
|
|
436
|
+
### 7.2 精度限制
|
|
850
437
|
|
|
851
|
-
|
|
852
|
-
const health = client.getHealth();
|
|
853
|
-
// {
|
|
854
|
-
// clientStatus: "running",
|
|
855
|
-
// markets: MarketDataStatus[],
|
|
856
|
-
// accounts: AccountDataStatus[],
|
|
857
|
-
// orders: OrderDataStatus[],
|
|
858
|
-
// updatedAt: 1710000000000,
|
|
859
|
-
// }
|
|
860
|
-
```
|
|
438
|
+
`createOrder()` 不会自动纠偏。调用方应先用 `MarketDefinition.priceStep`、`amountStep`、`minAmount`、`minNotional` 和 `normalizeOrderInput()` 处理输入。交易所拒单会包装成 `ORDER_CREATE_FAILED`。
|
|
861
439
|
|
|
862
|
-
|
|
440
|
+
Order 事件用于消费订单状态变化和 open orders 全量替换:
|
|
863
441
|
|
|
864
442
|
```ts
|
|
865
|
-
for await (const event of client.events.
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
console.log("market", event.venue, event.symbol, event.status.activity);
|
|
872
|
-
break;
|
|
873
|
-
case "account.status_changed":
|
|
874
|
-
console.log("account", event.accountId, event.status.runtimeStatus);
|
|
875
|
-
break;
|
|
876
|
-
case "order.status_changed":
|
|
877
|
-
console.log("order", event.accountId, event.status.runtimeStatus);
|
|
878
|
-
break;
|
|
443
|
+
for await (const event of client.order.events.updates({
|
|
444
|
+
accountId: "main-binance",
|
|
445
|
+
symbol: "BTC/USDT:USDT",
|
|
446
|
+
})) {
|
|
447
|
+
if (event.type === "order.filled") {
|
|
448
|
+
console.log(event.snapshot.filled);
|
|
879
449
|
}
|
|
450
|
+
break;
|
|
880
451
|
}
|
|
881
452
|
```
|
|
882
453
|
|
|
883
|
-
|
|
454
|
+
## 8. 健康与错误事件
|
|
884
455
|
|
|
885
456
|
```ts
|
|
886
|
-
|
|
887
|
-
for await (const e of client.events.health({ scope: "market" })) { /* ... */ }
|
|
888
|
-
|
|
889
|
-
// 只看某个 venue
|
|
890
|
-
for await (const e of client.events.health({ venue: "binance" })) { /* ... */ }
|
|
891
|
-
|
|
892
|
-
// 只看某个 account
|
|
893
|
-
for await (const e of client.events.health({ accountId: "main-binance" })) { /* ... */ }
|
|
894
|
-
```
|
|
457
|
+
const health = client.getHealth();
|
|
895
458
|
|
|
896
|
-
|
|
459
|
+
for await (const event of client.events.health({ venue: "binance" })) {
|
|
460
|
+
console.log(event.type);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
897
463
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
venue: err.venue,
|
|
902
|
-
accountId: err.accountId,
|
|
903
|
-
symbol: err.symbol,
|
|
904
|
-
});
|
|
464
|
+
for await (const error of client.events.errors()) {
|
|
465
|
+
console.error(error.source, error.error);
|
|
466
|
+
break;
|
|
905
467
|
}
|
|
906
468
|
```
|
|
907
469
|
|
|
908
|
-
`
|
|
909
|
-
|
|
910
|
-
## 9. 数据类型参考
|
|
470
|
+
`getHealth()` 聚合 client、market、account、order 的当前状态。`events.health(filter)` 只返回满足 filter 的事件;如果事件没有 filter 请求的字段,会被过滤掉。
|
|
911
471
|
|
|
912
|
-
|
|
472
|
+
## 9. 数据类型速查
|
|
913
473
|
|
|
914
|
-
|
|
915
|
-
import type {
|
|
916
|
-
Venue, ClientStatus, CreateClientOptions, VenueCapabilities,
|
|
917
|
-
VenueRuntimeStatus, VenueCapabilitySupport, VenueCapabilityReason,
|
|
918
|
-
FundingRateCapability, PrivateUpdateCapability,
|
|
919
|
-
CancelAllOrdersCapability, PositionSideCapability,
|
|
920
|
-
OrderTimeInForceCapability,
|
|
921
|
-
AccountCredentials,
|
|
922
|
-
MarketDefinition, L1Book, FundingRateSnapshot, MarketDataStatus,
|
|
923
|
-
MarketDataStreamStatus,
|
|
924
|
-
BalanceSnapshot, PositionSnapshot, RiskSnapshot, AccountSnapshot,
|
|
925
|
-
AccountDataStatus, CreateOrderInput, CancelOrderInput, CancelAllOrdersInput,
|
|
926
|
-
OrderSnapshot, OrderDataStatus, OrderSide, OrderStatus, PositionSide,
|
|
927
|
-
MarketEvent, AccountEvent, OrderEvent, HealthEvent,
|
|
928
|
-
AcexInternalError,
|
|
929
|
-
} from "@imbingox/acex";
|
|
930
|
-
import { BigNumber, AcexError } from "@imbingox/acex";
|
|
931
|
-
```
|
|
932
|
-
|
|
933
|
-
### 9.1 基础
|
|
474
|
+
以下类型均从 `@imbingox/acex` 根入口导出;以 package public types 为准。这里列常用形状,完整字段可由 TypeScript 自动补全。
|
|
934
475
|
|
|
935
476
|
```ts
|
|
936
|
-
|
|
937
|
-
type Venue = (typeof SUPPORTED_VENUES)[number];
|
|
938
|
-
|
|
477
|
+
type Venue = "binance" | "okx" | "bybit" | "gate" | "juplend";
|
|
939
478
|
type ClientStatus = "idle" | "starting" | "running" | "stopping" | "stopped";
|
|
940
|
-
type
|
|
941
|
-
type
|
|
942
|
-
type
|
|
943
|
-
|
|
944
|
-
|
|
479
|
+
type MarketType = "spot" | "swap" | "future";
|
|
480
|
+
type PositionSide = "long" | "short" | "net";
|
|
481
|
+
type CreateOrderType = "limit" | "market";
|
|
482
|
+
type OrderSide = "buy" | "sell";
|
|
483
|
+
type OrderStatus =
|
|
484
|
+
| "open"
|
|
485
|
+
| "partially_filled"
|
|
486
|
+
| "filled"
|
|
487
|
+
| "canceled"
|
|
488
|
+
| "rejected"
|
|
489
|
+
| "expired";
|
|
490
|
+
|
|
491
|
+
type PrivateRuntimeReason =
|
|
492
|
+
| "credentials_missing"
|
|
493
|
+
| "auth_failed"
|
|
494
|
+
| "http_failed"
|
|
495
|
+
| "rate_limited"
|
|
496
|
+
| "ws_disconnected"
|
|
497
|
+
| "heartbeat_timeout"
|
|
498
|
+
| "reconciling";
|
|
945
499
|
|
|
946
500
|
type SubscriptionActivity = "active" | "inactive";
|
|
947
501
|
type MarketFreshness = "fresh" | "stale" | "reconciling";
|
|
948
502
|
type PrivateRuntimeStatus =
|
|
949
|
-
| "bootstrap_pending"
|
|
950
|
-
| "
|
|
951
|
-
|
|
952
|
-
| "
|
|
953
|
-
| "
|
|
954
|
-
|
|
955
|
-
type OrderSide = "buy" | "sell";
|
|
956
|
-
type OrderStatus =
|
|
957
|
-
| "open" | "partially_filled" | "filled"
|
|
958
|
-
| "canceled" | "rejected" | "expired";
|
|
959
|
-
type PositionSide = "long" | "short" | "net";
|
|
960
|
-
type CreateOrderType = "limit" | "market";
|
|
961
|
-
type MarketType = "spot" | "swap" | "future";
|
|
503
|
+
| "bootstrap_pending"
|
|
504
|
+
| "healthy"
|
|
505
|
+
| "degraded"
|
|
506
|
+
| "reconnecting"
|
|
507
|
+
| "reconciling"
|
|
508
|
+
| "stopped";
|
|
962
509
|
```
|
|
963
510
|
|
|
964
|
-
### 9.2 Client 配置
|
|
965
|
-
|
|
966
511
|
```ts
|
|
967
|
-
interface
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
512
|
+
interface VenueCapabilities {
|
|
513
|
+
venue: Venue;
|
|
514
|
+
runtimeStatus: "available" | "type_only" | "reserved";
|
|
515
|
+
readOnly: boolean;
|
|
516
|
+
notes: string[];
|
|
517
|
+
market: {
|
|
518
|
+
catalog: "supported" | "unsupported";
|
|
519
|
+
serverTime: "supported" | "unsupported";
|
|
520
|
+
l1Book: "supported" | "unsupported";
|
|
521
|
+
fundingRate: "supported" | "unsupported" | "market_dependent";
|
|
522
|
+
marketTypes: MarketType[];
|
|
523
|
+
};
|
|
524
|
+
account: {
|
|
525
|
+
register: "supported" | "unsupported";
|
|
526
|
+
snapshot: "supported" | "unsupported";
|
|
527
|
+
updates: "websocket" | "polling" | "unsupported";
|
|
528
|
+
balances: "supported" | "unsupported";
|
|
529
|
+
positions: "supported" | "unsupported";
|
|
530
|
+
risk: "supported" | "unsupported";
|
|
531
|
+
lending: "supported" | "unsupported";
|
|
532
|
+
credentialsRequired: boolean;
|
|
533
|
+
};
|
|
534
|
+
order: {
|
|
535
|
+
supported: boolean;
|
|
536
|
+
openOrders: "supported" | "unsupported";
|
|
537
|
+
updates: "websocket" | "polling" | "unsupported";
|
|
538
|
+
create: "supported" | "unsupported";
|
|
539
|
+
cancel: "supported" | "unsupported";
|
|
540
|
+
cancelAll: "symbol" | "account" | "unsupported";
|
|
541
|
+
orderTypes: CreateOrderType[];
|
|
542
|
+
timeInForce: Array<"gtc" | "post_only">;
|
|
543
|
+
postOnly: boolean;
|
|
544
|
+
reduceOnly: boolean;
|
|
545
|
+
positionSide: "optional" | "required_for_hedge" | "unsupported";
|
|
546
|
+
clientOrderId: boolean;
|
|
547
|
+
reason?:
|
|
548
|
+
| "not_implemented"
|
|
549
|
+
| "read_only"
|
|
550
|
+
| "market_type_unsupported"
|
|
551
|
+
| "sdk_reserved";
|
|
552
|
+
};
|
|
972
553
|
}
|
|
554
|
+
```
|
|
973
555
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
556
|
+
```ts
|
|
557
|
+
interface CreateClientOptions {
|
|
558
|
+
sandbox?: boolean;
|
|
559
|
+
clock?: { now(): number };
|
|
560
|
+
rateLimiter?: RateLimiter;
|
|
561
|
+
logger?: Logger;
|
|
562
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
563
|
+
market?: {
|
|
564
|
+
l1InitialMessageTimeoutMs?: number;
|
|
565
|
+
l1StaleAfterMs?: number;
|
|
566
|
+
l1ReconnectDelayMs?: number;
|
|
567
|
+
l1ReconnectMaxDelayMs?: number;
|
|
981
568
|
};
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
569
|
+
account?: {
|
|
570
|
+
streamOpenTimeoutMs?: number;
|
|
571
|
+
streamReconnectDelayMs?: number;
|
|
572
|
+
streamReconnectMaxDelayMs?: number;
|
|
573
|
+
listenKeyKeepAliveMs?: number;
|
|
574
|
+
binance?: {
|
|
575
|
+
riskPollIntervalMs?: number;
|
|
576
|
+
};
|
|
577
|
+
juplend?: {
|
|
578
|
+
pollIntervalMs?: number;
|
|
579
|
+
rpcUrl?: string;
|
|
580
|
+
jupApiKey?: string;
|
|
581
|
+
};
|
|
986
582
|
};
|
|
987
583
|
}
|
|
988
584
|
|
|
989
|
-
interface
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
logger?: Logger; // 预留
|
|
994
|
-
logLevel?: LogLevel; // 预留
|
|
995
|
-
market?: MarketRuntimeOptions;
|
|
996
|
-
account?: AccountRuntimeOptions;
|
|
585
|
+
interface RateLimitScope {
|
|
586
|
+
venue: Venue;
|
|
587
|
+
accountId?: string;
|
|
588
|
+
endpointKey: string;
|
|
997
589
|
}
|
|
998
590
|
|
|
999
|
-
interface
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
password?: string;
|
|
1003
|
-
extra?: Record<string, string>;
|
|
591
|
+
interface RateLimitUsage {
|
|
592
|
+
weight?: Record<string, number>;
|
|
593
|
+
orderCount?: Record<string, number>;
|
|
1004
594
|
}
|
|
1005
595
|
|
|
1006
|
-
interface
|
|
1007
|
-
|
|
1008
|
-
recvWindow?: number;
|
|
596
|
+
interface RateLimitRequestContext {
|
|
597
|
+
scope: RateLimitScope;
|
|
1009
598
|
}
|
|
1010
599
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
600
|
+
interface RateLimitResponseContext {
|
|
601
|
+
status: number;
|
|
602
|
+
headers?: Headers;
|
|
603
|
+
usage?: RateLimitUsage;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
interface RateLimitTransportErrorContext {
|
|
607
|
+
status?: number;
|
|
608
|
+
headers?: Headers;
|
|
609
|
+
retryAfterMs?: number;
|
|
610
|
+
usage?: RateLimitUsage;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
interface RateLimitSnapshot {
|
|
614
|
+
scope: RateLimitScope;
|
|
615
|
+
usage?: RateLimitUsage;
|
|
616
|
+
blockedUntil?: number;
|
|
617
|
+
retryAfterMs?: number;
|
|
618
|
+
state: "ok" | "rate_limited" | "banned";
|
|
619
|
+
updatedAt?: number;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
interface RateLimiter {
|
|
623
|
+
beforeRequest(ctx: RateLimitRequestContext): Promise<void> | void;
|
|
624
|
+
afterResponse(
|
|
625
|
+
ctx: RateLimitRequestContext,
|
|
626
|
+
response: RateLimitResponseContext,
|
|
627
|
+
): Promise<void> | void;
|
|
628
|
+
onTransportError(
|
|
629
|
+
ctx: RateLimitRequestContext,
|
|
630
|
+
error: RateLimitTransportErrorContext,
|
|
631
|
+
): Promise<void> | void;
|
|
632
|
+
getSnapshot(scope: RateLimitScope): RateLimitSnapshot | undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
interface Logger {
|
|
636
|
+
debug(msg: string, context?: Record<string, unknown>): void;
|
|
637
|
+
info(msg: string, context?: Record<string, unknown>): void;
|
|
638
|
+
warn(msg: string, context?: Record<string, unknown>): void;
|
|
639
|
+
error(msg: string, context?: Record<string, unknown>): void;
|
|
640
|
+
}
|
|
1022
641
|
|
|
1023
642
|
type RegisterAccountInput =
|
|
1024
643
|
| {
|
|
1025
644
|
accountId: string;
|
|
1026
645
|
venue: "binance" | "okx" | "bybit" | "gate";
|
|
1027
646
|
credentials?: AccountCredentials;
|
|
1028
|
-
options?:
|
|
647
|
+
options?: {
|
|
648
|
+
timestamp?: number;
|
|
649
|
+
recvWindow?: number;
|
|
650
|
+
};
|
|
1029
651
|
}
|
|
1030
652
|
| {
|
|
1031
653
|
accountId: string;
|
|
1032
654
|
venue: "juplend";
|
|
1033
655
|
credentials?: AccountCredentials;
|
|
1034
|
-
options:
|
|
656
|
+
options:
|
|
657
|
+
| {
|
|
658
|
+
walletAddress: string;
|
|
659
|
+
vaultId?: string;
|
|
660
|
+
positionId?: string;
|
|
661
|
+
}
|
|
662
|
+
| {
|
|
663
|
+
walletAddress?: string;
|
|
664
|
+
vaultId: string;
|
|
665
|
+
positionId: string;
|
|
666
|
+
};
|
|
1035
667
|
};
|
|
1036
668
|
|
|
1037
|
-
interface
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
interface StopOptions {
|
|
1043
|
-
graceful?: boolean;
|
|
1044
|
-
timeoutMs?: number;
|
|
1045
|
-
}
|
|
1046
|
-
```
|
|
1047
|
-
|
|
1048
|
-
### 9.2.1 Venue Capabilities
|
|
1049
|
-
|
|
1050
|
-
```ts
|
|
1051
|
-
type FundingRateCapability =
|
|
1052
|
-
| VenueCapabilitySupport
|
|
1053
|
-
| "market_dependent";
|
|
1054
|
-
|
|
1055
|
-
type PrivateUpdateCapability =
|
|
1056
|
-
| "websocket"
|
|
1057
|
-
| "polling"
|
|
1058
|
-
| "unsupported";
|
|
1059
|
-
|
|
1060
|
-
type CancelAllOrdersCapability =
|
|
1061
|
-
| "symbol"
|
|
1062
|
-
| "account"
|
|
1063
|
-
| "unsupported";
|
|
1064
|
-
|
|
1065
|
-
type PositionSideCapability =
|
|
1066
|
-
| "optional"
|
|
1067
|
-
| "required_for_hedge"
|
|
1068
|
-
| "unsupported";
|
|
1069
|
-
|
|
1070
|
-
type OrderTimeInForceCapability = "gtc" | "post_only";
|
|
1071
|
-
|
|
1072
|
-
interface VenueCapabilities {
|
|
1073
|
-
venue: Venue;
|
|
1074
|
-
runtimeStatus: VenueRuntimeStatus;
|
|
1075
|
-
readOnly: boolean;
|
|
1076
|
-
notes: string[];
|
|
1077
|
-
market: {
|
|
1078
|
-
catalog: VenueCapabilitySupport;
|
|
1079
|
-
l1Book: VenueCapabilitySupport;
|
|
1080
|
-
fundingRate: FundingRateCapability;
|
|
1081
|
-
marketTypes: MarketType[];
|
|
1082
|
-
};
|
|
1083
|
-
account: {
|
|
1084
|
-
register: VenueCapabilitySupport;
|
|
1085
|
-
snapshot: VenueCapabilitySupport;
|
|
1086
|
-
updates: PrivateUpdateCapability;
|
|
1087
|
-
balances: VenueCapabilitySupport;
|
|
1088
|
-
positions: VenueCapabilitySupport;
|
|
1089
|
-
risk: VenueCapabilitySupport;
|
|
1090
|
-
lending: VenueCapabilitySupport;
|
|
1091
|
-
credentialsRequired: boolean;
|
|
1092
|
-
};
|
|
1093
|
-
order: {
|
|
1094
|
-
supported: boolean;
|
|
1095
|
-
openOrders: VenueCapabilitySupport;
|
|
1096
|
-
updates: PrivateUpdateCapability;
|
|
1097
|
-
create: VenueCapabilitySupport;
|
|
1098
|
-
cancel: VenueCapabilitySupport;
|
|
1099
|
-
cancelAll: CancelAllOrdersCapability;
|
|
1100
|
-
orderTypes: CreateOrderType[];
|
|
1101
|
-
timeInForce: OrderTimeInForceCapability[];
|
|
1102
|
-
postOnly: boolean;
|
|
1103
|
-
reduceOnly: boolean;
|
|
1104
|
-
positionSide: PositionSideCapability;
|
|
1105
|
-
clientOrderId: boolean;
|
|
1106
|
-
reason?: VenueCapabilityReason;
|
|
1107
|
-
};
|
|
669
|
+
interface AccountCredentials {
|
|
670
|
+
apiKey?: string;
|
|
671
|
+
secret?: string;
|
|
672
|
+
password?: string;
|
|
673
|
+
extra?: Record<string, string>;
|
|
1108
674
|
}
|
|
1109
675
|
```
|
|
1110
676
|
|
|
1111
|
-
### 9.3 Market
|
|
1112
|
-
|
|
1113
677
|
```ts
|
|
1114
678
|
interface MarketDefinition {
|
|
1115
679
|
venue: Venue;
|
|
1116
|
-
symbol: string;
|
|
1117
|
-
id: string;
|
|
680
|
+
symbol: string;
|
|
681
|
+
id: string;
|
|
1118
682
|
type: MarketType;
|
|
1119
683
|
base: string;
|
|
1120
684
|
quote: string;
|
|
@@ -1134,45 +698,12 @@ interface MarketDefinition {
|
|
|
1134
698
|
raw: Record<string, unknown>;
|
|
1135
699
|
}
|
|
1136
700
|
|
|
1137
|
-
interface
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
type NormalizeOrderInputRejectReason =
|
|
1145
|
-
| "price_not_positive"
|
|
1146
|
-
| "amount_not_positive"
|
|
1147
|
-
| "amount_below_min"
|
|
1148
|
-
| "notional_below_min";
|
|
1149
|
-
|
|
1150
|
-
interface NormalizeOrderInputInput extends MarketKeyInput {
|
|
1151
|
-
price: DecimalInput;
|
|
1152
|
-
amount: DecimalInput;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
interface NormalizedOrderInput {
|
|
1156
|
-
price: string;
|
|
1157
|
-
amount: string;
|
|
1158
|
-
rawPrice: string;
|
|
1159
|
-
rawAmount: string;
|
|
1160
|
-
adjusted: boolean;
|
|
1161
|
-
accepted: boolean;
|
|
1162
|
-
rejectReason?: NormalizeOrderInputRejectReason;
|
|
1163
|
-
priceStep: string;
|
|
1164
|
-
amountStep: string;
|
|
1165
|
-
minAmount?: string;
|
|
1166
|
-
minNotional?: string;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
interface SubscribeL1BookInput extends MarketKeyInput {}
|
|
1170
|
-
|
|
1171
|
-
interface SubscribeFundingRateInput extends MarketKeyInput {}
|
|
1172
|
-
|
|
1173
|
-
interface MarketEventFilter {
|
|
1174
|
-
venue?: Venue;
|
|
1175
|
-
symbol?: string;
|
|
701
|
+
interface VenueServerTime {
|
|
702
|
+
serverTime: number;
|
|
703
|
+
requestSentAt: number;
|
|
704
|
+
responseReceivedAt: number;
|
|
705
|
+
roundTripMs: number;
|
|
706
|
+
estimatedOffsetMs: number;
|
|
1176
707
|
}
|
|
1177
708
|
|
|
1178
709
|
interface L1Book {
|
|
@@ -1182,21 +713,6 @@ interface L1Book {
|
|
|
1182
713
|
bidSize: string;
|
|
1183
714
|
askPrice: string;
|
|
1184
715
|
askSize: string;
|
|
1185
|
-
exchangeTs?: number;
|
|
1186
|
-
receivedAt: number;
|
|
1187
|
-
updatedAt: number;
|
|
1188
|
-
version: number;
|
|
1189
|
-
status: MarketDataStreamStatus;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
interface FundingRateSnapshot {
|
|
1193
|
-
venue: Venue;
|
|
1194
|
-
symbol: string;
|
|
1195
|
-
fundingRate: string;
|
|
1196
|
-
nextFundingTime?: number;
|
|
1197
|
-
markPrice?: string;
|
|
1198
|
-
indexPrice?: string;
|
|
1199
|
-
exchangeTs?: number;
|
|
1200
716
|
receivedAt: number;
|
|
1201
717
|
updatedAt: number;
|
|
1202
718
|
version: number;
|
|
@@ -1213,22 +729,24 @@ interface MarketDataStreamStatus {
|
|
|
1213
729
|
reason?: "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
1214
730
|
}
|
|
1215
731
|
|
|
1216
|
-
interface MarketDataStatus {
|
|
732
|
+
interface MarketDataStatus extends MarketDataStreamStatus {
|
|
1217
733
|
venue: Venue;
|
|
1218
734
|
symbol: string;
|
|
1219
|
-
activity: SubscriptionActivity;
|
|
1220
|
-
ready: boolean;
|
|
1221
|
-
freshness?: MarketFreshness;
|
|
1222
|
-
lastReceivedAt?: number;
|
|
1223
|
-
lastReadyAt?: number;
|
|
1224
|
-
inactiveSince?: number;
|
|
1225
|
-
reason?: "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
1226
735
|
}
|
|
1227
|
-
```
|
|
1228
736
|
|
|
1229
|
-
|
|
737
|
+
interface FundingRateSnapshot {
|
|
738
|
+
venue: Venue;
|
|
739
|
+
symbol: string;
|
|
740
|
+
fundingRate: string;
|
|
741
|
+
nextFundingTime?: number;
|
|
742
|
+
markPrice?: string;
|
|
743
|
+
indexPrice?: string;
|
|
744
|
+
receivedAt: number;
|
|
745
|
+
updatedAt: number;
|
|
746
|
+
version: number;
|
|
747
|
+
status: MarketDataStreamStatus;
|
|
748
|
+
}
|
|
1230
749
|
|
|
1231
|
-
```ts
|
|
1232
750
|
interface BalanceSnapshot {
|
|
1233
751
|
accountId: string;
|
|
1234
752
|
venue: Venue;
|
|
@@ -1236,20 +754,14 @@ interface BalanceSnapshot {
|
|
|
1236
754
|
free: string;
|
|
1237
755
|
used: string;
|
|
1238
756
|
total: string;
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
supplied: string;
|
|
1248
|
-
borrowed: string;
|
|
1249
|
-
interest: string;
|
|
1250
|
-
netAsset: string;
|
|
1251
|
-
supplyAPY?: string;
|
|
1252
|
-
borrowAPY?: string;
|
|
757
|
+
lending?: {
|
|
758
|
+
supplied: string;
|
|
759
|
+
borrowed: string;
|
|
760
|
+
interest: string;
|
|
761
|
+
netAsset: string;
|
|
762
|
+
supplyAPY?: string;
|
|
763
|
+
borrowAPY?: string;
|
|
764
|
+
};
|
|
1253
765
|
}
|
|
1254
766
|
|
|
1255
767
|
interface PositionSnapshot {
|
|
@@ -1263,10 +775,6 @@ interface PositionSnapshot {
|
|
|
1263
775
|
unrealizedPnl?: string;
|
|
1264
776
|
leverage?: string;
|
|
1265
777
|
liquidationPrice?: string;
|
|
1266
|
-
exchangeTs?: number;
|
|
1267
|
-
receivedAt: number;
|
|
1268
|
-
updatedAt: number;
|
|
1269
|
-
seq: number;
|
|
1270
778
|
}
|
|
1271
779
|
|
|
1272
780
|
interface RiskSnapshot {
|
|
@@ -1276,33 +784,22 @@ interface RiskSnapshot {
|
|
|
1276
784
|
riskEquity?: string;
|
|
1277
785
|
riskRatio?: string;
|
|
1278
786
|
riskLeverage?: string;
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
interface LendingRiskFacet {
|
|
1289
|
-
marginLevel?: string;
|
|
1290
|
-
healthFactor?: string;
|
|
1291
|
-
ltv?: string;
|
|
1292
|
-
liquidationThreshold?: string;
|
|
1293
|
-
totalCollateralUSD?: string;
|
|
1294
|
-
totalDebtUSD?: string;
|
|
787
|
+
lending?: {
|
|
788
|
+
marginLevel?: string;
|
|
789
|
+
healthFactor?: string;
|
|
790
|
+
ltv?: string;
|
|
791
|
+
liquidationThreshold?: string;
|
|
792
|
+
totalCollateralUSD?: string;
|
|
793
|
+
totalDebtUSD?: string;
|
|
794
|
+
};
|
|
1295
795
|
}
|
|
1296
796
|
|
|
1297
797
|
interface AccountSnapshot {
|
|
1298
798
|
accountId: string;
|
|
1299
799
|
venue: Venue;
|
|
1300
|
-
balances: Record<string, BalanceSnapshot>;
|
|
800
|
+
balances: Record<string, BalanceSnapshot>;
|
|
1301
801
|
positions: PositionSnapshot[];
|
|
1302
802
|
risk?: RiskSnapshot;
|
|
1303
|
-
exchangeTs?: number;
|
|
1304
|
-
receivedAt: number;
|
|
1305
|
-
updatedAt: number;
|
|
1306
803
|
}
|
|
1307
804
|
|
|
1308
805
|
interface AccountDataStatus {
|
|
@@ -1311,25 +808,19 @@ interface AccountDataStatus {
|
|
|
1311
808
|
activity: SubscriptionActivity;
|
|
1312
809
|
ready: boolean;
|
|
1313
810
|
runtimeStatus?: PrivateRuntimeStatus;
|
|
1314
|
-
lastReceivedAt?: number;
|
|
1315
|
-
lastReadyAt?: number;
|
|
1316
|
-
inactiveSince?: number;
|
|
1317
811
|
reason?: PrivateRuntimeReason;
|
|
1318
812
|
}
|
|
1319
813
|
```
|
|
1320
814
|
|
|
1321
|
-
### 9.5 Order
|
|
1322
|
-
|
|
1323
815
|
```ts
|
|
1324
|
-
// limit / market 两个 variant
|
|
1325
816
|
type CreateOrderInput =
|
|
1326
817
|
| {
|
|
1327
818
|
accountId: string;
|
|
1328
819
|
symbol: string;
|
|
1329
820
|
side: OrderSide;
|
|
1330
821
|
type: "limit";
|
|
1331
|
-
price: string;
|
|
1332
|
-
amount: string;
|
|
822
|
+
price: string;
|
|
823
|
+
amount: string;
|
|
1333
824
|
postOnly?: boolean;
|
|
1334
825
|
clientOrderId?: string;
|
|
1335
826
|
reduceOnly?: boolean;
|
|
@@ -1340,7 +831,7 @@ type CreateOrderInput =
|
|
|
1340
831
|
symbol: string;
|
|
1341
832
|
side: OrderSide;
|
|
1342
833
|
type: "market";
|
|
1343
|
-
amount: string;
|
|
834
|
+
amount: string;
|
|
1344
835
|
clientOrderId?: string;
|
|
1345
836
|
reduceOnly?: boolean;
|
|
1346
837
|
positionSide?: PositionSide;
|
|
@@ -1358,12 +849,6 @@ interface CancelAllOrdersInput {
|
|
|
1358
849
|
symbol: string;
|
|
1359
850
|
}
|
|
1360
851
|
|
|
1361
|
-
interface GetOrderInput {
|
|
1362
|
-
accountId: string;
|
|
1363
|
-
orderId?: string;
|
|
1364
|
-
clientOrderId?: string;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
852
|
interface OrderSnapshot {
|
|
1368
853
|
accountId: string;
|
|
1369
854
|
venue: Venue;
|
|
@@ -1371,20 +856,13 @@ interface OrderSnapshot {
|
|
|
1371
856
|
clientOrderId?: string;
|
|
1372
857
|
symbol: string;
|
|
1373
858
|
side: OrderSide;
|
|
1374
|
-
type: string;
|
|
859
|
+
type: string;
|
|
1375
860
|
status: OrderStatus;
|
|
1376
861
|
price?: string;
|
|
1377
|
-
triggerPrice?: string;
|
|
1378
862
|
amount: string;
|
|
1379
863
|
filled: string;
|
|
1380
864
|
remaining?: string;
|
|
1381
|
-
reduceOnly?: boolean;
|
|
1382
865
|
positionSide?: PositionSide;
|
|
1383
|
-
avgFillPrice?: string;
|
|
1384
|
-
exchangeTs?: number;
|
|
1385
|
-
receivedAt: number;
|
|
1386
|
-
updatedAt: number;
|
|
1387
|
-
seq: number;
|
|
1388
866
|
}
|
|
1389
867
|
|
|
1390
868
|
interface OrderDataStatus {
|
|
@@ -1393,158 +871,81 @@ interface OrderDataStatus {
|
|
|
1393
871
|
activity: SubscriptionActivity;
|
|
1394
872
|
ready: boolean;
|
|
1395
873
|
runtimeStatus?: PrivateRuntimeStatus;
|
|
1396
|
-
lastReceivedAt?: number;
|
|
1397
|
-
lastReadyAt?: number;
|
|
1398
|
-
inactiveSince?: number;
|
|
1399
874
|
reason?: PrivateRuntimeReason;
|
|
1400
875
|
}
|
|
1401
876
|
```
|
|
1402
877
|
|
|
1403
|
-
### 9.6 事件
|
|
1404
|
-
|
|
1405
878
|
```ts
|
|
1406
|
-
// Market
|
|
1407
879
|
type MarketEvent =
|
|
1408
880
|
| { type: "l1_book.updated"; venue: Venue; symbol: string; snapshot: L1Book; ts: number }
|
|
1409
881
|
| { type: "funding_rate.updated"; venue: Venue; symbol: string; snapshot: FundingRateSnapshot; ts: number }
|
|
1410
882
|
| { type: "market.status_changed"; venue: Venue; symbol: string; status: MarketDataStatus; ts: number };
|
|
1411
883
|
|
|
1412
|
-
// Account
|
|
1413
884
|
type AccountEvent =
|
|
1414
|
-
| { type: "balance.updated"; accountId: string; venue: Venue;
|
|
1415
|
-
| { type: "position.updated"; accountId: string; venue: Venue;
|
|
1416
|
-
| { type: "risk.updated"; accountId: string; venue: Venue;
|
|
1417
|
-
| { type: "account.snapshot_replaced"; accountId: string; venue: Venue;
|
|
885
|
+
| { type: "balance.updated"; accountId: string; venue: Venue; asset: string; snapshot: BalanceSnapshot; ts: number }
|
|
886
|
+
| { type: "position.updated"; accountId: string; venue: Venue; symbol: string; snapshot: PositionSnapshot; ts: number }
|
|
887
|
+
| { type: "risk.updated"; accountId: string; venue: Venue; snapshot: RiskSnapshot; ts: number }
|
|
888
|
+
| { type: "account.snapshot_replaced"; accountId: string; venue: Venue; snapshot: AccountSnapshot; ts: number };
|
|
1418
889
|
|
|
1419
|
-
// Order
|
|
1420
890
|
type OrderEvent =
|
|
1421
|
-
| { type: "order.updated"; accountId: string; venue: Venue; symbol: string;
|
|
1422
|
-
| { type: "order.filled"; accountId: string; venue: Venue; symbol: string;
|
|
1423
|
-
| { type: "order.canceled"; accountId: string; venue: Venue; symbol: string;
|
|
1424
|
-
| { type: "order.rejected"; accountId: string; venue: Venue; symbol: string;
|
|
1425
|
-
| { type: "order.snapshot_replaced"; accountId: string; venue: Venue;
|
|
1426
|
-
|
|
1427
|
-
// Health
|
|
1428
|
-
type HealthEvent =
|
|
1429
|
-
| { type: "client.status_changed"; status: ClientStatus; ts: number }
|
|
1430
|
-
| { type: "market.status_changed"; venue: Venue; symbol: string; status: MarketDataStatus; ts: number }
|
|
1431
|
-
| { type: "account.status_changed"; accountId: string; venue: Venue; status: AccountDataStatus; ts: number }
|
|
1432
|
-
| { type: "order.status_changed"; accountId: string; venue: Venue; status: OrderDataStatus; ts: number };
|
|
1433
|
-
```
|
|
1434
|
-
|
|
1435
|
-
过滤器:
|
|
1436
|
-
|
|
1437
|
-
```ts
|
|
1438
|
-
interface MarketEventFilter { venue?: Venue; symbol?: string; }
|
|
1439
|
-
interface AccountEventFilter { accountId?: string; venue?: Venue; symbol?: string; }
|
|
1440
|
-
interface OrderEventFilter { accountId?: string; venue?: Venue; symbol?: string; }
|
|
1441
|
-
interface HealthEventFilter {
|
|
1442
|
-
scope?: "client" | "market" | "account" | "order";
|
|
1443
|
-
venue?: Venue;
|
|
1444
|
-
accountId?: string;
|
|
1445
|
-
symbol?: string;
|
|
1446
|
-
}
|
|
1447
|
-
```
|
|
1448
|
-
|
|
1449
|
-
### 9.7 错误
|
|
1450
|
-
|
|
1451
|
-
```ts
|
|
1452
|
-
interface AcexInternalError {
|
|
1453
|
-
source: "client" | "market" | "account" | "order" | "adapter" | "runtime";
|
|
1454
|
-
venue?: Venue;
|
|
1455
|
-
accountId?: string;
|
|
1456
|
-
symbol?: string;
|
|
1457
|
-
error: Error;
|
|
1458
|
-
ts: number;
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
interface AcexErrorDetails {
|
|
1462
|
-
venue?: Venue;
|
|
1463
|
-
accountId?: string;
|
|
1464
|
-
symbol?: string;
|
|
1465
|
-
venueError?: {
|
|
1466
|
-
code?: string;
|
|
1467
|
-
message?: string;
|
|
1468
|
-
};
|
|
1469
|
-
transport?: {
|
|
1470
|
-
kind?: "timeout" | "http" | "network" | "rate_limited" | "parse";
|
|
1471
|
-
status?: number;
|
|
1472
|
-
statusText?: string;
|
|
1473
|
-
retryAfterMs?: number;
|
|
1474
|
-
retryable?: boolean;
|
|
1475
|
-
attempts?: number;
|
|
1476
|
-
rawBody?: string;
|
|
1477
|
-
url?: string;
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
class AcexError extends Error {
|
|
1482
|
-
readonly code: AcexErrorCode;
|
|
1483
|
-
readonly details?: AcexErrorDetails;
|
|
1484
|
-
readonly cause?: unknown;
|
|
1485
|
-
}
|
|
891
|
+
| { type: "order.updated"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
892
|
+
| { type: "order.filled"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
893
|
+
| { type: "order.canceled"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
894
|
+
| { type: "order.rejected"; accountId: string; venue: Venue; symbol: string; snapshot: OrderSnapshot; ts: number }
|
|
895
|
+
| { type: "order.snapshot_replaced"; accountId: string; venue: Venue; snapshot: OrderSnapshot[]; ts: number };
|
|
1486
896
|
```
|
|
1487
897
|
|
|
1488
898
|
## 10. 错误处理
|
|
1489
899
|
|
|
1490
|
-
|
|
900
|
+
可预期错误统一抛 `AcexError`:
|
|
1491
901
|
|
|
1492
902
|
```ts
|
|
1493
903
|
import { AcexError } from "@imbingox/acex";
|
|
1494
904
|
|
|
1495
905
|
try {
|
|
1496
906
|
await client.market.subscribeL1Book({ venue: "binance", symbol: "X/Y:Z" });
|
|
1497
|
-
} catch (
|
|
1498
|
-
if (
|
|
1499
|
-
console.log(
|
|
1500
|
-
console.log(
|
|
1501
|
-
console.log(
|
|
907
|
+
} catch (error) {
|
|
908
|
+
if (error instanceof AcexError) {
|
|
909
|
+
console.log(error.code);
|
|
910
|
+
console.log(error.details?.venueError?.code);
|
|
911
|
+
console.log(error.details?.transport?.status);
|
|
1502
912
|
}
|
|
1503
913
|
}
|
|
1504
914
|
```
|
|
1505
915
|
|
|
1506
|
-
`details.venueError`
|
|
1507
|
-
|
|
1508
|
-
```ts
|
|
1509
|
-
{
|
|
1510
|
-
code: "-2010",
|
|
1511
|
-
message: "Order would immediately trigger.",
|
|
1512
|
-
}
|
|
1513
|
-
```
|
|
1514
|
-
|
|
1515
|
-
`details.transport` 保留已脱敏的 HTTP/transport 诊断信息,例如 `kind`、`status`、`retryAfterMs`、`attempts`、`rawBody`、`url`。`rawBody` 和 `url` 只用于排障兜底,不建议作为业务分支首选字段。market stream 首包超时时,`MARKET_STREAM_TIMEOUT` 会带 `details.venue` / `details.symbol` 和底层 `cause`,通常不填 `details.venueError`。`cause` 保留底层错误链,用于高级调试。
|
|
916
|
+
`details.venueError` 是读取交易所结构化拒绝原因的首选字段;`details.transport` 保存已脱敏的 HTTP / transport 诊断信息;`cause` 保留底层错误链。
|
|
1516
917
|
|
|
1517
|
-
|
|
918
|
+
完整错误码:
|
|
1518
919
|
|
|
1519
920
|
| Code | 典型场景 |
|
|
1520
921
|
|---|---|
|
|
1521
|
-
| `CLIENT_NOT_STARTED` | 未
|
|
1522
|
-
| `VENUE_NOT_SUPPORTED` | venue
|
|
1523
|
-
| `MARKET_CATALOG_LOAD_FAILED` |
|
|
922
|
+
| `CLIENT_NOT_STARTED` | 未 start 就调用订阅方法 |
|
|
923
|
+
| `VENUE_NOT_SUPPORTED` | venue runtime 未实现,或 read-only venue 被用于下单 |
|
|
924
|
+
| `MARKET_CATALOG_LOAD_FAILED` | market catalog 拉取失败 |
|
|
925
|
+
| `MARKET_SERVER_TIME_FETCH_FAILED` | server time 请求失败或响应结构不合法 |
|
|
926
|
+
| `MARKET_INACTIVE` | catalog 中 market 不活跃 |
|
|
927
|
+
| `MARKET_FUNDING_RATE_UNSUPPORTED` | 指定 market 不支持 funding rate |
|
|
1524
928
|
| `MARKET_NOT_FOUND` | 指定 symbol 不存在 |
|
|
1525
|
-
| `MARKET_INACTIVE` | 指定 symbol 在 catalog 中但不可交易 |
|
|
1526
|
-
| `MARKET_FUNDING_RATE_UNSUPPORTED` | 指定 market 不支持资金费率订阅 |
|
|
1527
929
|
| `MARKET_STREAM_TIMEOUT` | market stream 首条消息超时 |
|
|
1528
|
-
| `ACCOUNT_ALREADY_EXISTS` |
|
|
1529
|
-
| `
|
|
1530
|
-
| `
|
|
1531
|
-
| `CREDENTIALS_MISSING` |
|
|
1532
|
-
| `ORDER_BOOTSTRAP_FAILED` |
|
|
1533
|
-
| `ORDER_INPUT_INVALID` |
|
|
1534
|
-
| `ORDER_CREATE_FAILED` |
|
|
930
|
+
| `ACCOUNT_ALREADY_EXISTS` | 重复注册 accountId |
|
|
931
|
+
| `ACCOUNT_BOOTSTRAP_FAILED` | account bootstrap 失败 |
|
|
932
|
+
| `ACCOUNT_NOT_FOUND` | accountId 未注册或已移除 |
|
|
933
|
+
| `CREDENTIALS_MISSING` | private 订阅或下单缺凭证 |
|
|
934
|
+
| `ORDER_BOOTSTRAP_FAILED` | open orders bootstrap 失败 |
|
|
935
|
+
| `ORDER_INPUT_INVALID` | 本地订单输入校验失败 |
|
|
936
|
+
| `ORDER_CREATE_FAILED` | 下单 REST 失败或交易所拒单 |
|
|
1535
937
|
| `ORDER_CANCEL_FAILED` | 撤单失败 |
|
|
1536
938
|
| `ORDER_CANCEL_ALL_FAILED` | 批量撤单失败 |
|
|
1537
939
|
|
|
1538
940
|
## 11. 当前限制
|
|
1539
941
|
|
|
1540
|
-
-
|
|
1541
|
-
-
|
|
1542
|
-
-
|
|
1543
|
-
-
|
|
1544
|
-
-
|
|
1545
|
-
-
|
|
1546
|
-
-
|
|
1547
|
-
-
|
|
1548
|
-
-
|
|
1549
|
-
-
|
|
1550
|
-
- **Client options**:`sandbox` / `logger` / `logLevel` 是预留位,当前不生效
|
|
942
|
+
- market/order runtime 当前只支持 `binance`
|
|
943
|
+
- account runtime 支持 `binance` 和只读 `juplend`
|
|
944
|
+
- `okx` / `bybit` / `gate` 只在 `Venue` 类型中声明
|
|
945
|
+
- Funding Rate 仅支持 Binance 永续合约,包括 Binance TradFi Perps
|
|
946
|
+
- Binance order 命令固定走 PAPI UM,venue 级 `order.supported = true` 不代表 spot、COIN-M 或交割合约都能下单
|
|
947
|
+
- `cancelAllOrders()` 必须带 `symbol`,不支持账户级全撤
|
|
948
|
+
- `createOrder()` 不支持条件单、改单
|
|
949
|
+
- SDK 不自动纠偏订单精度;下游应使用 `normalizeOrderInput()`
|
|
950
|
+
- Juplend 只读,不支持链上写操作和 `OrderManager`
|
|
951
|
+
- `sandbox`、`logger`、`logLevel` 为预留位
|