@imbingox/acex 0.1.0-beta.0 → 0.1.0-beta.1

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.
Files changed (64) hide show
  1. package/README.md +12 -54
  2. package/index.ts +1 -0
  3. package/package.json +15 -24
  4. package/src/adapters/binance/adapter.ts +53 -0
  5. package/src/adapters/binance/book-ticker.ts +123 -0
  6. package/src/adapters/binance/market-catalog.ts +251 -0
  7. package/src/adapters/types.ts +43 -0
  8. package/src/client/context.ts +60 -0
  9. package/src/client/create-client.ts +6 -0
  10. package/src/client/runtime.ts +283 -0
  11. package/src/errors.ts +20 -0
  12. package/src/index.ts +4 -0
  13. package/src/internal/async-event-bus.ts +100 -0
  14. package/src/internal/filters.ts +119 -0
  15. package/src/internal/managed-websocket.ts +258 -0
  16. package/src/managers/account-manager.ts +315 -0
  17. package/src/managers/market-manager.ts +642 -0
  18. package/src/managers/order-manager.ts +304 -0
  19. package/src/types/account.ts +160 -0
  20. package/src/types/client.ts +79 -0
  21. package/src/types/index.ts +5 -0
  22. package/src/types/market.ts +136 -0
  23. package/src/types/order.ts +142 -0
  24. package/src/types/shared.ts +78 -0
  25. package/dist/adapters/ccxt/aster-ccxt-adapter.d.ts +0 -157
  26. package/dist/adapters/ccxt/aster-ccxt-adapter.js +0 -272
  27. package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.d.ts +0 -179
  28. package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.js +0 -537
  29. package/dist/adapters/fake/fake-aster-adapter.d.ts +0 -130
  30. package/dist/adapters/fake/fake-aster-adapter.js +0 -283
  31. package/dist/adapters/types.d.ts +0 -210
  32. package/dist/adapters/types.js +0 -1
  33. package/dist/core/client.d.ts +0 -37
  34. package/dist/core/client.js +0 -45
  35. package/dist/core/recovery.d.ts +0 -22
  36. package/dist/core/recovery.js +0 -18
  37. package/dist/core/runtime.d.ts +0 -26
  38. package/dist/core/runtime.js +0 -150
  39. package/dist/errors/acex-error.d.ts +0 -25
  40. package/dist/errors/acex-error.js +0 -54
  41. package/dist/index.d.ts +0 -5
  42. package/dist/index.js +0 -3
  43. package/dist/managers/account-manager.d.ts +0 -41
  44. package/dist/managers/account-manager.js +0 -80
  45. package/dist/managers/market-manager.d.ts +0 -16
  46. package/dist/managers/market-manager.js +0 -28
  47. package/dist/managers/order-manager.d.ts +0 -87
  48. package/dist/managers/order-manager.js +0 -122
  49. package/dist/runtime/async-queue.d.ts +0 -8
  50. package/dist/runtime/async-queue.js +0 -88
  51. package/dist/runtime/request-id.d.ts +0 -1
  52. package/dist/runtime/request-id.js +0 -5
  53. package/dist/store/account-store.d.ts +0 -52
  54. package/dist/store/account-store.js +0 -18
  55. package/dist/store/health-store.d.ts +0 -16
  56. package/dist/store/health-store.js +0 -29
  57. package/dist/store/market-store.d.ts +0 -42
  58. package/dist/store/market-store.js +0 -51
  59. package/dist/store/order-store.d.ts +0 -38
  60. package/dist/store/order-store.js +0 -49
  61. package/dist/testing/create-fake-runtime.d.ts +0 -5
  62. package/dist/testing/create-fake-runtime.js +0 -7
  63. package/dist/types/public.d.ts +0 -11
  64. package/dist/types/public.js +0 -1
package/README.md CHANGED
@@ -1,65 +1,23 @@
1
- # acex
1
+ # @imbingox/acex
2
2
 
