@imbingox/acex 0.1.0 → 0.2.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.
- package/README.md +92 -285
- package/index.ts +1 -0
- package/package.json +40 -23
- package/src/adapters/binance/adapter.ts +80 -0
- package/src/adapters/binance/book-ticker.ts +123 -0
- package/src/adapters/binance/mark-price.ts +126 -0
- package/src/adapters/binance/market-catalog.ts +258 -0
- package/src/adapters/binance/private-adapter.ts +833 -0
- package/src/adapters/types.ts +219 -0
- package/src/client/context.ts +123 -0
- package/src/client/create-client.ts +6 -0
- package/src/client/private-subscription-coordinator.ts +512 -0
- package/src/client/runtime.ts +410 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +5 -0
- package/src/internal/async-event-bus.ts +100 -0
- package/src/internal/filters.ts +117 -0
- package/src/internal/managed-websocket.ts +280 -0
- package/src/managers/account-manager.ts +609 -0
- package/src/managers/market-manager.ts +889 -0
- package/src/managers/order-manager.ts +685 -0
- package/src/types/account.ts +157 -0
- package/src/types/client.ts +79 -0
- package/src/types/index.ts +5 -0
- package/src/types/market.ts +150 -0
- package/src/types/order.ts +177 -0
- package/src/types/shared.ts +93 -0
- package/dist/adapters/binance/composite-adapter.d.ts +0 -116
- package/dist/adapters/binance/composite-adapter.js +0 -121
- package/dist/adapters/binance/market-types.d.ts +0 -63
- package/dist/adapters/binance/market-types.js +0 -1
- package/dist/adapters/binance/native-market-adapter.d.ts +0 -102
- package/dist/adapters/binance/native-market-adapter.js +0 -455
- package/dist/adapters/binance/normalizers.d.ts +0 -8
- package/dist/adapters/binance/normalizers.js +0 -123
- package/dist/adapters/binance/rest-client.d.ts +0 -17
- package/dist/adapters/binance/rest-client.js +0 -66
- package/dist/adapters/binance/symbol-router.d.ts +0 -9
- package/dist/adapters/binance/symbol-router.js +0 -174
- package/dist/adapters/binance/ws-client.d.ts +0 -24
- package/dist/adapters/binance/ws-client.js +0 -261
- package/dist/adapters/ccxt/aster-ccxt-adapter.d.ts +0 -157
- package/dist/adapters/ccxt/aster-ccxt-adapter.js +0 -272
- package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.d.ts +0 -180
- package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.js +0 -539
- package/dist/adapters/ccxt/binance-usdm-exchange.d.ts +0 -22
- package/dist/adapters/ccxt/binance-usdm-exchange.js +0 -23
- package/dist/adapters/fake/fake-aster-adapter.d.ts +0 -130
- package/dist/adapters/fake/fake-aster-adapter.js +0 -283
- package/dist/adapters/types.d.ts +0 -210
- package/dist/adapters/types.js +0 -1
- package/dist/core/client.d.ts +0 -50
- package/dist/core/client.js +0 -403
- package/dist/core/recovery.d.ts +0 -22
- package/dist/core/recovery.js +0 -18
- package/dist/core/runtime.d.ts +0 -26
- package/dist/core/runtime.js +0 -150
- package/dist/errors/acex-error.d.ts +0 -25
- package/dist/errors/acex-error.js +0 -54
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -3
- package/dist/managers/account-manager.d.ts +0 -41
- package/dist/managers/account-manager.js +0 -80
- package/dist/managers/market-manager.d.ts +0 -16
- package/dist/managers/market-manager.js +0 -28
- package/dist/managers/order-manager.d.ts +0 -87
- package/dist/managers/order-manager.js +0 -122
- package/dist/runtime/async-queue.d.ts +0 -8
- package/dist/runtime/async-queue.js +0 -88
- package/dist/runtime/request-id.d.ts +0 -1
- package/dist/runtime/request-id.js +0 -5
- package/dist/runtime/ws-connection-supervisor.d.ts +0 -76
- package/dist/runtime/ws-connection-supervisor.js +0 -522
- package/dist/store/account-store.d.ts +0 -52
- package/dist/store/account-store.js +0 -18
- package/dist/store/health-store.d.ts +0 -16
- package/dist/store/health-store.js +0 -29
- package/dist/store/market-store.d.ts +0 -42
- package/dist/store/market-store.js +0 -51
- package/dist/store/order-store.d.ts +0 -38
- package/dist/store/order-store.js +0 -49
- package/dist/testing/create-fake-runtime.d.ts +0 -5
- package/dist/testing/create-fake-runtime.js +0 -7
- package/dist/types/public.d.ts +0 -5
- package/dist/types/public.js +0 -1
|
@@ -1,174 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
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;
|
|
@@ -1,261 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import type { AdapterAccountBaseline, ExchangeCapabilities, NormalizedAccountEvent, NormalizedMarketEvent, NormalizedOrderEvent } from "../types.js";
|
|
2
|
-
interface CcxtTickerLike {
|
|
3
|
-
symbol: string;
|
|
4
|
-
bid: number | string | undefined;
|
|
5
|
-
bidVolume: number | string | undefined;
|
|
6
|
-
ask: number | string | undefined;
|
|
7
|
-
askVolume: number | string | undefined;
|
|
8
|
-
timestamp?: number;
|
|
9
|
-
}
|
|
10
|
-
interface CcxtMarketLike {
|
|
11
|
-
symbol: string;
|
|
12
|
-
base: string;
|
|
13
|
-
quote: string;
|
|
14
|
-
type: string;
|
|
15
|
-
precision: {
|
|
16
|
-
price: number;
|
|
17
|
-
amount: number;
|
|
18
|
-
};
|
|
19
|
-
active: boolean;
|
|
20
|
-
}
|
|
21
|
-
interface CcxtOrderLike {
|
|
22
|
-
id?: string;
|
|
23
|
-
clientOrderId?: string;
|
|
24
|
-
symbol?: string;
|
|
25
|
-
side?: string;
|
|
26
|
-
type?: string;
|
|
27
|
-
status?: string;
|
|
28
|
-
amount?: number | string;
|
|
29
|
-
filled?: number | string;
|
|
30
|
-
price?: number | string;
|
|
31
|
-
timestamp?: number;
|
|
32
|
-
}
|
|
33
|
-
interface CcxtBalanceLike {
|
|
34
|
-
free?: Record<string, number | string | undefined>;
|
|
35
|
-
used?: Record<string, number | string | undefined>;
|
|
36
|
-
total?: Record<string, number | string | undefined>;
|
|
37
|
-
}
|
|
38
|
-
interface CcxtExchangeLike {
|
|
39
|
-
watchTicker(symbol: string): Promise<CcxtTickerLike>;
|
|
40
|
-
fetchMarkets(): Promise<CcxtMarketLike[]>;
|
|
41
|
-
fetchBalance(params?: Record<string, unknown>): Promise<CcxtBalanceLike>;
|
|
42
|
-
fetchOpenOrders(symbol?: string, since?: number, limit?: number, params?: Record<string, unknown>): Promise<CcxtOrderLike[]>;
|
|
43
|
-
createOrder(symbol: string, type: string, side: "buy" | "sell", amount: number, price?: number, params?: Record<string, unknown>): Promise<CcxtOrderLike>;
|
|
44
|
-
cancelOrder(id?: string, symbol?: string, params?: Record<string, unknown>): Promise<CcxtOrderLike>;
|
|
45
|
-
}
|
|
46
|
-
export interface CreateCcxtAsterAdapterInput {
|
|
47
|
-
exchange: CcxtExchangeLike;
|
|
48
|
-
}
|
|
49
|
-
export declare class CcxtAsterAdapter {
|
|
50
|
-
#private;
|
|
51
|
-
readonly exchange = "aster";
|
|
52
|
-
readonly capabilities: ExchangeCapabilities;
|
|
53
|
-
constructor(input: CreateCcxtAsterAdapterInput);
|
|
54
|
-
start(): Promise<void>;
|
|
55
|
-
stop(): Promise<void>;
|
|
56
|
-
subscribeL1Book(input: {
|
|
57
|
-
exchange: string;
|
|
58
|
-
symbol: string;
|
|
59
|
-
}): Promise<void>;
|
|
60
|
-
subscribeFundingRate(): Promise<void>;
|
|
61
|
-
watchMarketEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedMarketEvent>;
|
|
62
|
-
setMarketEventSink(sink: ((event: NormalizedMarketEvent) => void) | undefined): void;
|
|
63
|
-
watchInternalErrors(): import("../../runtime/async-queue.js").AsyncQueue<Error>;
|
|
64
|
-
subscribeOrders(): Promise<void>;
|
|
65
|
-
watchOrderEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedOrderEvent>;
|
|
66
|
-
setOrderEventSink(sink: ((event: NormalizedOrderEvent) => void) | undefined): void;
|
|
67
|
-
watchAccountEvents(): import("../../runtime/async-queue.js").AsyncQueue<NormalizedAccountEvent>;
|
|
68
|
-
setAccountEventSink(sink: ((event: NormalizedAccountEvent) => void) | undefined): void;
|
|
69
|
-
mapTickerToL1(symbol: string): Promise<{
|
|
70
|
-
receivedAt: number;
|
|
71
|
-
exchangeTs?: number;
|
|
72
|
-
exchange: string;
|
|
73
|
-
symbol: string;
|
|
74
|
-
bidPrice: string;
|
|
75
|
-
bidSize: string;
|
|
76
|
-
askPrice: string;
|
|
77
|
-
askSize: string;
|
|
78
|
-
}>;
|
|
79
|
-
fetchMarketInfo(): Promise<{
|
|
80
|
-
exchange: string;
|
|
81
|
-
symbol: string;
|
|
82
|
-
baseAsset: string;
|
|
83
|
-
quoteAsset: string;
|
|
84
|
-
marketType: string;
|
|
85
|
-
pricePrecision: number;
|
|
86
|
-
amountPrecision: number;
|
|
87
|
-
active: boolean;
|
|
88
|
-
}[]>;
|
|
89
|
-
fetchAccountBaseline(accountId: string): Promise<AdapterAccountBaseline>;
|
|
90
|
-
fetchOpenOrdersBaseline(accountId: string): Promise<{
|
|
91
|
-
clientOrderId?: string;
|
|
92
|
-
orderId?: string;
|
|
93
|
-
seq: number;
|
|
94
|
-
receivedAt: number;
|
|
95
|
-
updatedAt: number;
|
|
96
|
-
price?: string;
|
|
97
|
-
accountId: string;
|
|
98
|
-
exchange: string;
|
|
99
|
-
symbol: string;
|
|
100
|
-
side: "buy" | "sell";
|
|
101
|
-
type: string;
|
|
102
|
-
status: string;
|
|
103
|
-
amount: string;
|
|
104
|
-
filled: string;
|
|
105
|
-
}[]>;
|
|
106
|
-
placeOrder(input: {
|
|
107
|
-
accountId: string;
|
|
108
|
-
exchange: string;
|
|
109
|
-
symbol: string;
|
|
110
|
-
side: "buy" | "sell";
|
|
111
|
-
amount: string;
|
|
112
|
-
clientOrderId: string;
|
|
113
|
-
type: string;
|
|
114
|
-
price?: string;
|
|
115
|
-
reduceOnly?: boolean;
|
|
116
|
-
}): Promise<{
|
|
117
|
-
receivedAt: number;
|
|
118
|
-
orderId?: string;
|
|
119
|
-
clientOrderId: string;
|
|
120
|
-
}>;
|
|
121
|
-
amendOrder(input: {
|
|
122
|
-
accountId: string;
|
|
123
|
-
exchange: string;
|
|
124
|
-
clientOrderId?: string;
|
|
125
|
-
orderId?: string;
|
|
126
|
-
symbol?: string;
|
|
127
|
-
newPrice?: string;
|
|
128
|
-
}): Promise<{
|
|
129
|
-
receivedAt: number;
|
|
130
|
-
orderId?: string;
|
|
131
|
-
clientOrderId?: string;
|
|
132
|
-
}>;
|
|
133
|
-
cancelOrder(input: {
|
|
134
|
-
accountId: string;
|
|
135
|
-
exchange: string;
|
|
136
|
-
clientOrderId?: string;
|
|
137
|
-
orderId?: string;
|
|
138
|
-
}): Promise<{
|
|
139
|
-
receivedAt: number;
|
|
140
|
-
orderId?: string;
|
|
141
|
-
clientOrderId?: string;
|
|
142
|
-
}>;
|
|
143
|
-
cancelAllOrders(input: {
|
|
144
|
-
accountId: string;
|
|
145
|
-
exchange: string;
|
|
146
|
-
}): Promise<{
|
|
147
|
-
accountId: string;
|
|
148
|
-
exchange: string;
|
|
149
|
-
canceledCount: number;
|
|
150
|
-
}>;
|
|
151
|
-
getHealth(): {
|
|
152
|
-
exchange: string;
|
|
153
|
-
status: string;
|
|
154
|
-
wsConnected: boolean;
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
export {};
|