@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 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`,否则使用默认 reactive limiter。Binance `riskPollIntervalMs` 默认 5s,用于风险和 mark-to-market 仓位刷新;`privateReconcileIntervalMs` 默认 60s,用于账户余额、仓位和订单状态 REST 对账,显式传 `0` 可关闭 private reconcile,但不关闭 risk polling。`sandbox`、`logger`、`logLevel` 目前是预留位。
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({ venue: "binance" })) {
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(ctx: RateLimitRequestContext): Promise<void> | void;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imbingox/acex",
3
- "version": "0.4.0-beta.16",
3
+ "version": "0.4.0-beta.18",
4
4
  "description": "Multi-exchange trading SDK for market data, account, and order management",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 = typeof fetch;
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
- await rateLimiter?.beforeRequest({ scope });
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: 3,
264
+ maxAttempts: 1,
256
265
  },
257
266
  messages: BINANCE_CATALOG_HTTP_MESSAGES,
258
267
  });
259
268
 
260
- await rateLimiter?.afterResponse(
261
- { scope },
262
- {
263
- status: response.status,
264
- headers: response.headers,
265
- usage: parseBinanceRateLimitUsage(response.headers),
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
- { scope },
274
- {
275
- status: error.status,
276
- headers: error.headers,
277
- retryAfterMs: error.retryAfterMs,
278
- usage: parseBinanceRateLimitUsage(error.headers),
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 = typeof fetch;
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 SAFE_READ_RETRY_POLICY: HttpRetryPolicy = {
183
+ const SINGLE_ATTEMPT_IDEMPOTENT_POLICY: HttpRetryPolicy = {
176
184
  idempotent: true,
177
- maxAttempts: 3,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- SAFE_READ_RETRY_POLICY,
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
- await this.options.rateLimiter?.beforeRequest({ scope });
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
- { scope },
1263
- {
1264
- status: response.status,
1265
- headers: response.headers,
1266
- usage: parseBinanceRateLimitUsage(response.headers),
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
- { scope },
1275
- {
1276
- status: error.status,
1277
- headers: error.headers,
1278
- retryAfterMs: error.retryAfterMs,
1279
- usage: parseBinanceRateLimitUsage(error.headers),
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
- LISTEN_KEY_KEEPALIVE_RETRY_POLICY,
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
- await this.options.rateLimiter?.beforeRequest({ scope });
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
- { scope },
1376
- {
1377
- status: response.status,
1378
- headers: response.headers,
1379
- usage: parseBinanceRateLimitUsage(response.headers),
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
- { scope },
1388
- {
1389
- status: error.status,
1390
- headers: error.headers,
1391
- retryAfterMs: error.retryAfterMs,
1392
- usage: parseBinanceRateLimitUsage(error.headers),
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;