3
- Bun-only trading SDK. Current official verification target: `binance`.
4
-
5
- ## Current Direction
6
-
7
- - Official release verification now focuses on the Binance USD-M testnet path.
8
- - `aster` remains in the repo as an exploration sample (via CCXT / CCXT Pro) but is not part of the current official support commitment.
9
- - The Aster branch was kept to preserve the generic runtime and adapter validation work without blocking the Binance verification focus.
10
-
11
- ## Verification Direction
12
-
13
- The current official verification path is Binance USD-M testnet. The current validation target scope consists of:
14
-
15
- - market WebSocket connectivity
16
- - private WebSocket connectivity
17
- - baseline bootstrap for runtime readiness
18
- - market `placeOrder`
19
- - reduce-only market `placeOrder`
20
-
21
- Run the verification script via:
3
+ To install dependencies:
22
4
 
23
5
  ```bash
24
- bun run scripts/verify-binance-usdm-testnet.ts
6
+ bun install
25
7
  ```
26
8
 
27
- The current script exercises bootstrap, subscription setup, and a real demo market open / reduce-only market close round-trip.
28
-
29
- The script expects the following environment variables for Binance USD-M testnet:
30
-
31
- - `BINANCE_USDM_API_KEY`
32
- - `BINANCE_USDM_API_SECRET`
33
- - `BINANCE_USDM_SYMBOL`
34
- - `BINANCE_USDM_ORDER_SIZE`
35
- - `BINANCE_USDM_ORDER_PRICE`
9
+ To run checks:
36
10
 
37
- `BINANCE_USDM_ORDER_PRICE` is currently kept as a reference configuration field for notional planning and result logging; the verification orders themselves are submitted as market orders.
38
-
39
- ## Verified Scope
40
-
41
- - Verified exchange: `binance`
42
- - Verified product: `USD-M`
43
- - Verified environment: `demo trading / testnet-style`
44
- - Verified date: `2026-03-31`
45
- - Verified runtime paths: `subscribeL1Book`, `fetchAccountBaseline`, `fetchOpenOrdersBaseline`, market `placeOrder`, reduce-only market `placeOrder`
46
- - Verified transports with direct evidence: `public WS (bookTicker)`, `private WS (runtime-level order confirmation + balance confirmation)`, `REST baseline`
47
- - Supplementary low-level evidence: direct `ccxt.pro.binanceusdm.watchOrders()` and `watchBalance()` both returned during the real demo open/close verification flow on `2026-03-31`
48
- - Deferred coverage: `amendOrder`, `cancelAllOrders`
49
-
50
- ## Current Caveats
51
-
52
- - Binance `watchTicker()` is not suitable for L1 mapping on USD-M because it returns a 24h ticker payload without `bid` / `ask`; the adapter now uses `watchBidsAsks()` / `bookTicker`.
53
- - `fetchOpenOrders()` without `symbol` requires `warnOnFetchOpenOrdersWithoutSymbol = false` on the CCXT Binance USD-M exchange instance.
54
- - Under Bun + CCXT Pro, websocket handles can remain alive after verification. The CLI script now exits explicitly after emitting the result so the verification command terminates deterministically.
55
- - A simple `place/cancel` flow was insufficient to trigger `watchBalance()` on Binance demo during the 2026-03-31 investigation; the verification script now uses a real market open followed by a reduce-only market close so balance private-WS confirmation is part of the success gate.
56
-
57
- ## Aster Exploration Notes
11
+ ```bash
12
+ bun run lint
13
+ bun run type-check
14
+ bun run test
15
+ ```
58
16
 
59
- The repository still contains the earlier Aster verification script skeleton that can be used for exploratory checks:
17
+ To run the current entry:
60
18
 
61
19
  ```bash
62
- bun run scripts/verify-aster-live.ts
20
+ bun run index.ts
63
21
  ```
64
22
 
