@imbingox/acex 0.3.0-beta.3 → 0.3.0-beta.5

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 CHANGED
@@ -60,11 +60,14 @@ await client.stop();
60
60
 
61
61
  ### 同一个 client 同时使用 Binance + Juplend
62
62
 
63
- `createClient({ account: { juplend: { pollIntervalMs } } })` 只是配置 Juplend 账户的 polling 间隔,不代表这个 client 只能注册 Juplend。一个 `AcexClient` 可以同时注册 Binance 交易账户和 Juplend 借贷只读账户,用同一个 `AccountManager` 对比风险值。
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` 只是 Juplend polling 配置,不会把 client 限定为 Juplend 专用:
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
  },
@@ -612,7 +618,9 @@ const btcPosition = client.account.getPosition({
612
618
  const risk = client.account.getRiskSnapshot("main-binance");
613
619
  ```
614
620
 
615
- 所有数量字段(`free` / `used` / `total` / `size` / `entryPrice` / `equity` / ...)都是 `BigNumber`。
621
+ 所有数量字段(`free` / `used` / `total` / `size` / `entryPrice` / `netEquity` / `riskEquity` / ...)都是 `BigNumber`。
622
+
623
+ `RiskSnapshot.netEquity` 表示不含风控折算的净资产价值;`riskEquity` 表示抵押系数或清算阈值折算后的风控净权益。Binance 使用 `actualEquity` / `accountEquity` 映射这两个字段;Juplend 使用 `totalCollateralUsd - totalDebtUsd` / `Σ(suppliedValue × liquidationThreshold) - totalDebtUsd`。
616
624
 
617
625
  > **注意**:`AccountSnapshot.balances` 是 `Record<string, BalanceSnapshot>`,不是数组;需要数组视图用 `getBalances()`。
618
626
 
@@ -902,9 +910,6 @@ type PrivateRuntimeStatus =
902
910
  type PrivateRuntimeReason =
903
911
  | "credentials_missing" | "auth_failed" | "http_failed" | "rate_limited"
904
912
  | "ws_disconnected" | "heartbeat_timeout" | "reconciling";
905
- type PrivateRuntimeReason =
906
- | "credentials_missing" | "auth_failed"
907
- | "ws_disconnected" | "heartbeat_timeout" | "reconciling";
908
913
 
909
914
  type OrderSide = "buy" | "sell";
910
915
  type OrderStatus =
