@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
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
2
|
+
import type { RawOrderUpdate } from "../adapters/types.ts";
|
|
1
3
|
import type {
|
|
2
4
|
AccountAwareManager,
|
|
3
5
|
ClientContext,
|
|
4
6
|
HealthReporter,
|
|
5
7
|
ManagerLifecycle,
|
|
8
|
+
PrivateOrderDataConsumer,
|
|
9
|
+
PrivateSubscriptionState,
|
|
6
10
|
} from "../client/context.ts";
|
|
11
|
+
import { AcexError } from "../errors.ts";
|
|
7
12
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
8
13
|
import { matchesOrderFilter } from "../internal/filters.ts";
|
|
9
14
|
import type {
|
|
15
|
+
CancelAllOrdersInput,
|
|
16
|
+
CancelOrderInput,
|
|
17
|
+
CreateOrderInput,
|
|
10
18
|
Exchange,
|
|
11
19
|
GetOrderInput,
|
|
12
20
|
OrderDataStatus,
|
|
@@ -32,12 +40,28 @@ function cloneOrderStatus(status: OrderDataStatus): OrderDataStatus {
|
|
|
32
40
|
return { ...status };
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
function getOrderLookupKey(input: {
|
|
44
|
+
orderId?: string;
|
|
45
|
+
clientOrderId?: string;
|
|
46
|
+
}): string | undefined {
|
|
47
|
+
if (input.orderId) {
|
|
48
|
+
return `order:${input.orderId}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (input.clientOrderId) {
|
|
52
|
+
return `client:${input.clientOrderId}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
export class OrderManagerImpl
|
|
36
59
|
implements
|
|
37
60
|
OrderManager,
|
|
38
61
|
ManagerLifecycle,
|
|
39
62
|
AccountAwareManager,
|
|
40
|
-
HealthReporter<OrderDataStatus
|
|
63
|
+
HealthReporter<OrderDataStatus>,
|
|
64
|
+
PrivateOrderDataConsumer
|
|
41
65
|
{
|
|
42
66
|
readonly events: OrderEventStreams;
|
|
43
67
|
|
|
@@ -81,24 +105,13 @@ export class OrderManagerImpl
|
|
|
81
105
|
|
|
82
106
|
const record = this.getOrCreateRecord(input.accountId, account.exchange);
|
|
83
107
|
record.subscribed = true;
|
|
84
|
-
record.status = {
|
|
85
|
-
...this.createStatus(input.accountId, account.exchange, "active"),
|
|
86
|
-
ready: true,
|
|
87
|
-
runtimeStatus: "healthy",
|
|
88
|
-
lastReceivedAt: this.context.now(),
|
|
89
|
-
lastReadyAt: this.context.now(),
|
|
90
|
-
};
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
this.orderBus.publish(event);
|
|
101
|
-
this.publishStatus(record);
|
|
109
|
+
try {
|
|
110
|
+
await this.context.subscribePrivateOrderFeed(input.accountId);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
record.subscribed = false;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
async unsubscribeOrders(input: UnsubscribeOrdersInput): Promise<void> {
|
|
@@ -107,16 +120,90 @@ export class OrderManagerImpl
|
|
|
107
120
|
return;
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
this.context.unsubscribePrivateOrderFeed(input.accountId);
|
|
110
124
|
record.subscribed = false;
|
|
111
125
|
record.status = {
|
|
112
126
|
...record.status,
|
|
113
127
|
activity: "inactive",
|
|
114
128
|
runtimeStatus: "stopped",
|
|
129
|
+
reason: undefined,
|
|
115
130
|
inactiveSince: this.context.now(),
|
|
116
131
|
};
|
|
117
132
|
this.publishStatus(record);
|
|
118
133
|
}
|
|
119
134
|
|
|
135
|
+
async createOrder(input: CreateOrderInput): Promise<OrderSnapshot> {
|
|
136
|
+
this.context.assertStarted();
|
|
137
|
+
const account = this.context.getRegisteredAccount(input.accountId);
|
|
138
|
+
this.context.ensurePrivateCredentials(input.accountId);
|
|
139
|
+
this.validateCreateOrderInput(input, account.exchange);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const update = await this.context.createOrder(input);
|
|
143
|
+
return this.applyCommandUpdate(input.accountId, account.exchange, update);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw this.wrapCommandError(
|
|
146
|
+
"ORDER_CREATE_FAILED",
|
|
147
|
+
`Failed to create order for ${input.accountId}: ${input.symbol}`,
|
|
148
|
+
error,
|
|
149
|
+
{
|
|
150
|
+
accountId: input.accountId,
|
|
151
|
+
exchange: account.exchange,
|
|
152
|
+
symbol: input.symbol,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async cancelOrder(input: CancelOrderInput): Promise<OrderSnapshot> {
|
|
159
|
+
this.context.assertStarted();
|
|
160
|
+
const account = this.context.getRegisteredAccount(input.accountId);
|
|
161
|
+
this.context.ensurePrivateCredentials(input.accountId);
|
|
162
|
+
this.validateCancelOrderInput(input, account.exchange);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const update = await this.context.cancelOrder(input);
|
|
166
|
+
return this.applyCommandUpdate(input.accountId, account.exchange, update);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw this.wrapCommandError(
|
|
169
|
+
"ORDER_CANCEL_FAILED",
|
|
170
|
+
`Failed to cancel order for ${input.accountId}: ${input.symbol}`,
|
|
171
|
+
error,
|
|
172
|
+
{
|
|
173
|
+
accountId: input.accountId,
|
|
174
|
+
exchange: account.exchange,
|
|
175
|
+
symbol: input.symbol,
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async cancelAllOrders(input: CancelAllOrdersInput): Promise<OrderSnapshot[]> {
|
|
182
|
+
this.context.assertStarted();
|
|
183
|
+
const account = this.context.getRegisteredAccount(input.accountId);
|
|
184
|
+
this.context.ensurePrivateCredentials(input.accountId);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const updates = await this.context.cancelAllOrders(input);
|
|
188
|
+
return this.applyCommandUpdates(
|
|
189
|
+
input.accountId,
|
|
190
|
+
account.exchange,
|
|
191
|
+
updates,
|
|
192
|
+
);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw this.wrapCommandError(
|
|
195
|
+
"ORDER_CANCEL_ALL_FAILED",
|
|
196
|
+
`Failed to cancel all orders for ${input.accountId}: ${input.symbol}`,
|
|
197
|
+
error,
|
|
198
|
+
{
|
|
199
|
+
accountId: input.accountId,
|
|
200
|
+
exchange: account.exchange,
|
|
201
|
+
symbol: input.symbol,
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
120
207
|
getOrder(input: GetOrderInput): OrderSnapshot | undefined {
|
|
121
208
|
const record = this.records.get(input.accountId);
|
|
122
209
|
if (!record) {
|
|
@@ -167,30 +254,7 @@ export class OrderManagerImpl
|
|
|
167
254
|
|
|
168
255
|
// --- ManagerLifecycle ---
|
|
169
256
|
|
|
170
|
-
onClientStarted(): void {
|
|
171
|
-
const now = this.context.now();
|
|
172
|
-
|
|
173
|
-
for (const [accountId, record] of this.records) {
|
|
174
|
-
if (!record.subscribed) {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const account = this.context.getRegisteredAccount(accountId);
|
|
179
|
-
const creds = account.credentials;
|
|
180
|
-
if (!creds?.apiKey || !creds.secret) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
record.status = {
|
|
185
|
-
...this.createStatus(accountId, account.exchange, "active"),
|
|
186
|
-
ready: true,
|
|
187
|
-
runtimeStatus: "healthy",
|
|
188
|
-
lastReceivedAt: now,
|
|
189
|
-
lastReadyAt: now,
|
|
190
|
-
};
|
|
191
|
-
this.publishStatus(record);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
257
|
+
onClientStarted(): void {}
|
|
194
258
|
|
|
195
259
|
onClientStopping(now: number): void {
|
|
196
260
|
for (const record of this.records.values()) {
|
|
@@ -202,6 +266,7 @@ export class OrderManagerImpl
|
|
|
202
266
|
...record.status,
|
|
203
267
|
activity: "inactive",
|
|
204
268
|
runtimeStatus: "stopped",
|
|
269
|
+
reason: undefined,
|
|
205
270
|
inactiveSince: now,
|
|
206
271
|
};
|
|
207
272
|
this.publishStatus(record);
|
|
@@ -221,6 +286,7 @@ export class OrderManagerImpl
|
|
|
221
286
|
...record.status,
|
|
222
287
|
activity: "inactive",
|
|
223
288
|
runtimeStatus: "stopped",
|
|
289
|
+
reason: undefined,
|
|
224
290
|
inactiveSince: now,
|
|
225
291
|
};
|
|
226
292
|
this.publishStatus(record);
|
|
@@ -233,10 +299,144 @@ export class OrderManagerImpl
|
|
|
233
299
|
return;
|
|
234
300
|
}
|
|
235
301
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
302
|
+
this.onPrivateOrderPending(accountId, exchange);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// --- PrivateOrderDataConsumer ---
|
|
306
|
+
|
|
307
|
+
onPrivateOrderPending(accountId: string, exchange: Exchange): void {
|
|
308
|
+
const record = this.getOrCreateRecord(accountId, exchange);
|
|
309
|
+
if (!record.subscribed) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
record.status = {
|
|
314
|
+
...this.createStatus(accountId, exchange, "active"),
|
|
315
|
+
ready: record.snapshots.size > 0,
|
|
316
|
+
runtimeStatus: "bootstrap_pending",
|
|
317
|
+
reason: undefined,
|
|
318
|
+
lastReceivedAt: record.status.lastReceivedAt,
|
|
319
|
+
lastReadyAt: record.status.lastReadyAt,
|
|
320
|
+
inactiveSince: undefined,
|
|
321
|
+
};
|
|
322
|
+
this.publishStatus(record);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
onPrivateOrderBootstrap(
|
|
326
|
+
accountId: string,
|
|
327
|
+
exchange: Exchange,
|
|
328
|
+
snapshots: RawOrderUpdate[],
|
|
329
|
+
): void {
|
|
330
|
+
const record = this.getOrCreateRecord(accountId, exchange);
|
|
331
|
+
if (!record.subscribed) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const nextSnapshots = new Map<string, OrderSnapshot>();
|
|
336
|
+
for (const update of snapshots) {
|
|
337
|
+
const snapshot = this.createSnapshot(
|
|
338
|
+
accountId,
|
|
339
|
+
exchange,
|
|
340
|
+
update,
|
|
341
|
+
this.getExistingSnapshot(record, update),
|
|
342
|
+
);
|
|
343
|
+
this.setSnapshot(nextSnapshots, snapshot);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
record.snapshots = nextSnapshots;
|
|
347
|
+
const orderedSnapshots = [...record.snapshots.values()];
|
|
348
|
+
const latestTs = orderedSnapshots.reduce(
|
|
349
|
+
(max, snapshot) => Math.max(max, snapshot.updatedAt),
|
|
350
|
+
0,
|
|
351
|
+
);
|
|
352
|
+
record.status = {
|
|
353
|
+
...record.status,
|
|
354
|
+
activity: "active",
|
|
355
|
+
ready: true,
|
|
356
|
+
runtimeStatus: "healthy",
|
|
357
|
+
reason: undefined,
|
|
358
|
+
lastReceivedAt: latestTs || record.status.lastReceivedAt,
|
|
359
|
+
lastReadyAt: latestTs || this.context.now(),
|
|
360
|
+
inactiveSince: undefined,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const event: OrderSnapshotReplacedEvent = {
|
|
364
|
+
type: "order.snapshot_replaced",
|
|
365
|
+
accountId,
|
|
366
|
+
exchange,
|
|
367
|
+
snapshot: orderedSnapshots,
|
|
368
|
+
ts: this.context.now(),
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
this.orderBus.publish(event);
|
|
372
|
+
this.publishStatus(record);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
onPrivateOrderUpdate(
|
|
376
|
+
accountId: string,
|
|
377
|
+
exchange: Exchange,
|
|
378
|
+
update: RawOrderUpdate,
|
|
379
|
+
): void {
|
|
380
|
+
const record = this.getOrCreateRecord(accountId, exchange);
|
|
381
|
+
if (!record.subscribed) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const previous = this.getExistingSnapshot(record, update);
|
|
386
|
+
const snapshot = this.createSnapshot(accountId, exchange, update, previous);
|
|
387
|
+
this.setSnapshot(record.snapshots, snapshot);
|
|
388
|
+
|
|
389
|
+
const eventType =
|
|
390
|
+
snapshot.status === "filled"
|
|
391
|
+
? "order.filled"
|
|
392
|
+
: snapshot.status === "rejected"
|
|
393
|
+
? "order.rejected"
|
|
394
|
+
: snapshot.status === "canceled" || snapshot.status === "expired"
|
|
395
|
+
? "order.canceled"
|
|
396
|
+
: "order.updated";
|
|
397
|
+
|
|
398
|
+
this.orderBus.publish({
|
|
399
|
+
type: eventType,
|
|
400
|
+
accountId,
|
|
401
|
+
exchange,
|
|
402
|
+
symbol: snapshot.symbol,
|
|
403
|
+
snapshot,
|
|
404
|
+
ts: this.context.now(),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
record.status = {
|
|
408
|
+
...record.status,
|
|
409
|
+
activity: "active",
|
|
410
|
+
ready: true,
|
|
411
|
+
runtimeStatus: "healthy",
|
|
412
|
+
reason: undefined,
|
|
413
|
+
lastReceivedAt: snapshot.receivedAt,
|
|
414
|
+
lastReadyAt: snapshot.updatedAt,
|
|
415
|
+
inactiveSince: undefined,
|
|
416
|
+
};
|
|
417
|
+
this.publishStatus(record);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
onPrivateOrderStreamState(
|
|
421
|
+
accountId: string,
|
|
422
|
+
exchange: Exchange,
|
|
423
|
+
state: PrivateSubscriptionState,
|
|
424
|
+
): void {
|
|
425
|
+
const record = this.getOrCreateRecord(accountId, exchange);
|
|
426
|
+
if (!record.subscribed) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
record.status = {
|
|
431
|
+
...record.status,
|
|
432
|
+
activity: "active",
|
|
433
|
+
ready: state.ready,
|
|
434
|
+
runtimeStatus: state.runtimeStatus,
|
|
435
|
+
reason: state.reason,
|
|
436
|
+
lastReceivedAt: state.lastReceivedAt ?? record.status.lastReceivedAt,
|
|
437
|
+
lastReadyAt: state.lastReadyAt ?? record.status.lastReadyAt,
|
|
438
|
+
inactiveSince: undefined,
|
|
439
|
+
};
|
|
240
440
|
this.publishStatus(record);
|
|
241
441
|
}
|
|
242
442
|
|
|
@@ -289,6 +489,86 @@ export class OrderManagerImpl
|
|
|
289
489
|
};
|
|
290
490
|
}
|
|
291
491
|
|
|
492
|
+
private getExistingSnapshot(
|
|
493
|
+
record: OrderRecord,
|
|
494
|
+
update: { orderId?: string; clientOrderId?: string },
|
|
495
|
+
): OrderSnapshot | undefined {
|
|
496
|
+
for (const snapshot of record.snapshots.values()) {
|
|
497
|
+
if (update.orderId && snapshot.orderId === update.orderId) {
|
|
498
|
+
return snapshot;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (
|
|
502
|
+
update.clientOrderId &&
|
|
503
|
+
snapshot.clientOrderId === update.clientOrderId
|
|
504
|
+
) {
|
|
505
|
+
return snapshot;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private setSnapshot(
|
|
513
|
+
snapshots: Map<string, OrderSnapshot>,
|
|
514
|
+
snapshot: OrderSnapshot,
|
|
515
|
+
): void {
|
|
516
|
+
const lookupKey =
|
|
517
|
+
getOrderLookupKey(snapshot) ??
|
|
518
|
+
getOrderLookupKey({
|
|
519
|
+
clientOrderId: snapshot.clientOrderId,
|
|
520
|
+
});
|
|
521
|
+
if (lookupKey) {
|
|
522
|
+
snapshots.set(lookupKey, snapshot);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private createSnapshot(
|
|
527
|
+
accountId: string,
|
|
528
|
+
exchange: Exchange,
|
|
529
|
+
input: RawOrderUpdate,
|
|
530
|
+
previous?: OrderSnapshot,
|
|
531
|
+
): OrderSnapshot {
|
|
532
|
+
const amount = new BigNumber(input.amount);
|
|
533
|
+
const filled = new BigNumber(input.filled);
|
|
534
|
+
const remaining =
|
|
535
|
+
input.remaining === undefined
|
|
536
|
+
? amount.minus(filled)
|
|
537
|
+
: new BigNumber(input.remaining);
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
accountId,
|
|
541
|
+
exchange,
|
|
542
|
+
orderId: input.orderId,
|
|
543
|
+
clientOrderId: input.clientOrderId,
|
|
544
|
+
symbol: input.symbol,
|
|
545
|
+
side: input.side,
|
|
546
|
+
type: input.type,
|
|
547
|
+
status: input.status,
|
|
548
|
+
price:
|
|
549
|
+
input.price === undefined
|
|
550
|
+
? previous?.price
|
|
551
|
+
: new BigNumber(input.price),
|
|
552
|
+
triggerPrice:
|
|
553
|
+
input.triggerPrice === undefined
|
|
554
|
+
? previous?.triggerPrice
|
|
555
|
+
: new BigNumber(input.triggerPrice),
|
|
556
|
+
amount,
|
|
557
|
+
filled,
|
|
558
|
+
remaining,
|
|
559
|
+
reduceOnly: input.reduceOnly ?? previous?.reduceOnly,
|
|
560
|
+
positionSide: input.positionSide ?? previous?.positionSide,
|
|
561
|
+
avgFillPrice:
|
|
562
|
+
input.avgFillPrice === undefined
|
|
563
|
+
? previous?.avgFillPrice
|
|
564
|
+
: new BigNumber(input.avgFillPrice),
|
|
565
|
+
exchangeTs: input.exchangeTs,
|
|
566
|
+
receivedAt: input.receivedAt,
|
|
567
|
+
updatedAt: input.receivedAt,
|
|
568
|
+
seq: (previous?.seq ?? 0) + 1,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
292
572
|
private publishStatus(record: OrderRecord): void {
|
|
293
573
|
const event: OrderStatusChangedEvent = {
|
|
294
574
|
type: "order.status_changed",
|
|
@@ -301,4 +581,105 @@ export class OrderManagerImpl
|
|
|
301
581
|
this.orderStatusBus.publish(event);
|
|
302
582
|
this.context.publishHealthEvent(event);
|
|
303
583
|
}
|
|
584
|
+
|
|
585
|
+
private validateCreateOrderInput(
|
|
586
|
+
input: CreateOrderInput,
|
|
587
|
+
exchange: Exchange,
|
|
588
|
+
): void {
|
|
589
|
+
if (input.type === "limit" && !input.price) {
|
|
590
|
+
throw this.createError(
|
|
591
|
+
"ORDER_INPUT_INVALID",
|
|
592
|
+
`Limit orders require price: ${input.accountId}`,
|
|
593
|
+
{
|
|
594
|
+
accountId: input.accountId,
|
|
595
|
+
exchange,
|
|
596
|
+
symbol: input.symbol,
|
|
597
|
+
},
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private validateCancelOrderInput(
|
|
603
|
+
input: CancelOrderInput,
|
|
604
|
+
exchange: Exchange,
|
|
605
|
+
): void {
|
|
606
|
+
if (input.orderId || input.clientOrderId) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
throw this.createError(
|
|
611
|
+
"ORDER_INPUT_INVALID",
|
|
612
|
+
`cancelOrder requires orderId or clientOrderId: ${input.accountId}`,
|
|
613
|
+
{
|
|
614
|
+
accountId: input.accountId,
|
|
615
|
+
exchange,
|
|
616
|
+
symbol: input.symbol,
|
|
617
|
+
},
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private applyCommandUpdate(
|
|
622
|
+
accountId: string,
|
|
623
|
+
exchange: Exchange,
|
|
624
|
+
update: RawOrderUpdate,
|
|
625
|
+
): OrderSnapshot {
|
|
626
|
+
const record = this.getOrCreateRecord(accountId, exchange);
|
|
627
|
+
const previous = this.getExistingSnapshot(record, update);
|
|
628
|
+
const snapshot = this.createSnapshot(accountId, exchange, update, previous);
|
|
629
|
+
this.setSnapshot(record.snapshots, snapshot);
|
|
630
|
+
return snapshot;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private applyCommandUpdates(
|
|
634
|
+
accountId: string,
|
|
635
|
+
exchange: Exchange,
|
|
636
|
+
updates: RawOrderUpdate[],
|
|
637
|
+
): OrderSnapshot[] {
|
|
638
|
+
return updates.map((update) =>
|
|
639
|
+
this.applyCommandUpdate(accountId, exchange, update),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private createError(
|
|
644
|
+
code:
|
|
645
|
+
| "ORDER_CANCEL_ALL_FAILED"
|
|
646
|
+
| "ORDER_CANCEL_FAILED"
|
|
647
|
+
| "ORDER_CREATE_FAILED"
|
|
648
|
+
| "ORDER_INPUT_INVALID",
|
|
649
|
+
message: string,
|
|
650
|
+
metadata: {
|
|
651
|
+
accountId: string;
|
|
652
|
+
exchange: Exchange;
|
|
653
|
+
symbol?: string;
|
|
654
|
+
},
|
|
655
|
+
): AcexError {
|
|
656
|
+
const error = new AcexError(code, message);
|
|
657
|
+
this.context.publishRuntimeError("order", error, metadata);
|
|
658
|
+
return error;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private wrapCommandError(
|
|
662
|
+
code:
|
|
663
|
+
| "ORDER_CANCEL_ALL_FAILED"
|
|
664
|
+
| "ORDER_CANCEL_FAILED"
|
|
665
|
+
| "ORDER_CREATE_FAILED",
|
|
666
|
+
message: string,
|
|
667
|
+
error: unknown,
|
|
668
|
+
metadata: {
|
|
669
|
+
accountId: string;
|
|
670
|
+
exchange: Exchange;
|
|
671
|
+
symbol: string;
|
|
672
|
+
},
|
|
673
|
+
): AcexError {
|
|
674
|
+
if (error instanceof AcexError) {
|
|
675
|
+
return error;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
this.context.publishRuntimeError(
|
|
679
|
+
"adapter",
|
|
680
|
+
error instanceof Error ? error : new Error(message),
|
|
681
|
+
metadata,
|
|
682
|
+
);
|
|
683
|
+
return new AcexError(code, message);
|
|
684
|
+
}
|
|
304
685
|
}
|
package/src/types/account.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type BigNumber from "bignumber.js";
|
|
1
2
|
import type {
|
|
2
3
|
Exchange,
|
|
4
|
+
PrivateRuntimeReason,
|
|
3
5
|
PrivateRuntimeStatus,
|
|
4
6
|
SubscriptionActivity,
|
|
5
7
|
} from "./shared.ts";
|
|
@@ -13,12 +15,7 @@ export interface AccountDataStatus {
|
|
|
13
15
|
lastReceivedAt?: number;
|
|
14
16
|
lastReadyAt?: number;
|
|
15
17
|
inactiveSince?: number;
|
|
16
|
-
reason?:
|
|
17
|
-
| "credentials_missing"
|
|
18
|
-
| "auth_failed"
|
|
19
|
-
| "ws_disconnected"
|
|
20
|
-
| "heartbeat_timeout"
|
|
21
|
-
| "reconciling";
|
|
18
|
+
reason?: PrivateRuntimeReason;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
export interface AccountStatusChangedEvent {
|
|
@@ -55,9 +52,9 @@ export interface BalanceSnapshot {
|
|
|
55
52
|
accountId: string;
|
|
56
53
|
exchange: Exchange;
|
|
57
54
|
asset: string;
|
|
58
|
-
free:
|
|
59
|
-
used:
|
|
60
|
-
total:
|
|
55
|
+
free: BigNumber;
|
|
56
|
+
used: BigNumber;
|
|
57
|
+
total: BigNumber;
|
|
61
58
|
exchangeTs?: number;
|
|
62
59
|
receivedAt: number;
|
|
63
60
|
updatedAt: number;
|
|
@@ -69,12 +66,12 @@ export interface PositionSnapshot {
|
|
|
69
66
|
exchange: Exchange;
|
|
70
67
|
symbol: string;
|
|
71
68
|
side: PositionSide;
|
|
72
|
-
size:
|
|
73
|
-
entryPrice?:
|
|
74
|
-
markPrice?:
|
|
75
|
-
unrealizedPnl?:
|
|
76
|
-
leverage?:
|
|
77
|
-
liquidationPrice?:
|
|
69
|
+
size: BigNumber;
|
|
70
|
+
entryPrice?: BigNumber;
|
|
71
|
+
markPrice?: BigNumber;
|
|
72
|
+
unrealizedPnl?: BigNumber;
|
|
73
|
+
leverage?: BigNumber;
|
|
74
|
+
liquidationPrice?: BigNumber;
|
|
78
75
|
exchangeTs?: number;
|
|
79
76
|
receivedAt: number;
|
|
80
77
|
updatedAt: number;
|
|
@@ -84,10 +81,10 @@ export interface PositionSnapshot {
|
|
|
84
81
|
export interface RiskSnapshot {
|
|
85
82
|
accountId: string;
|
|
86
83
|
exchange: Exchange;
|
|
87
|
-
equity?:
|
|
88
|
-
marginRatio?:
|
|
89
|
-
initialMargin?:
|
|
90
|
-
maintenanceMargin?:
|
|
84
|
+
equity?: BigNumber;
|
|
85
|
+
marginRatio?: BigNumber;
|
|
86
|
+
initialMargin?: BigNumber;
|
|
87
|
+
maintenanceMargin?: BigNumber;
|
|
91
88
|
exchangeTs?: number;
|
|
92
89
|
receivedAt: number;
|
|
93
90
|
updatedAt: number;
|
package/src/types/market.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type BigNumber from "bignumber.js";
|
|
1
2
|
import type {
|
|
2
3
|
Exchange,
|
|
3
4
|
MarketFreshness,
|
|
@@ -18,13 +19,13 @@ export interface MarketDefinition {
|
|
|
18
19
|
contract: boolean;
|
|
19
20
|
linear?: boolean;
|
|
20
21
|
inverse?: boolean;
|
|
21
|
-
contractSize?:
|
|
22
|
+
contractSize?: BigNumber;
|
|
22
23
|
pricePrecision: number;
|
|
23
24
|
amountPrecision: number;
|
|
24
|
-
priceStep:
|
|
25
|
-
amountStep:
|
|
26
|
-
minAmount?:
|
|
27
|
-
minNotional?:
|
|
25
|
+
priceStep: BigNumber;
|
|
26
|
+
amountStep: BigNumber;
|
|
27
|
+
minAmount?: BigNumber;
|
|
28
|
+
minNotional?: BigNumber;
|
|
28
29
|
expiry?: number;
|
|
29
30
|
raw: Record<string, unknown>;
|
|
30
31
|
}
|
|
@@ -58,10 +59,10 @@ export interface MarketEventFilter {
|
|
|
58
59
|
export interface L1Book {
|
|
59
60
|
exchange: Exchange;
|
|
60
61
|
symbol: string;
|
|
61
|
-
bidPrice:
|
|
62
|
-
bidSize:
|
|
63
|
-
askPrice:
|
|
64
|
-
askSize:
|
|
62
|
+
bidPrice: BigNumber;
|
|
63
|
+
bidSize: BigNumber;
|
|
64
|
+
askPrice: BigNumber;
|
|
65
|
+
askSize: BigNumber;
|
|
65
66
|
exchangeTs?: number;
|
|
66
67
|
receivedAt: number;
|
|
67
68
|
updatedAt: number;
|
|
@@ -71,10 +72,10 @@ export interface L1Book {
|
|
|
71
72
|
export interface FundingRateSnapshot {
|
|
72
73
|
exchange: Exchange;
|
|
73
74
|
symbol: string;
|
|
74
|
-
fundingRate:
|
|
75
|
+
fundingRate: BigNumber;
|
|
75
76
|
nextFundingTime?: number;
|
|
76
|
-
markPrice?:
|
|
77
|
-
indexPrice?:
|
|
77
|
+
markPrice?: BigNumber;
|
|
78
|
+
indexPrice?: BigNumber;
|
|
78
79
|
exchangeTs?: number;
|
|
79
80
|
receivedAt: number;
|
|
80
81
|
updatedAt: number;
|
|
@@ -128,8 +129,9 @@ export interface MarketManager {
|
|
|
128
129
|
subscribeFundingRate(input: SubscribeFundingRateInput): Promise<void>;
|
|
129
130
|
unsubscribeFundingRate(input: SubscribeFundingRateInput): Promise<void>;
|
|
130
131
|
|
|
131
|
-
getMarket(symbol: string): MarketDefinition | undefined;
|
|
132
|
-
|
|
132
|
+
getMarket(exchange: Exchange, symbol: string): MarketDefinition | undefined;
|
|
133
|
+
findMarkets(symbol: string): MarketDefinition[];
|
|
134
|
+
listMarkets(exchange?: Exchange): MarketDefinition[];
|
|
133
135
|
getL1Book(key: MarketKeyInput): L1Book | undefined;
|
|
134
136
|
getFundingRate(key: MarketKeyInput): FundingRateSnapshot | undefined;
|
|
135
137
|
getMarketStatus(key: MarketKeyInput): MarketDataStatus | undefined;
|