@imbingox/acex 0.4.0-beta.16 → 0.4.0-beta.18
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/CHANGELOG.md +12 -0
- package/docs/api.md +105 -7
- package/package.json +1 -1
- package/src/adapters/binance/adapter.ts +4 -1
- package/src/adapters/binance/market-catalog.ts +25 -20
- package/src/adapters/binance/private-adapter.ts +68 -53
- package/src/adapters/binance/rate-limit-topology.ts +257 -0
- package/src/adapters/binance/server-time.ts +20 -18
- package/src/client/runtime.ts +47 -6
- package/src/errors.ts +1 -0
- package/src/internal/async-event-bus.ts +75 -3
- package/src/internal/rate-limiter/snapshot.ts +67 -0
- package/src/internal/rate-limiter/state.ts +98 -0
- package/src/internal/rate-limiter/topology.ts +123 -0
- package/src/internal/rate-limiter/types.ts +49 -0
- package/src/internal/rate-limiter/usage.ts +48 -0
- package/src/internal/rate-limiter.ts +792 -74
- package/src/managers/account-manager.ts +43 -16
- package/src/managers/market-manager.ts +111 -7
- package/src/managers/order-manager.ts +42 -16
- package/src/types/account.ts +9 -2
- package/src/types/client.ts +8 -2
- package/src/types/market.ts +19 -4
- package/src/types/order.ts +9 -2
- package/src/types/shared.ts +209 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @imbingox/acex
|
|
2
2
|
|
|
3
|
+
## 0.4.0-beta.18
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3edefc1: Extend the public rate limiter SPI with optional topology plans, bucket reserve headroom, request priority, opaque reservations, and bucket-level snapshots. The default limiter now supports Binance REST topology registration, fixed-window bucket budget admission, cancel-priority reserve for Binance PAPI request weight, usage-header reconciliation, request-not-sent refunds, jittered bucket-level 429 fallback, and bucket-level 429/418 blocking while remaining backward compatible with existing custom `RateLimiter` implementations.
|
|
8
|
+
|
|
9
|
+
## 0.4.0-beta.17
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 35b8163: 事件流新增 `conflate` / `buffer` 与 `maxBuffer` 订阅选项:L1 Book 与 Funding Rate 默认改为 latest-wins,慢消费者只保留同一 `venue:symbol` 的最新事件;market status 事件按 activity/ready/freshness/reason 去重发布;buffer 溢出会丢弃最旧事件并通过 `EVENT_BUFFER_OVERFLOW` runtime error 告警。
|
|
14
|
+
|
|
3
15
|
## 0.4.0-beta.16
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/docs/api.md
CHANGED
|
@@ -253,7 +253,7 @@ const client = createClient({
|
|
|
253
253
|
});
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
`clock` 只用于 outbound request / signing timestamp,不驱动 WebSocket freshness 的 received-at 时钟。需要自定义 REST 限流行为时可传 `rateLimiter`,否则使用默认
|
|
256
|
+
`clock` 只用于 outbound request / signing timestamp,不驱动 WebSocket freshness 的 received-at 时钟。需要自定义 REST 限流行为时可传 `rateLimiter`,否则使用默认 bucket-aware budget limiter:它会注册 Binance REST topology,在 `beforeRequest` 中按固定窗口和 `rateLimit.utilizationTarget`(默认 0.9)主动预扣预算,接近上限时 sleep 到下一窗口;Binance PAPI request-weight 桶为 `priority:"cancel"` 保留 headroom,撤单请求仍计入真实 weight 但可使用保留区;响应后的 Binance usage header 会回填校正 bucket 用量,429/418 block 也会落到对应 bucket,缺少 `Retry-After` 的 429 会冷却到窗口结束并带小 jitter。Binance `riskPollIntervalMs` 默认 5s,用于风险和 mark-to-market 仓位刷新;`privateReconcileIntervalMs` 默认 60s,用于账户余额、仓位和订单状态 REST 对账,显式传 `0` 可关闭 private reconcile,但不关闭 risk polling。`sandbox`、`logger`、`logLevel` 目前是预留位。
|
|
257
257
|
|
|
258
258
|
### 4.2 `start()` / `stop()`
|
|
259
259
|
|
|
@@ -372,6 +372,26 @@ console.log(time.serverTime, time.roundTripMs, time.estimatedOffsetMs);
|
|
|
372
372
|
|
|
373
373
|
Funding Rate 当前通过 Binance mark price websocket 更新,仅支持永续合约(`MarketDefinition.type === "swap"`,包括 Binance TradFi Perps)。spot 或 future 订阅会抛 `MARKET_FUNDING_RATE_UNSUPPORTED`。
|
|
374
374
|
|
|
375
|
+
### 5.5 事件流 options
|
|
376
|
+
|
|
377
|
+
Market 事件流支持可选第二参:
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
type EventStreamOptions = {
|
|
381
|
+
mode?: "conflate" | "buffer";
|
|
382
|
+
maxBuffer?: number;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
client.market.events.l1BookUpdates(
|
|
386
|
+
{ venue: "binance", symbol: "BTC/USDT:USDT" },
|
|
387
|
+
{ mode: "buffer", maxBuffer: 50_000 },
|
|
388
|
+
);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`l1BookUpdates()` 与 `fundingRateUpdates()` 默认使用 `conflate`,同一 `venue:symbol` 慢消费者只保留最新事件,适合策略热路径。需要录制每个 tick 时显式传 `{ mode: "buffer" }`。`market.events.all()` 与 `market.events.status()` 默认使用 `buffer`;显式传 `{ mode: "conflate" }` 时,`all()` 按 `type:venue:symbol` 合并,`status()` 按 `venue:symbol` 合并。
|
|
392
|
+
|
|
393
|
+
`buffer` 模式默认每个订阅者最多积压 `10_000` 条事件,超过后丢弃最旧事件。每次积压 episode 只会向 `client.events.errors()` 发布一次 `EVENT_BUFFER_OVERFLOW` runtime error,事件 metadata 包含 `stream` 与 `maxBuffer`;队列排空后再次溢出会再次告警。`conflate` 模式天然有界,不使用 `maxBuffer`。
|
|
394
|
+
|
|
375
395
|
## 6. AccountManager
|
|
376
396
|
|
|
377
397
|
```ts
|
|
@@ -400,7 +420,7 @@ Account 事件用于消费余额、仓位、风险或全量快照替换:
|
|
|
400
420
|
```ts
|
|
401
421
|
for await (const event of client.account.events.updates({
|
|
402
422
|
accountId: "main-binance",
|
|
403
|
-
})) {
|
|
423
|
+
}, { maxBuffer: 20_000 })) {
|
|
404
424
|
if (event.type === "risk.updated") {
|
|
405
425
|
console.log(event.snapshot.riskRatio);
|
|
406
426
|
}
|
|
@@ -408,6 +428,8 @@ for await (const event of client.account.events.updates({
|
|
|
408
428
|
}
|
|
409
429
|
```
|
|
410
430
|
|
|
431
|
+
Account 事件流只支持 `{ maxBuffer?: number }`,不提供 conflate;余额、仓位、风险和状态事件默认按 buffer 语义保留顺序。
|
|
432
|
+
|
|
411
433
|
## 7. OrderManager
|
|
412
434
|
|
|
413
435
|
```ts
|
|
@@ -456,7 +478,7 @@ Order 事件用于消费订单状态变化和 open orders 快照校准。Binance
|
|
|
456
478
|
for await (const event of client.order.events.updates({
|
|
457
479
|
accountId: "main-binance",
|
|
458
480
|
symbol: "BTC/USDT:USDT",
|
|
459
|
-
})) {
|
|
481
|
+
}, { maxBuffer: 20_000 })) {
|
|
460
482
|
if (event.type === "order.filled") {
|
|
461
483
|
console.log(event.snapshot.filled);
|
|
462
484
|
}
|
|
@@ -464,23 +486,28 @@ for await (const event of client.order.events.updates({
|
|
|
464
486
|
}
|
|
465
487
|
```
|
|
466
488
|
|
|
489
|
+
Order 事件流只支持 `{ maxBuffer?: number }`,不提供 conflate;订单中间状态和错误恢复信号默认按 buffer 语义保留顺序。
|
|
490
|
+
|
|
467
491
|
## 8. 健康与错误事件
|
|
468
492
|
|
|
469
493
|
```ts
|
|
470
494
|
const health = client.getHealth();
|
|
471
495
|
|
|
472
|
-
for await (const event of client.events.health(
|
|
496
|
+
for await (const event of client.events.health(
|
|
497
|
+
{ venue: "binance" },
|
|
498
|
+
{ maxBuffer: 20_000 },
|
|
499
|
+
)) {
|
|
473
500
|
console.log(event.type);
|
|
474
501
|
break;
|
|
475
502
|
}
|
|
476
503
|
|
|
477
|
-
for await (const error of client.events.errors()) {
|
|
504
|
+
for await (const error of client.events.errors({ maxBuffer: 20_000 })) {
|
|
478
505
|
console.error(error.source, error.error);
|
|
479
506
|
break;
|
|
480
507
|
}
|
|
481
508
|
```
|
|
482
509
|
|
|
483
|
-
`getHealth()` 聚合 client、market、account、order 的当前状态。`events.health(filter)` 只返回满足 filter 的事件;如果事件没有 filter
|
|
510
|
+
`getHealth()` 聚合 client、market、account、order 的当前状态。`events.health(filter, options?)` 只返回满足 filter 的事件;如果事件没有 filter 请求的字段,会被过滤掉。`events.health()` 与 `events.errors()` 只支持 `{ maxBuffer?: number }`,默认 buffer 上限同样是 `10_000`;`errors()` 自身溢出时只丢弃最旧错误事件,不再发布新的 overflow 错误,避免递归。
|
|
484
511
|
|
|
485
512
|
## 9. 数据类型速查
|
|
486
513
|
|
|
@@ -572,6 +599,9 @@ interface CreateClientOptions {
|
|
|
572
599
|
sandbox?: boolean;
|
|
573
600
|
clock?: { now(): number };
|
|
574
601
|
rateLimiter?: RateLimiter;
|
|
602
|
+
rateLimit?: {
|
|
603
|
+
utilizationTarget?: number;
|
|
604
|
+
};
|
|
575
605
|
logger?: Logger;
|
|
576
606
|
logLevel?: "debug" | "info" | "warn" | "error";
|
|
577
607
|
market?: {
|
|
@@ -614,14 +644,77 @@ interface RateLimitUsage {
|
|
|
614
644
|
orderCount?: Record<string, number>;
|
|
615
645
|
}
|
|
616
646
|
|
|
647
|
+
type RateLimitPriority = "normal" | "cancel" | "risk" | (string & {});
|
|
648
|
+
type RateLimitBucketKind = "request_weight" | "orders" | (string & {});
|
|
649
|
+
type RateLimitScopeDimension = "venue" | "account" | "endpoint";
|
|
650
|
+
|
|
651
|
+
interface RateLimitBucketReserve {
|
|
652
|
+
priority: RateLimitPriority;
|
|
653
|
+
units: number;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
interface RateLimitBucketDescriptor {
|
|
657
|
+
id: string;
|
|
658
|
+
kind: RateLimitBucketKind;
|
|
659
|
+
limit: number;
|
|
660
|
+
intervalMs: number;
|
|
661
|
+
scope: readonly RateLimitScopeDimension[];
|
|
662
|
+
utilizationTarget?: number;
|
|
663
|
+
reserve?: RateLimitBucketReserve;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
interface RateLimitCost {
|
|
667
|
+
bucketId: string;
|
|
668
|
+
cost: number;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
interface RateLimitPlan {
|
|
672
|
+
id: string;
|
|
673
|
+
costs: readonly RateLimitCost[];
|
|
674
|
+
priority?: RateLimitPriority;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
interface RateLimitTopology {
|
|
678
|
+
id: string;
|
|
679
|
+
buckets: readonly RateLimitBucketDescriptor[];
|
|
680
|
+
plans: readonly RateLimitPlan[];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
interface RateLimitReservation {
|
|
684
|
+
readonly __opaqueRateLimitReservation?: never;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
interface RateLimitTopologyRegistry {
|
|
688
|
+
registerRateLimitTopology(topology: RateLimitTopology): void;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
interface RateLimitBucketSnapshot {
|
|
692
|
+
bucketId: string;
|
|
693
|
+
kind: RateLimitBucketKind;
|
|
694
|
+
limit: number;
|
|
695
|
+
intervalMs: number;
|
|
696
|
+
utilizationTarget?: number;
|
|
697
|
+
reserve?: RateLimitBucketReserve;
|
|
698
|
+
used?: number;
|
|
699
|
+
windowStartMs?: number;
|
|
700
|
+
windowEndMs?: number;
|
|
701
|
+
blockedUntil?: number;
|
|
702
|
+
retryAfterMs?: number;
|
|
703
|
+
state: "ok" | "rate_limited" | "banned";
|
|
704
|
+
updatedAt?: number;
|
|
705
|
+
}
|
|
706
|
+
|
|
617
707
|
interface RateLimitRequestContext {
|
|
618
708
|
scope: RateLimitScope;
|
|
709
|
+
planId?: string;
|
|
710
|
+
priority?: RateLimitPriority;
|
|
619
711
|
}
|
|
620
712
|
|
|
621
713
|
interface RateLimitResponseContext {
|
|
622
714
|
status: number;
|
|
623
715
|
headers?: Headers;
|
|
624
716
|
usage?: RateLimitUsage;
|
|
717
|
+
reservation?: RateLimitReservation;
|
|
625
718
|
}
|
|
626
719
|
|
|
627
720
|
interface RateLimitTransportErrorContext {
|
|
@@ -629,6 +722,8 @@ interface RateLimitTransportErrorContext {
|
|
|
629
722
|
headers?: Headers;
|
|
630
723
|
retryAfterMs?: number;
|
|
631
724
|
usage?: RateLimitUsage;
|
|
725
|
+
reservation?: RateLimitReservation;
|
|
726
|
+
requestNotSent?: boolean;
|
|
632
727
|
}
|
|
633
728
|
|
|
634
729
|
interface RateLimitSnapshot {
|
|
@@ -638,10 +733,13 @@ interface RateLimitSnapshot {
|
|
|
638
733
|
retryAfterMs?: number;
|
|
639
734
|
state: "ok" | "rate_limited" | "banned";
|
|
640
735
|
updatedAt?: number;
|
|
736
|
+
buckets?: RateLimitBucketSnapshot[];
|
|
641
737
|
}
|
|
642
738
|
|
|
643
739
|
interface RateLimiter {
|
|
644
|
-
beforeRequest(
|
|
740
|
+
beforeRequest(
|
|
741
|
+
ctx: RateLimitRequestContext,
|
|
742
|
+
): Promise<RateLimitReservation | void> | RateLimitReservation | void;
|
|
645
743
|
afterResponse(
|
|
646
744
|
ctx: RateLimitRequestContext,
|
|
647
745
|
response: RateLimitResponseContext,
|
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type BinanceMarketDefinition,
|
|
18
18
|
loadBinanceMarkets,
|
|
19
19
|
} from "./market-catalog.ts";
|
|
20
|
+
import { registerBinanceRateLimitTopology } from "./rate-limit-topology.ts";
|
|
20
21
|
import { fetchBinanceServerTime } from "./server-time.ts";
|
|
21
22
|
import {
|
|
22
23
|
type BinanceStreamDescriptor,
|
|
@@ -61,7 +62,9 @@ export class BinanceMarketAdapter implements MarketAdapter {
|
|
|
61
62
|
private readonly options: {
|
|
62
63
|
readonly rateLimiter?: RateLimiter;
|
|
63
64
|
} = {},
|
|
64
|
-
) {
|
|
65
|
+
) {
|
|
66
|
+
registerBinanceRateLimitTopology(this.options.rateLimiter);
|
|
67
|
+
}
|
|
65
68
|
|
|
66
69
|
async loadMarkets(): Promise<MarketDefinition[]> {
|
|
67
70
|
const markets = await loadBinanceMarkets(fetch, {
|
|
@@ -11,8 +11,12 @@ import type {
|
|
|
11
11
|
RateLimitScope,
|
|
12
12
|
} from "../../types/index.ts";
|
|
13
13
|
import { parseBinanceRateLimitUsage } from "./rate-limit.ts";
|
|
14
|
+
import { getBinanceCatalogRateLimitPlanId } from "./rate-limit-topology.ts";
|
|
14
15
|
|
|
15
|
-
type FetchLike =
|
|
16
|
+
type FetchLike = (
|
|
17
|
+
input: string | URL | Request,
|
|
18
|
+
init?: RequestInit,
|
|
19
|
+
) => Promise<Response>;
|
|
16
20
|
|
|
17
21
|
export type BinanceMarketFamily = "spot" | "usdm" | "coinm";
|
|
18
22
|
|
|
@@ -240,8 +244,13 @@ async function requestCatalogJson<T>(
|
|
|
240
244
|
venue: "binance",
|
|
241
245
|
endpointKey,
|
|
242
246
|
};
|
|
247
|
+
const requestContext = {
|
|
248
|
+
scope,
|
|
249
|
+
planId: getBinanceCatalogRateLimitPlanId(endpointKey),
|
|
250
|
+
};
|
|
243
251
|
|
|
244
|
-
|
|
252
|
+
const reservation =
|
|
253
|
+
(await rateLimiter?.beforeRequest(requestContext)) ?? undefined;
|
|
245
254
|
|
|
246
255
|
try {
|
|
247
256
|
const response = await httpRequest<T>({
|
|
@@ -252,32 +261,28 @@ async function requestCatalogJson<T>(
|
|
|
252
261
|
jsonParseMode: "response",
|
|
253
262
|
retryPolicy: {
|
|
254
263
|
idempotent: true,
|
|
255
|
-
maxAttempts:
|
|
264
|
+
maxAttempts: 1,
|
|
256
265
|
},
|
|
257
266
|
messages: BINANCE_CATALOG_HTTP_MESSAGES,
|
|
258
267
|
});
|
|
259
268
|
|
|
260
|
-
await rateLimiter?.afterResponse(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
},
|
|
267
|
-
);
|
|
269
|
+
await rateLimiter?.afterResponse(requestContext, {
|
|
270
|
+
status: response.status,
|
|
271
|
+
headers: response.headers,
|
|
272
|
+
usage: parseBinanceRateLimitUsage(response.headers),
|
|
273
|
+
reservation,
|
|
274
|
+
});
|
|
268
275
|
|
|
269
276
|
return response.body;
|
|
270
277
|
} catch (error) {
|
|
271
278
|
if (isTransportError(error)) {
|
|
272
|
-
await rateLimiter?.onTransportError(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
},
|
|
280
|
-
);
|
|
279
|
+
await rateLimiter?.onTransportError(requestContext, {
|
|
280
|
+
status: error.status,
|
|
281
|
+
headers: error.headers,
|
|
282
|
+
retryAfterMs: error.retryAfterMs,
|
|
283
|
+
usage: parseBinanceRateLimitUsage(error.headers),
|
|
284
|
+
reservation,
|
|
285
|
+
});
|
|
281
286
|
}
|
|
282
287
|
|
|
283
288
|
throw error;
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
AccountCredentials,
|
|
12
12
|
PositionSide,
|
|
13
13
|
RateLimiter,
|
|
14
|
+
RateLimitPriority,
|
|
14
15
|
RateLimitScope,
|
|
15
16
|
TimeProvider,
|
|
16
17
|
VenueAccountCapabilities,
|
|
@@ -35,10 +36,17 @@ import type {
|
|
|
35
36
|
} from "../types.ts";
|
|
36
37
|
import { normalizeBinanceErrorCode } from "./error-codes.ts";
|
|
37
38
|
import { parseBinanceRateLimitUsage } from "./rate-limit.ts";
|
|
39
|
+
import {
|
|
40
|
+
getBinancePapiRateLimitPlanId,
|
|
41
|
+
registerBinanceRateLimitTopology,
|
|
42
|
+
} from "./rate-limit-topology.ts";
|
|
38
43
|
|
|
39
44
|
type TimerHandle = ReturnType<typeof setInterval>;
|
|
40
45
|
type SignedRequestMethod = "GET" | "POST" | "DELETE";
|
|
41
|
-
type FetchLike =
|
|
46
|
+
type FetchLike = (
|
|
47
|
+
input: string | URL | Request,
|
|
48
|
+
init?: RequestInit,
|
|
49
|
+
) => Promise<Response>;
|
|
42
50
|
|
|
43
51
|
interface BinancePapiBalance {
|
|
44
52
|
asset?: string;
|
|
@@ -172,18 +180,14 @@ const BINANCE_PAPI_WS_BASE_URL = "wss://fstream.binance.com/pm/ws";
|
|
|
172
180
|
const DEFAULT_RECV_WINDOW = 5_000;
|
|
173
181
|
const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
|
|
174
182
|
const USDM_QUOTE_ASSETS = ["FDUSD", "USDC", "BUSD", "USDT"];
|
|
175
|
-
const
|
|
183
|
+
const SINGLE_ATTEMPT_IDEMPOTENT_POLICY: HttpRetryPolicy = {
|
|
176
184
|
idempotent: true,
|
|
177
|
-
maxAttempts:
|
|
185
|
+
maxAttempts: 1,
|
|
178
186
|
};
|
|
179
187
|
const NO_RETRY_POLICY: HttpRetryPolicy = {
|
|
180
188
|
idempotent: false,
|
|
181
189
|
maxAttempts: 1,
|
|
182
190
|
};
|
|
183
|
-
const LISTEN_KEY_KEEPALIVE_RETRY_POLICY: HttpRetryPolicy = {
|
|
184
|
-
idempotent: true,
|
|
185
|
-
maxAttempts: 3,
|
|
186
|
-
};
|
|
187
191
|
function getBinancePapiHttpMessages(timeoutMs: number): HttpClientMessages {
|
|
188
192
|
return {
|
|
189
193
|
http: ({ status, statusText, url, rawBody }) =>
|
|
@@ -674,7 +678,9 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
674
678
|
readonly signingClock?: TimeProvider;
|
|
675
679
|
readonly rateLimiter?: RateLimiter;
|
|
676
680
|
} = {},
|
|
677
|
-
) {
|
|
681
|
+
) {
|
|
682
|
+
registerBinanceRateLimitTopology(this.options.rateLimiter);
|
|
683
|
+
}
|
|
678
684
|
|
|
679
685
|
normalizeVenueErrorCode(code: string) {
|
|
680
686
|
return normalizeBinanceErrorCode(code);
|
|
@@ -692,7 +698,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
692
698
|
credentials,
|
|
693
699
|
accountOptions,
|
|
694
700
|
undefined,
|
|
695
|
-
|
|
701
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
696
702
|
),
|
|
697
703
|
this.signedRequest<BinancePapiAccount>(
|
|
698
704
|
"GET",
|
|
@@ -700,7 +706,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
700
706
|
credentials,
|
|
701
707
|
accountOptions,
|
|
702
708
|
undefined,
|
|
703
|
-
|
|
709
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
704
710
|
),
|
|
705
711
|
this.signedRequest<BinancePapiUmPosition[]>(
|
|
706
712
|
"GET",
|
|
@@ -708,7 +714,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
708
714
|
credentials,
|
|
709
715
|
accountOptions,
|
|
710
716
|
undefined,
|
|
711
|
-
|
|
717
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
712
718
|
),
|
|
713
719
|
]);
|
|
714
720
|
|
|
@@ -734,7 +740,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
734
740
|
credentials,
|
|
735
741
|
accountOptions,
|
|
736
742
|
undefined,
|
|
737
|
-
|
|
743
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
738
744
|
),
|
|
739
745
|
this.signedRequest<BinancePapiUmPosition[]>(
|
|
740
746
|
"GET",
|
|
@@ -742,7 +748,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
742
748
|
credentials,
|
|
743
749
|
accountOptions,
|
|
744
750
|
undefined,
|
|
745
|
-
|
|
751
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
746
752
|
),
|
|
747
753
|
]);
|
|
748
754
|
|
|
@@ -768,7 +774,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
768
774
|
credentials,
|
|
769
775
|
accountOptions,
|
|
770
776
|
undefined,
|
|
771
|
-
|
|
777
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
772
778
|
);
|
|
773
779
|
|
|
774
780
|
return {
|
|
@@ -797,7 +803,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
797
803
|
orderId: request.orderId,
|
|
798
804
|
origClientOrderId: request.clientOrderId,
|
|
799
805
|
},
|
|
800
|
-
|
|
806
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
801
807
|
);
|
|
802
808
|
|
|
803
809
|
return mapOpenOrder(response, receivedAt);
|
|
@@ -870,6 +876,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
870
876
|
origClientOrderId: request.clientOrderId,
|
|
871
877
|
},
|
|
872
878
|
NO_RETRY_POLICY,
|
|
879
|
+
"cancel",
|
|
873
880
|
);
|
|
874
881
|
|
|
875
882
|
const mapped = mapOpenOrder(response, receivedAt);
|
|
@@ -896,7 +903,8 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
896
903
|
{
|
|
897
904
|
symbol,
|
|
898
905
|
},
|
|
899
|
-
|
|
906
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
907
|
+
"cancel",
|
|
900
908
|
);
|
|
901
909
|
|
|
902
910
|
// Venue responds {code,msg}; returned updates are synthesized from the
|
|
@@ -911,6 +919,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
911
919
|
symbol,
|
|
912
920
|
},
|
|
913
921
|
NO_RETRY_POLICY,
|
|
922
|
+
"cancel",
|
|
914
923
|
);
|
|
915
924
|
|
|
916
925
|
if (response.code !== undefined && `${response.code}` !== "200") {
|
|
@@ -1216,10 +1225,18 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
1216
1225
|
accountOptions?: Record<string, unknown>,
|
|
1217
1226
|
queryParams?: Record<string, string | undefined>,
|
|
1218
1227
|
retryPolicy?: HttpRetryPolicy,
|
|
1228
|
+
priority?: RateLimitPriority,
|
|
1219
1229
|
): Promise<T> {
|
|
1220
1230
|
const { apiKey, secret } = requirePrivateCredentials(credentials);
|
|
1221
1231
|
const scope = this.rateLimitScope(method, path, accountOptions);
|
|
1222
|
-
|
|
1232
|
+
const requestContext = {
|
|
1233
|
+
scope,
|
|
1234
|
+
planId: getBinancePapiRateLimitPlanId(method, path, queryParams),
|
|
1235
|
+
priority,
|
|
1236
|
+
};
|
|
1237
|
+
const reservation =
|
|
1238
|
+
(await this.options.rateLimiter?.beforeRequest(requestContext)) ??
|
|
1239
|
+
undefined;
|
|
1223
1240
|
|
|
1224
1241
|
const params = new URLSearchParams();
|
|
1225
1242
|
for (const [key, value] of Object.entries(queryParams ?? {})) {
|
|
@@ -1258,27 +1275,23 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
1258
1275
|
messages: getBinancePapiHttpMessages(timeoutMs),
|
|
1259
1276
|
});
|
|
1260
1277
|
|
|
1261
|
-
await this.options.rateLimiter?.afterResponse(
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
},
|
|
1268
|
-
);
|
|
1278
|
+
await this.options.rateLimiter?.afterResponse(requestContext, {
|
|
1279
|
+
status: response.status,
|
|
1280
|
+
headers: response.headers,
|
|
1281
|
+
usage: parseBinanceRateLimitUsage(response.headers),
|
|
1282
|
+
reservation,
|
|
1283
|
+
});
|
|
1269
1284
|
|
|
1270
1285
|
return response.body;
|
|
1271
1286
|
} catch (error) {
|
|
1272
1287
|
if (isTransportError(error)) {
|
|
1273
|
-
await this.options.rateLimiter?.onTransportError(
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
},
|
|
1281
|
-
);
|
|
1288
|
+
await this.options.rateLimiter?.onTransportError(requestContext, {
|
|
1289
|
+
status: error.status,
|
|
1290
|
+
headers: error.headers,
|
|
1291
|
+
retryAfterMs: error.retryAfterMs,
|
|
1292
|
+
usage: parseBinanceRateLimitUsage(error.headers),
|
|
1293
|
+
reservation,
|
|
1294
|
+
});
|
|
1282
1295
|
}
|
|
1283
1296
|
|
|
1284
1297
|
throw error;
|
|
@@ -1312,7 +1325,7 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
1312
1325
|
"PUT",
|
|
1313
1326
|
credentials,
|
|
1314
1327
|
listenKey,
|
|
1315
|
-
|
|
1328
|
+
SINGLE_ATTEMPT_IDEMPOTENT_POLICY,
|
|
1316
1329
|
accountOptions,
|
|
1317
1330
|
);
|
|
1318
1331
|
}
|
|
@@ -1344,7 +1357,13 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
1344
1357
|
"/papi/v1/listenKey",
|
|
1345
1358
|
accountOptions,
|
|
1346
1359
|
);
|
|
1347
|
-
|
|
1360
|
+
const requestContext = {
|
|
1361
|
+
scope,
|
|
1362
|
+
planId: getBinancePapiRateLimitPlanId(method, "/papi/v1/listenKey"),
|
|
1363
|
+
};
|
|
1364
|
+
const reservation =
|
|
1365
|
+
(await this.options.rateLimiter?.beforeRequest(requestContext)) ??
|
|
1366
|
+
undefined;
|
|
1348
1367
|
|
|
1349
1368
|
const params = new URLSearchParams();
|
|
1350
1369
|
if (listenKey) {
|
|
@@ -1371,27 +1390,23 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
1371
1390
|
messages: getBinancePapiHttpMessages(timeoutMs),
|
|
1372
1391
|
});
|
|
1373
1392
|
|
|
1374
|
-
await this.options.rateLimiter?.afterResponse(
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
},
|
|
1381
|
-
);
|
|
1393
|
+
await this.options.rateLimiter?.afterResponse(requestContext, {
|
|
1394
|
+
status: response.status,
|
|
1395
|
+
headers: response.headers,
|
|
1396
|
+
usage: parseBinanceRateLimitUsage(response.headers),
|
|
1397
|
+
reservation,
|
|
1398
|
+
});
|
|
1382
1399
|
|
|
1383
1400
|
return response.body;
|
|
1384
1401
|
} catch (error) {
|
|
1385
1402
|
if (isTransportError(error)) {
|
|
1386
|
-
await this.options.rateLimiter?.onTransportError(
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
},
|
|
1394
|
-
);
|
|
1403
|
+
await this.options.rateLimiter?.onTransportError(requestContext, {
|
|
1404
|
+
status: error.status,
|
|
1405
|
+
headers: error.headers,
|
|
1406
|
+
retryAfterMs: error.retryAfterMs,
|
|
1407
|
+
usage: parseBinanceRateLimitUsage(error.headers),
|
|
1408
|
+
reservation,
|
|
1409
|
+
});
|
|
1395
1410
|
}
|
|
1396
1411
|
|
|
1397
1412
|
throw error;
|