@@ -930,6 +935,9 @@ interface AccountRuntimeOptions {
930
935
  streamReconnectDelayMs?: number;
931
936
  streamReconnectMaxDelayMs?: number;
932
937
  listenKeyKeepAliveMs?: number;
938
+ binance?: {
939
+ riskPollIntervalMs?: number; // 默认 5_000
940
+ };
933
941
  juplend?: {
934
942
  pollIntervalMs?: number;
935
943
  };
@@ -1216,8 +1224,10 @@ interface PositionSnapshot {
1216
1224
  interface RiskSnapshot {
1217
1225
  accountId: string;
1218
1226
  venue: Venue;
1219
- equity?: BigNumber;
1227
+ netEquity?: BigNumber;
1228
+ riskEquity?: BigNumber;
1220
1229
  riskRatio?: BigNumber;
1230
+ riskLeverage?: BigNumber;
1221
1231
  initialMargin?: BigNumber;
1222
1232
  maintenanceMargin?: BigNumber;
1223
1233
  exchangeTs?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imbingox/acex",
3
- "version": "0.3.0-beta.3",
3
+ "version": "0.3.0-beta.5",
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
  }
@@ -38,6 +38,7 @@ interface BinancePapiBalance {
38
38
 
39
39
  interface BinancePapiAccount {
40
40
  accountEquity?: string;
41
+ actualEquity?: string;
41
42
  totalEquity?: string;
42
43
  accountInitialMargin?: string;
43
44
  totalInitialMargin?: string;
@@ -56,6 +57,7 @@ interface BinancePapiUmPosition {
56
57
  unrealizedProfit?: string;
57
58
  liquidationPrice?: string;
58
59
  leverage?: string;
60
+ notional?: string;
59
61
  positionSide?: string;
60
62
  updateTime?: number;
61
63
  }
@@ -285,14 +287,20 @@ function mapBalance(
285
287
  function mapAccountRisk(
286
288
  input: BinancePapiAccount,
287
289
  receivedAt: number,
290
+ positions: BinancePapiUmPosition[] = [],
288
291
  ): RawRiskUpdate | undefined {
289
292
  const uniMmr = firstString(input.uniMMR);
290
293
  const riskRatio = uniMmr
291
294
  ? new BigNumber(1).dividedBy(uniMmr).toString(10)
292
295
  : undefined;
296
+ const netEquity = firstString(input.actualEquity);
297
+ const riskEquity = firstString(input.accountEquity, input.totalEquity);
298
+ const riskLeverage = calculateRiskLeverage(riskEquity, positions);
293
299
  const risk: RawRiskUpdate = {
294
- equity: firstString(input.accountEquity, input.totalEquity),
300
+ netEquity,
301
+ riskEquity,
295
302
  riskRatio,
303
+ riskLeverage,
296
304
  initialMargin: firstString(
297
305
  input.accountInitialMargin,
298
306
  input.totalInitialMargin,
@@ -306,8 +314,10 @@ function mapAccountRisk(
306
314
  };
307
315
 
308
316
  if (
309
- !risk.equity &&
317
+ !risk.netEquity &&
318
+ !risk.riskEquity &&
310
319
  !risk.riskRatio &&
320
+ !risk.riskLeverage &&
311
321
  !risk.initialMargin &&
312
322
  !risk.maintenanceMargin
313
323
  ) {
@@ -317,6 +327,34 @@ function mapAccountRisk(
317
327
  return risk;
318
328
  }
319
329
 
330
+ function calculateRiskLeverage(
331
+ riskEquity: string | undefined,
332
+ positions: BinancePapiUmPosition[],
333
+ ): string | undefined {
334
+ if (!riskEquity) {
335
+ return undefined;
336
+ }
337
+
338
+ const riskEquityValue = new BigNumber(riskEquity);
339
+ if (!riskEquityValue.isFinite() || riskEquityValue.isZero()) {
340
+ return undefined;
341
+ }
342
+
343
+ const grossExposure = positions.reduce((total, position) => {
344
+ const notional = firstString(position.notional);
345
+ if (!notional) {
346
+ return total;
347
+ }
348
+
349
+ const value = new BigNumber(notional);
350
+ return value.isFinite() ? total.plus(value.absoluteValue()) : total;
351
+ }, new BigNumber(0));
352
+
353
+ return grossExposure.isZero()
354
+ ? undefined
355
+ : grossExposure.dividedBy(riskEquityValue).toString(10);
356
+ }
357
+
320
358
  function mapUmPosition(
321
359
  input: BinancePapiUmPosition,
322
360
  receivedAt: number,
@@ -339,6 +377,22 @@ function mapUmPosition(
339
377
  };
340
378
  }
341
379
 
380
+ function mapAccountRefresh(
381
+ account: BinancePapiAccount,
382
+ positions: BinancePapiUmPosition[],
383
+ receivedAt: number,
384
+ ): RawAccountUpdate {
385
+ return {
386
+ positions: positions.flatMap((position) => {
387
+ const mapped = mapUmPosition(position, receivedAt);
388
+ return mapped ? [mapped] : [];
389
+ }),
390
+ risk: mapAccountRisk(account, receivedAt, positions),
391
+ exchangeTs: account.updateTime,
392
+ receivedAt,
393
+ };
394
+ }
395
+
342
396
  function mapOpenOrder(
343
397
  input: BinancePapiOpenOrder,
344
398
  receivedAt: number,
@@ -550,12 +604,35 @@ export class BinancePrivateAdapter implements PrivateUserDataAdapter {
550
604
  const mapped = mapUmPosition(position, receivedAt);
551
605
  return mapped ? [mapped] : [];
552
606
  }),
553
- risk: mapAccountRisk(account, receivedAt),
607
+ risk: mapAccountRisk(account, receivedAt, positions),
554
608
  exchangeTs: account.updateTime,
555
609
  receivedAt,
556
610
  };
557
611
  }
558
612
 
613
+ async refreshAccount(
614
+ credentials: AccountCredentials,
615
+ accountOptions?: Record<string, unknown>,
616
+ ): Promise<RawAccountUpdate> {
617
+ const receivedAt = Date.now();
618
+ const [account, positions] = await Promise.all([
619
+ this.signedRequest<BinancePapiAccount>(
620
+ "GET",
621
+ "/papi/v1/account",
622
+ credentials,
623
+ accountOptions,
624
+ ),
625
+ this.signedRequest<BinancePapiUmPosition[]>(
626
+ "GET",
627
+ "/papi/v1/um/positionRisk",
628
+ credentials,
629
+ accountOptions,
630
+ ),
631
+ ]);
632
+
633
+ return mapAccountRefresh(account, positions, receivedAt);
634
+ }
635
+
559
636
  async bootstrapOpenOrders(
560
637
  credentials: AccountCredentials,
561
638
  accountOptions?: Record<string, unknown>,
@@ -222,7 +222,8 @@ function buildRisk(input: {
222
222
  : undefined;
223
223
 
224
224
  return {
225
- equity: totalCollateralUsd.minus(totalDebtUsd).toString(10),
225
+ netEquity: totalCollateralUsd.minus(totalDebtUsd).toString(10),
226
+ riskEquity: weightedLiquidationValueUsd.minus(totalDebtUsd).toString(10),
226
227
  riskRatio,
227
228
  receivedAt: input.receivedAt,
228
229
  lending: {
@@ -119,8 +119,10 @@ export interface RawPositionUpdate {
119
119
  }
120
120
 
121
121
  export interface RawRiskUpdate {
122
- equity?: string;
122
+ netEquity?: string;
123
+ riskEquity?: string;
123
124
  riskRatio?: string;
125
+ riskLeverage?: string;
124
126
  initialMargin?: string;
125
127
  maintenanceMargin?: string;
126
128
  exchangeTs?: number;
@@ -222,6 +224,10 @@ export interface PrivateUserDataAdapter {
222
224
  credentials: AccountCredentials,
223
225
  accountOptions?: Record<string, unknown>,
224
226
  ): Promise<RawAccountBootstrap>;
227
+ refreshAccount?(
228
+ credentials: AccountCredentials,
229
+ accountOptions?: Record<string, unknown>,
230
+ ): Promise<RawAccountUpdate>;
225
231
  bootstrapOpenOrders(
226
232
  credentials: AccountCredentials,
227
233
  accountOptions?: Record<string, unknown>,
@@ -75,6 +75,7 @@ export interface PrivateAccountDataConsumer {
75
75
  accountId: string,
76
76
  venue: Venue,
77
77
  update: RawAccountUpdate,
78
+ options?: { preserveStatus?: boolean },
78
79
  ): void;
79
80
  onPrivateAccountStreamState(
80
81
  accountId: string,
@@ -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
- ...record.status,
363
- activity: "active",
364
- ready: true,
365
- runtimeStatus: "healthy",
366
- reason: undefined,
367
- lastReceivedAt: update.receivedAt,
368
- lastReadyAt: update.receivedAt,
369
- inactiveSince: undefined,
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
 
@@ -580,14 +589,22 @@ export class AccountManagerImpl
580
589
  return {
581
590
  accountId,
582
591
  venue,
583
- equity:
584
- input.equity === undefined
585
- ? previous?.equity
586
- : new BigNumber(input.equity),
592
+ netEquity:
593
+ input.netEquity === undefined
594
+ ? previous?.netEquity
595
+ : new BigNumber(input.netEquity),
596
+ riskEquity:
597
+ input.riskEquity === undefined
598
+ ? previous?.riskEquity
599
+ : new BigNumber(input.riskEquity),
587
600
  riskRatio:
588
601
  input.riskRatio === undefined
589
602
  ? previous?.riskRatio
590
603
  : new BigNumber(input.riskRatio),
604
+ riskLeverage:
605
+ input.riskLeverage === undefined
606
+ ? previous?.riskLeverage
607
+ : new BigNumber(input.riskLeverage),
591
608
  initialMargin:
592
609
  input.initialMargin === undefined
593
610
  ? previous?.initialMargin
@@ -91,8 +91,10 @@ export interface PositionSnapshot {
91
91
  export interface RiskSnapshot {
92
92
  accountId: string;
93
93
  venue: Venue;
94
- equity?: BigNumber;
94
+ netEquity?: BigNumber;
95
+ riskEquity?: BigNumber;
95
96
  riskRatio?: BigNumber;
97
+ riskLeverage?: BigNumber;
96
98
  initialMargin?: BigNumber;
97
99
  maintenanceMargin?: BigNumber;
98
100
  exchangeTs?: number;
@@ -36,6 +36,9 @@ export interface AccountRuntimeOptions {
36
36
  streamReconnectDelayMs?: number;
37
37
  streamReconnectMaxDelayMs?: number;
38
38
  listenKeyKeepAliveMs?: number;
39
+ binance?: {
40
+ riskPollIntervalMs?: number;
41
+ };
39
42
  juplend?: {
40
43
  pollIntervalMs?: number;
41
44
  };