@imbingox/acex 0.4.0-beta.1 → 0.4.0-beta.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imbingox/acex",
3
- "version": "0.4.0-beta.1",
3
+ "version": "0.4.0-beta.11",
4
4
  "description": "Multi-exchange trading SDK for market data, account, and order management",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,9 @@
1
1
  import { SubscriptionMultiplexer } from "../../internal/subscription-multiplexer.ts";
2
2
  import type {
3
3
  MarketDefinition,
4
+ RateLimiter,
4
5
  VenueMarketCapabilities,
6
+ VenueServerTime,
5
7
  } from "../../types/index.ts";
6
8
  import type {
7
9
  FundingRateStreamCallbacks,
@@ -15,6 +17,7 @@ import {
15
17
  type BinanceMarketDefinition,
16
18
  loadBinanceMarkets,
17
19
  } from "./market-catalog.ts";
20
+ import { fetchBinanceServerTime } from "./server-time.ts";
18
21
  import {
19
22
  type BinanceStreamDescriptor,
20
23
  type BinanceStreamMessage,
@@ -44,6 +47,7 @@ export class BinanceMarketAdapter implements MarketAdapter {
44
47
  readonly venue = "binance" as const;
45
48
  readonly marketCapabilities: VenueMarketCapabilities = {
46
49
  catalog: "supported",
50
+ serverTime: "supported",
47
51
  l1Book: "supported",
48
52
  fundingRate: "market_dependent",
49
53
  marketTypes: ["spot", "swap", "future"],
@@ -53,8 +57,16 @@ export class BinanceMarketAdapter implements MarketAdapter {
53
57
  private multiplexer: BinanceMarketMultiplexer | undefined;
54
58
  private multiplexerConfig: BinanceMultiplexerConfig | undefined;
55
59
 
60
+ constructor(
61
+ private readonly options: {
62
+ readonly rateLimiter?: RateLimiter;
63
+ } = {},
64
+ ) {}
65
+
56
66
  async loadMarkets(): Promise<MarketDefinition[]> {
57
- const markets = await loadBinanceMarkets();
67
+ const markets = await loadBinanceMarkets(fetch, {
68
+ rateLimiter: this.options.rateLimiter,
69
+ });
58
70
  this.definitions.clear();
59
71
 
60
72
  for (const market of markets) {
@@ -64,6 +76,12 @@ export class BinanceMarketAdapter implements MarketAdapter {
64
76
  return markets;
65
77
  }
66
78
 
79
+ async fetchServerTime(): Promise<VenueServerTime> {
80
+ return await fetchBinanceServerTime({
81
+ rateLimiter: this.options.rateLimiter,
82
+ });
83
+ }
84
+
67
85
  createL1BookStream(
68
86
  market: MarketDefinition,
69
87
  callbacks: L1BookStreamCallbacks,
@@ -1,5 +1,16 @@
1
1
  import { toCanonical } from "../../internal/decimal.ts";
2
- import type { MarketDefinition, MarketType } from "../../types/index.ts";
2
+ import {
3
+ type HttpClientMessages,
4
+ httpRequest,
5
+ isTransportError,
6
+ } from "../../internal/http-client.ts";
7
+ import type {
8
+ MarketDefinition,
9
+ MarketType,
10
+ RateLimiter,
11
+ RateLimitScope,
12
+ } from "../../types/index.ts";
13
+ import { parseBinanceRateLimitUsage } from "./rate-limit.ts";
3
14
 
4
15
  type FetchLike = typeof fetch;
5
16
 
@@ -54,6 +65,11 @@ const BINANCE_USDM_EXCHANGE_INFO_URL =
54
65
  "https://fapi.binance.com/fapi/v1/exchangeInfo";
55
66
  const BINANCE_COINM_EXCHANGE_INFO_URL =
56
67
  "https://dapi.binance.com/dapi/v1/exchangeInfo";
68
+ const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
69
+ const BINANCE_CATALOG_HTTP_MESSAGES: HttpClientMessages = {
70
+ http: ({ status, statusText }) =>
71
+ `Binance request failed: ${status} ${statusText ?? ""}`,
72
+ };
57
73
 
58
74
  function toRecord(value: unknown): Record<string, unknown> {
59
75
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -96,7 +112,9 @@ function inferContractType(
96
112
  contractType: string | undefined,
97
113
  deliveryDate: number | undefined,
98
114
  ): MarketType {
99
- if (contractType === "PERPETUAL") {
115
+ // Binance TradFi perpetuals expose a far-future deliveryDate, so the
116
+ // contractType is authoritative for perpetual classification.
117
+ if (contractType === "PERPETUAL" || contractType === "TRADIFI_PERPETUAL") {
100
118
  return "swap";
101
119
  }
102
120
 
@@ -212,15 +230,58 @@ function normalizeDerivativesSymbol(
212
230
  };
213
231
  }
214
232
 
215
- async function fetchJson<T>(fetchFn: FetchLike, url: string): Promise<T> {
216
- const response = await fetchFn(url);
217
- if (!response.ok) {
218
- throw new Error(
219
- `Binance request failed: ${response.status} ${response.statusText}`,
233
+ async function requestCatalogJson<T>(
234
+ fetchFn: FetchLike,
235
+ url: string,
236
+ rateLimiter: RateLimiter | undefined,
237
+ endpointKey: string,
238
+ ): Promise<T> {
239
+ const scope: RateLimitScope = {
240
+ venue: "binance",
241
+ endpointKey,
242
+ };
243
+
244
+ await rateLimiter?.beforeRequest({ scope });
245
+
246
+ try {
247
+ const response = await httpRequest<T>({
248
+ fetchFn,
249
+ url,
250
+ timeoutMs: DEFAULT_HTTP_TIMEOUT_MS,
251
+ parseAs: "json",
252
+ jsonParseMode: "response",
253
+ retryPolicy: {
254
+ idempotent: true,
255
+ maxAttempts: 3,
256
+ },
257
+ messages: BINANCE_CATALOG_HTTP_MESSAGES,
258
+ });
259
+
260
+ await rateLimiter?.afterResponse(
261
+ { scope },
262
+ {
263
+ status: response.status,
264
+ headers: response.headers,
265
+ usage: parseBinanceRateLimitUsage(response.headers),
266
+ },
220
267
  );
221
- }
222
268
 
223
- return (await response.json()) as T;
269
+ return response.body;
270
+ } catch (error) {
271
+ 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
+ );
281
+ }
282
+
283
+ throw error;
284
+ }
224
285
  }
225
286
 
226
287
  function sortMarkets(
@@ -233,16 +294,26 @@ function sortMarkets(
233
294
 
234
295
  export async function loadBinanceMarkets(
235
296
  fetchFn: FetchLike = fetch,
297
+ options: { readonly rateLimiter?: RateLimiter } = {},
236
298
  ): Promise<BinanceMarketDefinition[]> {
237
299
  const [spot, usdm, coinm] = await Promise.all([
238
- fetchJson<BinanceSpotExchangeInfo>(fetchFn, BINANCE_SPOT_EXCHANGE_INFO_URL),
239
- fetchJson<BinanceDerivativesExchangeInfo>(
300
+ requestCatalogJson<BinanceSpotExchangeInfo>(
301
+ fetchFn,
302
+ BINANCE_SPOT_EXCHANGE_INFO_URL,
303
+ options.rateLimiter,
304
+ "GET /api/v3/exchangeInfo",
305
+ ),
306
+ requestCatalogJson<BinanceDerivativesExchangeInfo>(
240
307
  fetchFn,
241
308
  BINANCE_USDM_EXCHANGE_INFO_URL,
309
+ options.rateLimiter,
310
+ "GET /fapi/v1/exchangeInfo",
242
311
  ),
243
- fetchJson<BinanceDerivativesExchangeInfo>(
312
+ requestCatalogJson<BinanceDerivativesExchangeInfo>(
244
313
  fetchFn,
245
314
  BINANCE_COINM_EXCHANGE_INFO_URL,
315
+ options.rateLimiter,
316
+ "GET /dapi/v1/exchangeInfo",
246
317
  ),
247
318
  ]);
248
319