@imbingox/acex 0.4.0-beta.10 → 0.4.0-beta.11
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/docs/api.md +4 -1
- package/package.json +1 -1
- package/src/managers/order-manager.ts +362 -170
package/docs/api.md
CHANGED
|
@@ -430,6 +430,7 @@ interface OrderManager {
|
|
|
430
430
|
|
|
431
431
|
- `createOrder()` 支持 `limit` / `market`
|
|
432
432
|
- `limit` 可传 `postOnly: true`,Binance PAPI UM 映射为 `timeInForce=GTX`
|
|
433
|
+
- 未传 `clientOrderId` 时,`createOrder()` 由 SDK 生成合规 client id(`acex-` 前缀,≤32)并作为 Binance `newClientOrderId` 发送,返回 snapshot 的 `clientOrderId` 即该值;自带 `clientOrderId` 超长或含非法字符会抛 `ORDER_INPUT_INVALID`
|
|
433
434
|
- `cancelOrder()` 必须传 `orderId` 或 `clientOrderId`
|
|
434
435
|
- `cancelAllOrders()` 必须传 `symbol`,不支持账户级全撤
|
|
435
436
|
- hedge mode 下必须显式传 `positionSide: "long" | "short"`
|
|
@@ -442,7 +443,9 @@ interface OrderManager {
|
|
|
442
443
|
|
|
443
444
|
- OrderManager 内部按 open / closed 分层缓存订单。**closed(filled / canceled / rejected / expired)订单按 symbol 各保留最近 N 个**,`N = CreateClientOptions.order.maxClosedOrdersPerSymbol`(默认 500,非正或非整数回退默认),超限按 FIFO 裁剪最旧;**open 订单不受此上限限制**。`getOpenOrders()` 查询复杂度与历史终态订单数量无关。
|
|
444
445
|
- `getOrder(input)` 需带 `orderId` 或 `clientOrderId`(否则返回 `undefined`),`symbol` 可选:
|
|
445
|
-
-
|
|
446
|
+
- **精确查单推荐传 `symbol + orderId`**(O(1) 精确索引、唯一命中)。
|
|
447
|
+
- 仅 `clientOrderId` 查询可命中 open 与未被裁剪的 closed;当 `clientOrderId` 唯一(你自定义的或 SDK 生成的 `acex-*`)时可精确命中,但同一 `clientOrderId` 命中多笔时返回**最新一笔**(精确定位历史某一笔请用 `symbol + orderId`)。
|
|
448
|
+
- 仅传 `orderId`(不带 `symbol`)时,跨 symbol 同 `orderId` 可能多命中,返回最新一笔;ADL / 系统单会共享 `clientOrderId`(如 `adl_autoclose`),必须用 `symbol + orderId` 精确定位。
|
|
446
449
|
- 同时给 `orderId` 与 `clientOrderId` 时,两者都匹配才命中。
|
|
447
450
|
- 已超出保留上限被裁剪的 closed 订单将查不到(返回 `undefined`)。
|
|
448
451
|
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
20
20
|
import { toCanonical } from "../internal/decimal.ts";
|
|
21
21
|
import { matchesOrderFilter } from "../internal/filters.ts";
|
|
22
|
+
import { isTransportError } from "../internal/http-client.ts";
|
|
22
23
|
import {
|
|
23
24
|
canDeleteMissingFromSnapshot,
|
|
24
25
|
shouldApplyWatermarkedUpdate,
|
|
@@ -46,9 +47,11 @@ interface OrderRecord {
|
|
|
46
47
|
subscribed: boolean;
|
|
47
48
|
openOrders: Map<string, Map<string, OrderSnapshot>>;
|
|
48
49
|
closedOrders: Map<string, Map<string, OrderSnapshot>>;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
localOrderLocations: Map<string, OrderLocation>;
|
|
51
|
+
orderIdIndex: Map<string, Map<string, string>>;
|
|
52
|
+
orderIdOnlyIndex: Map<string, Set<string>>;
|
|
53
|
+
clientOrderIdIndex: Map<string, Set<string>>;
|
|
54
|
+
pendingClientOrderIdIndex: Map<string, PendingOrderClaim>;
|
|
52
55
|
status: OrderDataStatus;
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -57,7 +60,12 @@ type OrderTable = "open" | "closed";
|
|
|
57
60
|
interface OrderLocation {
|
|
58
61
|
table: OrderTable;
|
|
59
62
|
symbol: string;
|
|
60
|
-
|
|
63
|
+
localOrderId: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PendingOrderClaim {
|
|
67
|
+
localOrderId: string;
|
|
68
|
+
symbol: string;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
interface OrderManagerOptions {
|
|
@@ -65,6 +73,14 @@ interface OrderManagerOptions {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
const DEFAULT_MAX_CLOSED_ORDERS_PER_SYMBOL = 500;
|
|
76
|
+
const SDK_CLIENT_ORDER_ID_PREFIX = "acex-";
|
|
77
|
+
const VENUE_CLIENT_ORDER_ID_PATTERN = /^[.A-Z:/a-z0-9_-]{1,32}$/;
|
|
78
|
+
|
|
79
|
+
const SYSTEM_CLIENT_ORDER_ID_PATTERNS = [
|
|
80
|
+
/^adl_autoclose$/,
|
|
81
|
+
/^autoclose-/,
|
|
82
|
+
/^settlement_autoclose-/,
|
|
83
|
+
];
|
|
68
84
|
|
|
69
85
|
function cloneOrderStatus(status: OrderDataStatus): OrderDataStatus {
|
|
70
86
|
return { ...status };
|
|
@@ -76,38 +92,24 @@ function normalizeMaxClosedOrdersPerSymbol(value: number | undefined): number {
|
|
|
76
92
|
: DEFAULT_MAX_CLOSED_ORDERS_PER_SYMBOL;
|
|
77
93
|
}
|
|
78
94
|
|
|
79
|
-
function
|
|
95
|
+
function getOrderLookupKeys(input: {
|
|
80
96
|
symbol: string;
|
|
81
97
|
orderId?: string;
|
|
82
98
|
clientOrderId?: string;
|
|
83
|
-
}): string
|
|
99
|
+
}): string[] {
|
|
100
|
+
const keys: string[] = [];
|
|
84
101
|
if (input.orderId) {
|
|
85
|
-
|
|
102
|
+
keys.push(`symbol:${input.symbol}:order:${input.orderId}`);
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
if (input.clientOrderId) {
|
|
89
|
-
|
|
106
|
+
keys.push(`symbol:${input.symbol}:client:${input.clientOrderId}`);
|
|
90
107
|
}
|
|
91
108
|
|
|
92
|
-
return
|
|
109
|
+
return keys;
|
|
93
110
|
}
|
|
94
111
|
|
|
95
|
-
function
|
|
96
|
-
orderId?: string;
|
|
97
|
-
clientOrderId?: string;
|
|
98
|
-
}): string | undefined {
|
|
99
|
-
if (input.orderId) {
|
|
100
|
-
return `order:${input.orderId}`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (input.clientOrderId) {
|
|
104
|
-
return `client:${input.clientOrderId}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function shouldMatchOrderIdentity(
|
|
112
|
+
function shouldMatchOrderQuery(
|
|
111
113
|
candidate: OrderSnapshot,
|
|
112
114
|
input: { symbol?: string; orderId?: string; clientOrderId?: string },
|
|
113
115
|
): boolean {
|
|
@@ -115,10 +117,15 @@ function shouldMatchOrderIdentity(
|
|
|
115
117
|
return false;
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
if (input.orderId && candidate.orderId !== input.orderId) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (input.clientOrderId && candidate.clientOrderId !== input.clientOrderId) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Boolean(input.orderId || input.clientOrderId);
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
function shouldMatchStoredOrderIdentity(
|
|
@@ -133,9 +140,12 @@ function shouldMatchStoredOrderIdentity(
|
|
|
133
140
|
return candidate.orderId === input.orderId;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
// clientOrderId
|
|
137
|
-
//
|
|
138
|
-
//
|
|
143
|
+
// clientOrderId is only a temporary identity for an order that does not yet
|
|
144
|
+
// have an orderId. A candidate that already carries an orderId (including an
|
|
145
|
+
// old order sitting in closed that reused this clientOrderId) must not be
|
|
146
|
+
// merged by a cid-only update; otherwise the stale orderId would be
|
|
147
|
+
// carried forward and pollute closed. When the orderId is later filled in,
|
|
148
|
+
// the candidate still lacks an orderId and matches normally.
|
|
139
149
|
return Boolean(
|
|
140
150
|
input.clientOrderId &&
|
|
141
151
|
candidate.clientOrderId === input.clientOrderId &&
|
|
@@ -211,6 +221,7 @@ export class OrderManagerImpl
|
|
|
211
221
|
private readonly orderStatusBus =
|
|
212
222
|
new AsyncEventBus<OrderStatusChangedEvent>();
|
|
213
223
|
private readonly records = new Map<string, OrderRecord>();
|
|
224
|
+
private localOrderSequence = 0;
|
|
214
225
|
|
|
215
226
|
constructor(context: ClientContext, options: OrderManagerOptions = {}) {
|
|
216
227
|
this.context = context;
|
|
@@ -291,11 +302,45 @@ export class OrderManagerImpl
|
|
|
291
302
|
const account = this.context.getRegisteredAccount(input.accountId);
|
|
292
303
|
this.context.ensurePrivateCredentials(input.accountId);
|
|
293
304
|
this.validateCreateOrderInput(input, account.venue);
|
|
305
|
+
const record = this.getOrCreateRecord(input.accountId, account.venue);
|
|
306
|
+
const localOrderId = this.generateLocalOrderId({
|
|
307
|
+
record,
|
|
308
|
+
avoidOpenClientOrderId: input.clientOrderId === undefined,
|
|
309
|
+
});
|
|
310
|
+
const venueClientOrderId = input.clientOrderId ?? localOrderId;
|
|
311
|
+
this.addPendingClientOrderClaim(
|
|
312
|
+
record,
|
|
313
|
+
input.symbol,
|
|
314
|
+
venueClientOrderId,
|
|
315
|
+
localOrderId,
|
|
316
|
+
);
|
|
294
317
|
|
|
295
318
|
try {
|
|
296
|
-
const
|
|
297
|
-
|
|
319
|
+
const commandInput: CreateOrderInput = {
|
|
320
|
+
...input,
|
|
321
|
+
clientOrderId: venueClientOrderId,
|
|
322
|
+
};
|
|
323
|
+
const update = await this.context.createOrder(commandInput);
|
|
324
|
+
const snapshot = this.applyCommandUpdate(
|
|
325
|
+
input.accountId,
|
|
326
|
+
account.venue,
|
|
327
|
+
update,
|
|
328
|
+
{ localOrderId },
|
|
329
|
+
);
|
|
330
|
+
this.clearPendingClientOrderClaim(
|
|
331
|
+
record,
|
|
332
|
+
venueClientOrderId,
|
|
333
|
+
localOrderId,
|
|
334
|
+
);
|
|
335
|
+
return snapshot;
|
|
298
336
|
} catch (error) {
|
|
337
|
+
if (!this.shouldRetainPendingClaimAfterCreateError(error)) {
|
|
338
|
+
this.clearPendingClientOrderClaim(
|
|
339
|
+
record,
|
|
340
|
+
venueClientOrderId,
|
|
341
|
+
localOrderId,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
299
344
|
throw this.wrapCommandError(
|
|
300
345
|
"ORDER_CREATE_FAILED",
|
|
301
346
|
`Failed to create order for ${input.accountId}: ${input.symbol}`,
|
|
@@ -365,13 +410,13 @@ export class OrderManagerImpl
|
|
|
365
410
|
}
|
|
366
411
|
|
|
367
412
|
if (input.symbol && input.orderId) {
|
|
368
|
-
const
|
|
413
|
+
const localOrderId = this.getLocalOrderIdForVenueOrderId(
|
|
369
414
|
record,
|
|
370
415
|
input.symbol,
|
|
371
416
|
input.orderId,
|
|
372
417
|
);
|
|
373
|
-
const snapshot =
|
|
374
|
-
? this.
|
|
418
|
+
const snapshot = localOrderId
|
|
419
|
+
? this.getSnapshotByLocalOrderId(record, localOrderId)
|
|
375
420
|
: undefined;
|
|
376
421
|
if (!snapshot) {
|
|
377
422
|
return undefined;
|
|
@@ -389,17 +434,8 @@ export class OrderManagerImpl
|
|
|
389
434
|
|
|
390
435
|
if (input.orderId) {
|
|
391
436
|
return this.selectLatestSnapshot(
|
|
392
|
-
this.getSnapshotsForOrderId(record, input.orderId).filter(
|
|
393
|
-
(snapshot)
|
|
394
|
-
shouldMatchOrderIdentity(snapshot, {
|
|
395
|
-
symbol: input.symbol,
|
|
396
|
-
orderId: input.orderId,
|
|
397
|
-
}) &&
|
|
398
|
-
(!input.clientOrderId ||
|
|
399
|
-
shouldMatchOrderIdentity(snapshot, {
|
|
400
|
-
symbol: input.symbol,
|
|
401
|
-
clientOrderId: input.clientOrderId,
|
|
402
|
-
})),
|
|
437
|
+
this.getSnapshotsForOrderId(record, input.orderId).filter((snapshot) =>
|
|
438
|
+
shouldMatchOrderQuery(snapshot, input),
|
|
403
439
|
),
|
|
404
440
|
);
|
|
405
441
|
}
|
|
@@ -407,11 +443,7 @@ export class OrderManagerImpl
|
|
|
407
443
|
if (input.clientOrderId) {
|
|
408
444
|
return this.selectLatestSnapshot(
|
|
409
445
|
this.getSnapshotsForClientOrderId(record, input.clientOrderId).filter(
|
|
410
|
-
(snapshot) =>
|
|
411
|
-
shouldMatchOrderIdentity(snapshot, {
|
|
412
|
-
symbol: input.symbol,
|
|
413
|
-
clientOrderId: input.clientOrderId,
|
|
414
|
-
}),
|
|
446
|
+
(snapshot) => shouldMatchOrderQuery(snapshot, input),
|
|
415
447
|
),
|
|
416
448
|
);
|
|
417
449
|
}
|
|
@@ -529,8 +561,7 @@ export class OrderManagerImpl
|
|
|
529
561
|
|
|
530
562
|
const openSetKeys = new Set<string>();
|
|
531
563
|
for (const update of snapshot.orders) {
|
|
532
|
-
const lookupKey
|
|
533
|
-
if (lookupKey) {
|
|
564
|
+
for (const lookupKey of getOrderLookupKeys(update)) {
|
|
534
565
|
openSetKeys.add(lookupKey);
|
|
535
566
|
}
|
|
536
567
|
const current = this.getExistingSnapshot(record, update);
|
|
@@ -545,13 +576,11 @@ export class OrderManagerImpl
|
|
|
545
576
|
},
|
|
546
577
|
);
|
|
547
578
|
if (nextSnapshot) {
|
|
548
|
-
const nextLookupKey
|
|
549
|
-
if (nextLookupKey) {
|
|
579
|
+
for (const nextLookupKey of getOrderLookupKeys(nextSnapshot)) {
|
|
550
580
|
openSetKeys.add(nextLookupKey);
|
|
551
581
|
}
|
|
552
582
|
} else if (current) {
|
|
553
|
-
const currentLookupKey
|
|
554
|
-
if (currentLookupKey) {
|
|
583
|
+
for (const currentLookupKey of getOrderLookupKeys(current)) {
|
|
555
584
|
openSetKeys.add(currentLookupKey);
|
|
556
585
|
}
|
|
557
586
|
}
|
|
@@ -562,8 +591,11 @@ export class OrderManagerImpl
|
|
|
562
591
|
return false;
|
|
563
592
|
}
|
|
564
593
|
|
|
565
|
-
const
|
|
566
|
-
if (
|
|
594
|
+
const lookupKeys = getOrderLookupKeys(order);
|
|
595
|
+
if (
|
|
596
|
+
lookupKeys.length === 0 ||
|
|
597
|
+
lookupKeys.some((lookupKey) => openSetKeys.has(lookupKey))
|
|
598
|
+
) {
|
|
567
599
|
return false;
|
|
568
600
|
}
|
|
569
601
|
|
|
@@ -704,9 +736,11 @@ export class OrderManagerImpl
|
|
|
704
736
|
subscribed: false,
|
|
705
737
|
openOrders: new Map(),
|
|
706
738
|
closedOrders: new Map(),
|
|
739
|
+
localOrderLocations: new Map(),
|
|
707
740
|
orderIdIndex: new Map(),
|
|
708
741
|
orderIdOnlyIndex: new Map(),
|
|
709
742
|
clientOrderIdIndex: new Map(),
|
|
743
|
+
pendingClientOrderIdIndex: new Map(),
|
|
710
744
|
status: this.createStatus(accountId, venue, "inactive"),
|
|
711
745
|
};
|
|
712
746
|
|
|
@@ -740,91 +774,125 @@ export class OrderManagerImpl
|
|
|
740
774
|
record: OrderRecord,
|
|
741
775
|
update: { symbol: string; orderId?: string; clientOrderId?: string },
|
|
742
776
|
): OrderLocation | undefined {
|
|
777
|
+
const resolution = this.resolveLocalOrderIdForUpdate(record, update);
|
|
778
|
+
return resolution.localOrderId
|
|
779
|
+
? record.localOrderLocations.get(resolution.localOrderId)
|
|
780
|
+
: undefined;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private resolveLocalOrderIdForUpdate(
|
|
784
|
+
record: OrderRecord,
|
|
785
|
+
update: { symbol: string; orderId?: string; clientOrderId?: string },
|
|
786
|
+
preferredLocalOrderId?: string,
|
|
787
|
+
): {
|
|
788
|
+
localOrderId?: string;
|
|
789
|
+
source?: "exact" | "pending" | "provisional" | "preferred";
|
|
790
|
+
} {
|
|
743
791
|
if (update.orderId) {
|
|
744
|
-
const
|
|
792
|
+
const exact = this.getLocalOrderIdForVenueOrderId(
|
|
745
793
|
record,
|
|
746
794
|
update.symbol,
|
|
747
795
|
update.orderId,
|
|
748
796
|
);
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
: undefined;
|
|
752
|
-
if (snapshot && shouldMatchStoredOrderIdentity(snapshot, update)) {
|
|
753
|
-
return location;
|
|
797
|
+
if (exact) {
|
|
798
|
+
return { localOrderId: exact, source: "exact" };
|
|
754
799
|
}
|
|
755
800
|
}
|
|
756
801
|
|
|
757
|
-
if (
|
|
758
|
-
return
|
|
802
|
+
if (preferredLocalOrderId) {
|
|
803
|
+
return { localOrderId: preferredLocalOrderId, source: "preferred" };
|
|
759
804
|
}
|
|
760
805
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (
|
|
766
|
-
return
|
|
806
|
+
if (update.clientOrderId) {
|
|
807
|
+
const pending = record.pendingClientOrderIdIndex.get(
|
|
808
|
+
update.clientOrderId,
|
|
809
|
+
);
|
|
810
|
+
if (pending?.symbol === update.symbol) {
|
|
811
|
+
return { localOrderId: pending.localOrderId, source: "pending" };
|
|
767
812
|
}
|
|
768
813
|
}
|
|
769
814
|
|
|
770
|
-
|
|
815
|
+
if (
|
|
816
|
+
update.clientOrderId &&
|
|
817
|
+
!this.isSystemClientOrderId(update.clientOrderId)
|
|
818
|
+
) {
|
|
819
|
+
for (const localOrderId of record.clientOrderIdIndex.get(
|
|
820
|
+
update.clientOrderId,
|
|
821
|
+
) ?? []) {
|
|
822
|
+
const snapshot = this.getSnapshotByLocalOrderId(record, localOrderId);
|
|
823
|
+
if (snapshot && shouldMatchStoredOrderIdentity(snapshot, update)) {
|
|
824
|
+
return { localOrderId, source: "provisional" };
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return {};
|
|
771
830
|
}
|
|
772
831
|
|
|
773
832
|
private setSnapshot(
|
|
774
833
|
record: OrderRecord,
|
|
834
|
+
localOrderId: string,
|
|
775
835
|
snapshot: OrderSnapshot,
|
|
776
|
-
|
|
836
|
+
previousLocation?: OrderLocation,
|
|
777
837
|
): OrderLocation | undefined {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
838
|
+
if (!snapshot.orderId && !snapshot.clientOrderId) {
|
|
839
|
+
this.warnDroppedUnkeyedTerminalOrder(record, snapshot);
|
|
840
|
+
return undefined;
|
|
841
|
+
}
|
|
782
842
|
|
|
783
|
-
|
|
784
|
-
|
|
843
|
+
const currentLocation =
|
|
844
|
+
previousLocation ?? record.localOrderLocations.get(localOrderId);
|
|
845
|
+
if (currentLocation) {
|
|
846
|
+
return this.moveSnapshot(record, currentLocation, localOrderId, snapshot);
|
|
785
847
|
}
|
|
786
848
|
|
|
787
|
-
return this.insertSnapshot(record, snapshot);
|
|
849
|
+
return this.insertSnapshot(record, localOrderId, snapshot);
|
|
788
850
|
}
|
|
789
851
|
|
|
790
852
|
private insertSnapshot(
|
|
791
853
|
record: OrderRecord,
|
|
854
|
+
localOrderId: string,
|
|
792
855
|
snapshot: OrderSnapshot,
|
|
793
856
|
): OrderLocation | undefined {
|
|
794
|
-
const
|
|
795
|
-
if (
|
|
796
|
-
this.
|
|
797
|
-
return undefined;
|
|
857
|
+
const existingLocation = record.localOrderLocations.get(localOrderId);
|
|
858
|
+
if (existingLocation) {
|
|
859
|
+
this.deleteSnapshot(record, existingLocation);
|
|
798
860
|
}
|
|
799
861
|
|
|
800
|
-
|
|
862
|
+
const location: OrderLocation = {
|
|
863
|
+
table: isOpenOrder(snapshot) ? "open" : "closed",
|
|
864
|
+
symbol: snapshot.symbol,
|
|
865
|
+
localOrderId,
|
|
866
|
+
};
|
|
801
867
|
|
|
802
868
|
const table = this.getOrderTable(record, location.table);
|
|
803
869
|
const symbolOrders = this.getOrCreateSymbolOrders(table, location.symbol);
|
|
804
|
-
symbolOrders.set(
|
|
805
|
-
|
|
870
|
+
symbolOrders.set(localOrderId, snapshot);
|
|
871
|
+
record.localOrderLocations.set(localOrderId, location);
|
|
806
872
|
|
|
807
873
|
if (snapshot.orderId) {
|
|
808
874
|
const symbolIndex = this.getOrCreateOrderIdSymbolIndex(
|
|
809
875
|
record,
|
|
810
876
|
snapshot.symbol,
|
|
811
877
|
);
|
|
812
|
-
symbolIndex.set(snapshot.orderId,
|
|
813
|
-
this.
|
|
878
|
+
symbolIndex.set(snapshot.orderId, localOrderId);
|
|
879
|
+
this.addLocalOrderIdToSetIndex(
|
|
814
880
|
record.orderIdOnlyIndex,
|
|
815
881
|
snapshot.orderId,
|
|
816
|
-
|
|
882
|
+
localOrderId,
|
|
817
883
|
);
|
|
818
884
|
}
|
|
819
885
|
|
|
820
886
|
if (snapshot.clientOrderId) {
|
|
821
|
-
this.
|
|
887
|
+
this.addLocalOrderIdToSetIndex(
|
|
822
888
|
record.clientOrderIdIndex,
|
|
823
889
|
snapshot.clientOrderId,
|
|
824
|
-
|
|
890
|
+
localOrderId,
|
|
825
891
|
);
|
|
826
892
|
}
|
|
827
893
|
|
|
894
|
+
this.trimClosedOrdersForSymbol(record, location);
|
|
895
|
+
this.warnSystemClientOrderIdOnlyClaim(record, snapshot);
|
|
828
896
|
this.warnProvisionalTerminalOrder(record, snapshot);
|
|
829
897
|
return location;
|
|
830
898
|
}
|
|
@@ -840,34 +908,35 @@ export class OrderManagerImpl
|
|
|
840
908
|
|
|
841
909
|
const table = this.getOrderTable(record, location.table);
|
|
842
910
|
const symbolOrders = table.get(location.symbol);
|
|
843
|
-
symbolOrders?.delete(location.
|
|
911
|
+
symbolOrders?.delete(location.localOrderId);
|
|
844
912
|
if (symbolOrders?.size === 0) {
|
|
845
913
|
table.delete(location.symbol);
|
|
846
914
|
}
|
|
915
|
+
record.localOrderLocations.delete(location.localOrderId);
|
|
847
916
|
|
|
848
917
|
if (snapshot.orderId) {
|
|
849
918
|
const symbolIndex = record.orderIdIndex.get(location.symbol);
|
|
850
919
|
if (
|
|
851
920
|
symbolIndex?.get(snapshot.orderId) &&
|
|
852
|
-
|
|
921
|
+
symbolIndex.get(snapshot.orderId) === location.localOrderId
|
|
853
922
|
) {
|
|
854
923
|
symbolIndex.delete(snapshot.orderId);
|
|
855
924
|
}
|
|
856
925
|
if (symbolIndex?.size === 0) {
|
|
857
926
|
record.orderIdIndex.delete(location.symbol);
|
|
858
927
|
}
|
|
859
|
-
this.
|
|
928
|
+
this.removeLocalOrderIdFromSetIndex(
|
|
860
929
|
record.orderIdOnlyIndex,
|
|
861
930
|
snapshot.orderId,
|
|
862
|
-
location,
|
|
931
|
+
location.localOrderId,
|
|
863
932
|
);
|
|
864
933
|
}
|
|
865
934
|
|
|
866
935
|
if (snapshot.clientOrderId) {
|
|
867
|
-
this.
|
|
936
|
+
this.removeLocalOrderIdFromSetIndex(
|
|
868
937
|
record.clientOrderIdIndex,
|
|
869
938
|
snapshot.clientOrderId,
|
|
870
|
-
location,
|
|
939
|
+
location.localOrderId,
|
|
871
940
|
);
|
|
872
941
|
}
|
|
873
942
|
|
|
@@ -877,10 +946,11 @@ export class OrderManagerImpl
|
|
|
877
946
|
private moveSnapshot(
|
|
878
947
|
record: OrderRecord,
|
|
879
948
|
previousLocation: OrderLocation,
|
|
949
|
+
localOrderId: string,
|
|
880
950
|
snapshot: OrderSnapshot,
|
|
881
951
|
): OrderLocation | undefined {
|
|
882
952
|
this.deleteSnapshot(record, previousLocation);
|
|
883
|
-
return this.insertSnapshot(record, snapshot);
|
|
953
|
+
return this.insertSnapshot(record, localOrderId, snapshot);
|
|
884
954
|
}
|
|
885
955
|
|
|
886
956
|
private trimClosedOrdersForSymbol(
|
|
@@ -910,40 +980,50 @@ export class OrderManagerImpl
|
|
|
910
980
|
this.deleteSnapshot(record, {
|
|
911
981
|
table: "closed",
|
|
912
982
|
symbol: location.symbol,
|
|
913
|
-
|
|
983
|
+
localOrderId: next.value,
|
|
914
984
|
});
|
|
915
985
|
}
|
|
916
986
|
symbolOrders = record.closedOrders.get(location.symbol);
|
|
917
987
|
}
|
|
918
988
|
}
|
|
919
989
|
|
|
920
|
-
private
|
|
990
|
+
private warnDroppedUnkeyedTerminalOrder(
|
|
991
|
+
record: OrderRecord,
|
|
921
992
|
snapshot: OrderSnapshot,
|
|
922
|
-
):
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
return undefined;
|
|
993
|
+
): void {
|
|
994
|
+
if (isOpenOrder(snapshot)) {
|
|
995
|
+
return;
|
|
926
996
|
}
|
|
927
997
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
998
|
+
this.context.publishRuntimeError(
|
|
999
|
+
"order",
|
|
1000
|
+
new Error(
|
|
1001
|
+
"Dropped terminal order update without orderId or clientOrderId",
|
|
1002
|
+
),
|
|
1003
|
+
{
|
|
1004
|
+
accountId: record.accountId,
|
|
1005
|
+
venue: record.venue,
|
|
1006
|
+
symbol: snapshot.symbol,
|
|
1007
|
+
},
|
|
1008
|
+
);
|
|
933
1009
|
}
|
|
934
1010
|
|
|
935
|
-
private
|
|
1011
|
+
private warnSystemClientOrderIdOnlyClaim(
|
|
936
1012
|
record: OrderRecord,
|
|
937
1013
|
snapshot: OrderSnapshot,
|
|
938
1014
|
): void {
|
|
939
|
-
if (
|
|
1015
|
+
if (
|
|
1016
|
+
snapshot.orderId ||
|
|
1017
|
+
!snapshot.clientOrderId ||
|
|
1018
|
+
!this.isSystemClientOrderId(snapshot.clientOrderId)
|
|
1019
|
+
) {
|
|
940
1020
|
return;
|
|
941
1021
|
}
|
|
942
1022
|
|
|
943
1023
|
this.context.publishRuntimeError(
|
|
944
1024
|
"order",
|
|
945
1025
|
new Error(
|
|
946
|
-
"
|
|
1026
|
+
"Received system clientOrderId without orderId; cid-only claim is unstable",
|
|
947
1027
|
),
|
|
948
1028
|
{
|
|
949
1029
|
accountId: record.accountId,
|
|
@@ -957,8 +1037,10 @@ export class OrderManagerImpl
|
|
|
957
1037
|
record: OrderRecord,
|
|
958
1038
|
snapshot: OrderSnapshot,
|
|
959
1039
|
): void {
|
|
960
|
-
//
|
|
961
|
-
//
|
|
1040
|
+
// Terminal order missing orderId but carrying clientOrderId: stored under a
|
|
1041
|
+
// provisional client key and warned. The adapter contract requires terminal
|
|
1042
|
+
// updates to carry orderId (see adapter-contract.md); clientOrderId alone
|
|
1043
|
+
// cannot guarantee a stable unique primary key.
|
|
962
1044
|
if (snapshot.orderId || isOpenOrder(snapshot) || !snapshot.clientOrderId) {
|
|
963
1045
|
return;
|
|
964
1046
|
}
|
|
@@ -982,7 +1064,15 @@ export class OrderManagerImpl
|
|
|
982
1064
|
): OrderSnapshot | undefined {
|
|
983
1065
|
return this.getOrderTable(record, location.table)
|
|
984
1066
|
.get(location.symbol)
|
|
985
|
-
?.get(location.
|
|
1067
|
+
?.get(location.localOrderId);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private getSnapshotByLocalOrderId(
|
|
1071
|
+
record: OrderRecord,
|
|
1072
|
+
localOrderId: string,
|
|
1073
|
+
): OrderSnapshot | undefined {
|
|
1074
|
+
const location = record.localOrderLocations.get(localOrderId);
|
|
1075
|
+
return location ? this.getSnapshotAtLocation(record, location) : undefined;
|
|
986
1076
|
}
|
|
987
1077
|
|
|
988
1078
|
private getOrderTable(
|
|
@@ -1009,22 +1099,22 @@ export class OrderManagerImpl
|
|
|
1009
1099
|
private getOrCreateOrderIdSymbolIndex(
|
|
1010
1100
|
record: OrderRecord,
|
|
1011
1101
|
symbol: string,
|
|
1012
|
-
): Map<string,
|
|
1102
|
+
): Map<string, string> {
|
|
1013
1103
|
const existing = record.orderIdIndex.get(symbol);
|
|
1014
1104
|
if (existing) {
|
|
1015
1105
|
return existing;
|
|
1016
1106
|
}
|
|
1017
1107
|
|
|
1018
|
-
const created = new Map<string,
|
|
1108
|
+
const created = new Map<string, string>();
|
|
1019
1109
|
record.orderIdIndex.set(symbol, created);
|
|
1020
1110
|
return created;
|
|
1021
1111
|
}
|
|
1022
1112
|
|
|
1023
|
-
private
|
|
1113
|
+
private getLocalOrderIdForVenueOrderId(
|
|
1024
1114
|
record: OrderRecord,
|
|
1025
1115
|
symbol: string,
|
|
1026
1116
|
orderId: string,
|
|
1027
|
-
):
|
|
1117
|
+
): string | undefined {
|
|
1028
1118
|
return record.orderIdIndex.get(symbol)?.get(orderId);
|
|
1029
1119
|
}
|
|
1030
1120
|
|
|
@@ -1032,7 +1122,7 @@ export class OrderManagerImpl
|
|
|
1032
1122
|
record: OrderRecord,
|
|
1033
1123
|
orderId: string,
|
|
1034
1124
|
): OrderSnapshot[] {
|
|
1035
|
-
return this.
|
|
1125
|
+
return this.getSnapshotsForLocalOrderIds(
|
|
1036
1126
|
record,
|
|
1037
1127
|
record.orderIdOnlyIndex.get(orderId),
|
|
1038
1128
|
);
|
|
@@ -1042,23 +1132,23 @@ export class OrderManagerImpl
|
|
|
1042
1132
|
record: OrderRecord,
|
|
1043
1133
|
clientOrderId: string,
|
|
1044
1134
|
): OrderSnapshot[] {
|
|
1045
|
-
return this.
|
|
1135
|
+
return this.getSnapshotsForLocalOrderIds(
|
|
1046
1136
|
record,
|
|
1047
1137
|
record.clientOrderIdIndex.get(clientOrderId),
|
|
1048
1138
|
);
|
|
1049
1139
|
}
|
|
1050
1140
|
|
|
1051
|
-
private
|
|
1141
|
+
private getSnapshotsForLocalOrderIds(
|
|
1052
1142
|
record: OrderRecord,
|
|
1053
|
-
|
|
1143
|
+
localOrderIds?: Iterable<string>,
|
|
1054
1144
|
): OrderSnapshot[] {
|
|
1055
|
-
if (!
|
|
1145
|
+
if (!localOrderIds) {
|
|
1056
1146
|
return [];
|
|
1057
1147
|
}
|
|
1058
1148
|
|
|
1059
1149
|
const snapshots: OrderSnapshot[] = [];
|
|
1060
|
-
for (const
|
|
1061
|
-
const snapshot = this.
|
|
1150
|
+
for (const localOrderId of localOrderIds) {
|
|
1151
|
+
const snapshot = this.getSnapshotByLocalOrderId(record, localOrderId);
|
|
1062
1152
|
if (snapshot) {
|
|
1063
1153
|
snapshots.push(snapshot);
|
|
1064
1154
|
}
|
|
@@ -1107,56 +1197,39 @@ export class OrderManagerImpl
|
|
|
1107
1197
|
return size;
|
|
1108
1198
|
}
|
|
1109
1199
|
|
|
1110
|
-
private
|
|
1111
|
-
index: Map<string, Set<
|
|
1200
|
+
private addLocalOrderIdToSetIndex(
|
|
1201
|
+
index: Map<string, Set<string>>,
|
|
1112
1202
|
key: string,
|
|
1113
|
-
|
|
1203
|
+
localOrderId: string,
|
|
1114
1204
|
): void {
|
|
1115
|
-
this.
|
|
1205
|
+
this.removeLocalOrderIdFromSetIndex(index, key, localOrderId);
|
|
1116
1206
|
|
|
1117
|
-
const
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1207
|
+
const localOrderIds = index.get(key);
|
|
1208
|
+
if (localOrderIds) {
|
|
1209
|
+
localOrderIds.add(localOrderId);
|
|
1120
1210
|
return;
|
|
1121
1211
|
}
|
|
1122
1212
|
|
|
1123
|
-
index.set(key, new Set([
|
|
1213
|
+
index.set(key, new Set([localOrderId]));
|
|
1124
1214
|
}
|
|
1125
1215
|
|
|
1126
|
-
private
|
|
1127
|
-
index: Map<string, Set<
|
|
1216
|
+
private removeLocalOrderIdFromSetIndex(
|
|
1217
|
+
index: Map<string, Set<string>>,
|
|
1128
1218
|
key: string,
|
|
1129
|
-
|
|
1219
|
+
localOrderId: string,
|
|
1130
1220
|
): void {
|
|
1131
|
-
const
|
|
1132
|
-
if (!
|
|
1221
|
+
const localOrderIds = index.get(key);
|
|
1222
|
+
if (!localOrderIds) {
|
|
1133
1223
|
return;
|
|
1134
1224
|
}
|
|
1135
1225
|
|
|
1136
|
-
|
|
1137
|
-
if (this.locationsEqual(candidate, location)) {
|
|
1138
|
-
locations.delete(candidate);
|
|
1139
|
-
break;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1226
|
+
localOrderIds.delete(localOrderId);
|
|
1142
1227
|
|
|
1143
|
-
if (
|
|
1228
|
+
if (localOrderIds.size === 0) {
|
|
1144
1229
|
index.delete(key);
|
|
1145
1230
|
}
|
|
1146
1231
|
}
|
|
1147
1232
|
|
|
1148
|
-
private locationsEqual(
|
|
1149
|
-
left: OrderLocation | undefined,
|
|
1150
|
-
right: OrderLocation,
|
|
1151
|
-
): boolean {
|
|
1152
|
-
return Boolean(
|
|
1153
|
-
left &&
|
|
1154
|
-
left.table === right.table &&
|
|
1155
|
-
left.symbol === right.symbol &&
|
|
1156
|
-
left.key === right.key,
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
1233
|
private selectLatestSnapshot(
|
|
1161
1234
|
snapshots: OrderSnapshot[],
|
|
1162
1235
|
): OrderSnapshot | undefined {
|
|
@@ -1170,15 +1243,18 @@ export class OrderManagerImpl
|
|
|
1170
1243
|
const snapshotOpen = isOpenOrder(snapshot);
|
|
1171
1244
|
const latestOpen = isOpenOrder(latest);
|
|
1172
1245
|
if (snapshotOpen !== latestOpen) {
|
|
1173
|
-
//
|
|
1246
|
+
// Open candidate has absolute priority: current active order takes
|
|
1247
|
+
// precedence over historical terminal state (when clientOrderId is
|
|
1248
|
+
// reused, the old order is already closed).
|
|
1174
1249
|
if (snapshotOpen) {
|
|
1175
1250
|
latest = snapshot;
|
|
1176
1251
|
}
|
|
1177
1252
|
continue;
|
|
1178
1253
|
}
|
|
1179
1254
|
|
|
1180
|
-
//
|
|
1181
|
-
//
|
|
1255
|
+
// Both open or both closed: take the latest by updatedAt.
|
|
1256
|
+
// seq must not be used -- seq is a per-order version number and is not
|
|
1257
|
+
// comparable across orders (e.g. different orders that reuse a cid).
|
|
1182
1258
|
if (snapshot.updatedAt > latest.updatedAt) {
|
|
1183
1259
|
latest = snapshot;
|
|
1184
1260
|
}
|
|
@@ -1194,7 +1270,12 @@ export class OrderManagerImpl
|
|
|
1194
1270
|
update: RawOrderUpdate,
|
|
1195
1271
|
options: { requestStartedAt?: number; preserveStatus?: boolean } = {},
|
|
1196
1272
|
): OrderSnapshot | undefined {
|
|
1197
|
-
const
|
|
1273
|
+
const resolution = this.resolveLocalOrderIdForUpdate(record, update);
|
|
1274
|
+
const localOrderId = resolution.localOrderId ?? this.generateLocalOrderId();
|
|
1275
|
+
const previousLocation = record.localOrderLocations.get(localOrderId);
|
|
1276
|
+
const previous = previousLocation
|
|
1277
|
+
? this.getSnapshotAtLocation(record, previousLocation)
|
|
1278
|
+
: undefined;
|
|
1198
1279
|
if (
|
|
1199
1280
|
!shouldApplyWatermarkedUpdate(previous, update, {
|
|
1200
1281
|
requestStartedAt: options.requestStartedAt,
|
|
@@ -1205,7 +1286,21 @@ export class OrderManagerImpl
|
|
|
1205
1286
|
}
|
|
1206
1287
|
|
|
1207
1288
|
const snapshot = this.createSnapshot(accountId, venue, update, previous);
|
|
1208
|
-
|
|
1289
|
+
const location = this.setSnapshot(
|
|
1290
|
+
record,
|
|
1291
|
+
localOrderId,
|
|
1292
|
+
snapshot,
|
|
1293
|
+
previousLocation,
|
|
1294
|
+
);
|
|
1295
|
+
if (location && resolution.source === "pending" && update.clientOrderId) {
|
|
1296
|
+
this.clearPendingClientOrderClaim(
|
|
1297
|
+
record,
|
|
1298
|
+
update.clientOrderId,
|
|
1299
|
+
localOrderId,
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
return location ? snapshot : undefined;
|
|
1209
1304
|
}
|
|
1210
1305
|
|
|
1211
1306
|
private createSnapshot(
|
|
@@ -1291,6 +1386,78 @@ export class OrderManagerImpl
|
|
|
1291
1386
|
this.context.publishHealthEvent(event);
|
|
1292
1387
|
}
|
|
1293
1388
|
|
|
1389
|
+
private generateLocalOrderId(options?: {
|
|
1390
|
+
record?: OrderRecord;
|
|
1391
|
+
avoidOpenClientOrderId?: boolean;
|
|
1392
|
+
}): string {
|
|
1393
|
+
while (true) {
|
|
1394
|
+
const candidate = `${SDK_CLIENT_ORDER_ID_PREFIX}${this.context.now().toString(36)}-${(this.localOrderSequence++).toString(36)}`;
|
|
1395
|
+
if (
|
|
1396
|
+
(options?.record &&
|
|
1397
|
+
options.avoidOpenClientOrderId &&
|
|
1398
|
+
this.isVenueClientOrderIdInUseForOpenOrder(
|
|
1399
|
+
options.record,
|
|
1400
|
+
candidate,
|
|
1401
|
+
)) ||
|
|
1402
|
+
options?.record?.pendingClientOrderIdIndex.has(candidate) ||
|
|
1403
|
+
!VENUE_CLIENT_ORDER_ID_PATTERN.test(candidate)
|
|
1404
|
+
) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return candidate;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
private isVenueClientOrderIdInUseForOpenOrder(
|
|
1413
|
+
record: OrderRecord,
|
|
1414
|
+
venueClientOrderId: string,
|
|
1415
|
+
): boolean {
|
|
1416
|
+
for (const localOrderId of record.clientOrderIdIndex.get(
|
|
1417
|
+
venueClientOrderId,
|
|
1418
|
+
) ?? []) {
|
|
1419
|
+
const location = record.localOrderLocations.get(localOrderId);
|
|
1420
|
+
if (location?.table === "open") {
|
|
1421
|
+
return true;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
return false;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private addPendingClientOrderClaim(
|
|
1429
|
+
record: OrderRecord,
|
|
1430
|
+
symbol: string,
|
|
1431
|
+
venueClientOrderId: string,
|
|
1432
|
+
localOrderId: string,
|
|
1433
|
+
): void {
|
|
1434
|
+
record.pendingClientOrderIdIndex.set(venueClientOrderId, {
|
|
1435
|
+
localOrderId,
|
|
1436
|
+
symbol,
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
private clearPendingClientOrderClaim(
|
|
1441
|
+
record: OrderRecord,
|
|
1442
|
+
venueClientOrderId: string,
|
|
1443
|
+
localOrderId: string,
|
|
1444
|
+
): void {
|
|
1445
|
+
const pending = record.pendingClientOrderIdIndex.get(venueClientOrderId);
|
|
1446
|
+
if (pending?.localOrderId === localOrderId) {
|
|
1447
|
+
record.pendingClientOrderIdIndex.delete(venueClientOrderId);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private shouldRetainPendingClaimAfterCreateError(error: unknown): boolean {
|
|
1452
|
+
return isTransportError(error) && error.kind === "timeout";
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
private isSystemClientOrderId(clientOrderId: string): boolean {
|
|
1456
|
+
return SYSTEM_CLIENT_ORDER_ID_PATTERNS.some((pattern) =>
|
|
1457
|
+
pattern.test(clientOrderId),
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1294
1461
|
private validateCreateOrderInput(
|
|
1295
1462
|
input: CreateOrderInput,
|
|
1296
1463
|
venue: Venue,
|
|
@@ -1306,6 +1473,21 @@ export class OrderManagerImpl
|
|
|
1306
1473
|
},
|
|
1307
1474
|
);
|
|
1308
1475
|
}
|
|
1476
|
+
|
|
1477
|
+
if (
|
|
1478
|
+
input.clientOrderId !== undefined &&
|
|
1479
|
+
!VENUE_CLIENT_ORDER_ID_PATTERN.test(input.clientOrderId)
|
|
1480
|
+
) {
|
|
1481
|
+
throw this.createError(
|
|
1482
|
+
"ORDER_INPUT_INVALID",
|
|
1483
|
+
`clientOrderId must be 1-32 Binance-safe characters: ${input.accountId}`,
|
|
1484
|
+
{
|
|
1485
|
+
accountId: input.accountId,
|
|
1486
|
+
venue,
|
|
1487
|
+
symbol: input.symbol,
|
|
1488
|
+
},
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1309
1491
|
}
|
|
1310
1492
|
|
|
1311
1493
|
private validateCancelOrderInput(
|
|
@@ -1331,11 +1513,21 @@ export class OrderManagerImpl
|
|
|
1331
1513
|
accountId: string,
|
|
1332
1514
|
venue: Venue,
|
|
1333
1515
|
update: RawOrderUpdate,
|
|
1516
|
+
options: { localOrderId?: string } = {},
|
|
1334
1517
|
): OrderSnapshot {
|
|
1335
1518
|
const record = this.getOrCreateRecord(accountId, venue);
|
|
1336
|
-
const
|
|
1519
|
+
const resolution = this.resolveLocalOrderIdForUpdate(
|
|
1520
|
+
record,
|
|
1521
|
+
update,
|
|
1522
|
+
options.localOrderId,
|
|
1523
|
+
);
|
|
1524
|
+
const localOrderId = resolution.localOrderId ?? this.generateLocalOrderId();
|
|
1525
|
+
const previousLocation = record.localOrderLocations.get(localOrderId);
|
|
1526
|
+
const previous = previousLocation
|
|
1527
|
+
? this.getSnapshotAtLocation(record, previousLocation)
|
|
1528
|
+
: undefined;
|
|
1337
1529
|
const snapshot = this.createSnapshot(accountId, venue, update, previous);
|
|
1338
|
-
this.setSnapshot(record, snapshot,
|
|
1530
|
+
this.setSnapshot(record, localOrderId, snapshot, previousLocation);
|
|
1339
1531
|
return snapshot;
|
|
1340
1532
|
}
|
|
1341
1533
|
|