@imbingox/acex 0.1.0-beta.1 → 0.1.0-beta.3
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 +664 -7
- package/package.json +26 -2
- package/src/adapters/binance/market-catalog.ts +16 -9
- package/src/adapters/binance/private-adapter.ts +833 -0
- package/src/adapters/types.ts +145 -1
- package/src/client/context.ts +63 -0
- package/src/client/private-subscription-coordinator.ts +512 -0
- package/src/client/runtime.ts +129 -2
- package/src/errors.ts +7 -1
- package/src/index.ts +1 -0
- package/src/internal/filters.ts +12 -14
- package/src/internal/managed-websocket.ts +24 -2
- package/src/managers/account-manager.ts +346 -52
- package/src/managers/market-manager.ts +21 -10
- package/src/managers/order-manager.ts +427 -46
- package/src/types/account.ts +16 -19
- package/src/types/market.ts +16 -14
- package/src/types/order.ts +47 -12
- package/src/types/shared.ts +15 -0
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type AcexErrorCode =
|
|
2
2
|
| "ACCOUNT_ALREADY_EXISTS"
|
|
3
|
+
| "ACCOUNT_BOOTSTRAP_FAILED"
|
|
3
4
|
| "ACCOUNT_NOT_FOUND"
|
|
4
5
|
| "CLIENT_NOT_STARTED"
|
|
5
6
|
| "CREDENTIALS_MISSING"
|
|
@@ -7,7 +8,12 @@ export type AcexErrorCode =
|
|
|
7
8
|
| "MARKET_CATALOG_LOAD_FAILED"
|
|
8
9
|
| "MARKET_INACTIVE"
|
|
9
10
|
| "MARKET_NOT_FOUND"
|
|
10
|
-
| "MARKET_STREAM_TIMEOUT"
|
|
11
|
+
| "MARKET_STREAM_TIMEOUT"
|
|
12
|
+
| "ORDER_BOOTSTRAP_FAILED"
|
|
13
|
+
| "ORDER_CANCEL_ALL_FAILED"
|
|
14
|
+
| "ORDER_CANCEL_FAILED"
|
|
15
|
+
| "ORDER_CREATE_FAILED"
|
|
16
|
+
| "ORDER_INPUT_INVALID";
|
|
11
17
|
|
|
12
18
|
export class AcexError extends Error {
|
|
13
19
|
readonly code: AcexErrorCode;
|
package/src/index.ts
CHANGED
package/src/internal/filters.ts
CHANGED
|
@@ -95,24 +95,22 @@ export function matchesHealthFilter(
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
if (
|
|
99
|
-
"exchange" in event
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
) {
|
|
103
|
-
return false;
|
|
98
|
+
if (filter.exchange) {
|
|
99
|
+
if (!("exchange" in event) || event.exchange !== filter.exchange) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
if (
|
|
107
|
-
"accountId" in event
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
) {
|
|
111
|
-
return false;
|
|
104
|
+
if (filter.accountId) {
|
|
105
|
+
if (!("accountId" in event) || event.accountId !== filter.accountId) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
if (
|
|
115
|
-
|
|
110
|
+
if (filter.symbol) {
|
|
111
|
+
if (!("symbol" in event) || event.symbol !== filter.symbol) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
return true;
|
|
@@ -11,14 +11,17 @@ export interface ManagedWebSocketReconnectOptions {
|
|
|
11
11
|
initialDelayMs: number;
|
|
12
12
|
maxDelayMs: number;
|
|
13
13
|
backoffMultiplier?: number;
|
|
14
|
+
reconnectWithoutMessages?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface ManagedWebSocketOptions<TMessage> {
|
|
17
18
|
url: string;
|
|
18
19
|
initialMessageTimeoutMs: number;
|
|
20
|
+
readyWhen?: "message" | "open";
|
|
19
21
|
parseMessage(data: string): TMessage | undefined;
|
|
20
22
|
onMessage(message: TMessage, receivedAt: number): void;
|
|
21
23
|
onUnexpectedClose(event: CloseEvent): void;
|
|
24
|
+
onOpen?(): void;
|
|
22
25
|
onError?(event: Event): void;
|
|
23
26
|
messageWatchdog?: ManagedWebSocketWatchdogOptions;
|
|
24
27
|
reconnect?: ManagedWebSocketReconnectOptions;
|
|
@@ -52,6 +55,7 @@ export function createManagedWebSocket<TMessage>(
|
|
|
52
55
|
const messageWatchdog = options.messageWatchdog;
|
|
53
56
|
const reconnect = options.reconnect;
|
|
54
57
|
const reconnectMultiplier = reconnect?.backoffMultiplier ?? 2;
|
|
58
|
+
const readyWhen = options.readyWhen ?? "message";
|
|
55
59
|
|
|
56
60
|
let closed = false;
|
|
57
61
|
let staleNotified = false;
|
|
@@ -131,7 +135,12 @@ export function createManagedWebSocket<TMessage>(
|
|
|
131
135
|
};
|
|
132
136
|
|
|
133
137
|
const scheduleReconnect = () => {
|
|
134
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
closed ||
|
|
140
|
+
!reconnect ||
|
|
141
|
+
reconnectTimeout ||
|
|
142
|
+
(!hasMessage && !reconnect.reconnectWithoutMessages)
|
|
143
|
+
) {
|
|
135
144
|
return;
|
|
136
145
|
}
|
|
137
146
|
|
|
@@ -171,6 +180,17 @@ export function createManagedWebSocket<TMessage>(
|
|
|
171
180
|
scheduleStaleTimeout();
|
|
172
181
|
}
|
|
173
182
|
|
|
183
|
+
socket.addEventListener("open", () => {
|
|
184
|
+
if (closed || activeSocket !== socket) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
options.onOpen?.();
|
|
189
|
+
if (readyWhen === "open") {
|
|
190
|
+
resolveIfPending();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
174
194
|
socket.addEventListener("message", (event) => {
|
|
175
195
|
if (closed || activeSocket !== socket || typeof event.data !== "string") {
|
|
176
196
|
return;
|
|
@@ -206,7 +226,9 @@ export function createManagedWebSocket<TMessage>(
|
|
|
206
226
|
scheduleStaleTimeout();
|
|
207
227
|
}
|
|
208
228
|
options.onMessage(parsed, lastMessageAt);
|
|
209
|
-
|
|
229
|
+
if (readyWhen === "message") {
|
|
230
|
+
resolveIfPending();
|
|
231
|
+
}
|
|
210
232
|
});
|
|
211
233
|
|
|
212
234
|
socket.addEventListener("error", (event) => {
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
2
|
+
import type {
|
|
3
|
+
RawAccountBootstrap,
|
|
4
|
+
RawAccountUpdate,
|
|
5
|
+
RawBalanceUpdate,
|
|
6
|
+
RawPositionUpdate,
|
|
7
|
+
RawRiskUpdate,
|
|
8
|
+
} from "../adapters/types.ts";
|
|
1
9
|
import type {
|
|
2
10
|
AccountAwareManager,
|
|
3
11
|
ClientContext,
|
|
4
12
|
HealthReporter,
|
|
5
13
|
ManagerLifecycle,
|
|
14
|
+
PrivateAccountDataConsumer,
|
|
15
|
+
PrivateSubscriptionState,
|
|
6
16
|
} from "../client/context.ts";
|
|
7
17
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
8
18
|
import { matchesAccountFilter } from "../internal/filters.ts";
|
|
@@ -35,12 +45,24 @@ function cloneAccountStatus(status: AccountDataStatus): AccountDataStatus {
|
|
|
35
45
|
return { ...status };
|
|
36
46
|
}
|
|
37
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
|
+
|
|
38
59
|
export class AccountManagerImpl
|
|
39
60
|
implements
|
|
40
61
|
AccountManager,
|
|
41
62
|
ManagerLifecycle,
|
|
42
63
|
AccountAwareManager,
|
|
43
|
-
HealthReporter<AccountDataStatus
|
|
64
|
+
HealthReporter<AccountDataStatus>,
|
|
65
|
+
PrivateAccountDataConsumer
|
|
44
66
|
{
|
|
45
67
|
readonly events: AccountEventStreams;
|
|
46
68
|
|
|
@@ -84,28 +106,13 @@ export class AccountManagerImpl
|
|
|
84
106
|
|
|
85
107
|
const record = this.getOrCreateRecord(input.accountId, account.exchange);
|
|
86
108
|
record.subscribed = true;
|
|
87
|
-
record.snapshot ??= this.createEmptySnapshot(
|
|
88
|
-
input.accountId,
|
|
89
|
-
account.exchange,
|
|
90
|
-
);
|
|
91
|
-
record.status = {
|
|
92
|
-
...this.createStatus(input.accountId, account.exchange, "active"),
|
|
93
|
-
ready: true,
|
|
94
|
-
runtimeStatus: "healthy",
|
|
95
|
-
lastReceivedAt: record.snapshot.updatedAt,
|
|
96
|
-
lastReadyAt: record.snapshot.updatedAt,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const event: AccountSnapshotReplacedEvent = {
|
|
100
|
-
type: "account.snapshot_replaced",
|
|
101
|
-
accountId: record.accountId,
|
|
102
|
-
exchange: record.exchange,
|
|
103
|
-
snapshot: record.snapshot,
|
|
104
|
-
ts: this.context.now(),
|
|
105
|
-
};
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
try {
|
|
111
|
+
await this.context.subscribePrivateAccountFeed(input.accountId);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
record.subscribed = false;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
async unsubscribeAccount(input: UnsubscribeAccountInput): Promise<void> {
|
|
@@ -114,11 +121,13 @@ export class AccountManagerImpl
|
|
|
114
121
|
return;
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
this.context.unsubscribePrivateAccountFeed(input.accountId);
|
|
117
125
|
record.subscribed = false;
|
|
118
126
|
record.status = {
|
|
119
127
|
...record.status,
|
|
120
128
|
activity: "inactive",
|
|
121
129
|
runtimeStatus: "stopped",
|
|
130
|
+
reason: undefined,
|
|
122
131
|
inactiveSince: this.context.now(),
|
|
123
132
|
};
|
|
124
133
|
this.publishStatus(record);
|
|
@@ -162,31 +171,7 @@ export class AccountManagerImpl
|
|
|
162
171
|
|
|
163
172
|
// --- ManagerLifecycle ---
|
|
164
173
|
|
|
165
|
-
onClientStarted(): void {
|
|
166
|
-
const now = this.context.now();
|
|
167
|
-
|
|
168
|
-
for (const [accountId, record] of this.records) {
|
|
169
|
-
if (!record.subscribed) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const account = this.context.getRegisteredAccount(accountId);
|
|
174
|
-
const creds = account.credentials;
|
|
175
|
-
if (!creds?.apiKey || !creds.secret) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
record.snapshot ??= this.createEmptySnapshot(accountId, account.exchange);
|
|
180
|
-
record.status = {
|
|
181
|
-
...this.createStatus(accountId, account.exchange, "active"),
|
|
182
|
-
ready: true,
|
|
183
|
-
runtimeStatus: "healthy",
|
|
184
|
-
lastReceivedAt: now,
|
|
185
|
-
lastReadyAt: record.snapshot.updatedAt,
|
|
186
|
-
};
|
|
187
|
-
this.publishStatus(record);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
174
|
+
onClientStarted(): void {}
|
|
190
175
|
|
|
191
176
|
onClientStopping(now: number): void {
|
|
192
177
|
for (const record of this.records.values()) {
|
|
@@ -198,6 +183,7 @@ export class AccountManagerImpl
|
|
|
198
183
|
...record.status,
|
|
199
184
|
activity: "inactive",
|
|
200
185
|
runtimeStatus: "stopped",
|
|
186
|
+
reason: undefined,
|
|
201
187
|
inactiveSince: now,
|
|
202
188
|
};
|
|
203
189
|
this.publishStatus(record);
|
|
@@ -217,6 +203,7 @@ export class AccountManagerImpl
|
|
|
217
203
|
...record.status,
|
|
218
204
|
activity: "inactive",
|
|
219
205
|
runtimeStatus: "stopped",
|
|
206
|
+
reason: undefined,
|
|
220
207
|
inactiveSince: now,
|
|
221
208
|
};
|
|
222
209
|
this.publishStatus(record);
|
|
@@ -229,11 +216,185 @@ export class AccountManagerImpl
|
|
|
229
216
|
return;
|
|
230
217
|
}
|
|
231
218
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
};
|
|
237
398
|
this.publishStatus(record);
|
|
238
399
|
}
|
|
239
400
|
|
|
@@ -285,6 +446,36 @@ export class AccountManagerImpl
|
|
|
285
446
|
};
|
|
286
447
|
}
|
|
287
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
|
+
|
|
288
479
|
private createEmptySnapshot(
|
|
289
480
|
accountId: string,
|
|
290
481
|
exchange: Exchange,
|
|
@@ -300,6 +491,109 @@ export class AccountManagerImpl
|
|
|
300
491
|
};
|
|
301
492
|
}
|
|
302
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
|
+
|
|
303
597
|
private publishStatus(record: AccountRecord): void {
|
|
304
598
|
const event: AccountStatusChangedEvent = {
|
|
305
599
|
type: "account.status_changed",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
1
2
|
import type {
|
|
2
3
|
L1BookStreamCallbacks,
|
|
3
4
|
L1BookStreamOptions,
|
|
@@ -208,13 +209,23 @@ export class MarketManagerImpl
|
|
|
208
209
|
this.updateActivity(record);
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
getMarket(symbol: string): MarketDefinition | undefined {
|
|
212
|
-
const market = this.definitions.get(symbol);
|
|
212
|
+
getMarket(exchange: Exchange, symbol: string): MarketDefinition | undefined {
|
|
213
|
+
const market = this.definitions.get(marketKey({ exchange, symbol }));
|
|
213
214
|
return market ? cloneMarketDefinition(market) : undefined;
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
|
|
217
|
+
findMarkets(symbol: string): MarketDefinition[] {
|
|
217
218
|
return [...this.definitions.values()]
|
|
219
|
+
.filter((market) => market.symbol === symbol)
|
|
220
|
+
.map((market) => cloneMarketDefinition(market));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
listMarkets(exchange?: Exchange): MarketDefinition[] {
|
|
224
|
+
const values = [...this.definitions.values()];
|
|
225
|
+
const filtered = exchange
|
|
226
|
+
? values.filter((market) => market.exchange === exchange)
|
|
227
|
+
: values;
|
|
228
|
+
return filtered
|
|
218
229
|
.sort((left, right) => left.symbol.localeCompare(right.symbol))
|
|
219
230
|
.map((market) => cloneMarketDefinition(market));
|
|
220
231
|
}
|
|
@@ -319,7 +330,7 @@ export class MarketManagerImpl
|
|
|
319
330
|
this.definitions.clear();
|
|
320
331
|
|
|
321
332
|
for (const market of markets) {
|
|
322
|
-
this.definitions.set(market
|
|
333
|
+
this.definitions.set(marketKey(market), market);
|
|
323
334
|
}
|
|
324
335
|
} catch (error) {
|
|
325
336
|
const wrapped = new AcexError(
|
|
@@ -344,7 +355,7 @@ export class MarketManagerImpl
|
|
|
344
355
|
this.assertSupportedExchange(input.exchange);
|
|
345
356
|
await this.loadMarketCatalog();
|
|
346
357
|
|
|
347
|
-
const market = this.definitions.get(input
|
|
358
|
+
const market = this.definitions.get(marketKey(input));
|
|
348
359
|
if (!market) {
|
|
349
360
|
throw this.createError(
|
|
350
361
|
"MARKET_NOT_FOUND",
|
|
@@ -517,10 +528,10 @@ export class MarketManagerImpl
|
|
|
517
528
|
return {
|
|
518
529
|
exchange,
|
|
519
530
|
symbol,
|
|
520
|
-
bidPrice: input.bidPrice,
|
|
521
|
-
bidSize: input.bidSize,
|
|
522
|
-
askPrice: input.askPrice,
|
|
523
|
-
askSize: input.askSize,
|
|
531
|
+
bidPrice: new BigNumber(input.bidPrice),
|
|
532
|
+
bidSize: new BigNumber(input.bidSize),
|
|
533
|
+
askPrice: new BigNumber(input.askPrice),
|
|
534
|
+
askSize: new BigNumber(input.askSize),
|
|
524
535
|
exchangeTs: input.exchangeTs,
|
|
525
536
|
receivedAt: input.receivedAt,
|
|
526
537
|
updatedAt: input.receivedAt,
|
|
@@ -538,7 +549,7 @@ export class MarketManagerImpl
|
|
|
538
549
|
return {
|
|
539
550
|
exchange,
|
|
540
551
|
symbol,
|
|
541
|
-
fundingRate: previous?.fundingRate ??
|
|
552
|
+
fundingRate: previous?.fundingRate ?? new BigNumber(0),
|
|
542
553
|
nextFundingTime: previous?.nextFundingTime,
|
|
543
554
|
markPrice: previous?.markPrice,
|
|
544
555
|
indexPrice: previous?.indexPrice,
|