65
- The Aster assets are retained for experimentation and validation reference but are not the current official release gate.
23
+ This project was created using `bun init` in bun v1.3.9. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/index.ts";
package/package.json CHANGED
@@ -1,34 +1,25 @@
1
1
  {
2
2
  "name": "@imbingox/acex",
3
- "version": "0.1.0-beta.0",
4
- "private": false,
3
+ "version": "0.1.0-beta.1",
4
+ "description": "Multi-exchange trading SDK for market data, account, and order management",
5
+ "module": "index.ts",
5
6
  "type": "module",
6
- "packageManager": "bun@1.2.8",
7
- "publishConfig": {
8
- "access": "public"
9
- },
10
- "files": ["dist"],
11
- "main": "./dist/index.js",
12
- "types": "./dist/index.d.ts",
13
7
  "exports": {
14
- ".": {
15
- "types": "./dist/index.d.ts",
16
- "import": "./dist/index.js"
17
- }
8
+ ".": "./index.ts"
18
9
  },
10
+ "files": [
11
+ "index.ts",
12
+ "src/"
13
+ ],
19
14
  "scripts": {
20
- "build": "tsc -p tsconfig.json",
21
- "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
22
- "test": "bun test",
23
- "lint": "biome check package.json tsconfig.json src tests",
24
- "format": "biome format --write ."
25
- },
26
- "dependencies": {
27
- "ccxt": "^4.5.45"
15
+ "lint": "biome check .",
16
+ "lint:fix": "biome check --write .",
17
+ "type-check": "tsc --noEmit",
18
+ "test": "bun test"
28
19
  },
29
20
  "devDependencies": {
30
- "@biomejs/biome": "^1.9.4",
31
- "typescript": "^5.8.2",
32
- "bun-types": "^1.3.11"
21
+ "@biomejs/biome": "^2.4.10",
22
+ "@types/bun": "latest",
23
+ "typescript": "^6.0.2"
33
24
  }
34
25
  }
