@imbingox/acex 0.3.0-beta.1 → 0.3.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 +71 -24
- package/docs/api.md +1457 -0
- package/package.json +5 -2
- package/src/adapters/binance/adapter.ts +11 -2
- package/src/adapters/binance/market-catalog.ts +2 -2
- package/src/adapters/binance/private-adapter.ts +44 -4
- package/src/adapters/juplend/private-adapter.ts +517 -0
- package/src/adapters/types.ts +34 -4
- package/src/client/context.ts +16 -11
- package/src/client/private-subscription-coordinator.ts +101 -47
- package/src/client/runtime.ts +64 -20
- package/src/client/venue-capabilities.ts +109 -0
- package/src/errors.ts +1 -1
- package/src/internal/filters.ts +9 -9
- package/src/managers/account-manager.ts +95 -58
- package/src/managers/market-manager.ts +45 -45
- package/src/managers/order-manager.ts +49 -56
- package/src/types/account.ts +30 -10
- package/src/types/client.ts +73 -2
- package/src/types/market.ts +12 -16
- package/src/types/order.ts +7 -7
- package/src/types/shared.ts +43 -7
package/src/client/runtime.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { BinanceMarketAdapter } from "../adapters/binance/adapter.ts";
|
|
2
2
|
import { BinancePrivateAdapter } from "../adapters/binance/private-adapter.ts";
|
|
3
|
+
import { JuplendPrivateAdapter } from "../adapters/juplend/private-adapter.ts";
|
|
3
4
|
import type {
|
|
4
5
|
CancelAllOrdersRequest,
|
|
5
6
|
CancelOrderRequest,
|
|
6
7
|
CreateOrderRequest,
|
|
8
|
+
MarketAdapter,
|
|
7
9
|
PrivateUserDataAdapter,
|
|
8
10
|
RawOrderUpdate,
|
|
9
11
|
} from "../adapters/types.ts";
|
|
@@ -33,6 +35,8 @@ import type {
|
|
|
33
35
|
RegisterAccountInput,
|
|
34
36
|
RegisterAccountResult,
|
|
35
37
|
StopOptions,
|
|
38
|
+
Venue,
|
|
39
|
+
VenueCapabilities,
|
|
36
40
|
} from "../types/index.ts";
|
|
37
41
|
import {
|
|
38
42
|
type ClientContext,
|
|
@@ -43,6 +47,10 @@ import {
|
|
|
43
47
|
type RegisteredAccountRecord,
|
|
44
48
|
} from "./context.ts";
|
|
45
49
|
import { PrivateSubscriptionCoordinator } from "./private-subscription-coordinator.ts";
|
|
50
|
+
import {
|
|
51
|
+
getVenueCapabilitiesSnapshot,
|
|
52
|
+
listVenueCapabilitiesSnapshots,
|
|
53
|
+
} from "./venue-capabilities.ts";
|
|
46
54
|
|
|
47
55
|
const activeClients = new Set<AcexClientImpl>();
|
|
48
56
|
|
|
@@ -87,14 +95,22 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
87
95
|
private readonly marketManager: MarketManagerImpl;
|
|
88
96
|
private readonly accountManager: AccountManagerImpl;
|
|
89
97
|
private readonly orderManager: OrderManagerImpl;
|
|
90
|
-
private readonly
|
|
98
|
+
private readonly marketAdapters: Map<Venue, MarketAdapter>;
|
|
99
|
+
private readonly privateAdapters: Map<Venue, PrivateUserDataAdapter>;
|
|
91
100
|
private readonly privateCoordinator: PrivateSubscriptionCoordinator;
|
|
92
101
|
|
|
93
102
|
constructor(options: CreateClientOptions = {}) {
|
|
94
103
|
activeClients.add(this);
|
|
95
104
|
|
|
96
105
|
const marketAdapter = new BinanceMarketAdapter();
|
|
97
|
-
this.
|
|
106
|
+
this.marketAdapters = new Map([[marketAdapter.venue, marketAdapter]]);
|
|
107
|
+
const privateAdapters = [
|
|
108
|
+
new BinancePrivateAdapter(),
|
|
109
|
+
new JuplendPrivateAdapter(),
|
|
110
|
+
];
|
|
111
|
+
this.privateAdapters = new Map(
|
|
112
|
+
privateAdapters.map((adapter) => [adapter.venue, adapter]),
|
|
113
|
+
);
|
|
98
114
|
|
|
99
115
|
this.marketManager = new MarketManagerImpl(this, marketAdapter, {
|
|
100
116
|
initialL1TimeoutMs: options.market?.l1InitialMessageTimeoutMs,
|
|
@@ -106,7 +122,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
106
122
|
this.orderManager = new OrderManagerImpl(this);
|
|
107
123
|
this.privateCoordinator = new PrivateSubscriptionCoordinator(
|
|
108
124
|
this,
|
|
109
|
-
|
|
125
|
+
privateAdapters,
|
|
110
126
|
this.accountManager as PrivateAccountDataConsumer,
|
|
111
127
|
this.orderManager as PrivateOrderDataConsumer,
|
|
112
128
|
options.account,
|
|
@@ -134,6 +150,20 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
134
150
|
};
|
|
135
151
|
}
|
|
136
152
|
|
|
153
|
+
getVenueCapabilities(venue: Venue): VenueCapabilities {
|
|
154
|
+
return getVenueCapabilitiesSnapshot(venue, {
|
|
155
|
+
marketAdapters: this.marketAdapters,
|
|
156
|
+
privateAdapters: this.privateAdapters,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
listVenueCapabilities(): VenueCapabilities[] {
|
|
161
|
+
return listVenueCapabilitiesSnapshots({
|
|
162
|
+
marketAdapters: this.marketAdapters,
|
|
163
|
+
privateAdapters: this.privateAdapters,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
137
167
|
async registerAccount(
|
|
138
168
|
input: RegisterAccountInput,
|
|
139
169
|
): Promise<RegisterAccountResult> {
|
|
@@ -141,20 +171,20 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
141
171
|
throw this.createError(
|
|
142
172
|
"ACCOUNT_ALREADY_EXISTS",
|
|
143
173
|
`Account already exists: ${input.accountId}`,
|
|
144
|
-
{ accountId: input.accountId,
|
|
174
|
+
{ accountId: input.accountId, venue: input.venue },
|
|
145
175
|
);
|
|
146
176
|
}
|
|
147
177
|
|
|
148
178
|
this.registeredAccounts.set(input.accountId, {
|
|
149
179
|
accountId: input.accountId,
|
|
150
|
-
|
|
180
|
+
venue: input.venue,
|
|
151
181
|
credentials: input.credentials,
|
|
152
|
-
options: input.options,
|
|
182
|
+
options: input.options as Record<string, unknown> | undefined,
|
|
153
183
|
});
|
|
154
184
|
|
|
155
185
|
return {
|
|
156
186
|
accountId: input.accountId,
|
|
157
|
-
|
|
187
|
+
venue: input.venue,
|
|
158
188
|
};
|
|
159
189
|
}
|
|
160
190
|
|
|
@@ -177,8 +207,8 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
177
207
|
return;
|
|
178
208
|
}
|
|
179
209
|
|
|
180
|
-
this.accountManager.onCredentialsUpdated(accountId, account.
|
|
181
|
-
this.orderManager.onCredentialsUpdated(accountId, account.
|
|
210
|
+
this.accountManager.onCredentialsUpdated(accountId, account.venue);
|
|
211
|
+
this.orderManager.onCredentialsUpdated(accountId, account.venue);
|
|
182
212
|
this.privateCoordinator.onCredentialsUpdated(accountId);
|
|
183
213
|
}
|
|
184
214
|
|
|
@@ -262,14 +292,14 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
262
292
|
|
|
263
293
|
ensurePrivateCredentials(accountId: string): void {
|
|
264
294
|
const account = this.getRegisteredAccount(accountId);
|
|
265
|
-
if (hasPrivateCredentials(account.credentials)) {
|
|
295
|
+
if (hasPrivateCredentials(account.credentials, account.venue)) {
|
|
266
296
|
return;
|
|
267
297
|
}
|
|
268
298
|
|
|
269
299
|
throw this.createError(
|
|
270
300
|
"CREDENTIALS_MISSING",
|
|
271
301
|
`Account credentials are required for private subscriptions: ${accountId}`,
|
|
272
|
-
{ accountId,
|
|
302
|
+
{ accountId, venue: account.venue },
|
|
273
303
|
);
|
|
274
304
|
}
|
|
275
305
|
|
|
@@ -303,7 +333,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
303
333
|
positionSide: input.positionSide,
|
|
304
334
|
};
|
|
305
335
|
|
|
306
|
-
return this.
|
|
336
|
+
return this.getPrivateAdapter(account.venue).createOrder(
|
|
307
337
|
account.credentials ?? {},
|
|
308
338
|
request,
|
|
309
339
|
account.options,
|
|
@@ -318,7 +348,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
318
348
|
clientOrderId: input.clientOrderId,
|
|
319
349
|
};
|
|
320
350
|
|
|
321
|
-
return this.
|
|
351
|
+
return this.getPrivateAdapter(account.venue).cancelOrder(
|
|
322
352
|
account.credentials ?? {},
|
|
323
353
|
request,
|
|
324
354
|
account.options,
|
|
@@ -331,7 +361,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
331
361
|
symbol: input.symbol,
|
|
332
362
|
};
|
|
333
363
|
|
|
334
|
-
return this.
|
|
364
|
+
return this.getPrivateAdapter(account.venue).cancelAllOrders(
|
|
335
365
|
account.credentials ?? {},
|
|
336
366
|
request,
|
|
337
367
|
account.options,
|
|
@@ -390,22 +420,36 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
390
420
|
|
|
391
421
|
private getPrivateCommandAccount(accountId: string): RegisteredAccountRecord {
|
|
392
422
|
const account = this.getRegisteredAccount(accountId);
|
|
393
|
-
|
|
423
|
+
const adapter = this.getPrivateAdapter(account.venue);
|
|
424
|
+
if (adapter.venue === "juplend") {
|
|
394
425
|
throw this.createError(
|
|
395
|
-
"
|
|
396
|
-
`
|
|
397
|
-
{ accountId,
|
|
426
|
+
"VENUE_NOT_SUPPORTED",
|
|
427
|
+
`Venue does not support private order commands: ${account.venue}`,
|
|
428
|
+
{ accountId, venue: account.venue },
|
|
398
429
|
);
|
|
399
430
|
}
|
|
400
431
|
|
|
401
|
-
if (!hasPrivateCredentials(account.credentials)) {
|
|
432
|
+
if (!hasPrivateCredentials(account.credentials, account.venue)) {
|
|
402
433
|
throw this.createError(
|
|
403
434
|
"CREDENTIALS_MISSING",
|
|
404
435
|
`Account credentials are required for private order commands: ${accountId}`,
|
|
405
|
-
{ accountId,
|
|
436
|
+
{ accountId, venue: account.venue },
|
|
406
437
|
);
|
|
407
438
|
}
|
|
408
439
|
|
|
409
440
|
return account;
|
|
410
441
|
}
|
|
442
|
+
|
|
443
|
+
private getPrivateAdapter(venue: Venue): PrivateUserDataAdapter {
|
|
444
|
+
const adapter = this.privateAdapters.get(venue);
|
|
445
|
+
if (!adapter) {
|
|
446
|
+
throw this.createError(
|
|
447
|
+
"VENUE_NOT_SUPPORTED",
|
|
448
|
+
`Venue is not supported yet: ${venue}`,
|
|
449
|
+
{ venue },
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return adapter;
|
|
454
|
+
}
|
|
411
455
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MarketAdapter,
|
|
3
|
+
PrivateUserDataAdapter,
|
|
4
|
+
} from "../adapters/types.ts";
|
|
5
|
+
import type {
|
|
6
|
+
VenueAccountCapabilities,
|
|
7
|
+
VenueCapabilities,
|
|
8
|
+
VenueMarketCapabilities,
|
|
9
|
+
VenueOrderCapabilities,
|
|
10
|
+
} from "../types/client.ts";
|
|
11
|
+
import { SUPPORTED_VENUES, type Venue } from "../types/shared.ts";
|
|
12
|
+
|
|
13
|
+
const unsupportedAccount: VenueAccountCapabilities = {
|
|
14
|
+
register: "unsupported",
|
|
15
|
+
snapshot: "unsupported",
|
|
16
|
+
updates: "unsupported",
|
|
17
|
+
balances: "unsupported",
|
|
18
|
+
positions: "unsupported",
|
|
19
|
+
risk: "unsupported",
|
|
20
|
+
lending: "unsupported",
|
|
21
|
+
credentialsRequired: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const unsupportedOrder: Omit<VenueOrderCapabilities, "reason"> = {
|
|
25
|
+
supported: false,
|
|
26
|
+
openOrders: "unsupported",
|
|
27
|
+
updates: "unsupported",
|
|
28
|
+
create: "unsupported",
|
|
29
|
+
cancel: "unsupported",
|
|
30
|
+
cancelAll: "unsupported",
|
|
31
|
+
orderTypes: [],
|
|
32
|
+
timeInForce: [],
|
|
33
|
+
postOnly: false,
|
|
34
|
+
reduceOnly: false,
|
|
35
|
+
positionSide: "unsupported",
|
|
36
|
+
clientOrderId: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const typeOnlyNotes = [
|
|
40
|
+
"Venue is declared in public types but has no runtime adapter yet.",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const unsupportedMarket: VenueMarketCapabilities = {
|
|
44
|
+
catalog: "unsupported",
|
|
45
|
+
l1Book: "unsupported",
|
|
46
|
+
fundingRate: "unsupported",
|
|
47
|
+
marketTypes: [],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export interface VenueCapabilityAdapterRegistry {
|
|
51
|
+
marketAdapters: ReadonlyMap<Venue, MarketAdapter>;
|
|
52
|
+
privateAdapters: ReadonlyMap<Venue, PrivateUserDataAdapter>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getVenueCapabilitiesSnapshot(
|
|
56
|
+
venue: Venue,
|
|
57
|
+
registry: VenueCapabilityAdapterRegistry,
|
|
58
|
+
): VenueCapabilities {
|
|
59
|
+
const marketAdapter = registry.marketAdapters.get(venue);
|
|
60
|
+
const privateAdapter = registry.privateAdapters.get(venue);
|
|
61
|
+
|
|
62
|
+
return cloneVenueCapabilities({
|
|
63
|
+
venue,
|
|
64
|
+
runtimeStatus: marketAdapter || privateAdapter ? "available" : "type_only",
|
|
65
|
+
readOnly: privateAdapter?.readOnly ?? false,
|
|
66
|
+
notes:
|
|
67
|
+
privateAdapter?.notes ??
|
|
68
|
+
(marketAdapter
|
|
69
|
+
? [
|
|
70
|
+
"Capabilities describe the current SDK runtime, not the venue's full API surface.",
|
|
71
|
+
]
|
|
72
|
+
: typeOnlyNotes),
|
|
73
|
+
market: marketAdapter?.marketCapabilities ?? unsupportedMarket,
|
|
74
|
+
account: privateAdapter?.accountCapabilities ?? unsupportedAccount,
|
|
75
|
+
order: privateAdapter?.orderCapabilities ?? {
|
|
76
|
+
...unsupportedOrder,
|
|
77
|
+
reason: "not_implemented",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function listVenueCapabilitiesSnapshots(
|
|
83
|
+
registry: VenueCapabilityAdapterRegistry,
|
|
84
|
+
): VenueCapabilities[] {
|
|
85
|
+
return SUPPORTED_VENUES.map((venue) =>
|
|
86
|
+
getVenueCapabilitiesSnapshot(venue, registry),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cloneVenueCapabilities(
|
|
91
|
+
capabilities: VenueCapabilities,
|
|
92
|
+
): VenueCapabilities {
|
|
93
|
+
return {
|
|
94
|
+
...capabilities,
|
|
95
|
+
notes: [...capabilities.notes],
|
|
96
|
+
market: {
|
|
97
|
+
...capabilities.market,
|
|
98
|
+
marketTypes: [...capabilities.market.marketTypes],
|
|
99
|
+
},
|
|
100
|
+
account: {
|
|
101
|
+
...capabilities.account,
|
|
102
|
+
},
|
|
103
|
+
order: {
|
|
104
|
+
...capabilities.order,
|
|
105
|
+
orderTypes: [...capabilities.order.orderTypes],
|
|
106
|
+
timeInForce: [...capabilities.order.timeInForce],
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
package/src/errors.ts
CHANGED
package/src/internal/filters.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AccountEventFilter,
|
|
3
|
-
Exchange,
|
|
4
3
|
HealthEvent,
|
|
5
4
|
HealthEventFilter,
|
|
6
5
|
MarketEventFilter,
|
|
7
6
|
OrderEventFilter,
|
|
7
|
+
Venue,
|
|
8
8
|
} from "../types/index.ts";
|
|
9
9
|
|
|
10
10
|
export function matchesMarketFilter(
|
|
11
|
-
event: {
|
|
11
|
+
event: { venue: Venue; symbol: string },
|
|
12
12
|
filter?: MarketEventFilter,
|
|
13
13
|
): boolean {
|
|
14
14
|
if (!filter) {
|
|
15
15
|
return true;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
if (filter.
|
|
18
|
+
if (filter.venue && event.venue !== filter.venue) {
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ export function matchesMarketFilter(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function matchesAccountFilter(
|
|
30
|
-
event: { accountId: string;
|
|
30
|
+
event: { accountId: string; venue: Venue; symbol?: string },
|
|
31
31
|
filter?: AccountEventFilter,
|
|
32
32
|
): boolean {
|
|
33
33
|
if (!filter) {
|
|
@@ -38,7 +38,7 @@ export function matchesAccountFilter(
|
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (filter.
|
|
41
|
+
if (filter.venue && event.venue !== filter.venue) {
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -50,7 +50,7 @@ export function matchesAccountFilter(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export function matchesOrderFilter(
|
|
53
|
-
event: { accountId: string;
|
|
53
|
+
event: { accountId: string; venue: Venue; symbol?: string },
|
|
54
54
|
filter?: OrderEventFilter,
|
|
55
55
|
): boolean {
|
|
56
56
|
if (!filter) {
|
|
@@ -61,7 +61,7 @@ export function matchesOrderFilter(
|
|
|
61
61
|
return false;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
if (filter.
|
|
64
|
+
if (filter.venue && event.venue !== filter.venue) {
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -95,8 +95,8 @@ export function matchesHealthFilter(
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
if (filter.
|
|
99
|
-
if (!("
|
|
98
|
+
if (filter.venue) {
|
|
99
|
+
if (!("venue" in event) || event.venue !== filter.venue) {
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
102
102
|
}
|