@imbingox/acex 0.3.0-beta.3 → 0.3.0-beta.4
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 +4 -1
- package/docs/api.md +11 -4
- package/package.json +2 -2
- package/src/adapters/binance/private-adapter.ts +75 -2
- package/src/adapters/types.ts +5 -0
- package/src/client/context.ts +1 -0
- package/src/client/private-subscription-coordinator.ts +173 -0
- package/src/managers/account-manager.ts +23 -10
- package/src/types/account.ts +1 -0
- package/src/types/shared.ts +3 -0
package/README.md
CHANGED
|
@@ -60,11 +60,14 @@ await client.stop();
|
|
|
60
60
|
|
|
61
61
|
### 同一个 client 同时使用 Binance + Juplend
|
|
62
62
|
|
|
63
|
-
`createClient({ account: { juplend: { pollIntervalMs } } })`
|
|
63
|
+
`createClient({ account: { binance: { riskPollIntervalMs }, juplend: { pollIntervalMs } } })` 只是分别配置 Binance 风险/仓位校准间隔和 Juplend 账户 polling 间隔,不代表这个 client 只能注册某个 venue。一个 `AcexClient` 可以同时注册 Binance 交易账户和 Juplend 借贷只读账户,用同一个 `AccountManager` 对比风险值。
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
66
|
const client = createClient({
|
|
67
67
|
account: {
|
|
68
|
+
binance: {
|
|
69
|
+
riskPollIntervalMs: 5_000,
|
|
70
|
+
},
|
|
68
71
|
juplend: {
|
|
69
72
|
pollIntervalMs: 30_000,
|
|
70
73
|
},
|
package/docs/api.md
CHANGED
|
@@ -64,11 +64,14 @@ for await (const event of client.market.events.l1BookUpdates({
|
|
|
64
64
|
await client.stop();
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
同一个 client 可以同时注册 Binance 交易账户和 Juplend 借贷只读账户。`account.juplend.pollIntervalMs`
|
|
67
|
+
同一个 client 可以同时注册 Binance 交易账户和 Juplend 借贷只读账户。`account.binance.riskPollIntervalMs` 只配置 Binance 风险/仓位校准间隔;`account.juplend.pollIntervalMs` 只配置 Juplend polling 间隔,不会把 client 限定为某个 venue:
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
70
|
const client = createClient({
|
|
71
71
|
account: {
|
|
72
|
+
binance: {
|
|
73
|
+
riskPollIntervalMs: 5_000,
|
|
74
|
+
},
|
|
72
75
|
juplend: {
|
|
73
76
|
pollIntervalMs: 30_000,
|
|
74
77
|
},
|
|
@@ -208,6 +211,9 @@ const client = createClient({
|
|
|
208
211
|
streamReconnectDelayMs: 1_000,
|
|
209
212
|
streamReconnectMaxDelayMs: 10_000,
|
|
210
213
|
listenKeyKeepAliveMs: 30 * 60_000,
|
|
214
|
+
binance: {
|
|
215
|
+
riskPollIntervalMs: 5_000,
|
|
216
|
+
},
|
|
211
217
|
juplend: {
|
|
212
218
|
pollIntervalMs: 30_000,
|
|
213
219
|
},
|
|
@@ -902,9 +908,6 @@ type PrivateRuntimeStatus =
|
|
|
902
908
|
type PrivateRuntimeReason =
|
|
903
909
|
| "credentials_missing" | "auth_failed" | "http_failed" | "rate_limited"
|
|
904
910
|
| "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
905
|
-
type PrivateRuntimeReason =
|
|
906
|
-
| "credentials_missing" | "auth_failed"
|
|
907
|
-
| "ws_disconnected" | "heartbeat_timeout" | "reconciling";
|
|
908
911
|
|
|
909
912
|
type OrderSide = "buy" | "sell";
|
|
910
913
|
type OrderStatus =
|
|
@@ -930,6 +933,9 @@ interface AccountRuntimeOptions {
|
|
|
930
933
|
streamReconnectDelayMs?: number;
|
|
931
934
|
streamReconnectMaxDelayMs?: number;
|
|
932
935
|
listenKeyKeepAliveMs?: number;
|
|
936
|
+
binance?: {
|
|
937
|
+
riskPollIntervalMs?: number; // 默认 5_000
|
|
938
|
+
};
|
|
933
939
|
juplend?: {
|
|
934
940
|
pollIntervalMs?: number;
|
|
935
941
|
};
|
|
@@ -1218,6 +1224,7 @@ interface RiskSnapshot {
|
|
|
1218
1224
|
venue: Venue;
|
|
1219
1225
|
equity?: BigNumber;
|
|
1220
1226
|
riskRatio?: BigNumber;
|
|
1227
|
+
actualLeverage?: BigNumber;
|
|
1221
1228
|
initialMargin?: BigNumber;
|
|
1222
1229
|
maintenanceMargin?: BigNumber;
|
|
1223
1230
|
exchangeTs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imbingox/acex",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.4",
|
|
4
4
|
"description": "Multi-exchange trading SDK for market data, account, and order management",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@biomejs/biome": "^2.4.10",
|
|
50
50
|
"@changesets/cli": "^2.31.0",
|
|
51
|
+
"@mindfoldhq/trellis": "^0.4.0",
|
|
51
52
|
"@types/bun": "latest",
|
|
52
53
|
"typescript": "^6.0.2"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"@mindfoldhq/trellis": "^0.4.0",
|
|
56
56
|
"bignumber.js": "^11.0.0"
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -56,6 +56,7 @@ interface BinancePapiUmPosition {
|
|
|
56
56
|
unrealizedProfit?: string;
|
|
57
57
|
liquidationPrice?: string;
|
|
58
58
|
leverage?: string;
|
|
59
|
+
notional?: string;
|
|
59
60
|
positionSide?: string;
|
|
60
61
|
updateTime?: number;
|
|
61
62
|
}
|
|
@@ -285,14 +286,18 @@ function mapBalance(
|
|
|
285
286
|
function mapAccountRisk(
|
|
286
287
|
input: BinancePapiAccount,
|
|
287
288
|
receivedAt: number,
|
|
289
|
+
positions: BinancePapiUmPosition[] = [],
|
|
288
290
|
): RawRiskUpdate | undefined {
|
|
289
291
|
const uniMmr = firstString(input.uniMMR);
|
|
290
292
|
const riskRatio = uniMmr
|
|
291
293
|
? new BigNumber(1).dividedBy(uniMmr).toString(10)
|
|
292
294
|
: undefined;
|
|
295
|
+
const equity = firstString(input.accountEquity, input.totalEquity);
|
|
296
|
+
const actualLeverage = calculateActualLeverage(equity, positions);
|
|
293
297
|
const risk: RawRiskUpdate = {
|
|
294
|
-
equity
|
|
298
|
+
equity,
|
|
295
299
|
riskRatio,
|
|
300
|
+
actualLeverage,
|
|
296
301
|
initialMargin: firstString(
|
|
297
302
|
input.accountInitialMargin,
|
|
298
303
|
input.totalInitialMargin,
|
|
@@ -308,6 +313,7 @@ function mapAccountRisk(
|
|
|
308
313
|
if (
|
|
309
314
|
!risk.equity &&
|
|
310
315
|
!risk.riskRatio &&
|
|
316
|
+
!risk.actualLeverage &&
|
|
311
317
|
!risk.initialMargin &&
|
|
312
318
|
!risk.maintenanceMargin
|
|
313
319
|
) {
|
|
@@ -317,6 +323,34 @@ function mapAccountRisk(
|
|
|
317
323
|
return risk;
|
|
318
324
|
}
|
|
319
325
|
|
|
326
|
+
function calculateActualLeverage(
|
|
327
|
+
equity: string | undefined,
|
|
328
|
+
positions: BinancePapiUmPosition[],
|
|
329
|
+
): string | undefined {
|
|
330
|
+
if (!equity) {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const equityValue = new BigNumber(equity);
|
|
335
|
+
if (!equityValue.isFinite() || equityValue.isZero()) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const grossExposure = positions.reduce((total, position) => {
|
|
340
|
+
const notional = firstString(position.notional);
|
|
341
|
+
if (!notional) {
|
|
342
|
+
return total;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const value = new BigNumber(notional);
|
|
346
|
+
return value.isFinite() ? total.plus(value.absoluteValue()) : total;
|
|
347
|
+
}, new BigNumber(0));
|
|
348
|
+
|
|
349
|
+
return grossExposure.isZero()
|
|
350
|
+
? undefined
|
|
351
|
+
: grossExposure.dividedBy(equityValue).toString(10);
|
|
352
|
+
}
|
|
353
|
+
|
|
320
354
|
function mapUmPosition(
|
|
321
355
|
input: BinancePapiUmPosition,
|
|
322
356
|
receivedAt: number,
|
|
@@ -339,6 +373,22 @@ function mapUmPosition(
|
|
|
339
373
|
};
|
|
340
374
|
}
|
|
341
375
|
|
|
376
|
+
function mapAccountRefresh(
|
|
377
|
+
account: BinancePapiAccount,
|
|
378
|
+
positions: BinancePapiUmPosition[],
|
|
379
|
+
receivedAt: number,
|
|
380
|
+
): RawAccountUpdate {
|
|
381
|
+
return {
|
|
382
|
+
positions: positions.flatMap((position) => {
|
|
383
|
+
const mapped = mapUmPosition(position, receivedAt);
|
|
384
|
+
return mapped ? [mapped] : [];
|
|
385
|
+
}),
|
|
386
|
+
risk: mapAccountRisk(account, receivedAt, positions),
|
|
387
|
+
exchangeTs: account.updateTime,
|
|
388
|
+
receivedAt,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
342
392
|
function mapOpenOrder(
|
|
343
393
|
input: BinancePapiOpenOrder,
|
|
344
394
|
receivedAt: number,
|
|
@@ -550,12 +600,35 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
|
|
|
550
600
|
const mapped = mapUmPosition(position, receivedAt);
|
|
551
601
|
return mapped ? [mapped] : [];
|
|
552
602
|
}),
|
|
553
|
-
risk: mapAccountRisk(account, receivedAt),
|
|
603
|
+
risk: mapAccountRisk(account, receivedAt, positions),
|
|
554
604
|
exchangeTs: account.updateTime,
|
|
555
605
|
receivedAt,
|
|
556
606
|
};
|
|
557
607
|
}
|
|
558
608
|
|
|
609
|
+
async refreshAccount(
|
|
610
|
+
credentials: AccountCredentials,
|
|
611
|
+
accountOptions?: Record<string, unknown>,
|
|
612
|
+
): Promise<RawAccountUpdate> {
|
|
613
|
+
const receivedAt = Date.now();
|
|
614
|
+
const [account, positions] = await Promise.all([
|
|
615
|
+
this.signedRequest<BinancePapiAccount>(
|
|
616
|
+
"GET",
|
|
617
|
+
"/papi/v1/account",
|
|
618
|
+
credentials,
|
|
619
|
+
accountOptions,
|
|
620
|
+
),
|
|
621
|
+
this.signedRequest<BinancePapiUmPosition[]>(
|
|
622
|
+
"GET",
|
|
623
|
+
"/papi/v1/um/positionRisk",
|
|
624
|
+
credentials,
|
|
625
|
+
accountOptions,
|
|
626
|
+
),
|
|
627
|
+
]);
|
|
628
|
+
|
|
629
|
+
return mapAccountRefresh(account, positions, receivedAt);
|
|
630
|
+
}
|
|
631
|
+
|
|
559
632
|
async bootstrapOpenOrders(
|
|
560
633
|
credentials: AccountCredentials,
|
|
561
634
|
accountOptions?: Record<string, unknown>,
|
package/src/adapters/types.ts
CHANGED
|
@@ -121,6 +121,7 @@ export interface RawPositionUpdate {
|
|
|
121
121
|
export interface RawRiskUpdate {
|
|
122
122
|
equity?: string;
|
|
123
123
|
riskRatio?: string;
|
|
124
|
+
actualLeverage?: string;
|
|
124
125
|
initialMargin?: string;
|
|
125
126
|
maintenanceMargin?: string;
|
|
126
127
|
exchangeTs?: number;
|
|
@@ -222,6 +223,10 @@ export interface PrivateUserDataAdapter {
|
|
|
222
223
|
credentials: AccountCredentials,
|
|
223
224
|
accountOptions?: Record<string, unknown>,
|
|
224
225
|
): Promise<RawAccountBootstrap>;
|
|
226
|
+
refreshAccount?(
|
|
227
|
+
credentials: AccountCredentials,
|
|
228
|
+
accountOptions?: Record<string, unknown>,
|
|
229
|
+
): Promise<RawAccountUpdate>;
|
|
225
230
|
bootstrapOpenOrders(
|
|
226
231
|
credentials: AccountCredentials,
|
|
227
232
|
accountOptions?: Record<string, unknown>,
|
package/src/client/context.ts
CHANGED
|
@@ -19,6 +19,9 @@ interface PrivateSubscriptionRecord {
|
|
|
19
19
|
accountReady: boolean;
|
|
20
20
|
orderReady: boolean;
|
|
21
21
|
stream?: StreamHandle;
|
|
22
|
+
accountRefreshTimer?: ReturnType<typeof setTimeout>;
|
|
23
|
+
accountRefreshInFlight?: Promise<void>;
|
|
24
|
+
accountRefreshGeneration: number;
|
|
22
25
|
startPromise?: Promise<void>;
|
|
23
26
|
reconcilePromise?: Promise<void>;
|
|
24
27
|
}
|
|
@@ -27,6 +30,16 @@ const DEFAULT_STREAM_OPEN_TIMEOUT_MS = 15_000;
|
|
|
27
30
|
const DEFAULT_STREAM_RECONNECT_DELAY_MS = 1_000;
|
|
28
31
|
const DEFAULT_STREAM_RECONNECT_MAX_DELAY_MS = 10_000;
|
|
29
32
|
const DEFAULT_LISTEN_KEY_KEEPALIVE_MS = 30 * 60 * 1_000;
|
|
33
|
+
const DEFAULT_BINANCE_RISK_POLL_INTERVAL_MS = 5_000;
|
|
34
|
+
|
|
35
|
+
function normalizePositiveInterval(
|
|
36
|
+
value: number | undefined,
|
|
37
|
+
fallback: number,
|
|
38
|
+
): number {
|
|
39
|
+
return value !== undefined && Number.isFinite(value) && value > 0
|
|
40
|
+
? value
|
|
41
|
+
: fallback;
|
|
42
|
+
}
|
|
30
43
|
|
|
31
44
|
export class PrivateSubscriptionCoordinator {
|
|
32
45
|
private readonly context: ClientContext;
|
|
@@ -37,6 +50,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
37
50
|
private readonly streamReconnectDelayMs: number;
|
|
38
51
|
private readonly streamReconnectMaxDelayMs: number;
|
|
39
52
|
private readonly listenKeyKeepAliveMs: number;
|
|
53
|
+
private readonly binanceRiskPollIntervalMs: number;
|
|
40
54
|
private readonly juplendPollIntervalMs?: number;
|
|
41
55
|
private readonly records = new Map<string, PrivateSubscriptionRecord>();
|
|
42
56
|
|
|
@@ -62,6 +76,10 @@ export class PrivateSubscriptionCoordinator {
|
|
|
62
76
|
DEFAULT_STREAM_RECONNECT_MAX_DELAY_MS;
|
|
63
77
|
this.listenKeyKeepAliveMs =
|
|
64
78
|
options.listenKeyKeepAliveMs ?? DEFAULT_LISTEN_KEY_KEEPALIVE_MS;
|
|
79
|
+
this.binanceRiskPollIntervalMs = normalizePositiveInterval(
|
|
80
|
+
options.binance?.riskPollIntervalMs,
|
|
81
|
+
DEFAULT_BINANCE_RISK_POLL_INTERVAL_MS,
|
|
82
|
+
);
|
|
65
83
|
this.juplendPollIntervalMs = options.juplend?.pollIntervalMs;
|
|
66
84
|
}
|
|
67
85
|
|
|
@@ -81,6 +99,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
81
99
|
} else {
|
|
82
100
|
await this.ensureStream(record, account);
|
|
83
101
|
await this.bootstrapAccount(record, account);
|
|
102
|
+
this.ensureAccountRefreshPolling(record);
|
|
84
103
|
}
|
|
85
104
|
} catch (error) {
|
|
86
105
|
record.accountSubscribed = false;
|
|
@@ -96,6 +115,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
record.accountSubscribed = false;
|
|
118
|
+
this.stopAccountRefreshPolling(record);
|
|
99
119
|
this.closeIfUnused(record);
|
|
100
120
|
}
|
|
101
121
|
|
|
@@ -153,6 +173,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
153
173
|
|
|
154
174
|
onClientStopping(): void {
|
|
155
175
|
for (const record of this.records.values()) {
|
|
176
|
+
this.stopAccountRefreshPolling(record);
|
|
156
177
|
this.closeStream(record);
|
|
157
178
|
}
|
|
158
179
|
}
|
|
@@ -164,6 +185,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
164
185
|
}
|
|
165
186
|
|
|
166
187
|
this.closeStream(record);
|
|
188
|
+
this.stopAccountRefreshPolling(record);
|
|
167
189
|
this.records.delete(accountId);
|
|
168
190
|
}
|
|
169
191
|
|
|
@@ -186,6 +208,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
186
208
|
private async resumeRecord(record: PrivateSubscriptionRecord): Promise<void> {
|
|
187
209
|
const account = this.getAccount(record.accountId);
|
|
188
210
|
this.closeStream(record);
|
|
211
|
+
this.stopAccountRefreshPolling(record);
|
|
189
212
|
|
|
190
213
|
try {
|
|
191
214
|
if (record.venue === "juplend" && record.accountSubscribed) {
|
|
@@ -195,6 +218,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
195
218
|
await this.ensureStream(record, account);
|
|
196
219
|
if (record.accountSubscribed) {
|
|
197
220
|
await this.bootstrapAccount(record, account);
|
|
221
|
+
this.ensureAccountRefreshPolling(record);
|
|
198
222
|
}
|
|
199
223
|
}
|
|
200
224
|
if (record.ordersSubscribed) {
|
|
@@ -244,6 +268,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
244
268
|
ordersSubscribed: false,
|
|
245
269
|
accountReady: false,
|
|
246
270
|
orderReady: false,
|
|
271
|
+
accountRefreshGeneration: 0,
|
|
247
272
|
};
|
|
248
273
|
|
|
249
274
|
this.records.set(account.accountId, record);
|
|
@@ -259,6 +284,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
259
284
|
return;
|
|
260
285
|
}
|
|
261
286
|
|
|
287
|
+
this.stopAccountRefreshPolling(record);
|
|
262
288
|
this.closeStream(record);
|
|
263
289
|
this.records.delete(record.accountId);
|
|
264
290
|
}
|
|
@@ -268,6 +294,153 @@ export class PrivateSubscriptionCoordinator {
|
|
|
268
294
|
record.stream = undefined;
|
|
269
295
|
}
|
|
270
296
|
|
|
297
|
+
private ensureAccountRefreshPolling(record: PrivateSubscriptionRecord): void {
|
|
298
|
+
if (
|
|
299
|
+
record.venue !== "binance" ||
|
|
300
|
+
!record.accountSubscribed ||
|
|
301
|
+
record.accountRefreshTimer ||
|
|
302
|
+
record.accountRefreshInFlight
|
|
303
|
+
) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.scheduleAccountRefreshPoll(record);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private stopAccountRefreshPolling(record: PrivateSubscriptionRecord): void {
|
|
311
|
+
record.accountRefreshGeneration += 1;
|
|
312
|
+
if (record.accountRefreshTimer) {
|
|
313
|
+
clearTimeout(record.accountRefreshTimer);
|
|
314
|
+
record.accountRefreshTimer = undefined;
|
|
315
|
+
}
|
|
316
|
+
record.accountRefreshInFlight = undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private scheduleAccountRefreshPoll(record: PrivateSubscriptionRecord): void {
|
|
320
|
+
if (record.venue !== "binance" || !record.accountSubscribed) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const generation = record.accountRefreshGeneration;
|
|
325
|
+
record.accountRefreshTimer = setTimeout(() => {
|
|
326
|
+
record.accountRefreshTimer = undefined;
|
|
327
|
+
if (
|
|
328
|
+
generation !== record.accountRefreshGeneration ||
|
|
329
|
+
record.venue !== "binance" ||
|
|
330
|
+
!record.accountSubscribed
|
|
331
|
+
) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let latestAccount: RegisteredAccountRecord;
|
|
336
|
+
try {
|
|
337
|
+
latestAccount = this.getAccount(record.accountId);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
this.handleAccountRefreshLookupError(record, error);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
record.accountRefreshInFlight = this.refreshAccount(
|
|
344
|
+
record,
|
|
345
|
+
latestAccount,
|
|
346
|
+
generation,
|
|
347
|
+
)
|
|
348
|
+
.catch(() => {})
|
|
349
|
+
.finally(() => {
|
|
350
|
+
if (generation !== record.accountRefreshGeneration) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
record.accountRefreshInFlight = undefined;
|
|
355
|
+
if (record.accountSubscribed && record.venue === "binance") {
|
|
356
|
+
this.scheduleAccountRefreshPoll(record);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}, this.binanceRiskPollIntervalMs);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private handleAccountRefreshLookupError(
|
|
363
|
+
record: PrivateSubscriptionRecord,
|
|
364
|
+
error: unknown,
|
|
365
|
+
): void {
|
|
366
|
+
this.stopAccountRefreshPolling(record);
|
|
367
|
+
if (error instanceof AcexError && error.code === "ACCOUNT_NOT_FOUND") {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this.context.publishRuntimeError(
|
|
372
|
+
"adapter",
|
|
373
|
+
error instanceof Error
|
|
374
|
+
? error
|
|
375
|
+
: new Error(`Failed to load ${record.venue} account for risk refresh`),
|
|
376
|
+
{
|
|
377
|
+
accountId: record.accountId,
|
|
378
|
+
venue: record.venue,
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private async refreshAccount(
|
|
384
|
+
record: PrivateSubscriptionRecord,
|
|
385
|
+
account: RegisteredAccountRecord,
|
|
386
|
+
generation: number,
|
|
387
|
+
): Promise<void> {
|
|
388
|
+
const adapter = this.getAdapter(record.venue);
|
|
389
|
+
if (!adapter.refreshAccount) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
const update = await adapter.refreshAccount(account.credentials ?? {}, {
|
|
395
|
+
...account.options,
|
|
396
|
+
accountId: account.accountId,
|
|
397
|
+
});
|
|
398
|
+
if (
|
|
399
|
+
!record.accountSubscribed ||
|
|
400
|
+
generation !== record.accountRefreshGeneration
|
|
401
|
+
) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
record.accountReady = true;
|
|
406
|
+
this.accountConsumer.onPrivateAccountUpdate(
|
|
407
|
+
record.accountId,
|
|
408
|
+
record.venue,
|
|
409
|
+
update,
|
|
410
|
+
{ preserveStatus: true },
|
|
411
|
+
);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (
|
|
414
|
+
!record.accountSubscribed ||
|
|
415
|
+
generation !== record.accountRefreshGeneration
|
|
416
|
+
) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.context.publishRuntimeError(
|
|
421
|
+
"adapter",
|
|
422
|
+
error instanceof Error
|
|
423
|
+
? error
|
|
424
|
+
: new Error(
|
|
425
|
+
`Failed to refresh ${record.venue} private account state`,
|
|
426
|
+
),
|
|
427
|
+
{
|
|
428
|
+
accountId: record.accountId,
|
|
429
|
+
venue: record.venue,
|
|
430
|
+
},
|
|
431
|
+
);
|
|
432
|
+
this.accountConsumer.onPrivateAccountStreamState(
|
|
433
|
+
record.accountId,
|
|
434
|
+
record.venue,
|
|
435
|
+
{
|
|
436
|
+
runtimeStatus: "degraded",
|
|
437
|
+
ready: record.accountReady,
|
|
438
|
+
reason: "http_failed",
|
|
439
|
+
},
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
271
444
|
private async ensureStream(
|
|
272
445
|
record: PrivateSubscriptionRecord,
|
|
273
446
|
account: RegisteredAccountRecord,
|
|
@@ -277,6 +277,7 @@ export class AccountManagerImpl
|
|
|
277
277
|
accountId: string,
|
|
278
278
|
venue: Venue,
|
|
279
279
|
update: RawAccountUpdate,
|
|
280
|
+
options: { preserveStatus?: boolean } = {},
|
|
280
281
|
): void {
|
|
281
282
|
const record = this.getOrCreateRecord(accountId, venue);
|
|
282
283
|
if (!record.subscribed) {
|
|
@@ -358,16 +359,24 @@ export class AccountManagerImpl
|
|
|
358
359
|
receivedAt: update.receivedAt,
|
|
359
360
|
updatedAt: update.receivedAt,
|
|
360
361
|
};
|
|
361
|
-
record.status =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
362
|
+
record.status = options.preserveStatus
|
|
363
|
+
? {
|
|
364
|
+
...record.status,
|
|
365
|
+
activity: "active",
|
|
366
|
+
lastReceivedAt: update.receivedAt,
|
|
367
|
+
lastReadyAt: record.status.lastReadyAt ?? update.receivedAt,
|
|
368
|
+
inactiveSince: undefined,
|
|
369
|
+
}
|
|
370
|
+
: {
|
|
371
|
+
...record.status,
|
|
372
|
+
activity: "active",
|
|
373
|
+
ready: true,
|
|
374
|
+
runtimeStatus: "healthy",
|
|
375
|
+
reason: undefined,
|
|
376
|
+
lastReceivedAt: update.receivedAt,
|
|
377
|
+
lastReadyAt: update.receivedAt,
|
|
378
|
+
inactiveSince: undefined,
|
|
379
|
+
};
|
|
371
380
|
this.publishStatus(record);
|
|
372
381
|
}
|
|
373
382
|
|
|
@@ -588,6 +597,10 @@ export class AccountManagerImpl
|
|
|
588
597
|
input.riskRatio === undefined
|
|
589
598
|
? previous?.riskRatio
|
|
590
599
|
: new BigNumber(input.riskRatio),
|
|
600
|
+
actualLeverage:
|
|
601
|
+
input.actualLeverage === undefined
|
|
602
|
+
? previous?.actualLeverage
|
|
603
|
+
: new BigNumber(input.actualLeverage),
|
|
591
604
|
initialMargin:
|
|
592
605
|
input.initialMargin === undefined
|
|
593
606
|
? previous?.initialMargin
|
package/src/types/account.ts
CHANGED
package/src/types/shared.ts
CHANGED