@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.
Files changed (85) hide show
  1. package/README.md +92 -285
  2. package/index.ts +1 -0
  3. package/package.json +40 -23
  4. package/src/adapters/binance/adapter.ts +80 -0
  5. package/src/adapters/binance/book-ticker.ts +123 -0
  6. package/src/adapters/binance/mark-price.ts +126 -0
  7. package/src/adapters/binance/market-catalog.ts +258 -0
  8. package/src/adapters/binance/private-adapter.ts +833 -0
  9. package/src/adapters/types.ts +219 -0
  10. package/src/client/context.ts +123 -0
  11. package/src/client/create-client.ts +6 -0
  12. package/src/client/private-subscription-coordinator.ts +512 -0
  13. package/src/client/runtime.ts +410 -0
  14. package/src/errors.ts +27 -0
  15. package/src/index.ts +5 -0
  16. package/src/internal/async-event-bus.ts +100 -0
  17. package/src/internal/filters.ts +117 -0
  18. package/src/internal/managed-websocket.ts +280 -0
  19. package/src/managers/account-manager.ts +609 -0
  20. package/src/managers/market-manager.ts +889 -0
  21. package/src/managers/order-manager.ts +685 -0
  22. package/src/types/account.ts +157 -0
  23. package/src/types/client.ts +79 -0
  24. package/src/types/index.ts +5 -0
  25. package/src/types/market.ts +150 -0
  26. package/src/types/order.ts +177 -0
  27. package/src/types/shared.ts +93 -0
  28. package/dist/adapters/binance/composite-adapter.d.ts +0 -116
  29. package/dist/adapters/binance/composite-adapter.js +0 -121
  30. package/dist/adapters/binance/market-types.d.ts +0 -63
  31. package/dist/adapters/binance/market-types.js +0 -1
  32. package/dist/adapters/binance/native-market-adapter.d.ts +0 -102
  33. package/dist/adapters/binance/native-market-adapter.js +0 -455
  34. package/dist/adapters/binance/normalizers.d.ts +0 -8
  35. package/dist/adapters/binance/normalizers.js +0 -123
  36. package/dist/adapters/binance/rest-client.d.ts +0 -17
  37. package/dist/adapters/binance/rest-client.js +0 -66
  38. package/dist/adapters/binance/symbol-router.d.ts +0 -9
  39. package/dist/adapters/binance/symbol-router.js +0 -174
  40. package/dist/adapters/binance/ws-client.d.ts +0 -24
  41. package/dist/adapters/binance/ws-client.js +0 -261
  42. package/dist/adapters/ccxt/aster-ccxt-adapter.d.ts +0 -157
  43. package/dist/adapters/ccxt/aster-ccxt-adapter.js +0 -272
  44. package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.d.ts +0 -180
  45. package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.js +0 -539
  46. package/dist/adapters/ccxt/binance-usdm-exchange.d.ts +0 -22
  47. package/dist/adapters/ccxt/binance-usdm-exchange.js +0 -23
  48. package/dist/adapters/fake/fake-aster-adapter.d.ts +0 -130
  49. package/dist/adapters/fake/fake-aster-adapter.js +0 -283
  50. package/dist/adapters/types.d.ts +0 -210
  51. package/dist/adapters/types.js +0 -1
  52. package/dist/core/client.d.ts +0 -50
  53. package/dist/core/client.js +0 -403
  54. package/dist/core/recovery.d.ts +0 -22
  55. package/dist/core/recovery.js +0 -18
  56. package/dist/core/runtime.d.ts +0 -26
  57. package/dist/core/runtime.js +0 -150
  58. package/dist/errors/acex-error.d.ts +0 -25
  59. package/dist/errors/acex-error.js +0 -54
  60. package/dist/index.d.ts +0 -6
  61. package/dist/index.js +0 -3
  62. package/dist/managers/account-manager.d.ts +0 -41
  63. package/dist/managers/account-manager.js +0 -80
  64. package/dist/managers/market-manager.d.ts +0 -16
  65. package/dist/managers/market-manager.js +0 -28
  66. package/dist/managers/order-manager.d.ts +0 -87
  67. package/dist/managers/order-manager.js +0 -122
  68. package/dist/runtime/async-queue.d.ts +0 -8
  69. package/dist/runtime/async-queue.js +0 -88
  70. package/dist/runtime/request-id.d.ts +0 -1
  71. package/dist/runtime/request-id.js +0 -5
  72. package/dist/runtime/ws-connection-supervisor.d.ts +0 -76
  73. package/dist/runtime/ws-connection-supervisor.js +0 -522
  74. package/dist/store/account-store.d.ts +0 -52
  75. package/dist/store/account-store.js +0 -18
  76. package/dist/store/health-store.d.ts +0 -16
  77. package/dist/store/health-store.js +0 -29
  78. package/dist/store/market-store.d.ts +0 -42
  79. package/dist/store/market-store.js +0 -51
  80. package/dist/store/order-store.d.ts +0 -38
  81. package/dist/store/order-store.js +0 -49
  82. package/dist/testing/create-fake-runtime.d.ts +0 -5
  83. package/dist/testing/create-fake-runtime.js +0 -7
  84. package/dist/types/public.d.ts +0 -5
  85. package/dist/types/public.js +0 -1
