@imbingox/acex 0.4.0-beta.12 → 0.4.0-beta.14

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.
@@ -28,6 +28,17 @@ export function shouldApplyWatermarkedUpdate(
28
28
  const graceMs = options.graceMs ?? CROSS_CLOCK_WATERMARK_GRACE_MS;
29
29
  const requestStartedAt = options.requestStartedAt;
30
30
 
31
+ if (options.source === "command" && requestStartedAt !== undefined) {
32
+ const hasMissingExchangeTs =
33
+ current.exchangeTs === undefined || incoming.exchangeTs === undefined;
34
+ if (hasMissingExchangeTs) {
35
+ return (
36
+ current.receivedAt <= requestStartedAt &&
37
+ incoming.receivedAt >= current.receivedAt
38
+ );
39
+ }
40
+ }
41
+
31
42
  if (
32
43
  options.source === "rest" &&
33
44
  requestStartedAt !== undefined &&
@@ -0,0 +1,61 @@
1
+ import type { OrderDataStatus, Venue } from "../../types/index.ts";
2
+
3
+ export const DEFAULT_MAX_CLOSED_ORDERS_PER_SYMBOL = 500;
4
+
5
+ export function cloneOrderStatus(status: OrderDataStatus): OrderDataStatus {
6
+ return { ...status };
7
+ }
8
+
9
+ export function createOrderDataStatus(
10
+ accountId: string,
11
+ venue: Venue,
12
+ activity: "active" | "inactive",
13
+ ): OrderDataStatus {
14
+ return {
15
+ accountId,
16
+ venue,
17
+ activity,
18
+ ready: false,
19
+ runtimeStatus: activity === "active" ? "bootstrap_pending" : "stopped",
20
+ };
21
+ }
22
+
23
+ export function normalizeMaxClosedOrdersPerSymbol(
24
+ value: number | undefined,
25
+ ): number {
26
+ return value !== undefined && Number.isInteger(value) && value > 0
27
+ ? value
28
+ : DEFAULT_MAX_CLOSED_ORDERS_PER_SYMBOL;
29
+ }
30
+
31
+ export function successfulStatus(
32
+ status: OrderDataStatus,
33
+ options: {
34
+ ready?: boolean;
35
+ lastReceivedAt?: number;
36
+ lastReadyAt?: number;
37
+ preserveStatus?: boolean;
38
+ },
39
+ ): OrderDataStatus {
40
+ const preservesStreamState =
41
+ options.preserveStatus &&
42
+ (status.runtimeStatus === "reconnecting" ||
43
+ status.reason === "ws_disconnected" ||
44
+ status.reason === "heartbeat_timeout");
45
+ const ready = options.ready ?? true;
46
+
47
+ return {
48
+ ...status,
49
+ activity: "active",
50
+ ready,
51
+ runtimeStatus: preservesStreamState ? status.runtimeStatus : "healthy",
52
+ reason: preservesStreamState ? status.reason : undefined,
53
+ lastReceivedAt: options.lastReceivedAt ?? status.lastReceivedAt,
54
+ lastReadyAt: ready
55
+ ? (options.lastReadyAt ??
56
+ (options.preserveStatus ? status.lastReadyAt : undefined) ??
57
+ Date.now())
58
+ : status.lastReadyAt,
59
+ inactiveSince: undefined,
60
+ };
61
+ }
@@ -0,0 +1,77 @@
1
+ import type { OrderSnapshot } from "../../types/index.ts";
2
+
3
+ export const SDK_CLIENT_ORDER_ID_PREFIX = "acex-";
4
+ export const VENUE_CLIENT_ORDER_ID_PATTERN = /^[.A-Z:/a-z0-9_-]{1,32}$/;
5
+
6
+ const SYSTEM_CLIENT_ORDER_ID_PATTERNS = [
7
+ /^adl_autoclose$/,
8
+ /^autoclose-/,
9
+ /^settlement_autoclose-/,
10
+ ];
11
+
12
+ export function getOrderLookupKeys(input: {
13
+ symbol: string;
14
+ orderId?: string;
15
+ clientOrderId?: string;
16
+ }): string[] {
17
+ const keys: string[] = [];
18
+ if (input.orderId) {
19
+ keys.push(`symbol:${input.symbol}:order:${input.orderId}`);
20
+ }
21
+
22
+ if (input.clientOrderId) {
23
+ keys.push(`symbol:${input.symbol}:client:${input.clientOrderId}`);
24
+ }
25
+
26
+ return keys;
27
+ }
28
+
29
+ export function shouldMatchOrderQuery(
30
+ candidate: OrderSnapshot,
31
+ input: { symbol?: string; orderId?: string; clientOrderId?: string },
32
+ ): boolean {
33
+ if (input.symbol && candidate.symbol !== input.symbol) {
34
+ return false;
35
+ }
36
+
37
+ if (input.orderId && candidate.orderId !== input.orderId) {
38
+ return false;
39
+ }
40
+
41
+ if (input.clientOrderId && candidate.clientOrderId !== input.clientOrderId) {
42
+ return false;
43
+ }
44
+
45
+ return Boolean(input.orderId || input.clientOrderId);
46
+ }
47
+
48
+ export function shouldMatchStoredOrderIdentity(
49
+ candidate: OrderSnapshot,
50
+ input: { symbol: string; orderId?: string; clientOrderId?: string },
51
+ ): boolean {
52
+ if (candidate.symbol !== input.symbol) {
53
+ return false;
54
+ }
55
+
56
+ if (candidate.orderId && input.orderId) {
57
+ return candidate.orderId === input.orderId;
58
+ }
59
+
60
+ // clientOrderId is only a temporary identity for an order that does not yet
61
+ // have an orderId. A candidate that already carries an orderId (including an
62
+ // old order sitting in closed that reused this clientOrderId) must not be
63
+ // merged by a cid-only update; otherwise the stale orderId would be carried
64
+ // forward and pollute closed. When the orderId is later filled in, the
65
+ // candidate still lacks an orderId and matches normally.
66
+ return Boolean(
67
+ input.clientOrderId &&
68
+ candidate.clientOrderId === input.clientOrderId &&
69
+ !candidate.orderId,
70
+ );
71
+ }
72
+
73
+ export function isSystemClientOrderId(clientOrderId: string): boolean {
74
+ return SYSTEM_CLIENT_ORDER_ID_PATTERNS.some((pattern) =>
75
+ pattern.test(clientOrderId),
76
+ );
77
+ }
@@ -0,0 +1,36 @@
1
+ import type {
2
+ OrderDataStatus,
3
+ OrderSnapshot,
4
+ Venue,
5
+ } from "../../types/index.ts";
6
+
7
+ export interface OrderRecord {
8
+ accountId: string;
9
+ venue: Venue;
10
+ subscribed: boolean;
11
+ openOrders: Map<string, Map<string, OrderSnapshot>>;
12
+ closedOrders: Map<string, Map<string, OrderSnapshot>>;
13
+ localOrderLocations: Map<string, OrderLocation>;
14
+ orderIdIndex: Map<string, Map<string, string>>;
15
+ orderIdOnlyIndex: Map<string, Set<string>>;
16
+ clientOrderIdIndex: Map<string, Set<string>>;
17
+ pendingClientOrderIdIndex: Map<string, PendingOrderClaim>;
18
+ status: OrderDataStatus;
19
+ }
20
+
21
+ export type OrderTable = "open" | "closed";
22
+
23
+ export interface OrderLocation {
24
+ table: OrderTable;
25
+ symbol: string;
26
+ localOrderId: string;
27
+ }
28
+
29
+ export interface PendingOrderClaim {
30
+ localOrderId: string;
31
+ symbol: string;
32
+ }
33
+
34
+ export interface OrderManagerOptions {
35
+ maxClosedOrdersPerSymbol?: number;
36
+ }
@@ -0,0 +1,87 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { RawOrderUpdate } from "../../adapters/types.ts";
3
+ import { toCanonical } from "../../internal/decimal.ts";
4
+ import type { OrderSnapshot, Venue } from "../../types/index.ts";
5
+
6
+ export function createSnapshot(
7
+ accountId: string,
8
+ venue: Venue,
9
+ input: RawOrderUpdate,
10
+ previous?: OrderSnapshot,
11
+ ): OrderSnapshot {
12
+ const amount = new BigNumber(input.amount);
13
+ const rawFilled = new BigNumber(input.filled);
14
+ const filled = previous
15
+ ? BigNumber.maximum(rawFilled, previous.filled)
16
+ : rawFilled;
17
+ const filledWasClamped = !filled.eq(rawFilled);
18
+ const remaining =
19
+ input.remaining === undefined || filledWasClamped
20
+ ? amount.minus(filled)
21
+ : new BigNumber(input.remaining);
22
+
23
+ return {
24
+ accountId,
25
+ venue,
26
+ orderId: input.orderId ?? previous?.orderId,
27
+ clientOrderId: input.clientOrderId ?? previous?.clientOrderId,
28
+ symbol: input.symbol,
29
+ side: input.side,
30
+ type: input.type,
31
+ status: mergeOrderStatus(input, previous),
32
+ price:
33
+ input.price === undefined ? previous?.price : toCanonical(input.price),
34
+ triggerPrice:
35
+ input.triggerPrice === undefined
36
+ ? previous?.triggerPrice
37
+ : toCanonical(input.triggerPrice),
38
+ amount: toCanonical(amount),
39
+ filled: toCanonical(filled),
40
+ remaining: toCanonical(remaining),
41
+ reduceOnly: input.reduceOnly ?? previous?.reduceOnly,
42
+ positionSide: input.positionSide ?? previous?.positionSide,
43
+ avgFillPrice:
44
+ input.avgFillPrice === undefined
45
+ ? previous?.avgFillPrice
46
+ : toCanonical(input.avgFillPrice),
47
+ exchangeTs: input.exchangeTs,
48
+ receivedAt: input.receivedAt,
49
+ updatedAt: input.receivedAt,
50
+ seq: (previous?.seq ?? 0) + 1,
51
+ };
52
+ }
53
+
54
+ export function mergeOrderStatus(
55
+ input: RawOrderUpdate,
56
+ previous?: OrderSnapshot,
57
+ ): OrderSnapshot["status"] {
58
+ if (!previous) {
59
+ return input.status;
60
+ }
61
+
62
+ if (orderPriority(input.status) < orderPriority(previous.status)) {
63
+ return previous.status;
64
+ }
65
+
66
+ return input.status;
67
+ }
68
+
69
+ export function isOpenOrder(snapshot: OrderSnapshot): boolean {
70
+ return snapshot.status === "open" || snapshot.status === "partially_filled";
71
+ }
72
+
73
+ export function orderPriority(status: OrderSnapshot["status"]): number {
74
+ switch (status) {
75
+ case "filled":
76
+ return 5;
77
+ case "canceled":
78
+ case "expired":
79
+ return 4;
80
+ case "rejected":
81
+ return 3;
82
+ case "partially_filled":
83
+ return 2;
84
+ case "open":
85
+ return 1;
86
+ }
87
+ }