@imbingox/acex 0.4.0-beta.16 → 0.4.0-beta.18
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/CHANGELOG.md +12 -0
- package/docs/api.md +105 -7
- package/package.json +1 -1
- package/src/adapters/binance/adapter.ts +4 -1
- package/src/adapters/binance/market-catalog.ts +25 -20
- package/src/adapters/binance/private-adapter.ts +68 -53
- package/src/adapters/binance/rate-limit-topology.ts +257 -0
- package/src/adapters/binance/server-time.ts +20 -18
- package/src/client/runtime.ts +47 -6
- package/src/errors.ts +1 -0
- package/src/internal/async-event-bus.ts +75 -3
- package/src/internal/rate-limiter/snapshot.ts +67 -0
- package/src/internal/rate-limiter/state.ts +98 -0
- package/src/internal/rate-limiter/topology.ts +123 -0
- package/src/internal/rate-limiter/types.ts +49 -0
- package/src/internal/rate-limiter/usage.ts +48 -0
- package/src/internal/rate-limiter.ts +792 -74
- package/src/managers/account-manager.ts +43 -16
- package/src/managers/market-manager.ts +111 -7
- package/src/managers/order-manager.ts +42 -16
- package/src/types/account.ts +9 -2
- package/src/types/client.ts +8 -2
- package/src/types/market.ts +19 -4
- package/src/types/order.ts +9 -2
- package/src/types/shared.ts +209 -1
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
PrivateAccountDataConsumer,
|
|
15
15
|
PrivateSubscriptionState,
|
|
16
16
|
} from "../client/context.ts";
|
|
17
|
+
import { AcexError } from "../errors.ts";
|
|
18
|
+
import type { AsyncEventBusOverflowInfo } from "../internal/async-event-bus.ts";
|
|
17
19
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
18
20
|
import { toCanonical } from "../internal/decimal.ts";
|
|
19
21
|
import { matchesAccountFilter } from "../internal/filters.ts";
|
|
@@ -125,23 +127,33 @@ export class AccountManagerImpl
|
|
|
125
127
|
this.context = context;
|
|
126
128
|
|
|
127
129
|
this.events = {
|
|
128
|
-
status: (filter) =>
|
|
129
|
-
this.accountStatusBus.stream(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
status: (filter, options) =>
|
|
131
|
+
this.accountStatusBus.stream(
|
|
132
|
+
(event) =>
|
|
133
|
+
matchesAccountFilter(
|
|
134
|
+
{ accountId: event.accountId, venue: event.venue },
|
|
135
|
+
filter,
|
|
136
|
+
),
|
|
137
|
+
{
|
|
138
|
+
maxBuffer: options?.maxBuffer,
|
|
139
|
+
onOverflow: this.createOverflowHandler("account.status"),
|
|
140
|
+
},
|
|
134
141
|
),
|
|
135
|
-
updates: (filter) =>
|
|
136
|
-
this.accountBus.stream(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
updates: (filter, options) =>
|
|
143
|
+
this.accountBus.stream(
|
|
144
|
+
(event) =>
|
|
145
|
+
matchesAccountFilter(
|
|
146
|
+
{
|
|
147
|
+
accountId: event.accountId,
|
|
148
|
+
venue: event.venue,
|
|
149
|
+
symbol: "symbol" in event ? event.symbol : undefined,
|
|
150
|
+
},
|
|
151
|
+
filter,
|
|
152
|
+
),
|
|
153
|
+
{
|
|
154
|
+
maxBuffer: options?.maxBuffer,
|
|
155
|
+
onOverflow: this.createOverflowHandler("account.updates"),
|
|
156
|
+
},
|
|
145
157
|
),
|
|
146
158
|
};
|
|
147
159
|
}
|
|
@@ -872,4 +884,19 @@ export class AccountManagerImpl
|
|
|
872
884
|
this.accountStatusBus.publish(event);
|
|
873
885
|
this.context.publishHealthEvent(event);
|
|
874
886
|
}
|
|
887
|
+
|
|
888
|
+
private createOverflowHandler(
|
|
889
|
+
stream: string,
|
|
890
|
+
): (info: AsyncEventBusOverflowInfo) => void {
|
|
891
|
+
return ({ maxBuffer }) => {
|
|
892
|
+
const error = new AcexError(
|
|
893
|
+
"EVENT_BUFFER_OVERFLOW",
|
|
894
|
+
`Event stream buffer overflow: ${stream}`,
|
|
895
|
+
);
|
|
896
|
+
this.context.publishRuntimeError("account", error, {
|
|
897
|
+
stream,
|
|
898
|
+
maxBuffer,
|
|
899
|
+
});
|
|
900
|
+
};
|
|
901
|
+
}
|
|
875
902
|
}
|
|
@@ -19,10 +19,15 @@ import {
|
|
|
19
19
|
buildAcexErrorDetails,
|
|
20
20
|
formatAcexErrorMessage,
|
|
21
21
|
} from "../errors.ts";
|
|
22
|
+
import type {
|
|
23
|
+
AsyncEventBusOverflowInfo,
|
|
24
|
+
AsyncEventBusStreamOptions,
|
|
25
|
+
} from "../internal/async-event-bus.ts";
|
|
22
26
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
23
27
|
import { toCanonical } from "../internal/decimal.ts";
|
|
24
28
|
import { matchesMarketFilter } from "../internal/filters.ts";
|
|
25
29
|
import type {
|
|
30
|
+
EventStreamOptions,
|
|
26
31
|
FundingRateSnapshot,
|
|
27
32
|
FundingRateUpdatedEvent,
|
|
28
33
|
L1Book,
|
|
@@ -67,6 +72,14 @@ interface MarketRecord {
|
|
|
67
72
|
status: MarketDataStatus;
|
|
68
73
|
l1BookStream?: StreamHandle;
|
|
69
74
|
fundingRateStream?: StreamHandle;
|
|
75
|
+
lastPublishedStatusKey?: MarketStatusPublicationKey;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface MarketStatusPublicationKey {
|
|
79
|
+
activity: MarketDataStatus["activity"];
|
|
80
|
+
ready: MarketDataStatus["ready"];
|
|
81
|
+
freshness: MarketDataStatus["freshness"];
|
|
82
|
+
reason: MarketDataStatus["reason"];
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
interface CatalogFetchResult {
|
|
@@ -85,10 +98,38 @@ function marketKey(input: MarketKeyInput): string {
|
|
|
85
98
|
return `${input.venue}:${input.symbol}`;
|
|
86
99
|
}
|
|
87
100
|
|
|
101
|
+
function marketEventConflateKey(event: MarketEvent): string {
|
|
102
|
+
return `${event.type}:${marketKey(event)}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
88
105
|
function cloneMarketStatus(status: MarketDataStatus): MarketDataStatus {
|
|
89
106
|
return { ...status };
|
|
90
107
|
}
|
|
91
108
|
|
|
109
|
+
function statusPublicationKey(
|
|
110
|
+
status: MarketDataStatus,
|
|
111
|
+
): MarketStatusPublicationKey {
|
|
112
|
+
return {
|
|
113
|
+
activity: status.activity,
|
|
114
|
+
ready: status.ready,
|
|
115
|
+
freshness: status.freshness,
|
|
116
|
+
reason: status.reason,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function sameStatusPublicationKey(
|
|
121
|
+
current: MarketStatusPublicationKey,
|
|
122
|
+
previous: MarketStatusPublicationKey | undefined,
|
|
123
|
+
): boolean {
|
|
124
|
+
return (
|
|
125
|
+
previous !== undefined &&
|
|
126
|
+
current.activity === previous.activity &&
|
|
127
|
+
current.ready === previous.ready &&
|
|
128
|
+
current.freshness === previous.freshness &&
|
|
129
|
+
current.reason === previous.reason
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
92
133
|
function cloneStreamStatus(
|
|
93
134
|
status: MarketDataStreamStatus,
|
|
94
135
|
): MarketDataStreamStatus {
|
|
@@ -153,23 +194,49 @@ export class MarketManagerImpl
|
|
|
153
194
|
options.l1ReconnectMaxDelayMs ?? DEFAULT_L1_RECONNECT_MAX_DELAY_MS;
|
|
154
195
|
|
|
155
196
|
this.events = {
|
|
156
|
-
all: (filter) =>
|
|
157
|
-
this.marketBus.stream(
|
|
158
|
-
|
|
197
|
+
all: (filter, options) =>
|
|
198
|
+
this.marketBus.stream(
|
|
199
|
+
(event) => matchesMarketFilter(event, filter),
|
|
200
|
+
this.createStreamOptions(
|
|
201
|
+
"market.all",
|
|
202
|
+
options,
|
|
203
|
+
"buffer",
|
|
204
|
+
marketEventConflateKey,
|
|
205
|
+
),
|
|
206
|
+
),
|
|
207
|
+
fundingRateUpdates: (filter, options) =>
|
|
159
208
|
this.marketBus.stream(
|
|
160
209
|
(event): event is FundingRateUpdatedEvent =>
|
|
161
210
|
event.type === "funding_rate.updated" &&
|
|
162
211
|
matchesMarketFilter(event, filter),
|
|
212
|
+
this.createStreamOptions(
|
|
213
|
+
"market.fundingRateUpdates",
|
|
214
|
+
options,
|
|
215
|
+
"conflate",
|
|
216
|
+
marketKey,
|
|
217
|
+
),
|
|
163
218
|
),
|
|
164
|
-
l1BookUpdates: (filter) =>
|
|
219
|
+
l1BookUpdates: (filter, options) =>
|
|
165
220
|
this.marketBus.stream(
|
|
166
221
|
(event): event is L1BookUpdatedEvent =>
|
|
167
222
|
event.type === "l1_book.updated" &&
|
|
168
223
|
matchesMarketFilter(event, filter),
|
|
224
|
+
this.createStreamOptions(
|
|
225
|
+
"market.l1BookUpdates",
|
|
226
|
+
options,
|
|
227
|
+
"conflate",
|
|
228
|
+
marketKey,
|
|
229
|
+
),
|
|
169
230
|
),
|
|
170
|
-
status: (filter) =>
|
|
171
|
-
this.marketStatusBus.stream(
|
|
172
|
-
matchesMarketFilter(event, filter),
|
|
231
|
+
status: (filter, options) =>
|
|
232
|
+
this.marketStatusBus.stream(
|
|
233
|
+
(event) => matchesMarketFilter(event, filter),
|
|
234
|
+
this.createStreamOptions(
|
|
235
|
+
"market.status",
|
|
236
|
+
options,
|
|
237
|
+
"buffer",
|
|
238
|
+
marketKey,
|
|
239
|
+
),
|
|
173
240
|
),
|
|
174
241
|
};
|
|
175
242
|
}
|
|
@@ -1064,6 +1131,14 @@ export class MarketManagerImpl
|
|
|
1064
1131
|
record.status.lastReadyAt = this.resolveLastReadyAt(record);
|
|
1065
1132
|
}
|
|
1066
1133
|
|
|
1134
|
+
const publicationKey = statusPublicationKey(record.status);
|
|
1135
|
+
if (
|
|
1136
|
+
sameStatusPublicationKey(publicationKey, record.lastPublishedStatusKey)
|
|
1137
|
+
) {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
record.lastPublishedStatusKey = publicationKey;
|
|
1067
1142
|
this.publishStatus(record);
|
|
1068
1143
|
}
|
|
1069
1144
|
|
|
@@ -1184,6 +1259,35 @@ export class MarketManagerImpl
|
|
|
1184
1259
|
this.context.publishHealthEvent(event);
|
|
1185
1260
|
}
|
|
1186
1261
|
|
|
1262
|
+
private createStreamOptions<U extends { venue: Venue; symbol: string }>(
|
|
1263
|
+
stream: string,
|
|
1264
|
+
options: EventStreamOptions | undefined,
|
|
1265
|
+
defaultMode: "buffer" | "conflate",
|
|
1266
|
+
conflateKey: (event: U) => string,
|
|
1267
|
+
): AsyncEventBusStreamOptions<U> {
|
|
1268
|
+
return {
|
|
1269
|
+
mode: options?.mode ?? defaultMode,
|
|
1270
|
+
maxBuffer: options?.maxBuffer,
|
|
1271
|
+
conflateKey,
|
|
1272
|
+
onOverflow: this.createOverflowHandler(stream),
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
private createOverflowHandler(
|
|
1277
|
+
stream: string,
|
|
1278
|
+
): (info: AsyncEventBusOverflowInfo) => void {
|
|
1279
|
+
return ({ maxBuffer }) => {
|
|
1280
|
+
const error = new AcexError(
|
|
1281
|
+
"EVENT_BUFFER_OVERFLOW",
|
|
1282
|
+
`Event stream buffer overflow: ${stream}`,
|
|
1283
|
+
);
|
|
1284
|
+
this.context.publishRuntimeError("market", error, {
|
|
1285
|
+
stream,
|
|
1286
|
+
maxBuffer,
|
|
1287
|
+
});
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1187
1291
|
private async resumeStreams(): Promise<void> {
|
|
1188
1292
|
for (const record of this.records.values()) {
|
|
1189
1293
|
const market = record.market;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
buildAcexErrorDetails,
|
|
18
18
|
formatAcexErrorMessage,
|
|
19
19
|
} from "../errors.ts";
|
|
20
|
+
import type { AsyncEventBusOverflowInfo } from "../internal/async-event-bus.ts";
|
|
20
21
|
import { AsyncEventBus } from "../internal/async-event-bus.ts";
|
|
21
22
|
import { matchesOrderFilter } from "../internal/filters.ts";
|
|
22
23
|
import { isTransportError } from "../internal/http-client.ts";
|
|
@@ -116,23 +117,33 @@ export class OrderManagerImpl
|
|
|
116
117
|
);
|
|
117
118
|
|
|
118
119
|
this.events = {
|
|
119
|
-
status: (filter) =>
|
|
120
|
-
this.orderStatusBus.stream(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
status: (filter, options) =>
|
|
121
|
+
this.orderStatusBus.stream(
|
|
122
|
+
(event) =>
|
|
123
|
+
matchesOrderFilter(
|
|
124
|
+
{ accountId: event.accountId, venue: event.venue },
|
|
125
|
+
filter,
|
|
126
|
+
),
|
|
127
|
+
{
|
|
128
|
+
maxBuffer: options?.maxBuffer,
|
|
129
|
+
onOverflow: this.createOverflowHandler("order.status"),
|
|
130
|
+
},
|
|
125
131
|
),
|
|
126
|
-
updates: (filter) =>
|
|
127
|
-
this.orderBus.stream(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
updates: (filter, options) =>
|
|
133
|
+
this.orderBus.stream(
|
|
134
|
+
(event) =>
|
|
135
|
+
matchesOrderFilter(
|
|
136
|
+
{
|
|
137
|
+
accountId: event.accountId,
|
|
138
|
+
venue: event.venue,
|
|
139
|
+
symbol: "symbol" in event ? event.symbol : undefined,
|
|
140
|
+
},
|
|
141
|
+
filter,
|
|
142
|
+
),
|
|
143
|
+
{
|
|
144
|
+
maxBuffer: options?.maxBuffer,
|
|
145
|
+
onOverflow: this.createOverflowHandler("order.updates"),
|
|
146
|
+
},
|
|
136
147
|
),
|
|
137
148
|
};
|
|
138
149
|
}
|
|
@@ -1270,6 +1281,21 @@ export class OrderManagerImpl
|
|
|
1270
1281
|
},
|
|
1271
1282
|
};
|
|
1272
1283
|
}
|
|
1284
|
+
|
|
1285
|
+
private createOverflowHandler(
|
|
1286
|
+
stream: string,
|
|
1287
|
+
): (info: AsyncEventBusOverflowInfo) => void {
|
|
1288
|
+
return ({ maxBuffer }) => {
|
|
1289
|
+
const error = new AcexError(
|
|
1290
|
+
"EVENT_BUFFER_OVERFLOW",
|
|
1291
|
+
`Event stream buffer overflow: ${stream}`,
|
|
1292
|
+
);
|
|
1293
|
+
this.context.publishRuntimeError("order", error, {
|
|
1294
|
+
stream,
|
|
1295
|
+
maxBuffer,
|
|
1296
|
+
});
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1273
1299
|
}
|
|
1274
1300
|
|
|
1275
1301
|
function isOrderErrorCode(
|
package/src/types/account.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BufferedEventStreamOptions,
|
|
2
3
|
PrivateRuntimeReason,
|
|
3
4
|
PrivateRuntimeStatus,
|
|
4
5
|
SubscriptionActivity,
|
|
@@ -158,8 +159,14 @@ export type AccountEvent =
|
|
|
158
159
|
| AccountSnapshotReplacedEvent;
|
|
159
160
|
|
|
160
161
|
export interface AccountEventStreams {
|
|
161
|
-
updates(
|
|
162
|
-
|
|
162
|
+
updates(
|
|
163
|
+
filter?: AccountEventFilter,
|
|
164
|
+
options?: BufferedEventStreamOptions,
|
|
165
|
+
): AsyncIterable<AccountEvent>;
|
|
166
|
+
status(
|
|
167
|
+
filter?: AccountEventFilter,
|
|
168
|
+
options?: BufferedEventStreamOptions,
|
|
169
|
+
): AsyncIterable<AccountStatusChangedEvent>;
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
export interface AccountManager {
|
package/src/types/client.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
import type {
|
|
19
19
|
AccountCredentials,
|
|
20
20
|
AcexInternalError,
|
|
21
|
+
BufferedEventStreamOptions,
|
|
21
22
|
ClientStatus,
|
|
22
23
|
CreateClientOptions,
|
|
23
24
|
RegisterAccountInput,
|
|
@@ -54,8 +55,13 @@ export interface HealthEventFilter {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
export interface ClientEventStreams {
|
|
57
|
-
health(
|
|
58
|
-
|
|
58
|
+
health(
|
|
59
|
+
filter?: HealthEventFilter,
|
|
60
|
+
options?: BufferedEventStreamOptions,
|
|
61
|
+
): AsyncIterable<HealthEvent>;
|
|
62
|
+
errors(
|
|
63
|
+
options?: BufferedEventStreamOptions,
|
|
64
|
+
): AsyncIterable<AcexInternalError>;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
export type VenueRuntimeStatus = "available" | "type_only" | "reserved";
|
package/src/types/market.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type BigNumber from "bignumber.js";
|
|
2
2
|
import type { AcexError } from "../errors.ts";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
EventStreamOptions,
|
|
5
|
+
MarketFreshness,
|
|
6
|
+
SubscriptionActivity,
|
|
7
|
+
Venue,
|
|
8
|
+
} from "./shared.ts";
|
|
4
9
|
|
|
5
10
|
export type MarketType = "spot" | "swap" | "future";
|
|
6
11
|
|
|
@@ -170,12 +175,22 @@ export type MarketEvent =
|
|
|
170
175
|
| MarketStatusChangedEvent;
|
|
171
176
|
|
|
172
177
|
export interface MarketEventStreams {
|
|
173
|
-
l1BookUpdates(
|
|
178
|
+
l1BookUpdates(
|
|
179
|
+
filter?: MarketEventFilter,
|
|
180
|
+
options?: EventStreamOptions,
|
|
181
|
+
): AsyncIterable<L1BookUpdatedEvent>;
|
|
174
182
|
fundingRateUpdates(
|
|
175
183
|
filter?: MarketEventFilter,
|
|
184
|
+
options?: EventStreamOptions,
|
|
176
185
|
): AsyncIterable<FundingRateUpdatedEvent>;
|
|
177
|
-
status(
|
|
178
|
-
|
|
186
|
+
status(
|
|
187
|
+
filter?: MarketEventFilter,
|
|
188
|
+
options?: EventStreamOptions,
|
|
189
|
+
): AsyncIterable<MarketStatusChangedEvent>;
|
|
190
|
+
all(
|
|
191
|
+
filter?: MarketEventFilter,
|
|
192
|
+
options?: EventStreamOptions,
|
|
193
|
+
): AsyncIterable<MarketEvent>;
|
|
179
194
|
}
|
|
180
195
|
|
|
181
196
|
export interface MarketManager {
|
package/src/types/order.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PositionSide } from "./account.ts";
|
|
2
2
|
import type {
|
|
3
|
+
BufferedEventStreamOptions,
|
|
3
4
|
PrivateRuntimeReason,
|
|
4
5
|
PrivateRuntimeStatus,
|
|
5
6
|
SubscriptionActivity,
|
|
@@ -160,8 +161,14 @@ export type OrderEvent =
|
|
|
160
161
|
| OrderSnapshotReplacedEvent;
|
|
161
162
|
|
|
162
163
|
export interface OrderEventStreams {
|
|
163
|
-
updates(
|
|
164
|
-
|
|
164
|
+
updates(
|
|
165
|
+
filter?: OrderEventFilter,
|
|
166
|
+
options?: BufferedEventStreamOptions,
|
|
167
|
+
): AsyncIterable<OrderEvent>;
|
|
168
|
+
status(
|
|
169
|
+
filter?: OrderEventFilter,
|
|
170
|
+
options?: BufferedEventStreamOptions,
|
|
171
|
+
): AsyncIterable<OrderStatusChangedEvent>;
|
|
165
172
|
}
|
|
166
173
|
|
|
167
174
|
export interface OrderManager {
|