@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,609 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type {
3
+ RawAccountBootstrap,
4
+ RawAccountUpdate,
5
+ RawBalanceUpdate,
6
+ RawPositionUpdate,
7
+ RawRiskUpdate,
8
+ } from "../adapters/types.ts";
9
+ import type {
10
+ AccountAwareManager,
11
+ ClientContext,
12
+ HealthReporter,
13
+ ManagerLifecycle,
14
+ PrivateAccountDataConsumer,
15
+ PrivateSubscriptionState,
16
+ } from "../client/context.ts";
17
+ import { AsyncEventBus } from "../internal/async-event-bus.ts";
18
+ import { matchesAccountFilter } from "../internal/filters.ts";
19
+ import type {
20
+ AccountDataStatus,
21
+ AccountEvent,
22
+ AccountEventStreams,
23
+ AccountManager,
24
+ AccountSnapshot,
25
+ AccountSnapshotReplacedEvent,
26
+ AccountStatusChangedEvent,
27
+ BalanceSnapshot,
28
+ Exchange,
29
+ PositionKeyInput,
30
+ PositionSnapshot,
31
+ RiskSnapshot,
32
+ SubscribeAccountInput,
33
+ UnsubscribeAccountInput,
34
+ } from "../types/index.ts";
35
+
36
+ interface AccountRecord {
37
+ accountId: string;
38
+ exchange: Exchange;
39
+ subscribed: boolean;
40
+ snapshot?: AccountSnapshot;
41
+ status: AccountDataStatus;
42
+ }
43
+
44
+ function cloneAccountStatus(status: AccountDataStatus): AccountDataStatus {
45
+ return { ...status };
46
+ }
47
+
48
+ function positionKey(symbol: string, side: PositionSnapshot["side"]): string {
49
+ return `${symbol}:${side}`;
50
+ }
51
+
52
+ function getBigNumber(
53
+ value: string | undefined,
54
+ fallback: BigNumber,
55
+ ): BigNumber {
56
+ return value === undefined ? fallback : new BigNumber(value);
57
+ }
58
+
59
+ export class AccountManagerImpl
60
+ implements
61
+ AccountManager,
62
+ ManagerLifecycle,
63
+ AccountAwareManager,
64
+ HealthReporter<AccountDataStatus>,
65
+ PrivateAccountDataConsumer
66
+ {
67
+ readonly events: AccountEventStreams;
68
+
69
+ private readonly context: ClientContext;
70
+ private readonly accountBus = new AsyncEventBus<AccountEvent>();
71
+ private readonly accountStatusBus =
72
+ new AsyncEventBus<AccountStatusChangedEvent>();
73
+ private readonly records = new Map<string, AccountRecord>();
74
+
75
+ constructor(context: ClientContext) {
76
+ this.context = context;
77
+
78
+ this.events = {
79
+ status: (filter) =>
80
+ this.accountStatusBus.stream((event) =>
81
+ matchesAccountFilter(
82
+ { accountId: event.accountId, exchange: event.exchange },
83
+ filter,
84
+ ),
85
+ ),
86
+ updates: (filter) =>
87
+ this.accountBus.stream((event) =>
88
+ matchesAccountFilter(
89
+ {
90
+ accountId: event.accountId,
91
+ exchange: event.exchange,
92
+ symbol: "symbol" in event ? event.symbol : undefined,
93
+ },
94
+ filter,
95
+ ),
96
+ ),
97
+ };
98
+ }
99
+
100
+ // --- AccountManager public API ---
101
+
102
+ async subscribeAccount(input: SubscribeAccountInput): Promise<void> {
103
+ this.context.assertStarted();
104
+ const account = this.context.getRegisteredAccount(input.accountId);
105
+ this.context.ensurePrivateCredentials(input.accountId);
106
+
107
+ const record = this.getOrCreateRecord(input.accountId, account.exchange);
108
+ record.subscribed = true;
109
+
110
+ try {
111
+ await this.context.subscribePrivateAccountFeed(input.accountId);
112
+ } catch (error) {
113
+ record.subscribed = false;
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ async unsubscribeAccount(input: UnsubscribeAccountInput): Promise<void> {
119
+ const record = this.records.get(input.accountId);
120
+ if (!record?.subscribed) {
121
+ return;
122
+ }
123
+
124
+ this.context.unsubscribePrivateAccountFeed(input.accountId);
125
+ record.subscribed = false;
126
+ record.status = {
127
+ ...record.status,
128
+ activity: "inactive",
129
+ runtimeStatus: "stopped",
130
+ reason: undefined,
131
+ inactiveSince: this.context.now(),
132
+ };
133
+ this.publishStatus(record);
134
+ }
135
+
136
+ getAccountSnapshot(accountId: string): AccountSnapshot | undefined {
137
+ return this.records.get(accountId)?.snapshot;
138
+ }
139
+
140
+ getBalance(accountId: string, asset: string): BalanceSnapshot | undefined {
141
+ return this.records.get(accountId)?.snapshot?.balances[asset];
142
+ }
143
+
144
+ getBalances(accountId: string): BalanceSnapshot[] {
145
+ const balances = this.records.get(accountId)?.snapshot?.balances;
146
+ return balances ? Object.values(balances) : [];
147
+ }
148
+
149
+ getPosition(input: PositionKeyInput): PositionSnapshot | undefined {
150
+ return this.getPositions(input.accountId, input.symbol).find(
151
+ (position) => input.side === undefined || position.side === input.side,
152
+ );
153
+ }
154
+
155
+ getPositions(accountId: string, symbol?: string): PositionSnapshot[] {
156
+ const positions = this.records.get(accountId)?.snapshot?.positions ?? [];
157
+ if (!symbol) {
158
+ return [...positions];
159
+ }
160
+ return positions.filter((position) => position.symbol === symbol);
161
+ }
162
+
163
+ getRiskSnapshot(accountId: string): RiskSnapshot | undefined {
164
+ return this.records.get(accountId)?.snapshot?.risk;
165
+ }
166
+
167
+ getAccountStatus(accountId: string): AccountDataStatus | undefined {
168
+ const status = this.records.get(accountId)?.status;
169
+ return status ? cloneAccountStatus(status) : undefined;
170
+ }
171
+
172
+ // --- ManagerLifecycle ---
173
+
174
+ onClientStarted(): void {}
175
+
176
+ onClientStopping(now: number): void {
177
+ for (const record of this.records.values()) {
178
+ if (!record.subscribed) {
179
+ continue;
180
+ }
181
+
182
+ record.status = {
183
+ ...record.status,
184
+ activity: "inactive",
185
+ runtimeStatus: "stopped",
186
+ reason: undefined,
187
+ inactiveSince: now,
188
+ };
189
+ this.publishStatus(record);
190
+ }
191
+ }
192
+
193
+ // --- AccountAwareManager ---
194
+
195
+ onAccountRemoved(accountId: string, now: number): void {
196
+ const record = this.records.get(accountId);
197
+ if (!record) {
198
+ return;
199
+ }
200
+
201
+ record.subscribed = false;
202
+ record.status = {
203
+ ...record.status,
204
+ activity: "inactive",
205
+ runtimeStatus: "stopped",
206
+ reason: undefined,
207
+ inactiveSince: now,
208
+ };
209
+ this.publishStatus(record);
210
+ this.records.delete(accountId);
211
+ }
212
+
213
+ onCredentialsUpdated(accountId: string, exchange: Exchange): void {
214
+ const record = this.records.get(accountId);
215
+ if (!record?.subscribed) {
216
+ return;
217
+ }
218
+
219
+ this.onPrivateAccountPending(accountId, exchange);
220
+ }
221
+
222
+ // --- PrivateAccountDataConsumer ---
223
+
224
+ onPrivateAccountPending(accountId: string, exchange: Exchange): void {
225
+ const record = this.getOrCreateRecord(accountId, exchange);
226
+ if (!record.subscribed) {
227
+ return;
228
+ }
229
+
230
+ record.status = {
231
+ ...this.createStatus(accountId, exchange, "active"),
232
+ ready: Boolean(record.snapshot),
233
+ runtimeStatus: "bootstrap_pending",
234
+ reason: undefined,
235
+ lastReceivedAt: record.snapshot?.updatedAt,
236
+ lastReadyAt: record.snapshot?.updatedAt,
237
+ inactiveSince: undefined,
238
+ };
239
+ this.publishStatus(record);
240
+ }
241
+
242
+ onPrivateAccountBootstrap(
243
+ accountId: string,
244
+ exchange: Exchange,
245
+ bootstrap: RawAccountBootstrap,
246
+ ): void {
247
+ const record = this.getOrCreateRecord(accountId, exchange);
248
+ if (!record.subscribed) {
249
+ return;
250
+ }
251
+
252
+ record.snapshot = this.createBootstrapSnapshot(
253
+ accountId,
254
+ exchange,
255
+ bootstrap,
256
+ );
257
+ record.status = {
258
+ ...record.status,
259
+ activity: "active",
260
+ ready: true,
261
+ runtimeStatus: "healthy",
262
+ reason: undefined,
263
+ lastReceivedAt: record.snapshot.receivedAt,
264
+ lastReadyAt: record.snapshot.updatedAt,
265
+ inactiveSince: undefined,
266
+ };
267
+
268
+ const event: AccountSnapshotReplacedEvent = {
269
+ type: "account.snapshot_replaced",
270
+ accountId,
271
+ exchange,
272
+ snapshot: record.snapshot,
273
+ ts: this.context.now(),
274
+ };
275
+
276
+ this.accountBus.publish(event);
277
+ this.publishStatus(record);
278
+ }
279
+
280
+ onPrivateAccountUpdate(
281
+ accountId: string,
282
+ exchange: Exchange,
283
+ update: RawAccountUpdate,
284
+ ): void {
285
+ const record = this.getOrCreateRecord(accountId, exchange);
286
+ if (!record.subscribed) {
287
+ return;
288
+ }
289
+
290
+ const previous =
291
+ record.snapshot ?? this.createEmptySnapshot(accountId, exchange);
292
+ const balances = { ...previous.balances };
293
+ const positions = new Map(
294
+ previous.positions.map((position) => [
295
+ positionKey(position.symbol, position.side),
296
+ position,
297
+ ]),
298
+ );
299
+ let risk = previous.risk;
300
+
301
+ for (const balance of update.balances ?? []) {
302
+ const nextBalance = this.createBalance(
303
+ accountId,
304
+ exchange,
305
+ balance,
306
+ balances[balance.asset],
307
+ );
308
+ balances[balance.asset] = nextBalance;
309
+ this.accountBus.publish({
310
+ type: "balance.updated",
311
+ accountId,
312
+ exchange,
313
+ asset: balance.asset,
314
+ snapshot: nextBalance,
315
+ ts: this.context.now(),
316
+ });
317
+ }
318
+
319
+ for (const position of update.positions ?? []) {
320
+ const key = positionKey(position.symbol, position.side);
321
+ const nextPosition = this.createPosition(
322
+ accountId,
323
+ exchange,
324
+ position,
325
+ positions.get(key),
326
+ );
327
+
328
+ if (nextPosition.size.isZero()) {
329
+ positions.delete(key);
330
+ } else {
331
+ positions.set(key, nextPosition);
332
+ }
333
+
334
+ this.accountBus.publish({
335
+ type: "position.updated",
336
+ accountId,
337
+ exchange,
338
+ symbol: position.symbol,
339
+ snapshot: nextPosition,
340
+ ts: this.context.now(),
341
+ });
342
+ }
343
+
344
+ if (update.risk) {
345
+ risk = this.createRisk(accountId, exchange, update.risk, previous.risk);
346
+ this.accountBus.publish({
347
+ type: "risk.updated",
348
+ accountId,
349
+ exchange,
350
+ snapshot: risk,
351
+ ts: this.context.now(),
352
+ });
353
+ }
354
+
355
+ record.snapshot = {
356
+ accountId,
357
+ exchange,
358
+ balances,
359
+ positions: [...positions.values()],
360
+ risk,
361
+ exchangeTs: update.exchangeTs ?? previous.exchangeTs,
362
+ receivedAt: update.receivedAt,
363
+ updatedAt: update.receivedAt,
364
+ };
365
+ record.status = {
366
+ ...record.status,
367
+ activity: "active",
368
+ ready: true,
369
+ runtimeStatus: "healthy",
370
+ reason: undefined,
371
+ lastReceivedAt: update.receivedAt,
372
+ lastReadyAt: update.receivedAt,
373
+ inactiveSince: undefined,
374
+ };
375
+ this.publishStatus(record);
376
+ }
377
+
378
+ onPrivateAccountStreamState(
379
+ accountId: string,
380
+ exchange: Exchange,
381
+ state: PrivateSubscriptionState,
382
+ ): void {
383
+ const record = this.getOrCreateRecord(accountId, exchange);
384
+ if (!record.subscribed) {
385
+ return;
386
+ }
387
+
388
+ record.status = {
389
+ ...record.status,
390
+ activity: "active",
391
+ ready: state.ready,
392
+ runtimeStatus: state.runtimeStatus,
393
+ reason: state.reason,
394
+ lastReceivedAt: state.lastReceivedAt ?? record.status.lastReceivedAt,
395
+ lastReadyAt: state.lastReadyAt ?? record.status.lastReadyAt,
396
+ inactiveSince: undefined,
397
+ };
398
+ this.publishStatus(record);
399
+ }
400
+
401
+ // --- HealthReporter ---
402
+
403
+ getStatuses(): AccountDataStatus[] {
404
+ return [...this.records.values()]
405
+ .map((record) => cloneAccountStatus(record.status))
406
+ .sort((left, right) =>
407
+ `${left.exchange}:${left.accountId}`.localeCompare(
408
+ `${right.exchange}:${right.accountId}`,
409
+ ),
410
+ );
411
+ }
412
+
413
+ // --- Internal helpers ---
414
+
415
+ private getOrCreateRecord(
416
+ accountId: string,
417
+ exchange: Exchange,
418
+ ): AccountRecord {
419
+ const existing = this.records.get(accountId);
420
+ if (existing) {
421
+ return existing;
422
+ }
423
+
424
+ const record: AccountRecord = {
425
+ accountId,
426
+ exchange,
427
+ subscribed: false,
428
+ status: this.createStatus(accountId, exchange, "inactive"),
429
+ };
430
+
431
+ this.records.set(accountId, record);
432
+ return record;
433
+ }
434
+
435
+ private createStatus(
436
+ accountId: string,
437
+ exchange: Exchange,
438
+ activity: "active" | "inactive",
439
+ ): AccountDataStatus {
440
+ return {
441
+ accountId,
442
+ exchange,
443
+ activity,
444
+ ready: false,
445
+ runtimeStatus: activity === "active" ? "bootstrap_pending" : "stopped",
446
+ };
447
+ }
448
+
449
+ private createBootstrapSnapshot(
450
+ accountId: string,
451
+ exchange: Exchange,
452
+ bootstrap: RawAccountBootstrap,
453
+ ): AccountSnapshot {
454
+ const balances = Object.fromEntries(
455
+ bootstrap.balances.map((balance) => [
456
+ balance.asset,
457
+ this.createBalance(accountId, exchange, balance),
458
+ ]),
459
+ );
460
+ const positions = bootstrap.positions
461
+ .map((position) => this.createPosition(accountId, exchange, position))
462
+ .filter((position) => !position.size.isZero());
463
+ const risk = bootstrap.risk
464
+ ? this.createRisk(accountId, exchange, bootstrap.risk)
465
+ : undefined;
466
+
467
+ return {
468
+ accountId,
469
+ exchange,
470
+ balances,
471
+ positions,
472
+ risk,
473
+ exchangeTs: bootstrap.exchangeTs,
474
+ receivedAt: bootstrap.receivedAt,
475
+ updatedAt: bootstrap.receivedAt,
476
+ };
477
+ }
478
+
479
+ private createEmptySnapshot(
480
+ accountId: string,
481
+ exchange: Exchange,
482
+ ): AccountSnapshot {
483
+ const now = this.context.now();
484
+ return {
485
+ accountId,
486
+ exchange,
487
+ balances: {},
488
+ positions: [],
489
+ receivedAt: now,
490
+ updatedAt: now,
491
+ };
492
+ }
493
+
494
+ private createBalance(
495
+ accountId: string,
496
+ exchange: Exchange,
497
+ input: RawBalanceUpdate,
498
+ previous?: BalanceSnapshot,
499
+ ): BalanceSnapshot {
500
+ const previousFree = previous?.free ?? new BigNumber(0);
501
+ const previousUsed = previous?.used ?? new BigNumber(0);
502
+ const previousTotal = previous?.total ?? previousFree.plus(previousUsed);
503
+ const free = getBigNumber(input.free, previousFree);
504
+ const total = getBigNumber(input.total, previousTotal);
505
+ const used =
506
+ input.used !== undefined
507
+ ? new BigNumber(input.used)
508
+ : input.total !== undefined || input.free !== undefined
509
+ ? total.minus(free)
510
+ : previousUsed;
511
+
512
+ return {
513
+ accountId,
514
+ exchange,
515
+ asset: input.asset,
516
+ free,
517
+ used,
518
+ total,
519
+ exchangeTs: input.exchangeTs,
520
+ receivedAt: input.receivedAt,
521
+ updatedAt: input.receivedAt,
522
+ seq: (previous?.seq ?? 0) + 1,
523
+ };
524
+ }
525
+
526
+ private createPosition(
527
+ accountId: string,
528
+ exchange: Exchange,
529
+ input: RawPositionUpdate,
530
+ previous?: PositionSnapshot,
531
+ ): PositionSnapshot {
532
+ return {
533
+ accountId,
534
+ exchange,
535
+ symbol: input.symbol,
536
+ side: input.side,
537
+ size: new BigNumber(input.size),
538
+ entryPrice:
539
+ input.entryPrice === undefined
540
+ ? previous?.entryPrice
541
+ : new BigNumber(input.entryPrice),
542
+ markPrice:
543
+ input.markPrice === undefined
544
+ ? previous?.markPrice
545
+ : new BigNumber(input.markPrice),
546
+ unrealizedPnl:
547
+ input.unrealizedPnl === undefined
548
+ ? previous?.unrealizedPnl
549
+ : new BigNumber(input.unrealizedPnl),
550
+ leverage:
551
+ input.leverage === undefined
552
+ ? previous?.leverage
553
+ : new BigNumber(input.leverage),
554
+ liquidationPrice:
555
+ input.liquidationPrice === undefined
556
+ ? previous?.liquidationPrice
557
+ : new BigNumber(input.liquidationPrice),
558
+ exchangeTs: input.exchangeTs,
559
+ receivedAt: input.receivedAt,
560
+ updatedAt: input.receivedAt,
561
+ seq: (previous?.seq ?? 0) + 1,
562
+ };
563
+ }
564
+
565
+ private createRisk(
566
+ accountId: string,
567
+ exchange: Exchange,
568
+ input: RawRiskUpdate,
569
+ previous?: RiskSnapshot,
570
+ ): RiskSnapshot {
571
+ return {
572
+ accountId,
573
+ exchange,
574
+ equity:
575
+ input.equity === undefined
576
+ ? previous?.equity
577
+ : new BigNumber(input.equity),
578
+ marginRatio:
579
+ input.marginRatio === undefined
580
+ ? previous?.marginRatio
581
+ : new BigNumber(input.marginRatio),
582
+ initialMargin:
583
+ input.initialMargin === undefined
584
+ ? previous?.initialMargin
585
+ : new BigNumber(input.initialMargin),
586
+ maintenanceMargin:
587
+ input.maintenanceMargin === undefined
588
+ ? previous?.maintenanceMargin
589
+ : new BigNumber(input.maintenanceMargin),
590
+ exchangeTs: input.exchangeTs,
591
+ receivedAt: input.receivedAt,
592
+ updatedAt: input.receivedAt,
593
+ seq: (previous?.seq ?? 0) + 1,
594
+ };
595
+ }
596
+
597
+ private publishStatus(record: AccountRecord): void {
598
+ const event: AccountStatusChangedEvent = {
599
+ type: "account.status_changed",
600
+ accountId: record.accountId,
601
+ exchange: record.exchange,
602
+ status: cloneAccountStatus(record.status),
603
+ ts: this.context.now(),
604
+ };
605
+
606
+ this.accountStatusBus.publish(event);
607
+ this.context.publishHealthEvent(event);
608
+ }
609
+ }