@@ -0,0 +1,410 @@
1
+ import { BinanceMarketAdapter } from "../adapters/binance/adapter.ts";
2
+ import { BinancePrivateAdapter } from "../adapters/binance/private-adapter.ts";
3
+ import type {
4
+ CancelAllOrdersRequest,
5
+ CancelOrderRequest,
6
+ CreateOrderRequest,
7
+ PrivateUserDataAdapter,
8
+ RawOrderUpdate,
9
+ } from "../adapters/types.ts";
10
+ import { AcexError, type AcexErrorCode } from "../errors.ts";
11
+ import { AsyncEventBus } from "../internal/async-event-bus.ts";
12
+ import { matchesHealthFilter } from "../internal/filters.ts";
13
+ import { AccountManagerImpl } from "../managers/account-manager.ts";
14
+ import { MarketManagerImpl } from "../managers/market-manager.ts";
15
+ import { OrderManagerImpl } from "../managers/order-manager.ts";
16
+ import type {
17
+ AccountCredentials,
18
+ AccountManager,
19
+ AcexClient,
20
+ AcexInternalError,
21
+ CancelAllOrdersInput,
22
+ CancelOrderInput,
23
+ ClientEventStreams,
24
+ ClientHealthSnapshot,
25
+ ClientStatus,
26
+ ClientStatusChangedEvent,
27
+ CreateClientOptions,
28
+ CreateOrderInput,
29
+ HealthEvent,
30
+ HealthEventFilter,
31
+ MarketManager,
32
+ OrderManager,
33
+ RegisterAccountInput,
34
+ RegisterAccountResult,
35
+ StopOptions,
36
+ } from "../types/index.ts";
37
+ import {
38
+ type ClientContext,
39
+ hasPrivateCredentials,
40
+ mergeCredentials,
41
+ type PrivateAccountDataConsumer,
42
+ type PrivateOrderDataConsumer,
43
+ type RegisteredAccountRecord,
44
+ } from "./context.ts";
45
+ import { PrivateSubscriptionCoordinator } from "./private-subscription-coordinator.ts";
46
+
47
+ const activeClients = new Set<AcexClientImpl>();
48
+
49
+ export function stopAllClientsForTests(): void {
50
+ const clients = [...activeClients];
51
+ activeClients.clear();
52
+ for (const client of clients) {
53
+ void client.stop().catch(() => {
54
+ // Test cleanup should be best-effort and never mask the original failure.
55
+ });
56
+ }
57
+ }
58
+
59
+ class ClientEventStreamsImpl implements ClientEventStreams {
60
+ constructor(
61
+ private readonly healthBus: AsyncEventBus<HealthEvent>,
62
+ private readonly errorBus: AsyncEventBus<AcexInternalError>,
63
+ ) {}
64
+
65
+ errors(): AsyncIterable<AcexInternalError> {
66
+ return this.errorBus.stream();
67
+ }
68
+
69
+ health(filter?: HealthEventFilter): AsyncIterable<HealthEvent> {
70
+ return this.healthBus.stream((event) => matchesHealthFilter(event, filter));
71
+ }
72
+ }
73
+
74
+ export class AcexClientImpl implements AcexClient, ClientContext {
75
+ readonly market: MarketManager;
76
+ readonly account: AccountManager;
77
+ readonly order: OrderManager;
78
+ readonly events: ClientEventStreams;
79
+
80
+ private status: ClientStatus = "idle";
81
+ private readonly healthBus = new AsyncEventBus<HealthEvent>();
82
+ private readonly errorBus = new AsyncEventBus<AcexInternalError>();
83
+ private readonly registeredAccounts = new Map<
84
+ string,
85
+ RegisteredAccountRecord
86
+ >();
87
+ private readonly marketManager: MarketManagerImpl;
88
+ private readonly accountManager: AccountManagerImpl;
89
+ private readonly orderManager: OrderManagerImpl;
90
+ private readonly privateAdapter: PrivateUserDataAdapter;
91
+ private readonly privateCoordinator: PrivateSubscriptionCoordinator;
92
+
93
+ constructor(options: CreateClientOptions = {}) {
94
+ activeClients.add(this);
95
+
96
+ const marketAdapter = new BinanceMarketAdapter();
97
+ this.privateAdapter = new BinancePrivateAdapter();
98
+
99
+ this.marketManager = new MarketManagerImpl(this, marketAdapter, {
100
+ initialL1TimeoutMs: options.market?.l1InitialMessageTimeoutMs,
101
+ l1StaleAfterMs: options.market?.l1StaleAfterMs,
102
+ l1ReconnectDelayMs: options.market?.l1ReconnectDelayMs,
103
+ l1ReconnectMaxDelayMs: options.market?.l1ReconnectMaxDelayMs,
104
+ });
105
+ this.accountManager = new AccountManagerImpl(this);
106
+ this.orderManager = new OrderManagerImpl(this);
107
+ this.privateCoordinator = new PrivateSubscriptionCoordinator(
108
+ this,
109
+ this.privateAdapter,
110
+ this.accountManager as PrivateAccountDataConsumer,
111
+ this.orderManager as PrivateOrderDataConsumer,
112
+ options.account,
113
+ );
114
+
115
+ this.market = this.marketManager;
116
+ this.account = this.accountManager;
117
+ this.order = this.orderManager;
118
+ this.events = new ClientEventStreamsImpl(this.healthBus, this.errorBus);
119
+ }
120
+
121
+ // --- AcexClient public API ---
122
+
123
+ getStatus(): ClientStatus {
124
+ return this.status;
125
+ }
126
+
127
+ getHealth(): ClientHealthSnapshot {
128
+ return {
129
+ clientStatus: this.status,
130
+ markets: this.marketManager.getStatuses(),
131
+ accounts: this.accountManager.getStatuses(),
132
+ orders: this.orderManager.getStatuses(),
133
+ updatedAt: this.now(),
134
+ };
135
+ }
136
+
137
+ async registerAccount(
138
+ input: RegisterAccountInput,
139
+ ): Promise<RegisterAccountResult> {
140
+ if (this.registeredAccounts.has(input.accountId)) {
141
+ throw this.createError(
142
+ "ACCOUNT_ALREADY_EXISTS",
143
+ `Account already exists: ${input.accountId}`,
144
+ { accountId: input.accountId, exchange: input.exchange },
145
+ );
146
+ }
147
+
148
+ this.registeredAccounts.set(input.accountId, {
149
+ accountId: input.accountId,
150
+ exchange: input.exchange,
151
+ credentials: input.credentials,
152
+ options: input.options,
153
+ });
154
+
155
+ return {
156
+ accountId: input.accountId,
157
+ exchange: input.exchange,
158
+ };
159
+ }
160
+
161
+ async updateAccountCredentials(
162
+ accountId: string,
163
+ credentials: AccountCredentials,
164
+ ): Promise<void> {
165
+ const account = this.registeredAccounts.get(accountId);
166
+ if (!account) {
167
+ throw this.createError(
168
+ "ACCOUNT_NOT_FOUND",
169
+ `Account not found: ${accountId}`,
170
+ { accountId },
171
+ );
172
+ }
173
+
174
+ account.credentials = mergeCredentials(account.credentials, credentials);
175
+
176
+ if (this.status !== "running") {
177
+ return;
178
+ }
179
+
180
+ this.accountManager.onCredentialsUpdated(accountId, account.exchange);
181
+ this.orderManager.onCredentialsUpdated(accountId, account.exchange);
182
+ this.privateCoordinator.onCredentialsUpdated(accountId);
183
+ }
184
+
185
+ async removeAccount(accountId: string): Promise<void> {
186
+ const account = this.registeredAccounts.get(accountId);
187
+ if (!account) {
188
+ throw this.createError(
189
+ "ACCOUNT_NOT_FOUND",
190
+ `Account not found: ${accountId}`,
191
+ { accountId },
192
+ );
193
+ }
194
+
195
+ const now = this.now();
196
+ this.privateCoordinator.onAccountRemoved(accountId);
197
+ this.accountManager.onAccountRemoved(accountId, now);
198
+ this.orderManager.onAccountRemoved(accountId, now);
199
+ this.registeredAccounts.delete(accountId);
200
+ }
201
+
202
+ async start(): Promise<void> {
203
+ if (this.status === "running") {
204
+ return;
205
+ }
206
+
207
+ this.setClientStatus("starting");
208
+ this.setClientStatus("running");
209
+
210
+ this.marketManager.onClientStarted();
211
+ this.accountManager.onClientStarted();
212
+ this.orderManager.onClientStarted();
213
+ this.privateCoordinator.onClientStarted();
214
+ }
215
+
216
+ async stop(_options?: StopOptions): Promise<void> {
217
+ if (this.status === "stopped" || this.status === "idle") {
218
+ if (this.status !== "stopped") {
219
+ this.setClientStatus("stopped");
220
+ }
221
+ return;
222
+ }
223
+
224
+ this.setClientStatus("stopping");
225
+
226
+ const now = this.now();
227
+ this.privateCoordinator.onClientStopping();
228
+ this.marketManager.onClientStopping(now);
229
+ this.accountManager.onClientStopping(now);
230
+ this.orderManager.onClientStopping(now);
231
+
232
+ this.setClientStatus("stopped");
233
+ }
234
+
235
+ // --- ClientContext ---
236
+
237
+ now(): number {
238
+ return Date.now();
239
+ }
240
+
241
+ assertStarted(): void {
242
+ if (this.status !== "running") {
243
+ throw this.createError(
244
+ "CLIENT_NOT_STARTED",
245
+ "Client must be started before subscribing to data",
246
+ );
247
+ }
248
+ }
249
+
250
+ getRegisteredAccount(accountId: string): RegisteredAccountRecord {
251
+ const account = this.registeredAccounts.get(accountId);
252
+ if (!account) {
253
+ throw this.createError(
254
+ "ACCOUNT_NOT_FOUND",
255
+ `Account not found: ${accountId}`,
256
+ { accountId },
257
+ );
258
+ }
259
+
260
+ return account;
261
+ }
262
+
263
+ ensurePrivateCredentials(accountId: string): void {
264
+ const account = this.getRegisteredAccount(accountId);
265
+ if (hasPrivateCredentials(account.credentials)) {
266
+ return;
267
+ }
268
+
269
+ throw this.createError(
270
+ "CREDENTIALS_MISSING",
271
+ `Account credentials are required for private subscriptions: ${accountId}`,
272
+ { accountId, exchange: account.exchange },
273
+ );
274
+ }
275
+
276
+ subscribePrivateAccountFeed(accountId: string): Promise<void> {
277
+ return this.privateCoordinator.subscribeAccountFeed(accountId);
278
+ }
279
+
280
+ unsubscribePrivateAccountFeed(accountId: string): void {
281
+ this.privateCoordinator.unsubscribeAccountFeed(accountId);
282
+ }
283
+
284
+ subscribePrivateOrderFeed(accountId: string): Promise<void> {
285
+ return this.privateCoordinator.subscribeOrderFeed(accountId);
286
+ }
287
+
288
+ unsubscribePrivateOrderFeed(accountId: string): void {
289
+ this.privateCoordinator.unsubscribeOrderFeed(accountId);
290
+ }
291
+
292
+ createOrder(input: CreateOrderInput): Promise<RawOrderUpdate> {
293
+ const account = this.getPrivateCommandAccount(input.accountId);
294
+ const request: CreateOrderRequest = {
295
+ symbol: input.symbol,
296
+ side: input.side,
297
+ type: input.type,
298
+ amount: input.amount,
299
+ price: input.type === "limit" ? input.price : undefined,
300
+ clientOrderId: input.clientOrderId,
301
+ reduceOnly: input.reduceOnly,
302
+ positionSide: input.positionSide,
303
+ };
304
+
305
+ return this.privateAdapter.createOrder(
306
+ account.credentials ?? {},
307
+ request,
308
+ account.options,
309
+ );
310
+ }
311
+
312
+ cancelOrder(input: CancelOrderInput): Promise<RawOrderUpdate> {
313
+ const account = this.getPrivateCommandAccount(input.accountId);
314
+ const request: CancelOrderRequest = {
315
+ symbol: input.symbol,
316
+ orderId: input.orderId,
317
+ clientOrderId: input.clientOrderId,
318
+ };
319
+
320
+ return this.privateAdapter.cancelOrder(
321
+ account.credentials ?? {},
322
+ request,
323
+ account.options,
324
+ );
325
+ }
326
+
327
+ cancelAllOrders(input: CancelAllOrdersInput): Promise<RawOrderUpdate[]> {
328
+ const account = this.getPrivateCommandAccount(input.accountId);
329
+ const request: CancelAllOrdersRequest = {
330
+ symbol: input.symbol,
331
+ };
332
+
333
+ return this.privateAdapter.cancelAllOrders(
334
+ account.credentials ?? {},
335
+ request,
336
+ account.options,
337
+ );
338
+ }
339
+
340
+ publishRuntimeError(
341
+ source: AcexInternalError["source"],
342
+ error: Error,
343
+ metadata?: Omit<AcexInternalError, "error" | "source" | "ts">,
344
+ ): void {
345
+ this.errorBus.publish({
346
+ source,
347
+ ts: this.now(),
348
+ error,
349
+ ...metadata,
350
+ });
351
+ }
352
+
353
+ publishHealthEvent(event: HealthEvent): void {
354
+ this.healthBus.publish(event);
355
+ }
356
+
357
+ // --- Private ---
358
+
359
+ private setClientStatus(status: ClientStatus): void {
360
+ if (this.status === status) {
361
+ return;
362
+ }
363
+
364
+ this.status = status;
365
+
366
+ const event: ClientStatusChangedEvent = {
367
+ type: "client.status_changed",
368
+ status,
369
+ ts: this.now(),
370
+ };
371
+
372
+ this.healthBus.publish(event);
373
+ }
374
+
375
+ private createError(
376
+ code: AcexErrorCode,
377
+ message: string,
378
+ metadata?: Omit<AcexInternalError, "error" | "source" | "ts">,
379
+ ): AcexError {
380
+ const error = new AcexError(code, message);
381
+ this.errorBus.publish({
382
+ source: "client",
383
+ ts: this.now(),
384
+ error,
385
+ ...metadata,
386
+ });
387
+ return error;
388
+ }
389
+
390
+ private getPrivateCommandAccount(accountId: string): RegisteredAccountRecord {
391
+ const account = this.getRegisteredAccount(accountId);
392
+ if (account.exchange !== this.privateAdapter.exchange) {
393
+ throw this.createError(
394
+ "EXCHANGE_NOT_SUPPORTED",
395
+ `Exchange is not supported yet: ${account.exchange}`,
396
+ { accountId, exchange: account.exchange },
397
+ );
398
+ }
399
+
400
+ if (!hasPrivateCredentials(account.credentials)) {
401
+ throw this.createError(
402
+ "CREDENTIALS_MISSING",
403
+ `Account credentials are required for private order commands: ${accountId}`,
404
+ { accountId, exchange: account.exchange },
405
+ );
406
+ }
407
+
408
+ return account;
409
+ }
410
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,27 @@
1
+ export type AcexErrorCode =
2
+ | "ACCOUNT_ALREADY_EXISTS"
3
+ | "ACCOUNT_BOOTSTRAP_FAILED"
4
+ | "ACCOUNT_NOT_FOUND"
5
+ | "CLIENT_NOT_STARTED"
6
+ | "CREDENTIALS_MISSING"
7
+ | "EXCHANGE_NOT_SUPPORTED"
8
+ | "MARKET_CATALOG_LOAD_FAILED"
9
+ | "MARKET_INACTIVE"
10
+ | "MARKET_FUNDING_RATE_UNSUPPORTED"
11
+ | "MARKET_NOT_FOUND"
12
+ | "MARKET_STREAM_TIMEOUT"
13
+ | "ORDER_BOOTSTRAP_FAILED"
14
+ | "ORDER_CANCEL_ALL_FAILED"
15
+ | "ORDER_CANCEL_FAILED"
16
+ | "ORDER_CREATE_FAILED"
17
+ | "ORDER_INPUT_INVALID";
18
+
19
+ export class AcexError extends Error {
20
+ readonly code: AcexErrorCode;
21
+
22
+ constructor(code: AcexErrorCode, message: string) {
23
+ super(message);
24
+ this.name = "AcexError";
25
+ this.code = code;
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { BigNumber } from "bignumber.js";
2
+ export { createClient } from "./client/create-client.ts";
3
+ export type { AcexErrorCode } from "./errors.ts";
4
+ export { AcexError } from "./errors.ts";
5
+ export * from "./types/index.ts";
@@ -0,0 +1,100 @@
1
+ type EventPredicate<T> = (event: T) => boolean;
2
+
3
+ interface BusListener<T> {
4
+ close(): void;
5
+ dispatch(event: T): void;
6
+ }
7
+
8
+ function doneResult<T>(): IteratorResult<T> {
9
+ return { done: true, value: undefined as T };
10
+ }
11
+
12
+ export class AsyncEventBus<T> {
13
+ private readonly listeners = new Set<BusListener<T>>();
14
+
15
+ publish(event: T): void {
16
+ for (const listener of this.listeners) {
17
+ listener.dispatch(event);
18
+ }
19
+ }
20
+
21
+ stream<U extends T = T>(
22
+ filter: ((event: T) => event is U) | EventPredicate<T> = () => true,
23
+ ): AsyncIterable<U> {
24
+ let closed = false;
25
+ const queue: U[] = [];
26
+ let pendingResolve: ((result: IteratorResult<U>) => void) | undefined;
27
+
28
+ const close = () => {
29
+ if (closed) {
30
+ return;
31
+ }
32
+
33
+ closed = true;
34
+ this.listeners.delete(listener);
35
+
36
+ if (pendingResolve) {
37
+ const resolve = pendingResolve;
38
+ pendingResolve = undefined;
39
+ resolve(doneResult<U>());
40
+ }
41
+ };
42
+
43
+ const listener: BusListener<T> = {
44
+ close,
45
+ dispatch: (event) => {
46
+ if (closed || !filter(event)) {
47
+ return;
48
+ }
49
+
50
+ const typedEvent = event as U;
51
+ if (pendingResolve) {
52
+ const resolve = pendingResolve;
53
+ pendingResolve = undefined;
54
+ resolve({ done: false, value: typedEvent });
55
+ return;
56
+ }
57
+
58
+ queue.push(typedEvent);
59
+ },
60
+ };
61
+
62
+ this.listeners.add(listener);
63
+
64
+ const iterator: AsyncIterableIterator<U> = {
65
+ [Symbol.asyncIterator]() {
66
+ return iterator;
67
+ },
68
+ next: async () => {
69
+ if (closed) {
70
+ return doneResult<U>();
71
+ }
72
+
73
+ const queued = queue.shift();
74
+ if (queued !== undefined) {
75
+ return { done: false, value: queued };
76
+ }
77
+
78
+ return await new Promise<IteratorResult<U>>((resolve) => {
79
+ pendingResolve = resolve;
80
+ });
81
+ },
82
+ return: async () => {
83
+ close();
84
+ return doneResult<U>();
85
+ },
86
+ throw: async (error?: unknown) => {
87
+ close();
88
+ throw error;
89
+ },
90
+ };
91
+
92
+ return iterator;
93
+ }
94
+
95
+ close(): void {
96
+ for (const listener of [...this.listeners]) {
97
+ listener.close();
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,117 @@
1
+ import type {
2
+ AccountEventFilter,
3
+ Exchange,
4
+ HealthEvent,
5
+ HealthEventFilter,
6
+ MarketEventFilter,
7
+ OrderEventFilter,
8
+ } from "../types/index.ts";
9
+
10
+ export function matchesMarketFilter(
11
+ event: { exchange: Exchange; symbol: string },
12
+ filter?: MarketEventFilter,
13
+ ): boolean {
14
+ if (!filter) {
15
+ return true;
16
+ }
17
+
18
+ if (filter.exchange && event.exchange !== filter.exchange) {
19
+ return false;
20
+ }
21
+
22
+ if (filter.symbol && event.symbol !== filter.symbol) {
23
+ return false;
24
+ }
25
+
26
+ return true;
27
+ }
28
+
29
+ export function matchesAccountFilter(
30
+ event: { accountId: string; exchange: Exchange; symbol?: string },
31
+ filter?: AccountEventFilter,
32
+ ): boolean {
33
+ if (!filter) {
34
+ return true;
35
+ }
36
+
37
+ if (filter.accountId && event.accountId !== filter.accountId) {
38
+ return false;
39
+ }
40
+
41
+ if (filter.exchange && event.exchange !== filter.exchange) {
42
+ return false;
43
+ }
44
+
45
+ if (filter.symbol && event.symbol !== filter.symbol) {
46
+ return false;
47
+ }
48
+
49
+ return true;
50
+ }
51
+
52
+ export function matchesOrderFilter(
53
+ event: { accountId: string; exchange: Exchange; symbol?: string },
54
+ filter?: OrderEventFilter,
55
+ ): boolean {
56
+ if (!filter) {
57
+ return true;
58
+ }
59
+
60
+ if (filter.accountId && event.accountId !== filter.accountId) {
61
+ return false;
62
+ }
63
+
64
+ if (filter.exchange && event.exchange !== filter.exchange) {
65
+ return false;
66
+ }
67
+
68
+ if (filter.symbol && event.symbol !== filter.symbol) {
69
+ return false;
70
+ }
71
+
72
+ return true;
73
+ }
74
+
75
+ export function matchesHealthFilter(
76
+ event: HealthEvent,
77
+ filter?: HealthEventFilter,
78
+ ): boolean {
79
+ if (!filter) {
80
+ return true;
81
+ }
82
+
83
+ if (filter.scope) {
84
+ const actualScope =
85
+ event.type === "client.status_changed"
86
+ ? "client"
87
+ : event.type === "market.status_changed"
88
+ ? "market"
89
+ : event.type === "account.status_changed"
90
+ ? "account"
91
+ : "order";
92
+
93
+ if (actualScope !== filter.scope) {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ if (filter.exchange) {
99
+ if (!("exchange" in event) || event.exchange !== filter.exchange) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ if (filter.accountId) {
105
+ if (!("accountId" in event) || event.accountId !== filter.accountId) {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ if (filter.symbol) {
111
+ if (!("symbol" in event) || event.symbol !== filter.symbol) {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ return true;
117
+ }