@@ -0,0 +1,53 @@
1
+ import type { MarketDefinition } from "../../types/index.ts";
2
+ import type {
3
+ L1BookStreamCallbacks,
4
+ L1BookStreamOptions,
5
+ MarketAdapter,
6
+ StreamHandle,
7
+ } from "../types.ts";
8
+ import { subscribeBinanceBookTicker } from "./book-ticker.ts";
9
+ import {
10
+ type BinanceMarketDefinition,
11
+ loadBinanceMarkets,
12
+ } from "./market-catalog.ts";
13
+
14
+ export class BinanceMarketAdapter implements MarketAdapter {
15
+ readonly exchange = "binance" as const;
16
+
17
+ private readonly definitions = new Map<string, BinanceMarketDefinition>();
18
+
19
+ async loadMarkets(): Promise<MarketDefinition[]> {
20
+ const markets = await loadBinanceMarkets();
21
+ this.definitions.clear();
22
+
23
+ for (const market of markets) {
24
+ this.definitions.set(market.symbol, market);
25
+ }
26
+
27
+ return markets;
28
+ }
29
+
30
+ createL1BookStream(
31
+ market: MarketDefinition,
32
+ callbacks: L1BookStreamCallbacks,
33
+ options: L1BookStreamOptions,
34
+ ): StreamHandle {
35
+ const binanceMarket = this.definitions.get(market.symbol);
36
+ if (!binanceMarket) {
37
+ throw new Error(`Unknown Binance market: ${market.symbol}`);
38
+ }
39
+
40
+ return subscribeBinanceBookTicker(
41
+ binanceMarket,
42
+ {
43
+ onBookTicker(update) {
44
+ callbacks.onUpdate(update);
45
+ },
46
+ onFreshnessChange: callbacks.onFreshnessChange,
47
+ onDisconnected: callbacks.onDisconnected,
48
+ onError: callbacks.onError,
49
+ },
50
+ options,
51
+ );
52
+ }
53
+ }
@@ -0,0 +1,123 @@
1
+ import { createManagedWebSocket } from "../../internal/managed-websocket.ts";
2
+ import type { BinanceMarketDefinition } from "./market-catalog.ts";
3
+
4
+ export interface BinanceL1BookUpdate {
5
+ bidPrice: string;
6
+ bidSize: string;
7
+ askPrice: string;
8
+ askSize: string;
9
+ exchangeTs?: number;
10
+ receivedAt: number;
11
+ }
12
+
13
+ export interface BinanceBookTickerSubscription {
14
+ readonly ready: Promise<void>;
15
+ close(): void;
16
+ }
17
+
18
+ export interface BinanceBookTickerCallbacks {
19
+ onBookTicker(update: BinanceL1BookUpdate): void;
20
+ onFreshnessChange(
21
+ freshness: "fresh" | "stale",
22
+ reason?: "heartbeat_timeout",
23
+ ): void;
24
+ onDisconnected(): void;
25
+ onError?(error: Error): void;
26
+ }
27
+
28
+ export interface BinanceBookTickerOptions {
29
+ initialMessageTimeoutMs: number;
30
+ staleAfterMs: number;
31
+ reconnectDelayMs: number;
32
+ reconnectMaxDelayMs: number;
33
+ now?: () => number;
34
+ }
35
+
36
+ interface BinanceBookTickerMessage {
37
+ b?: string;
38
+ B?: string;
39
+ a?: string;
40
+ A?: string;
41
+ T?: number;
42
+ }
43
+
44
+ const BINANCE_SPOT_WS_BASE_URL = "wss://stream.binance.com:9443/ws";
45
+ const BINANCE_USDM_WS_BASE_URL = "wss://fstream.binance.com/ws";
46
+ const BINANCE_COINM_WS_BASE_URL = "wss://dstream.binance.com/ws";
47
+
48
+ function getWsBaseUrl(market: BinanceMarketDefinition): string {
49
+ switch (market.family) {
50
+ case "spot":
51
+ return BINANCE_SPOT_WS_BASE_URL;
52
+ case "usdm":
53
+ return BINANCE_USDM_WS_BASE_URL;
54
+ case "coinm":
55
+ return BINANCE_COINM_WS_BASE_URL;
56
+ }
57
+ }
58
+
59
+ function buildBookTickerUrl(market: BinanceMarketDefinition): string {
60
+ return `${getWsBaseUrl(market)}/${market.id.toLowerCase()}@bookTicker`;
61
+ }
62
+
63
+ function parseBookTickerMessage(
64
+ data: string,
65
+ ): BinanceBookTickerMessage | undefined {
66
+ const parsed = JSON.parse(data) as BinanceBookTickerMessage;
67
+ if (!parsed.b || !parsed.B || !parsed.a || !parsed.A) {
68
+ return undefined;
69
+ }
70
+
71
+ return parsed;
72
+ }
73
+
74
+ export function subscribeBinanceBookTicker(
75
+ market: BinanceMarketDefinition,
76
+ callbacks: BinanceBookTickerCallbacks,
77
+ options: BinanceBookTickerOptions,
78
+ ): BinanceBookTickerSubscription {
79
+ const session = createManagedWebSocket<BinanceBookTickerMessage>({
80
+ url: buildBookTickerUrl(market),
81
+ initialMessageTimeoutMs: options.initialMessageTimeoutMs,
82
+ now: options.now,
83
+ messageWatchdog: {
84
+ staleAfterMs: options.staleAfterMs,
85
+ onStale() {
86
+ callbacks.onFreshnessChange("stale", "heartbeat_timeout");
87
+ },
88
+ },
89
+ reconnect: {
90
+ initialDelayMs: options.reconnectDelayMs,
91
+ maxDelayMs: options.reconnectMaxDelayMs,
92
+ },
93
+ parseMessage: parseBookTickerMessage,
94
+ onMessage(message, receivedAt) {
95
+ if (!message.b || !message.B || !message.a || !message.A) {
96
+ return;
97
+ }
98
+
99
+ callbacks.onBookTicker({
100
+ bidPrice: message.b,
101
+ bidSize: message.B,
102
+ askPrice: message.a,
103
+ askSize: message.A,
104
+ exchangeTs: message.T,
105
+ receivedAt,
106
+ });
107
+ callbacks.onFreshnessChange("fresh");
108
+ },
109
+ onUnexpectedClose() {
110
+ callbacks.onDisconnected();
111
+ },
112
+ onError() {
113
+ callbacks.onError?.(new Error(`WebSocket error for ${market.symbol}`));
114
+ },
115
+ });
116
+
117
+ return {
118
+ ready: session.ready,
119
+ close() {
120
+ session.close();
121
+ },
122
+ };
123
+ }
@@ -0,0 +1,251 @@
1
+ import type { MarketDefinition, MarketType } from "../../types/index.ts";
2
+
3
+ type FetchLike = typeof fetch;
4
+
5
+ export type BinanceMarketFamily = "spot" | "usdm" | "coinm";
6
+
7
+ export interface BinanceMarketDefinition extends MarketDefinition {
8
+ family: BinanceMarketFamily;
9
+ }
10
+
11
+ interface BinanceSymbolFilter {
12
+ filterType?: string;
13
+ tickSize?: string;
14
+ stepSize?: string;
15
+ minQty?: string;
16
+ minNotional?: string;
17
+ notional?: string;
18
+ }
19
+
20
+ interface BinanceSpotSymbolInfo {
21
+ symbol: string;
22
+ status: string;
23
+ baseAsset: string;
24
+ quoteAsset: string;
25
+ filters?: BinanceSymbolFilter[];
26
+ }
27
+
28
+ interface BinanceSpotExchangeInfo {
29
+ symbols?: BinanceSpotSymbolInfo[];
30
+ }
31
+
32
+ interface BinanceDerivativesSymbolInfo {
33
+ symbol: string;
34
+ status: string;
35
+ contractType?: string;
36
+ deliveryDate?: number;
37
+ baseAsset: string;
38
+ quoteAsset: string;
39
+ marginAsset?: string;
40
+ pricePrecision?: number;
41
+ quantityPrecision?: number;
42
+ contractSize?: number | string;
43
+ filters?: BinanceSymbolFilter[];
44
+ }
45
+
46
+ interface BinanceDerivativesExchangeInfo {
47
+ symbols?: BinanceDerivativesSymbolInfo[];
48
+ }
49
+
50
+ const BINANCE_SPOT_EXCHANGE_INFO_URL =
51
+ "https://api.binance.com/api/v3/exchangeInfo";
52
+ const BINANCE_USDM_EXCHANGE_INFO_URL =
53
+ "https://fapi.binance.com/fapi/v1/exchangeInfo";
54
+ const BINANCE_COINM_EXCHANGE_INFO_URL =
55
+ "https://dapi.binance.com/dapi/v1/exchangeInfo";
56
+
57
+ function toRecord(value: unknown): Record<string, unknown> {
58
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
59
+ return {};
60
+ }
61
+
62
+ return value as Record<string, unknown>;
63
+ }
64
+
65
+ function getFilter(
66
+ filters: BinanceSymbolFilter[] | undefined,
67
+ filterType: string,
68
+ ): BinanceSymbolFilter | undefined {
69
+ return filters?.find((filter) => filter.filterType === filterType);
70
+ }
71
+
72
+ function normalizeStep(step: string | undefined, fallback = "1"): string {
73
+ return step && step.length > 0 ? step : fallback;
74
+ }
75
+
76
+ function precisionFromStep(step: string): number {
77
+ const normalized = step.replace(/0+$/, "");
78
+ const dotIndex = normalized.indexOf(".");
79
+ if (dotIndex === -1) {
80
+ return 0;
81
+ }
82
+
83
+ return normalized.length - dotIndex - 1;
84
+ }
85
+
86
+ function formatExpiry(expiry: number): string {
87
+ const date = new Date(expiry);
88
+ const year = date.getUTCFullYear();
89
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
90
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
91
+ return `${year}${month}${day}`;
92
+ }
93
+
94
+ function inferContractType(
95
+ contractType: string | undefined,
96
+ deliveryDate: number | undefined,
97
+ ): MarketType {
98
+ if (contractType === "PERPETUAL") {
99
+ return "swap";
100
+ }
101
+
102
+ if (deliveryDate && Number.isFinite(deliveryDate) && deliveryDate > 0) {
103
+ return "future";
104
+ }
105
+
106
+ return "swap";
107
+ }
108
+
109
+ function buildFuturesSymbol(
110
+ base: string,
111
+ quote: string,
112
+ settle: string,
113
+ type: MarketType,
114
+ expiry: number | undefined,
115
+ ): string {
116
+ const prefix = `${base}/${quote}:${settle}`;
117
+ if (type !== "future" || !expiry) {
118
+ return prefix;
119
+ }
120
+
121
+ return `${prefix}-${formatExpiry(expiry)}`;
122
+ }
123
+
124
+ function normalizeSpotSymbol(
125
+ symbol: BinanceSpotSymbolInfo,
126
+ ): BinanceMarketDefinition {
127
+ const priceFilter = getFilter(symbol.filters, "PRICE_FILTER");
128
+ const lotSizeFilter = getFilter(symbol.filters, "LOT_SIZE");
129
+ const notionalFilter =
130
+ getFilter(symbol.filters, "NOTIONAL") ??
131
+ getFilter(symbol.filters, "MIN_NOTIONAL");
132
+ const priceStep = normalizeStep(priceFilter?.tickSize);
133
+ const amountStep = normalizeStep(lotSizeFilter?.stepSize);
134
+
135
+ return {
136
+ exchange: "binance",
137
+ family: "spot",
138
+ symbol: `${symbol.baseAsset}/${symbol.quoteAsset}`,
139
+ id: symbol.symbol,
140
+ type: "spot",
141
+ base: symbol.baseAsset,
142
+ quote: symbol.quoteAsset,
143
+ active: symbol.status === "TRADING",
144
+ contract: false,
145
+ pricePrecision: precisionFromStep(priceStep),
146
+ amountPrecision: precisionFromStep(amountStep),
147
+ priceStep,
148
+ amountStep,
149
+ minAmount: lotSizeFilter?.minQty,
150
+ minNotional: notionalFilter?.minNotional ?? notionalFilter?.notional,
151
+ raw: toRecord(symbol),
152
+ };
153
+ }
154
+
155
+ function normalizeDerivativesSymbol(
156
+ symbol: BinanceDerivativesSymbolInfo,
157
+ family: BinanceMarketFamily,
158
+ ): BinanceMarketDefinition {
159
+ const priceFilter = getFilter(symbol.filters, "PRICE_FILTER");
160
+ const lotSizeFilter = getFilter(symbol.filters, "LOT_SIZE");
161
+ const notionalFilter =
162
+ getFilter(symbol.filters, "NOTIONAL") ??
163
+ getFilter(symbol.filters, "MIN_NOTIONAL");
164
+ const priceStep = normalizeStep(priceFilter?.tickSize);
165
+ const amountStep = normalizeStep(lotSizeFilter?.stepSize);
166
+ const type = inferContractType(symbol.contractType, symbol.deliveryDate);
167
+ const settle =
168
+ symbol.marginAsset ??
169
+ (family === "usdm" ? symbol.quoteAsset : symbol.baseAsset);
170
+ const contractSize =
171
+ symbol.contractSize !== undefined
172
+ ? `${symbol.contractSize}`
173
+ : family === "usdm"
174
+ ? "1"
175
+ : undefined;
176
+
177
+ return {
178
+ exchange: "binance",
179
+ family,
180
+ symbol: buildFuturesSymbol(
181
+ symbol.baseAsset,
182
+ symbol.quoteAsset,
183
+ settle,
184
+ type,
185
+ type === "future" ? symbol.deliveryDate : undefined,
186
+ ),
187
+ id: symbol.symbol,
188
+ type,
189
+ base: symbol.baseAsset,
190
+ quote: symbol.quoteAsset,
191
+ settle,
192
+ active: symbol.status === "TRADING",
193
+ contract: true,
194
+ linear: family === "usdm",
195
+ inverse: family === "coinm",
196
+ contractSize,
197
+ pricePrecision: precisionFromStep(priceStep),
198
+ amountPrecision: precisionFromStep(amountStep),
199
+ priceStep,
200
+ amountStep,
201
+ minAmount: lotSizeFilter?.minQty,
202
+ minNotional: notionalFilter?.minNotional ?? notionalFilter?.notional,
203
+ expiry: type === "future" ? symbol.deliveryDate : undefined,
204
+ raw: toRecord(symbol),
205
+ };
206
+ }
207
+
208
+ async function fetchJson<T>(fetchFn: FetchLike, url: string): Promise<T> {
209
+ const response = await fetchFn(url);
210
+ if (!response.ok) {
211
+ throw new Error(
212
+ `Binance request failed: ${response.status} ${response.statusText}`,
213
+ );
214
+ }
215
+
216
+ return (await response.json()) as T;
217
+ }
218
+
219
+ function sortMarkets(
220
+ markets: BinanceMarketDefinition[],
221
+ ): BinanceMarketDefinition[] {
222
+ return [...markets].sort((left, right) =>
223
+ left.symbol.localeCompare(right.symbol),
224
+ );
225
+ }
226
+
227
+ export async function loadBinanceMarkets(
228
+ fetchFn: FetchLike = fetch,
229
+ ): Promise<BinanceMarketDefinition[]> {
230
+ const [spot, usdm, coinm] = await Promise.all([
231
+ fetchJson<BinanceSpotExchangeInfo>(fetchFn, BINANCE_SPOT_EXCHANGE_INFO_URL),
232
+ fetchJson<BinanceDerivativesExchangeInfo>(
233
+ fetchFn,
234
+ BINANCE_USDM_EXCHANGE_INFO_URL,
235
+ ),
236
+ fetchJson<BinanceDerivativesExchangeInfo>(
237
+ fetchFn,
238
+ BINANCE_COINM_EXCHANGE_INFO_URL,
239
+ ),
240
+ ]);
241
+
242
+ return sortMarkets([
243
+ ...(spot.symbols ?? []).map(normalizeSpotSymbol),
244
+ ...(usdm.symbols ?? []).map((symbol) =>
245
+ normalizeDerivativesSymbol(symbol, "usdm"),
246
+ ),
247
+ ...(coinm.symbols ?? []).map((symbol) =>
248
+ normalizeDerivativesSymbol(symbol, "coinm"),
249
+ ),
250
+ ]);
251
+ }
@@ -0,0 +1,43 @@
1
+ import type { Exchange, MarketDefinition } from "../types/index.ts";
2
+
3
+ export interface StreamHandle {
4
+ readonly ready: Promise<void>;
5
+ close(): void;
6
+ }
7
+
8
+ export interface RawL1BookUpdate {
9
+ bidPrice: string;
10
+ bidSize: string;
11
+ askPrice: string;
12
+ askSize: string;
13
+ exchangeTs?: number;
14
+ receivedAt: number;
15
+ }
16
+
17
+ export interface L1BookStreamCallbacks {
18
+ onUpdate(update: RawL1BookUpdate): void;
19
+ onFreshnessChange(
20
+ freshness: "fresh" | "stale",
21
+ reason?: "heartbeat_timeout",
22
+ ): void;
23
+ onDisconnected(): void;
24
+ onError(error: Error): void;
25
+ }
26
+
27
+ export interface L1BookStreamOptions {
28
+ initialMessageTimeoutMs: number;
29
+ staleAfterMs: number;
30
+ reconnectDelayMs: number;
31
+ reconnectMaxDelayMs: number;
32
+ now?: () => number;
33
+ }
34
+
35
+ export interface MarketAdapter {
36
+ readonly exchange: Exchange;
37
+ loadMarkets(): Promise<MarketDefinition[]>;
38
+ createL1BookStream(
39
+ market: MarketDefinition,
40
+ callbacks: L1BookStreamCallbacks,
41
+ options: L1BookStreamOptions,
42
+ ): StreamHandle;
43
+ }
@@ -0,0 +1,60 @@
1
+ import type {
2
+ AccountCredentials,
3
+ AcexInternalError,
4
+ Exchange,
5
+ HealthEvent,
6
+ } from "../types/index.ts";
7
+
8
+ export interface RegisteredAccountRecord {
9
+ accountId: string;
10
+ exchange: Exchange;
11
+ credentials?: AccountCredentials;
12
+ options?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface ClientContext {
16
+ now(): number;
17
+ assertStarted(): void;
18
+ getRegisteredAccount(accountId: string): RegisteredAccountRecord;
19
+ ensurePrivateCredentials(accountId: string): void;
20
+ publishRuntimeError(
21
+ source: AcexInternalError["source"],
22
+ error: Error,
23
+ metadata?: Omit<AcexInternalError, "error" | "source" | "ts">,
24
+ ): void;
25
+ publishHealthEvent(event: HealthEvent): void;
26
+ }
27
+
28
+ export interface ManagerLifecycle {
29
+ onClientStarted(): void;
30
+ onClientStopping(now: number): void;
31
+ }
32
+
33
+ export interface AccountAwareManager {
34
+ onAccountRemoved(accountId: string, now: number): void;
35
+ onCredentialsUpdated(accountId: string, exchange: Exchange): void;
36
+ }
37
+
38
+ export interface HealthReporter<T> {
39
+ getStatuses(): T[];
40
+ }
41
+
42
+ export function hasPrivateCredentials(
43
+ credentials?: AccountCredentials,
44
+ ): boolean {
45
+ return Boolean(credentials?.apiKey && credentials.secret);
46
+ }
47
+
48
+ export function mergeCredentials(
49
+ current: AccountCredentials | undefined,
50
+ next: AccountCredentials,
51
+ ): AccountCredentials {
52
+ return {
53
+ ...current,
54
+ ...next,
55
+ extra: {
56
+ ...(current?.extra ?? {}),
57
+ ...(next.extra ?? {}),
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,6 @@
1
+ import type { AcexClient, CreateClientOptions } from "../types/index.ts";
2
+ import { AcexClientImpl } from "./runtime.ts";
3
+
4
+ export function createClient(options?: CreateClientOptions): AcexClient {
5
+ return new AcexClientImpl(options);
6
+ }