@imbingox/acex 0.1.0-beta.0 → 0.1.0

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.
@@ -0,0 +1,17 @@
1
+ import type { BinanceCoinmExchangeInfo, BinanceSpotExchangeInfo, BinanceUsdmExchangeInfo } from "./market-types.js";
2
+ export interface BinanceMarketRestClient {
3
+ fetchAllExchangeInfo(): Promise<{
4
+ spot: BinanceSpotExchangeInfo;
5
+ usdm: BinanceUsdmExchangeInfo;
6
+ coinm: BinanceCoinmExchangeInfo;
7
+ }>;
8
+ }
9
+ export interface CreateBinanceMarketRestClientInput {
10
+ fetchImpl?: typeof fetch;
11
+ baseUrls?: {
12
+ spot: string;
13
+ usdm: string;
14
+ coinm: string;
15
+ };
16
+ }
17
+ export declare function createBinanceMarketRestClient(input?: CreateBinanceMarketRestClientInput): BinanceMarketRestClient;
@@ -0,0 +1,66 @@
1
+ import { createAcexError } from "../../errors/acex-error.js";
2
+ const DEFAULT_BASE_URLS = {
3
+ spot: "https://api.binance.com",
4
+ usdm: "https://fapi.binance.com",
5
+ coinm: "https://dapi.binance.com",
6
+ };
7
+ function createTransportError(message, cause) {
8
+ return createAcexError({
9
+ code: "TRANSPORT_UNAVAILABLE",
10
+ message,
11
+ retryable: true,
12
+ exchange: "binance",
13
+ ...(cause === undefined ? {} : { cause }),
14
+ });
15
+ }
16
+ function createPayloadError(message, cause) {
17
+ return createAcexError({
18
+ code: "VALIDATION_ERROR",
19
+ message,
20
+ retryable: false,
21
+ exchange: "binance",
22
+ ...(cause === undefined ? {} : { cause }),
23
+ });
24
+ }
25
+ export function createBinanceMarketRestClient(input = {}) {
26
+ const fetchImpl = input.fetchImpl ?? fetch;
27
+ const baseUrls = input.baseUrls ?? DEFAULT_BASE_URLS;
28
+ async function getJson(url) {
29
+ let response;
30
+ try {
31
+ response = await fetchImpl(url);
32
+ }
33
+ catch (error) {
34
+ throw createTransportError(`binance market metadata request failed: ${url}`, error);
35
+ }
36
+ if (!response.ok) {
37
+ throw createTransportError(`binance market metadata request failed: ${url} (${response.status})`);
38
+ }
39
+ try {
40
+ return (await response.json());
41
+ }
42
+ catch (error) {
43
+ throw createPayloadError(`invalid binance market metadata payload: ${url}`, error);
44
+ }
45
+ }
46
+ function requireSymbolsArray(family, payload) {
47
+ if (!Array.isArray(payload.symbols)) {
48
+ throw createPayloadError(`invalid binance market metadata payload: ${family}`);
49
+ }
50
+ return payload;
51
+ }
52
+ return {
53
+ async fetchAllExchangeInfo() {
54
+ const [spot, usdm, coinm] = await Promise.all([
55
+ getJson(`${baseUrls.spot}/api/v3/exchangeInfo`),
56
+ getJson(`${baseUrls.usdm}/fapi/v1/exchangeInfo`),
57
+ getJson(`${baseUrls.coinm}/dapi/v1/exchangeInfo`),
58
+ ]);
59
+ return {
60
+ spot: requireSymbolsArray("spot", spot),
61
+ usdm: requireSymbolsArray("usdm", usdm),
62
+ coinm: requireSymbolsArray("coinm", coinm),
63
+ };
64
+ },
65
+ };
66
+ }
@@ -0,0 +1,9 @@
1
+ import type { BinanceCoinmExchangeInfo, BinanceMarketIndex, BinanceMarketRecord, BinanceSpotExchangeInfo, BinanceUsdmExchangeInfo } from "./market-types.js";
2
+ export declare function buildBinanceMarketIndex(input: {
3
+ spot: BinanceSpotExchangeInfo;
4
+ usdm: BinanceUsdmExchangeInfo;
5
+ coinm: BinanceCoinmExchangeInfo;
6
+ }): BinanceMarketIndex;
7
+ export declare function requireMarketRecord(index: BinanceMarketIndex, unifiedSymbol: string): BinanceMarketRecord;
8
+ export declare function requireFundingMarketRecord(index: BinanceMarketIndex, unifiedSymbol: string): BinanceMarketRecord;
9
+ export declare function getRecordByNativeSymbol(index: BinanceMarketIndex, family: BinanceMarketRecord["family"], nativeSymbol: string): BinanceMarketRecord | undefined;
@@ -0,0 +1,174 @@
1
+ import { createAcexError } from "../../errors/acex-error.js";
2
+ function keyFor(family, nativeSymbol) {
3
+ return `${family}:${nativeSymbol}`;
4
+ }
5
+ function createInvalidMetadataError(message) {
6
+ return createAcexError({
7
+ code: "VALIDATION_ERROR",
8
+ message: `invalid binance market metadata: ${message}`,
9
+ retryable: false,
10
+ exchange: "binance",
11
+ });
12
+ }
13
+ function createDuplicateSymbolError(message) {
14
+ return createAcexError({
15
+ code: "VALIDATION_ERROR",
16
+ message: `duplicate binance market symbol: ${message}`,
17
+ retryable: false,
18
+ exchange: "binance",
19
+ });
20
+ }
21
+ function getFilterValue(filters, filterType, key) {
22
+ return filters.find((filter) => filter.filterType === filterType)?.[key];
23
+ }
24
+ function parseTickOrStepSize(filters, nativeSymbol, filterType, key) {
25
+ const value = getFilterValue(filters, filterType, key);
26
+ if (value === undefined) {
27
+ throw createInvalidMetadataError(`missing ${filterType}.${key} for ${nativeSymbol}`);
28
+ }
29
+ const parsedSize = Number(value);
30
+ if (!Number.isFinite(parsedSize) || parsedSize <= 0) {
31
+ throw createInvalidMetadataError(`invalid ${filterType}.${key} for ${nativeSymbol}: ${value}`);
32
+ }
33
+ return parsedSize;
34
+ }
35
+ function parseDeliveryDateSuffix(nativeSymbol, deliveryDate) {
36
+ if (deliveryDate === undefined) {
37
+ throw createInvalidMetadataError(`missing delivery suffix for ${nativeSymbol}`);
38
+ }
39
+ if (!Number.isInteger(deliveryDate) ||
40
+ deliveryDate < 1_000_000_000_000 ||
41
+ deliveryDate > 10_000_000_000_000) {
42
+ throw createInvalidMetadataError(`invalid deliveryDate for ${nativeSymbol}: ${deliveryDate}`);
43
+ }
44
+ const delivery = new Date(deliveryDate);
45
+ if (Number.isNaN(delivery.getTime())) {
46
+ throw createInvalidMetadataError(`invalid deliveryDate for ${nativeSymbol}: ${deliveryDate}`);
47
+ }
48
+ const year = String(delivery.getUTCFullYear()).slice(-2);
49
+ const month = String(delivery.getUTCMonth() + 1).padStart(2, "0");
50
+ const day = String(delivery.getUTCDate()).padStart(2, "0");
51
+ return `${year}${month}${day}`;
52
+ }
53
+ function toDeliverySuffix(nativeSymbol, deliveryDate) {
54
+ const nativeSuffix = nativeSymbol.match(/_(\d{6})$/)?.[1];
55
+ const deliveryDateSuffix = deliveryDate === undefined ? undefined : parseDeliveryDateSuffix(nativeSymbol, deliveryDate);
56
+ if (nativeSuffix !== undefined) {
57
+ if (deliveryDateSuffix !== undefined && deliveryDateSuffix !== nativeSuffix) {
58
+ throw createInvalidMetadataError(`deliveryDate does not match native suffix for ${nativeSymbol}: ${deliveryDateSuffix} !== ${nativeSuffix}`);
59
+ }
60
+ return `-${nativeSuffix}`;
61
+ }
62
+ if (deliveryDateSuffix !== undefined) {
63
+ return `-${deliveryDateSuffix}`;
64
+ }
65
+ throw createInvalidMetadataError(`missing delivery suffix for ${nativeSymbol}`);
66
+ }
67
+ function createMarketMaps(markets) {
68
+ const byUnifiedSymbol = new Map();
69
+ const byNativeSymbol = new Map();
70
+ for (const record of markets) {
71
+ if (byUnifiedSymbol.has(record.unifiedSymbol)) {
72
+ throw createDuplicateSymbolError(record.unifiedSymbol);
73
+ }
74
+ const nativeKey = keyFor(record.family, record.nativeSymbol);
75
+ if (byNativeSymbol.has(nativeKey)) {
76
+ throw createDuplicateSymbolError(nativeKey);
77
+ }
78
+ byUnifiedSymbol.set(record.unifiedSymbol, record);
79
+ byNativeSymbol.set(nativeKey, record);
80
+ }
81
+ return {
82
+ byUnifiedSymbol,
83
+ byNativeSymbol,
84
+ };
85
+ }
86
+ function buildSpotRecord(symbol) {
87
+ return {
88
+ exchange: "binance",
89
+ family: "spot",
90
+ nativeSymbol: symbol.symbol,
91
+ unifiedSymbol: `${symbol.baseAsset}/${symbol.quoteAsset}`,
92
+ baseAsset: symbol.baseAsset,
93
+ quoteAsset: symbol.quoteAsset,
94
+ fundingEligible: false,
95
+ active: symbol.status === "TRADING",
96
+ pricePrecision: parseTickOrStepSize(symbol.filters, symbol.symbol, "PRICE_FILTER", "tickSize"),
97
+ amountPrecision: parseTickOrStepSize(symbol.filters, symbol.symbol, "LOT_SIZE", "stepSize"),
98
+ };
99
+ }
100
+ function buildUsdmRecord(symbol) {
101
+ return {
102
+ exchange: "binance",
103
+ family: "usdm",
104
+ nativeSymbol: symbol.symbol,
105
+ unifiedSymbol: `${symbol.baseAsset}/${symbol.quoteAsset}:${symbol.marginAsset}`,
106
+ baseAsset: symbol.baseAsset,
107
+ quoteAsset: symbol.quoteAsset,
108
+ settleAsset: symbol.marginAsset,
109
+ contractType: symbol.contractType,
110
+ fundingEligible: symbol.contractType === "PERPETUAL",
111
+ active: symbol.status === "TRADING",
112
+ pricePrecision: parseTickOrStepSize(symbol.filters, symbol.symbol, "PRICE_FILTER", "tickSize"),
113
+ amountPrecision: parseTickOrStepSize(symbol.filters, symbol.symbol, "LOT_SIZE", "stepSize"),
114
+ };
115
+ }
116
+ function buildCoinmRecord(symbol) {
117
+ const isPerpetual = symbol.contractType === "PERPETUAL";
118
+ const deliverySuffix = isPerpetual ? "" : toDeliverySuffix(symbol.symbol, symbol.deliveryDate);
119
+ const settleSegment = `${symbol.marginAsset}${deliverySuffix}`;
120
+ return {
121
+ exchange: "binance",
122
+ family: "coinm",
123
+ nativeSymbol: symbol.symbol,
124
+ unifiedSymbol: `${symbol.baseAsset}/${symbol.quoteAsset}:${settleSegment}`,
125
+ baseAsset: symbol.baseAsset,
126
+ quoteAsset: symbol.quoteAsset,
127
+ settleAsset: symbol.marginAsset,
128
+ contractType: symbol.contractType,
129
+ fundingEligible: symbol.contractType === "PERPETUAL",
130
+ active: symbol.contractStatus === "TRADING",
131
+ pricePrecision: parseTickOrStepSize(symbol.filters, symbol.symbol, "PRICE_FILTER", "tickSize"),
132
+ amountPrecision: parseTickOrStepSize(symbol.filters, symbol.symbol, "LOT_SIZE", "stepSize"),
133
+ };
134
+ }
135
+ export function buildBinanceMarketIndex(input) {
136
+ const markets = [
137
+ ...input.spot.symbols.map(buildSpotRecord),
138
+ ...input.usdm.symbols.map(buildUsdmRecord),
139
+ ...input.coinm.symbols.map(buildCoinmRecord),
140
+ ];
141
+ const maps = createMarketMaps(markets);
142
+ return {
143
+ markets,
144
+ byUnifiedSymbol: maps.byUnifiedSymbol,
145
+ byNativeSymbol: maps.byNativeSymbol,
146
+ };
147
+ }
148
+ export function requireMarketRecord(index, unifiedSymbol) {
149
+ const record = index.byUnifiedSymbol.get(unifiedSymbol);
150
+ if (record === undefined) {
151
+ throw createAcexError({
152
+ code: "VALIDATION_ERROR",
153
+ message: `unsupported binance market symbol: ${unifiedSymbol}`,
154
+ retryable: false,
155
+ exchange: "binance",
156
+ });
157
+ }
158
+ return record;
159
+ }
160
+ export function requireFundingMarketRecord(index, unifiedSymbol) {
161
+ const record = requireMarketRecord(index, unifiedSymbol);
162
+ if (!record.fundingEligible) {
163
+ throw createAcexError({
164
+ code: "CAPABILITY_NOT_SUPPORTED",
165
+ message: "funding rate is not supported for spot or delivery markets",
166
+ retryable: false,
167
+ exchange: "binance",
168
+ });
169
+ }
170
+ return record;
171
+ }
172
+ export function getRecordByNativeSymbol(index, family, nativeSymbol) {
173
+ return index.byNativeSymbol.get(keyFor(family, nativeSymbol));
174
+ }
@@ -0,0 +1,24 @@
1
+ import { type WsSocketLike } from "../../runtime/ws-connection-supervisor.js";
2
+ import type { BinanceMarketFamily } from "./market-types.js";
3
+ export interface BinanceWsSocketLike extends WsSocketLike {
4
+ send(data: string): void;
5
+ }
6
+ export interface BinanceMarketWsTransport {
7
+ connect(): Promise<void>;
8
+ close(): Promise<void>;
9
+ ensureSubscribed(stream: string): Promise<void>;
10
+ }
11
+ export interface CreateBinanceMarketWsTransportInput {
12
+ family: BinanceMarketFamily;
13
+ createSocket: (url: string) => BinanceWsSocketLike;
14
+ url?: string;
15
+ onMessage?: (payload: unknown) => void;
16
+ onClose?: () => void;
17
+ onReconnect?: () => void | Promise<void>;
18
+ reconnectDelayMs?: number;
19
+ heartbeatIntervalMs?: number;
20
+ idleTimeoutMs?: number;
21
+ now?: () => number;
22
+ sleepImpl?: (ms: number) => Promise<void>;
23
+ }
24
+ export declare function createBinanceMarketWsTransport(input: CreateBinanceMarketWsTransportInput): BinanceMarketWsTransport;
@@ -0,0 +1,261 @@
1
+ import { createAcexError, isAcexError } from "../../errors/acex-error.js";
2
+ import { createWsConnectionSupervisor, } from "../../runtime/ws-connection-supervisor.js";
3
+ const DEFAULT_WS_URLS = {
4
+ spot: "wss://stream.binance.com:9443/ws",
5
+ usdm: "wss://fstream.binance.com/ws",
6
+ coinm: "wss://dstream.binance.com/ws",
7
+ };
8
+ function toMessageString(data) {
9
+ if (typeof data === "string") {
10
+ return data;
11
+ }
12
+ if (data instanceof ArrayBuffer) {
13
+ return Buffer.from(data).toString("utf8");
14
+ }
15
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
16
+ }
17
+ function createTransportError(message, cause) {
18
+ return createAcexError({
19
+ code: "TRANSPORT_UNAVAILABLE",
20
+ message,
21
+ retryable: true,
22
+ exchange: "binance",
23
+ ...(cause === undefined ? {} : { cause }),
24
+ });
25
+ }
26
+ function toTransportError(message, cause) {
27
+ if (isAcexError(cause) && cause.exchange === "binance") {
28
+ return cause;
29
+ }
30
+ return createTransportError(message, cause);
31
+ }
32
+ function isSocketNotOpenError(error) {
33
+ return (isAcexError(error) &&
34
+ error.code === "TRANSPORT_UNAVAILABLE" &&
35
+ error.exchange === undefined &&
36
+ error.message === "websocket is not open");
37
+ }
38
+ export function createBinanceMarketWsTransport(input) {
39
+ const url = input.url ?? DEFAULT_WS_URLS[input.family];
40
+ const reconnectDelayMs = input.reconnectDelayMs ?? 250;
41
+ const sleep = input.sleepImpl ??
42
+ ((ms) => {
43
+ return new Promise((resolve) => setTimeout(resolve, ms));
44
+ });
45
+ const subscribed = new Set();
46
+ const pendingSubscriptions = new Set();
47
+ const inFlightSubscriptions = new Map();
48
+ const subscriptionErrors = new Map();
49
+ let requestId = 1;
50
+ let manualClose = false;
51
+ let awaitingReconnect = false;
52
+ let recoveryAttempts = 0;
53
+ let reconnectRecoveryTask;
54
+ const sendSubscribe = (stream) => {
55
+ try {
56
+ supervisor.send(JSON.stringify({
57
+ method: "SUBSCRIBE",
58
+ params: [stream],
59
+ id: requestId++,
60
+ }));
61
+ subscribed.add(stream);
62
+ pendingSubscriptions.delete(stream);
63
+ subscriptionErrors.delete(stream);
64
+ }
65
+ catch (error) {
66
+ if (isSocketNotOpenError(error)) {
67
+ pendingSubscriptions.add(stream);
68
+ return;
69
+ }
70
+ const transportError = createTransportError(`failed to subscribe binance ${input.family} stream: ${stream}`, error);
71
+ subscribed.delete(stream);
72
+ pendingSubscriptions.delete(stream);
73
+ subscriptionErrors.set(stream, transportError);
74
+ throw transportError;
75
+ }
76
+ };
77
+ const restoreDesiredSubscriptions = () => {
78
+ const desiredSubscriptions = new Set([...pendingSubscriptions, ...subscribed]);
79
+ pendingSubscriptions.clear();
80
+ for (const stream of desiredSubscriptions) {
81
+ pendingSubscriptions.add(stream);
82
+ }
83
+ };
84
+ const flushPendingSubscriptions = () => {
85
+ for (const stream of [...pendingSubscriptions]) {
86
+ try {
87
+ sendSubscribe(stream);
88
+ }
89
+ catch { }
90
+ }
91
+ };
92
+ const completeReconnect = async () => {
93
+ restoreDesiredSubscriptions();
94
+ flushPendingSubscriptions();
95
+ if (!awaitingReconnect) {
96
+ return;
97
+ }
98
+ if (supervisor.getState() !== "open" || pendingSubscriptions.size > 0) {
99
+ scheduleReconnectRecovery();
100
+ return;
101
+ }
102
+ awaitingReconnect = false;
103
+ recoveryAttempts = 0;
104
+ await input.onReconnect?.();
105
+ };
106
+ const hasDesiredStreams = () => {
107
+ return subscribed.size > 0 || pendingSubscriptions.size > 0 || inFlightSubscriptions.size > 0;
108
+ };
109
+ const connectViaSupervisor = async () => {
110
+ manualClose = false;
111
+ try {
112
+ await supervisor.connect();
113
+ }
114
+ catch (error) {
115
+ throw toTransportError(`failed to connect binance ${input.family} websocket`, error);
116
+ }
117
+ };
118
+ const recoveryDelayMs = () => {
119
+ const uncappedDelay = reconnectDelayMs * 2 ** recoveryAttempts;
120
+ const cappedDelay = Math.min(uncappedDelay, Math.max(reconnectDelayMs, 5_000));
121
+ const spread = Math.floor(cappedDelay * 0.2);
122
+ const jitterDelta = Math.floor(Math.random() * (spread * 2 + 1)) - spread;
123
+ return Math.max(0, cappedDelay + jitterDelta);
124
+ };
125
+ const scheduleReconnectRecovery = () => {
126
+ if (reconnectRecoveryTask !== undefined ||
127
+ manualClose ||
128
+ !awaitingReconnect ||
129
+ !hasDesiredStreams()) {
130
+ return;
131
+ }
132
+ reconnectRecoveryTask = (async () => {
133
+ try {
134
+ while (!manualClose && awaitingReconnect && hasDesiredStreams()) {
135
+ await sleep(recoveryDelayMs());
136
+ if (manualClose || !awaitingReconnect || !hasDesiredStreams()) {
137
+ return;
138
+ }
139
+ recoveryAttempts += 1;
140
+ restoreDesiredSubscriptions();
141
+ try {
142
+ await supervisor.connect();
143
+ }
144
+ catch {
145
+ continue;
146
+ }
147
+ if (manualClose || !awaitingReconnect) {
148
+ return;
149
+ }
150
+ await completeReconnect();
151
+ if (!awaitingReconnect) {
152
+ return;
153
+ }
154
+ }
155
+ }
156
+ finally {
157
+ reconnectRecoveryTask = undefined;
158
+ }
159
+ })();
160
+ };
161
+ const supervisor = createWsConnectionSupervisor({
162
+ createSocket: () => input.createSocket(url),
163
+ heartbeat: {
164
+ kind: "native_ping_pong",
165
+ heartbeatIntervalMs: input.heartbeatIntervalMs ?? 10_000,
166
+ idleTimeoutMs: input.idleTimeoutMs ?? 30_000,
167
+ },
168
+ backoff: {
169
+ baseDelayMs: input.reconnectDelayMs ?? 250,
170
+ maxDelayMs: Math.max(input.reconnectDelayMs ?? 250, 5_000),
171
+ jitter: true,
172
+ },
173
+ ...(input.now === undefined ? {} : { now: input.now }),
174
+ ...(input.sleepImpl === undefined ? {} : { sleep: input.sleepImpl }),
175
+ onMessage(event) {
176
+ const raw = toMessageString(event.data);
177
+ try {
178
+ const parsed = JSON.parse(raw);
179
+ const payload = typeof parsed === "object" && parsed !== null && "data" in parsed
180
+ ? parsed.data
181
+ : parsed;
182
+ input.onMessage?.(payload);
183
+ }
184
+ catch {
185
+ return;
186
+ }
187
+ },
188
+ onDisconnect() {
189
+ awaitingReconnect = true;
190
+ input.onClose?.();
191
+ },
192
+ async onReconnect() {
193
+ await completeReconnect();
194
+ },
195
+ onError() {
196
+ queueMicrotask(() => {
197
+ if (supervisor.getState() === "idle") {
198
+ scheduleReconnectRecovery();
199
+ }
200
+ });
201
+ },
202
+ });
203
+ return {
204
+ async connect() {
205
+ if (supervisor.getState() === "open") {
206
+ return;
207
+ }
208
+ restoreDesiredSubscriptions();
209
+ await connectViaSupervisor();
210
+ await completeReconnect();
211
+ },
212
+ async close() {
213
+ manualClose = true;
214
+ awaitingReconnect = false;
215
+ try {
216
+ await supervisor.close();
217
+ }
218
+ catch (error) {
219
+ throw toTransportError(`failed to close binance ${input.family} websocket`, error);
220
+ }
221
+ },
222
+ async ensureSubscribed(stream) {
223
+ if (subscribed.has(stream)) {
224
+ return;
225
+ }
226
+ const existing = inFlightSubscriptions.get(stream);
227
+ if (existing !== undefined) {
228
+ await existing;
229
+ return;
230
+ }
231
+ const task = (async () => {
232
+ pendingSubscriptions.add(stream);
233
+ if (awaitingReconnect) {
234
+ restoreDesiredSubscriptions();
235
+ }
236
+ await connectViaSupervisor();
237
+ if (!subscribed.has(stream)) {
238
+ if (awaitingReconnect) {
239
+ await completeReconnect();
240
+ return;
241
+ }
242
+ if (pendingSubscriptions.has(stream)) {
243
+ sendSubscribe(stream);
244
+ return;
245
+ }
246
+ const error = subscriptionErrors.get(stream);
247
+ if (error !== undefined) {
248
+ throw error;
249
+ }
250
+ }
251
+ })();
252
+ inFlightSubscriptions.set(stream, task);
253
+ try {
254
+ await task;
255
+ }
256
+ finally {
257
+ inFlightSubscriptions.delete(stream);
258
+ }
259
+ },
260
+ };
261
+ }
@@ -62,6 +62,7 @@ interface CcxtExchangeLike {
62
62
  }
