@drift-labs/sdk 2.145.0 → 2.146.0-alpha.13
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/.env +4 -0
- package/VERSION +1 -1
- package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/browser/adminClient.d.ts +5 -1
- package/lib/browser/adminClient.js +57 -23
- package/lib/browser/constants/numericConstants.d.ts +2 -0
- package/lib/browser/constants/numericConstants.js +5 -1
- package/lib/browser/constants/perpMarkets.js +0 -2
- package/lib/browser/decode/user.js +4 -0
- package/lib/browser/driftClient.d.ts +25 -10
- package/lib/browser/driftClient.js +238 -41
- package/lib/browser/driftClientConfig.d.ts +7 -2
- package/lib/browser/idl/drift.json +245 -22
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +9 -1
- package/lib/browser/marginCalculation.d.ts +86 -0
- package/lib/browser/marginCalculation.js +209 -0
- package/lib/browser/math/margin.d.ts +1 -1
- package/lib/browser/math/margin.js +8 -1
- package/lib/browser/math/position.d.ts +1 -0
- package/lib/browser/math/position.js +10 -2
- package/lib/browser/math/spotPosition.d.ts +1 -1
- package/lib/browser/math/spotPosition.js +3 -2
- package/lib/browser/math/superStake.d.ts +3 -2
- package/lib/browser/types.d.ts +13 -0
- package/lib/browser/types.js +12 -1
- package/lib/browser/user.d.ts +59 -11
- package/lib/browser/user.js +348 -43
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -1
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/node/adminClient.d.ts +5 -1
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +57 -23
- package/lib/node/constants/numericConstants.d.ts +2 -0
- package/lib/node/constants/numericConstants.d.ts.map +1 -1
- package/lib/node/constants/numericConstants.js +5 -1
- package/lib/node/constants/perpMarkets.d.ts.map +1 -1
- package/lib/node/constants/perpMarkets.js +0 -2
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +4 -0
- package/lib/node/driftClient.d.ts +25 -10
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +238 -41
- package/lib/node/driftClientConfig.d.ts +7 -2
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/idl/drift.json +245 -22
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +9 -1
- package/lib/node/marginCalculation.d.ts +87 -0
- package/lib/node/marginCalculation.d.ts.map +1 -0
- package/lib/node/marginCalculation.js +209 -0
- package/lib/node/math/margin.d.ts +1 -1
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +8 -1
- package/lib/node/math/position.d.ts +1 -0
- package/lib/node/math/position.d.ts.map +1 -1
- package/lib/node/math/position.js +10 -2
- package/lib/node/math/spotPosition.d.ts +1 -1
- package/lib/node/math/spotPosition.d.ts.map +1 -1
- package/lib/node/math/spotPosition.js +3 -2
- package/lib/node/math/superStake.d.ts +3 -2
- package/lib/node/math/superStake.d.ts.map +1 -1
- package/lib/node/types.d.ts +13 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +12 -1
- package/lib/node/user.d.ts +59 -11
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +348 -43
- package/package.json +1 -1
- package/scripts/deposit-isolated-positions.ts +110 -0
- package/scripts/single-grpc-client-test.ts +71 -21
- package/scripts/withdraw-isolated-positions.ts +174 -0
- package/src/accounts/grpcMultiUserAccountSubscriber.ts +8 -1
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +566 -167
- package/src/adminClient.ts +74 -25
- package/src/constants/numericConstants.ts +5 -0
- package/src/constants/perpMarkets.ts +0 -3
- package/src/decode/user.ts +7 -1
- package/src/driftClient.ts +465 -52
- package/src/driftClientConfig.ts +15 -8
- package/src/idl/drift.json +246 -23
- package/src/index.ts +4 -0
- package/src/margin/README.md +143 -0
- package/src/marginCalculation.ts +306 -0
- package/src/math/margin.ts +13 -1
- package/src/math/position.ts +12 -2
- package/src/math/spotPosition.ts +6 -2
- package/src/types.ts +16 -0
- package/src/user.ts +623 -81
- package/tests/amm/test.ts +1 -1
- package/tests/dlob/helpers.ts +6 -3
- package/tests/user/getMarginCalculation.ts +405 -0
- package/tests/user/test.ts +0 -7
package/src/user.ts
CHANGED
|
@@ -68,6 +68,7 @@ import { getUser30dRollingVolumeEstimate } from './math/trade';
|
|
|
68
68
|
import {
|
|
69
69
|
MarketType,
|
|
70
70
|
PositionDirection,
|
|
71
|
+
PositionFlag,
|
|
71
72
|
SpotBalanceType,
|
|
72
73
|
SpotMarketAccount,
|
|
73
74
|
} from './types';
|
|
@@ -106,6 +107,9 @@ import { StrictOraclePrice } from './oracles/strictOraclePrice';
|
|
|
106
107
|
|
|
107
108
|
import { calculateSpotFuelBonus, calculatePerpFuelBonus } from './math/fuel';
|
|
108
109
|
import { grpcUserAccountSubscriber } from './accounts/grpcUserAccountSubscriber';
|
|
110
|
+
import { MarginCalculation, MarginContext } from './marginCalculation';
|
|
111
|
+
|
|
112
|
+
export type MarginType = 'Cross' | 'Isolated';
|
|
109
113
|
|
|
110
114
|
export class User {
|
|
111
115
|
driftClient: DriftClient;
|
|
@@ -338,9 +342,27 @@ export class User {
|
|
|
338
342
|
lastQuoteAssetAmountPerLp: ZERO,
|
|
339
343
|
perLpBase: 0,
|
|
340
344
|
maxMarginRatio: 0,
|
|
345
|
+
positionFlag: 0,
|
|
346
|
+
isolatedPositionScaledBalance: ZERO,
|
|
341
347
|
};
|
|
342
348
|
}
|
|
343
349
|
|
|
350
|
+
public getIsolatePerpPositionTokenAmount(perpMarketIndex: number): BN {
|
|
351
|
+
const perpPosition = this.getPerpPosition(perpMarketIndex);
|
|
352
|
+
const perpMarket = this.driftClient.getPerpMarketAccount(perpMarketIndex);
|
|
353
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
354
|
+
perpMarket.quoteSpotMarketIndex
|
|
355
|
+
);
|
|
356
|
+
if (perpPosition === undefined) {
|
|
357
|
+
return ZERO;
|
|
358
|
+
}
|
|
359
|
+
return getTokenAmount(
|
|
360
|
+
perpPosition.isolatedPositionScaledBalance,
|
|
361
|
+
spotMarket,
|
|
362
|
+
SpotBalanceType.DEPOSIT
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
344
366
|
public getClonedPosition(position: PerpPosition): PerpPosition {
|
|
345
367
|
const clonedPosition = Object.assign({}, position);
|
|
346
368
|
return clonedPosition;
|
|
@@ -513,62 +535,113 @@ export class User {
|
|
|
513
535
|
*/
|
|
514
536
|
public getFreeCollateral(
|
|
515
537
|
marginCategory: MarginCategory = 'Initial',
|
|
516
|
-
enterHighLeverageMode =
|
|
538
|
+
enterHighLeverageMode = false,
|
|
539
|
+
perpMarketIndex?: number
|
|
517
540
|
): BN {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
541
|
+
const marginCalc = this.getMarginCalculation(marginCategory, {
|
|
542
|
+
enteringHighLeverage: enterHighLeverageMode,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (perpMarketIndex !== undefined) {
|
|
546
|
+
return marginCalc.getIsolatedFreeCollateral(perpMarketIndex);
|
|
547
|
+
} else {
|
|
548
|
+
return marginCalc.getCrossFreeCollateral();
|
|
549
|
+
}
|
|
525
550
|
}
|
|
526
551
|
|
|
527
552
|
/**
|
|
528
|
-
* @
|
|
553
|
+
* @deprecated Use the overload that includes { marginType, perpMarketIndex }
|
|
529
554
|
*/
|
|
530
555
|
public getMarginRequirement(
|
|
531
556
|
marginCategory: MarginCategory,
|
|
532
557
|
liquidationBuffer?: BN,
|
|
533
|
-
strict
|
|
534
|
-
includeOpenOrders
|
|
535
|
-
enteringHighLeverage
|
|
558
|
+
strict?: boolean,
|
|
559
|
+
includeOpenOrders?: boolean,
|
|
560
|
+
enteringHighLeverage?: boolean
|
|
561
|
+
): BN;
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Calculates the margin requirement based on the specified parameters.
|
|
565
|
+
*
|
|
566
|
+
* @param marginCategory - The category of margin to calculate ('Initial' or 'Maintenance').
|
|
567
|
+
* @param liquidationBuffer - Optional buffer amount to consider during liquidation scenarios.
|
|
568
|
+
* @param strict - Optional flag to enforce strict margin calculations.
|
|
569
|
+
* @param includeOpenOrders - Optional flag to include open orders in the margin calculation.
|
|
570
|
+
* @param enteringHighLeverage - Optional flag indicating if the user is entering high leverage mode.
|
|
571
|
+
* @param perpMarketIndex - Optional index of the perpetual market. Required if marginType is 'Isolated'.
|
|
572
|
+
*
|
|
573
|
+
* @returns The calculated margin requirement as a BN (BigNumber).
|
|
574
|
+
*/
|
|
575
|
+
public getMarginRequirement(
|
|
576
|
+
marginCategory: MarginCategory,
|
|
577
|
+
liquidationBuffer?: BN,
|
|
578
|
+
strict?: boolean,
|
|
579
|
+
includeOpenOrders?: boolean,
|
|
580
|
+
enteringHighLeverage?: boolean,
|
|
581
|
+
perpMarketIndex?: number
|
|
582
|
+
): BN;
|
|
583
|
+
|
|
584
|
+
public getMarginRequirement(
|
|
585
|
+
marginCategory: MarginCategory,
|
|
586
|
+
liquidationBuffer?: BN,
|
|
587
|
+
strict?: boolean,
|
|
588
|
+
includeOpenOrders?: boolean,
|
|
589
|
+
enteringHighLeverage?: boolean,
|
|
590
|
+
perpMarketIndex?: number
|
|
536
591
|
): BN {
|
|
537
|
-
|
|
538
|
-
marginCategory,
|
|
539
|
-
liquidationBuffer,
|
|
540
|
-
includeOpenOrders,
|
|
592
|
+
const marginCalc = this.getMarginCalculation(marginCategory, {
|
|
541
593
|
strict,
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
594
|
+
includeOpenOrders,
|
|
595
|
+
enteringHighLeverage,
|
|
596
|
+
liquidationBuffer,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// If perpMarketIndex is provided, compute only for that market index
|
|
600
|
+
if (perpMarketIndex !== undefined) {
|
|
601
|
+
const isolatedMarginCalculation =
|
|
602
|
+
marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
|
|
603
|
+
const { marginRequirement } = isolatedMarginCalculation;
|
|
604
|
+
|
|
605
|
+
return marginRequirement;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Default: Cross margin requirement
|
|
609
|
+
// TODO: should we be using plus buffer sometimes?
|
|
610
|
+
return marginCalc.marginRequirement;
|
|
552
611
|
}
|
|
553
612
|
|
|
554
613
|
/**
|
|
555
614
|
* @returns The initial margin requirement in USDC. : QUOTE_PRECISION
|
|
556
615
|
*/
|
|
557
|
-
public getInitialMarginRequirement(
|
|
616
|
+
public getInitialMarginRequirement(
|
|
617
|
+
enterHighLeverageMode = false,
|
|
618
|
+
perpMarketIndex?: number
|
|
619
|
+
): BN {
|
|
558
620
|
return this.getMarginRequirement(
|
|
559
621
|
'Initial',
|
|
560
622
|
undefined,
|
|
561
|
-
|
|
623
|
+
false,
|
|
562
624
|
undefined,
|
|
563
|
-
enterHighLeverageMode
|
|
625
|
+
enterHighLeverageMode,
|
|
626
|
+
perpMarketIndex
|
|
564
627
|
);
|
|
565
628
|
}
|
|
566
629
|
|
|
567
630
|
/**
|
|
568
631
|
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
569
632
|
*/
|
|
570
|
-
public getMaintenanceMarginRequirement(
|
|
571
|
-
|
|
633
|
+
public getMaintenanceMarginRequirement(
|
|
634
|
+
liquidationBuffer?: BN,
|
|
635
|
+
perpMarketIndex?: number
|
|
636
|
+
): BN {
|
|
637
|
+
return this.getMarginRequirement(
|
|
638
|
+
'Maintenance',
|
|
639
|
+
liquidationBuffer,
|
|
640
|
+
true, // strict default
|
|
641
|
+
true, // includeOpenOrders default
|
|
642
|
+
false, // enteringHighLeverage default
|
|
643
|
+
perpMarketIndex
|
|
644
|
+
);
|
|
572
645
|
}
|
|
573
646
|
|
|
574
647
|
public getActivePerpPositionsForUserAccount(
|
|
@@ -578,7 +651,8 @@ export class User {
|
|
|
578
651
|
(pos) =>
|
|
579
652
|
!pos.baseAssetAmount.eq(ZERO) ||
|
|
580
653
|
!pos.quoteAssetAmount.eq(ZERO) ||
|
|
581
|
-
!(pos.openOrders == 0)
|
|
654
|
+
!(pos.openOrders == 0) ||
|
|
655
|
+
pos.isolatedPositionScaledBalance.gt(ZERO)
|
|
582
656
|
);
|
|
583
657
|
}
|
|
584
658
|
|
|
@@ -639,6 +713,7 @@ export class User {
|
|
|
639
713
|
const market = this.driftClient.getPerpMarketAccount(
|
|
640
714
|
perpPosition.marketIndex
|
|
641
715
|
);
|
|
716
|
+
if (!market) return unrealizedPnl;
|
|
642
717
|
const oraclePriceData = this.getMMOracleDataForPerpMarket(
|
|
643
718
|
market.marketIndex
|
|
644
719
|
);
|
|
@@ -1151,22 +1226,21 @@ export class User {
|
|
|
1151
1226
|
marginCategory: MarginCategory = 'Initial',
|
|
1152
1227
|
strict = false,
|
|
1153
1228
|
includeOpenOrders = true,
|
|
1154
|
-
liquidationBuffer?: BN
|
|
1229
|
+
liquidationBuffer?: BN,
|
|
1230
|
+
perpMarketIndex?: number
|
|
1155
1231
|
): BN {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
marginCategory,
|
|
1232
|
+
const marginCalc = this.getMarginCalculation(marginCategory, {
|
|
1233
|
+
strict,
|
|
1159
1234
|
includeOpenOrders,
|
|
1160
|
-
|
|
1161
|
-
)
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
);
|
|
1235
|
+
liquidationBuffer,
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
if (perpMarketIndex !== undefined) {
|
|
1239
|
+
return marginCalc.isolatedMarginCalculations.get(perpMarketIndex)
|
|
1240
|
+
.totalCollateral;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
return marginCalc.totalCollateral;
|
|
1170
1244
|
}
|
|
1171
1245
|
|
|
1172
1246
|
public getLiquidationBuffer(): BN | undefined {
|
|
@@ -1184,13 +1258,26 @@ export class User {
|
|
|
1184
1258
|
* calculates User Health by comparing total collateral and maint. margin requirement
|
|
1185
1259
|
* @returns : number (value from [0, 100])
|
|
1186
1260
|
*/
|
|
1187
|
-
public getHealth(): number {
|
|
1188
|
-
|
|
1261
|
+
public getHealth(perpMarketIndex?: number): number {
|
|
1262
|
+
const marginCalc = this.getMarginCalculation('Maintenance');
|
|
1263
|
+
if (this.isCrossMarginBeingLiquidated(marginCalc) && !perpMarketIndex) {
|
|
1189
1264
|
return 0;
|
|
1190
1265
|
}
|
|
1191
1266
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1267
|
+
let totalCollateral: BN;
|
|
1268
|
+
let maintenanceMarginReq: BN;
|
|
1269
|
+
|
|
1270
|
+
if (perpMarketIndex) {
|
|
1271
|
+
const isolatedMarginCalc =
|
|
1272
|
+
marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
|
|
1273
|
+
if (isolatedMarginCalc) {
|
|
1274
|
+
totalCollateral = isolatedMarginCalc.totalCollateral;
|
|
1275
|
+
maintenanceMarginReq = isolatedMarginCalc.marginRequirement;
|
|
1276
|
+
}
|
|
1277
|
+
} else {
|
|
1278
|
+
totalCollateral = marginCalc.totalCollateral;
|
|
1279
|
+
maintenanceMarginReq = marginCalc.marginRequirement;
|
|
1280
|
+
}
|
|
1194
1281
|
|
|
1195
1282
|
let health: number;
|
|
1196
1283
|
|
|
@@ -1226,6 +1313,8 @@ export class User {
|
|
|
1226
1313
|
perpPosition.marketIndex
|
|
1227
1314
|
);
|
|
1228
1315
|
|
|
1316
|
+
if (!market) return ZERO;
|
|
1317
|
+
|
|
1229
1318
|
let valuationPrice = this.getOracleDataForPerpMarket(
|
|
1230
1319
|
market.marketIndex
|
|
1231
1320
|
).price;
|
|
@@ -1489,9 +1578,9 @@ export class User {
|
|
|
1489
1578
|
* calculates current user leverage which is (total liability size) / (net asset value)
|
|
1490
1579
|
* @returns : Precision TEN_THOUSAND
|
|
1491
1580
|
*/
|
|
1492
|
-
public getLeverage(includeOpenOrders = true): BN {
|
|
1581
|
+
public getLeverage(includeOpenOrders = true, perpMarketIndex?: number): BN {
|
|
1493
1582
|
return this.calculateLeverageFromComponents(
|
|
1494
|
-
this.getLeverageComponents(includeOpenOrders)
|
|
1583
|
+
this.getLeverageComponents(includeOpenOrders, undefined, perpMarketIndex)
|
|
1495
1584
|
);
|
|
1496
1585
|
}
|
|
1497
1586
|
|
|
@@ -1519,13 +1608,61 @@ export class User {
|
|
|
1519
1608
|
|
|
1520
1609
|
getLeverageComponents(
|
|
1521
1610
|
includeOpenOrders = true,
|
|
1522
|
-
marginCategory: MarginCategory = undefined
|
|
1611
|
+
marginCategory: MarginCategory = undefined,
|
|
1612
|
+
perpMarketIndex?: number
|
|
1523
1613
|
): {
|
|
1524
1614
|
perpLiabilityValue: BN;
|
|
1525
1615
|
perpPnl: BN;
|
|
1526
1616
|
spotAssetValue: BN;
|
|
1527
1617
|
spotLiabilityValue: BN;
|
|
1528
1618
|
} {
|
|
1619
|
+
if (perpMarketIndex) {
|
|
1620
|
+
const perpPosition = this.getPerpPositionOrEmpty(perpMarketIndex);
|
|
1621
|
+
const perpLiability = this.calculateWeightedPerpPositionLiability(
|
|
1622
|
+
perpPosition,
|
|
1623
|
+
marginCategory,
|
|
1624
|
+
undefined,
|
|
1625
|
+
includeOpenOrders
|
|
1626
|
+
);
|
|
1627
|
+
const perpMarket = this.driftClient.getPerpMarketAccount(
|
|
1628
|
+
perpPosition.marketIndex
|
|
1629
|
+
);
|
|
1630
|
+
|
|
1631
|
+
const oraclePriceData = this.getOracleDataForPerpMarket(
|
|
1632
|
+
perpPosition.marketIndex
|
|
1633
|
+
);
|
|
1634
|
+
const quoteSpotMarket = this.driftClient.getSpotMarketAccount(
|
|
1635
|
+
perpMarket.quoteSpotMarketIndex
|
|
1636
|
+
);
|
|
1637
|
+
const quoteOraclePriceData = this.getOracleDataForSpotMarket(
|
|
1638
|
+
perpMarket.quoteSpotMarketIndex
|
|
1639
|
+
);
|
|
1640
|
+
const strictOracle = new StrictOraclePrice(
|
|
1641
|
+
quoteOraclePriceData.price,
|
|
1642
|
+
quoteOraclePriceData.twap
|
|
1643
|
+
);
|
|
1644
|
+
|
|
1645
|
+
const positionUnrealizedPnl = calculatePositionPNL(
|
|
1646
|
+
perpMarket,
|
|
1647
|
+
perpPosition,
|
|
1648
|
+
true,
|
|
1649
|
+
oraclePriceData
|
|
1650
|
+
);
|
|
1651
|
+
|
|
1652
|
+
const spotAssetValue = getStrictTokenValue(
|
|
1653
|
+
perpPosition.isolatedPositionScaledBalance,
|
|
1654
|
+
quoteSpotMarket.decimals,
|
|
1655
|
+
strictOracle
|
|
1656
|
+
);
|
|
1657
|
+
|
|
1658
|
+
return {
|
|
1659
|
+
perpLiabilityValue: perpLiability,
|
|
1660
|
+
perpPnl: positionUnrealizedPnl,
|
|
1661
|
+
spotAssetValue,
|
|
1662
|
+
spotLiabilityValue: ZERO,
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1529
1666
|
const perpLiability = this.getTotalPerpPositionLiability(
|
|
1530
1667
|
marginCategory,
|
|
1531
1668
|
undefined,
|
|
@@ -1819,35 +1956,113 @@ export class User {
|
|
|
1819
1956
|
canBeLiquidated: boolean;
|
|
1820
1957
|
marginRequirement: BN;
|
|
1821
1958
|
totalCollateral: BN;
|
|
1959
|
+
liquidationStatuses: Map<
|
|
1960
|
+
'cross' | number,
|
|
1961
|
+
{ canBeLiquidated: boolean; marginRequirement: BN; totalCollateral: BN }
|
|
1962
|
+
>;
|
|
1822
1963
|
} {
|
|
1823
|
-
|
|
1964
|
+
// Deprecated signature retained for backward compatibility in type only
|
|
1965
|
+
// but implementation now delegates to the new Map-based API and returns cross margin status.
|
|
1966
|
+
const map = this.getLiquidationStatuses();
|
|
1967
|
+
const cross = map.get('cross');
|
|
1968
|
+
return cross
|
|
1969
|
+
? { ...cross, liquidationStatuses: map }
|
|
1970
|
+
: {
|
|
1971
|
+
canBeLiquidated: false,
|
|
1972
|
+
marginRequirement: ZERO,
|
|
1973
|
+
totalCollateral: ZERO,
|
|
1974
|
+
liquidationStatuses: map,
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1824
1977
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1978
|
+
/**
|
|
1979
|
+
* New API: Returns liquidation status for cross and each isolated perp position.
|
|
1980
|
+
* Map keys:
|
|
1981
|
+
* - 'cross' for cross margin
|
|
1982
|
+
* - marketIndex (number) for each isolated perp position
|
|
1983
|
+
*/
|
|
1984
|
+
public getLiquidationStatuses(
|
|
1985
|
+
marginCalc?: MarginCalculation
|
|
1986
|
+
): Map<
|
|
1987
|
+
'cross' | number,
|
|
1988
|
+
{ canBeLiquidated: boolean; marginRequirement: BN; totalCollateral: BN }
|
|
1989
|
+
> {
|
|
1990
|
+
// If not provided, use buffer-aware calc for canBeLiquidated checks
|
|
1991
|
+
if (!marginCalc) {
|
|
1992
|
+
const liquidationBuffer = this.getLiquidationBuffer();
|
|
1993
|
+
marginCalc = this.getMarginCalculation('Maintenance', {
|
|
1994
|
+
liquidationBuffer,
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1831
1997
|
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
|
|
1998
|
+
const result = new Map<
|
|
1999
|
+
'cross' | number,
|
|
2000
|
+
{
|
|
2001
|
+
canBeLiquidated: boolean;
|
|
2002
|
+
marginRequirement: BN;
|
|
2003
|
+
totalCollateral: BN;
|
|
2004
|
+
}
|
|
2005
|
+
>();
|
|
2006
|
+
|
|
2007
|
+
// Cross margin status
|
|
2008
|
+
const crossTotalCollateral = marginCalc.totalCollateral;
|
|
2009
|
+
const crossMarginRequirement = marginCalc.marginRequirement;
|
|
2010
|
+
result.set('cross', {
|
|
2011
|
+
canBeLiquidated: crossTotalCollateral.lt(crossMarginRequirement),
|
|
2012
|
+
marginRequirement: crossMarginRequirement,
|
|
2013
|
+
totalCollateral: crossTotalCollateral,
|
|
2014
|
+
});
|
|
1835
2015
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2016
|
+
// Isolated positions status
|
|
2017
|
+
for (const [
|
|
2018
|
+
marketIndex,
|
|
2019
|
+
isoCalc,
|
|
2020
|
+
] of marginCalc.isolatedMarginCalculations) {
|
|
2021
|
+
const isoTotalCollateral = isoCalc.totalCollateral;
|
|
2022
|
+
const isoMarginRequirement = isoCalc.marginRequirement;
|
|
2023
|
+
result.set(marketIndex, {
|
|
2024
|
+
canBeLiquidated: isoTotalCollateral.lt(isoMarginRequirement),
|
|
2025
|
+
marginRequirement: isoMarginRequirement,
|
|
2026
|
+
totalCollateral: isoTotalCollateral,
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
return result;
|
|
1841
2031
|
}
|
|
1842
2032
|
|
|
1843
|
-
public isBeingLiquidated(): boolean {
|
|
1844
|
-
|
|
2033
|
+
public isBeingLiquidated(marginCalc?: MarginCalculation): boolean {
|
|
2034
|
+
// Consider on-chain flags OR computed margin status (cross or any isolated)
|
|
2035
|
+
const hasOnChainFlag =
|
|
1845
2036
|
(this.getUserAccount().status &
|
|
1846
2037
|
(UserStatus.BEING_LIQUIDATED | UserStatus.BANKRUPT)) >
|
|
1847
|
-
0
|
|
2038
|
+
0;
|
|
2039
|
+
const calc = marginCalc ?? this.getMarginCalculation('Maintenance');
|
|
2040
|
+
return (
|
|
2041
|
+
hasOnChainFlag ||
|
|
2042
|
+
this.isCrossMarginBeingLiquidated(calc) ||
|
|
2043
|
+
this.isIsolatedMarginBeingLiquidated(calc)
|
|
1848
2044
|
);
|
|
1849
2045
|
}
|
|
1850
2046
|
|
|
2047
|
+
/** Returns true if cross margin is currently below maintenance requirement (no buffer). */
|
|
2048
|
+
public isCrossMarginBeingLiquidated(marginCalc?: MarginCalculation): boolean {
|
|
2049
|
+
const calc = marginCalc ?? this.getMarginCalculation('Maintenance');
|
|
2050
|
+
return calc.totalCollateral.lt(calc.marginRequirement);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
/** Returns true if any isolated perp position is currently below its maintenance requirement (no buffer). */
|
|
2054
|
+
public isIsolatedMarginBeingLiquidated(
|
|
2055
|
+
marginCalc?: MarginCalculation
|
|
2056
|
+
): boolean {
|
|
2057
|
+
const calc = marginCalc ?? this.getMarginCalculation('Maintenance');
|
|
2058
|
+
for (const [, isoCalc] of calc.isolatedMarginCalculations) {
|
|
2059
|
+
if (isoCalc.totalCollateral.lt(isoCalc.marginRequirement)) {
|
|
2060
|
+
return true;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return false;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1851
2066
|
public hasStatus(status: UserStatus): boolean {
|
|
1852
2067
|
return (this.getUserAccount().status & status) > 0;
|
|
1853
2068
|
}
|
|
@@ -2004,8 +2219,61 @@ export class User {
|
|
|
2004
2219
|
marginCategory: MarginCategory = 'Maintenance',
|
|
2005
2220
|
includeOpenOrders = false,
|
|
2006
2221
|
offsetCollateral = ZERO,
|
|
2007
|
-
enteringHighLeverage =
|
|
2222
|
+
enteringHighLeverage = false,
|
|
2223
|
+
marginType?: MarginType
|
|
2008
2224
|
): BN {
|
|
2225
|
+
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
2226
|
+
|
|
2227
|
+
const oracle =
|
|
2228
|
+
this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
2229
|
+
|
|
2230
|
+
const oraclePrice =
|
|
2231
|
+
this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
|
|
2232
|
+
|
|
2233
|
+
const currentPerpPosition = this.getPerpPositionOrEmpty(marketIndex);
|
|
2234
|
+
|
|
2235
|
+
if (marginType === 'Isolated') {
|
|
2236
|
+
const marginCalculation = this.getMarginCalculation(marginCategory, {
|
|
2237
|
+
strict: false,
|
|
2238
|
+
includeOpenOrders,
|
|
2239
|
+
enteringHighLeverage,
|
|
2240
|
+
});
|
|
2241
|
+
const isolatedMarginCalculation =
|
|
2242
|
+
marginCalculation.isolatedMarginCalculations.get(marketIndex);
|
|
2243
|
+
const { totalCollateral, marginRequirement } = isolatedMarginCalculation;
|
|
2244
|
+
|
|
2245
|
+
const freeCollateral = BN.max(
|
|
2246
|
+
ZERO,
|
|
2247
|
+
totalCollateral.sub(marginRequirement)
|
|
2248
|
+
).add(offsetCollateral);
|
|
2249
|
+
|
|
2250
|
+
const freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(
|
|
2251
|
+
market,
|
|
2252
|
+
currentPerpPosition,
|
|
2253
|
+
positionBaseSizeChange,
|
|
2254
|
+
oraclePrice,
|
|
2255
|
+
marginCategory,
|
|
2256
|
+
includeOpenOrders,
|
|
2257
|
+
enteringHighLeverage
|
|
2258
|
+
);
|
|
2259
|
+
|
|
2260
|
+
if (freeCollateralDelta.eq(ZERO)) {
|
|
2261
|
+
return new BN(-1);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
const liqPriceDelta = freeCollateral
|
|
2265
|
+
.mul(QUOTE_PRECISION)
|
|
2266
|
+
.div(freeCollateralDelta);
|
|
2267
|
+
|
|
2268
|
+
const liqPrice = oraclePrice.sub(liqPriceDelta);
|
|
2269
|
+
|
|
2270
|
+
if (liqPrice.lt(ZERO)) {
|
|
2271
|
+
return new BN(-1);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
return liqPrice;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2009
2277
|
const totalCollateral = this.getTotalCollateral(
|
|
2010
2278
|
marginCategory,
|
|
2011
2279
|
false,
|
|
@@ -2023,15 +2291,6 @@ export class User {
|
|
|
2023
2291
|
totalCollateral.sub(marginRequirement)
|
|
2024
2292
|
).add(offsetCollateral);
|
|
2025
2293
|
|
|
2026
|
-
const oracle =
|
|
2027
|
-
this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
2028
|
-
|
|
2029
|
-
const oraclePrice =
|
|
2030
|
-
this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
|
|
2031
|
-
|
|
2032
|
-
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
2033
|
-
const currentPerpPosition = this.getPerpPositionOrEmpty(marketIndex);
|
|
2034
|
-
|
|
2035
2294
|
positionBaseSizeChange = standardizeBaseAssetAmount(
|
|
2036
2295
|
positionBaseSizeChange,
|
|
2037
2296
|
market.amm.orderStepSize
|
|
@@ -3913,4 +4172,287 @@ export class User {
|
|
|
3913
4172
|
activeSpotPositions: activeSpotMarkets,
|
|
3914
4173
|
};
|
|
3915
4174
|
}
|
|
4175
|
+
|
|
4176
|
+
/**
|
|
4177
|
+
* Compute a consolidated margin snapshot once, without caching.
|
|
4178
|
+
* Consumers can use this to avoid duplicating work across separate calls.
|
|
4179
|
+
*/
|
|
4180
|
+
// TODO: need another param to tell it give it back leverage compnents
|
|
4181
|
+
// TODO: change get leverage functions need to pull the right values from
|
|
4182
|
+
public getMarginCalculation(
|
|
4183
|
+
marginCategory: MarginCategory = 'Initial',
|
|
4184
|
+
opts?: {
|
|
4185
|
+
strict?: boolean; // mirror StrictOraclePrice application
|
|
4186
|
+
includeOpenOrders?: boolean;
|
|
4187
|
+
enteringHighLeverage?: boolean;
|
|
4188
|
+
liquidationBuffer?: BN; // margin_buffer analog for buffer mode
|
|
4189
|
+
marginRatioOverride?: number; // mirrors context.margin_ratio_override
|
|
4190
|
+
}
|
|
4191
|
+
): MarginCalculation {
|
|
4192
|
+
const strict = opts?.strict ?? false;
|
|
4193
|
+
const enteringHighLeverage = opts?.enteringHighLeverage ?? false;
|
|
4194
|
+
const includeOpenOrders = opts?.includeOpenOrders ?? true; // TODO: remove this ??
|
|
4195
|
+
const marginBuffer = opts?.liquidationBuffer; // treat as MARGIN_BUFFER ratio if provided
|
|
4196
|
+
const marginRatioOverride = opts?.marginRatioOverride;
|
|
4197
|
+
|
|
4198
|
+
// Equivalent to on-chain user_custom_margin_ratio
|
|
4199
|
+
let userCustomMarginRatio =
|
|
4200
|
+
marginCategory === 'Initial' ? this.getUserAccount().maxMarginRatio : 0;
|
|
4201
|
+
if (marginRatioOverride !== undefined) {
|
|
4202
|
+
userCustomMarginRatio = Math.max(
|
|
4203
|
+
userCustomMarginRatio,
|
|
4204
|
+
marginRatioOverride
|
|
4205
|
+
);
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
// Initialize calc via JS mirror of Rust MarginCalculation
|
|
4209
|
+
const ctx = MarginContext.standard(marginCategory)
|
|
4210
|
+
.strictMode(strict)
|
|
4211
|
+
.setMarginBuffer(marginBuffer)
|
|
4212
|
+
.setMarginRatioOverride(userCustomMarginRatio);
|
|
4213
|
+
const calc = new MarginCalculation(ctx);
|
|
4214
|
+
|
|
4215
|
+
// SPOT POSITIONS
|
|
4216
|
+
// TODO: include open orders in the worst-case simulation in the same way on both spot and perp positions
|
|
4217
|
+
for (const spotPosition of this.getUserAccount().spotPositions) {
|
|
4218
|
+
if (isSpotPositionAvailable(spotPosition)) continue;
|
|
4219
|
+
|
|
4220
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(
|
|
4221
|
+
spotPosition.marketIndex
|
|
4222
|
+
);
|
|
4223
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(
|
|
4224
|
+
spotPosition.marketIndex
|
|
4225
|
+
);
|
|
4226
|
+
const twap5 = strict
|
|
4227
|
+
? calculateLiveOracleTwap(
|
|
4228
|
+
spotMarket.historicalOracleData,
|
|
4229
|
+
oraclePriceData,
|
|
4230
|
+
new BN(Math.floor(Date.now() / 1000)),
|
|
4231
|
+
FIVE_MINUTE
|
|
4232
|
+
)
|
|
4233
|
+
: undefined;
|
|
4234
|
+
const strictOracle = new StrictOraclePrice(oraclePriceData.price, twap5);
|
|
4235
|
+
|
|
4236
|
+
if (spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX) {
|
|
4237
|
+
const tokenAmount = getSignedTokenAmount(
|
|
4238
|
+
getTokenAmount(
|
|
4239
|
+
spotPosition.scaledBalance,
|
|
4240
|
+
spotMarket,
|
|
4241
|
+
spotPosition.balanceType
|
|
4242
|
+
),
|
|
4243
|
+
spotPosition.balanceType
|
|
4244
|
+
);
|
|
4245
|
+
if (isVariant(spotPosition.balanceType, 'deposit')) {
|
|
4246
|
+
// add deposit value to total collateral
|
|
4247
|
+
const tokenValue = getStrictTokenValue(
|
|
4248
|
+
tokenAmount,
|
|
4249
|
+
spotMarket.decimals,
|
|
4250
|
+
strictOracle
|
|
4251
|
+
);
|
|
4252
|
+
calc.addCrossMarginTotalCollateral(tokenValue);
|
|
4253
|
+
} else {
|
|
4254
|
+
// borrow on quote contributes to margin requirement
|
|
4255
|
+
const tokenValueAbs = getStrictTokenValue(
|
|
4256
|
+
tokenAmount,
|
|
4257
|
+
spotMarket.decimals,
|
|
4258
|
+
strictOracle
|
|
4259
|
+
).abs();
|
|
4260
|
+
calc.addCrossMarginRequirement(tokenValueAbs, tokenValueAbs);
|
|
4261
|
+
calc.addSpotLiability();
|
|
4262
|
+
}
|
|
4263
|
+
continue;
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
// Non-quote spot: worst-case simulation
|
|
4267
|
+
const {
|
|
4268
|
+
tokenAmount: worstCaseTokenAmount,
|
|
4269
|
+
ordersValue: worstCaseOrdersValue,
|
|
4270
|
+
tokenValue: worstCaseTokenValue,
|
|
4271
|
+
weightedTokenValue: worstCaseWeightedTokenValue,
|
|
4272
|
+
} = getWorstCaseTokenAmounts(
|
|
4273
|
+
spotPosition,
|
|
4274
|
+
spotMarket,
|
|
4275
|
+
strictOracle,
|
|
4276
|
+
marginCategory,
|
|
4277
|
+
userCustomMarginRatio,
|
|
4278
|
+
includeOpenOrders
|
|
4279
|
+
);
|
|
4280
|
+
|
|
4281
|
+
// open order IM
|
|
4282
|
+
calc.addCrossMarginRequirement(
|
|
4283
|
+
new BN(spotPosition.openOrders).mul(OPEN_ORDER_MARGIN_REQUIREMENT),
|
|
4284
|
+
ZERO
|
|
4285
|
+
);
|
|
4286
|
+
|
|
4287
|
+
if (worstCaseTokenAmount.gt(ZERO)) {
|
|
4288
|
+
// asset side increases total collateral (weighted)
|
|
4289
|
+
calc.addCrossMarginTotalCollateral(worstCaseWeightedTokenValue);
|
|
4290
|
+
} else if (worstCaseTokenAmount.lt(ZERO)) {
|
|
4291
|
+
// liability side increases margin requirement (weighted >= abs(token_value))
|
|
4292
|
+
const liabilityWeighted = worstCaseWeightedTokenValue.abs();
|
|
4293
|
+
calc.addCrossMarginRequirement(
|
|
4294
|
+
liabilityWeighted,
|
|
4295
|
+
worstCaseTokenValue.abs()
|
|
4296
|
+
);
|
|
4297
|
+
calc.addSpotLiability();
|
|
4298
|
+
calc.addSpotLiabilityValue(worstCaseTokenValue.abs());
|
|
4299
|
+
} else if (spotPosition.openOrders !== 0) {
|
|
4300
|
+
calc.addSpotLiability();
|
|
4301
|
+
calc.addSpotLiabilityValue(worstCaseTokenValue.abs());
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
// orders value contributes to collateral or requirement
|
|
4305
|
+
if (worstCaseOrdersValue.gt(ZERO)) {
|
|
4306
|
+
calc.addCrossMarginTotalCollateral(worstCaseOrdersValue);
|
|
4307
|
+
} else if (worstCaseOrdersValue.lt(ZERO)) {
|
|
4308
|
+
const absVal = worstCaseOrdersValue.abs();
|
|
4309
|
+
calc.addCrossMarginRequirement(absVal, absVal);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
// PERP POSITIONS
|
|
4314
|
+
for (const marketPosition of this.getActivePerpPositions()) {
|
|
4315
|
+
const market = this.driftClient.getPerpMarketAccount(
|
|
4316
|
+
marketPosition.marketIndex
|
|
4317
|
+
);
|
|
4318
|
+
const quoteSpotMarket = this.driftClient.getSpotMarketAccount(
|
|
4319
|
+
market.quoteSpotMarketIndex
|
|
4320
|
+
);
|
|
4321
|
+
const quoteOraclePriceData = this.getOracleDataForSpotMarket(
|
|
4322
|
+
market.quoteSpotMarketIndex
|
|
4323
|
+
);
|
|
4324
|
+
const oraclePriceData = this.getOracleDataForPerpMarket(
|
|
4325
|
+
market.marketIndex
|
|
4326
|
+
);
|
|
4327
|
+
|
|
4328
|
+
// Worst-case perp liability and weighted pnl
|
|
4329
|
+
const { worstCaseBaseAssetAmount, worstCaseLiabilityValue } =
|
|
4330
|
+
calculateWorstCasePerpLiabilityValue(
|
|
4331
|
+
marketPosition,
|
|
4332
|
+
market,
|
|
4333
|
+
oraclePriceData.price,
|
|
4334
|
+
includeOpenOrders
|
|
4335
|
+
);
|
|
4336
|
+
|
|
4337
|
+
// margin ratio for this perp
|
|
4338
|
+
const customMarginRatio = Math.max(
|
|
4339
|
+
this.getUserAccount().maxMarginRatio,
|
|
4340
|
+
marketPosition.maxMarginRatio
|
|
4341
|
+
);
|
|
4342
|
+
let marginRatio = new BN(
|
|
4343
|
+
calculateMarketMarginRatio(
|
|
4344
|
+
market,
|
|
4345
|
+
worstCaseBaseAssetAmount.abs(),
|
|
4346
|
+
marginCategory,
|
|
4347
|
+
customMarginRatio,
|
|
4348
|
+
this.isHighLeverageMode(marginCategory) || enteringHighLeverage
|
|
4349
|
+
)
|
|
4350
|
+
);
|
|
4351
|
+
if (isVariant(market.status, 'settlement')) {
|
|
4352
|
+
marginRatio = ZERO;
|
|
4353
|
+
}
|
|
4354
|
+
|
|
4355
|
+
// convert liability to quote value and apply margin ratio
|
|
4356
|
+
const quotePrice = strict
|
|
4357
|
+
? BN.max(
|
|
4358
|
+
quoteOraclePriceData.price,
|
|
4359
|
+
quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
|
|
4360
|
+
)
|
|
4361
|
+
: quoteOraclePriceData.price;
|
|
4362
|
+
let perpMarginRequirement = worstCaseLiabilityValue
|
|
4363
|
+
.mul(quotePrice)
|
|
4364
|
+
.div(PRICE_PRECISION)
|
|
4365
|
+
.mul(marginRatio)
|
|
4366
|
+
.div(MARGIN_PRECISION);
|
|
4367
|
+
// add open orders IM
|
|
4368
|
+
perpMarginRequirement = perpMarginRequirement.add(
|
|
4369
|
+
new BN(marketPosition.openOrders).mul(OPEN_ORDER_MARGIN_REQUIREMENT)
|
|
4370
|
+
);
|
|
4371
|
+
|
|
4372
|
+
// weighted unrealized pnl
|
|
4373
|
+
let positionUnrealizedPnl = calculatePositionPNL(
|
|
4374
|
+
market,
|
|
4375
|
+
marketPosition,
|
|
4376
|
+
true,
|
|
4377
|
+
oraclePriceData
|
|
4378
|
+
);
|
|
4379
|
+
let pnlQuotePrice: BN;
|
|
4380
|
+
if (strict && positionUnrealizedPnl.gt(ZERO)) {
|
|
4381
|
+
pnlQuotePrice = BN.min(
|
|
4382
|
+
quoteOraclePriceData.price,
|
|
4383
|
+
quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
|
|
4384
|
+
);
|
|
4385
|
+
} else if (strict && positionUnrealizedPnl.lt(ZERO)) {
|
|
4386
|
+
pnlQuotePrice = BN.max(
|
|
4387
|
+
quoteOraclePriceData.price,
|
|
4388
|
+
quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
|
|
4389
|
+
);
|
|
4390
|
+
} else {
|
|
4391
|
+
pnlQuotePrice = quoteOraclePriceData.price;
|
|
4392
|
+
}
|
|
4393
|
+
positionUnrealizedPnl = positionUnrealizedPnl
|
|
4394
|
+
.mul(pnlQuotePrice)
|
|
4395
|
+
.div(PRICE_PRECISION);
|
|
4396
|
+
|
|
4397
|
+
// Add perp contribution: isolated vs cross
|
|
4398
|
+
const isIsolated = this.isPerpPositionIsolated(marketPosition);
|
|
4399
|
+
if (isIsolated) {
|
|
4400
|
+
// derive isolated quote deposit value, mirroring on-chain logic
|
|
4401
|
+
let depositValue = ZERO;
|
|
4402
|
+
if (marketPosition.isolatedPositionScaledBalance.gt(ZERO)) {
|
|
4403
|
+
const quoteSpotMarket = this.driftClient.getSpotMarketAccount(
|
|
4404
|
+
market.quoteSpotMarketIndex
|
|
4405
|
+
);
|
|
4406
|
+
const quoteOraclePriceData = this.getOracleDataForSpotMarket(
|
|
4407
|
+
market.quoteSpotMarketIndex
|
|
4408
|
+
);
|
|
4409
|
+
const strictQuote = new StrictOraclePrice(
|
|
4410
|
+
quoteOraclePriceData.price,
|
|
4411
|
+
strict
|
|
4412
|
+
? quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
|
|
4413
|
+
: undefined
|
|
4414
|
+
);
|
|
4415
|
+
const quoteTokenAmount = getTokenAmount(
|
|
4416
|
+
marketPosition.isolatedPositionScaledBalance,
|
|
4417
|
+
quoteSpotMarket,
|
|
4418
|
+
SpotBalanceType.DEPOSIT
|
|
4419
|
+
);
|
|
4420
|
+
depositValue = getStrictTokenValue(
|
|
4421
|
+
quoteTokenAmount,
|
|
4422
|
+
quoteSpotMarket.decimals,
|
|
4423
|
+
strictQuote
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
calc.addIsolatedMarginCalculation(
|
|
4427
|
+
market.marketIndex,
|
|
4428
|
+
depositValue,
|
|
4429
|
+
positionUnrealizedPnl,
|
|
4430
|
+
worstCaseLiabilityValue,
|
|
4431
|
+
perpMarginRequirement
|
|
4432
|
+
);
|
|
4433
|
+
calc.addPerpLiability();
|
|
4434
|
+
calc.addPerpLiabilityValue(worstCaseLiabilityValue);
|
|
4435
|
+
} else {
|
|
4436
|
+
// cross: add to global requirement and collateral
|
|
4437
|
+
calc.addCrossMarginRequirement(
|
|
4438
|
+
perpMarginRequirement,
|
|
4439
|
+
worstCaseLiabilityValue
|
|
4440
|
+
);
|
|
4441
|
+
calc.addCrossMarginTotalCollateral(positionUnrealizedPnl);
|
|
4442
|
+
const hasPerpLiability =
|
|
4443
|
+
!marketPosition.baseAssetAmount.eq(ZERO) ||
|
|
4444
|
+
marketPosition.quoteAssetAmount.lt(ZERO) ||
|
|
4445
|
+
marketPosition.openOrders !== 0;
|
|
4446
|
+
if (hasPerpLiability) {
|
|
4447
|
+
calc.addPerpLiability();
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
return calc;
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
private isPerpPositionIsolated(perpPosition: PerpPosition): boolean {
|
|
4456
|
+
return (perpPosition.positionFlag & PositionFlag.IsolatedPosition) !== 0;
|
|
4457
|
+
}
|
|
3916
4458
|
}
|