63
63
  export interface CreateCcxtBinanceUsdMAdapterInput {
64
64
  exchange: CcxtExchangeLike;
65
+ accountId?: string;
65
66
  }
66
67
  export declare class CcxtBinanceUsdMAdapter implements ExchangeAdapter {
67
68
  #private;
@@ -17,6 +17,7 @@ export class CcxtBinanceUsdMAdapter {
17
17
  fetchOrderById: true,
18
18
  };
19
19
  #exchange;
20
+ #accountId;
20
21
  #started = false;
21
22
  #privateWatchTasks = new Set();
22
23
  #orderSymbolsByClientOrderId = new Map();
@@ -30,6 +31,7 @@ export class CcxtBinanceUsdMAdapter {
30
31
  #accountEventSink;
31
32
  constructor(input) {
32
33
  this.#exchange = input.exchange;
34
+ this.#accountId = input.accountId ?? "main";
33
35
  }
34
36
  async start() {
35
37
  this.#started = true;
@@ -428,11 +430,11 @@ export class CcxtBinanceUsdMAdapter {
428
430
  : "order.updated";
429
431
  this.#emitOrderEvent({
430
432
  type: eventType,
431
- accountId: "main",
433
+ accountId: this.#accountId,
432
434
  exchange: this.exchange,
433
435
  receivedAt: now,
434
436
  snapshot: {
435
- accountId: "main",
437
+ accountId: this.#accountId,
436
438
  exchange: this.exchange,
437
439
  symbol,
438
440
  side,
@@ -469,11 +471,11 @@ export class CcxtBinanceUsdMAdapter {
469
471
  for (const asset of assets) {
470
472
  this.#emitAccountEvent({
471
473
  type: "balance.updated",
472
- accountId: "main",
474
+ accountId: this.#accountId,
473
475
  exchange: this.exchange,
474
476
  asset,
475
477
  snapshot: {
476
- accountId: "main",
478
+ accountId: this.#accountId,
477
479
  exchange: this.exchange,
478
480
  asset,
479
481
  free: valueToString(balance.free?.[asset]),
@@ -0,0 +1,22 @@
1
+ export interface BinanceUsdmExchangeConfig {
2
+ apiKey: string;
3
+ secret: string;
4
+ enableRateLimit: boolean;
5
+ }
6
+ export interface BinanceUsdmExchangeLike {
7
+ options: {
8
+ defaultType?: string;
9
+ warnOnFetchOpenOrdersWithoutSymbol?: boolean;
10
+ };
11
+ enableDemoTrading?(value: boolean): void;
12
+ }
13
+ export type BinanceUsdmExchangeCtor = new (config: BinanceUsdmExchangeConfig) => BinanceUsdmExchangeLike;
14
+ export interface CreateBinanceUsdmExchangeInput {
15
+ apiKey: string;
16
+ secret: string;
17
+ sandbox?: boolean;
18
+ enableRateLimit?: boolean;
19
+ exchangeCtor?: BinanceUsdmExchangeCtor;
20
+ }
21
+ export declare function getCcxtProBinanceUsdmCtor(): BinanceUsdmExchangeCtor;
22
+ export declare function createBinanceUsdmExchange(input: CreateBinanceUsdmExchangeInput): BinanceUsdmExchangeLike;
@@ -0,0 +1,23 @@
1
+ import ccxt from "ccxt";
2
+ export function getCcxtProBinanceUsdmCtor() {
3
+ const proNamespace = ccxt
4
+ .pro;
5
+ if (proNamespace === undefined || proNamespace.binanceusdm === undefined) {
6
+ throw new Error("ccxt.pro.binanceusdm is not available");
7
+ }
8
+ return proNamespace.binanceusdm;
9
+ }
10
+ export function createBinanceUsdmExchange(input) {
11
+ const ExchangeCtor = input.exchangeCtor ?? getCcxtProBinanceUsdmCtor();
12
+ const exchange = new ExchangeCtor({
13
+ apiKey: input.apiKey,
14
+ secret: input.secret,
15
+ enableRateLimit: input.enableRateLimit ?? true,
16
+ });
17
+ exchange.options.defaultType = "future";
18
+ exchange.options.warnOnFetchOpenOrdersWithoutSymbol = false;
19
+ if (input.sandbox ?? true) {
20
+ exchange.enableDemoTrading?.(true);
21
+ }
22
+ return exchange;
23
+ }