@drift-labs/sdk 2.15.0-beta.0 → 2.16.0-beta.0

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/src/user.ts CHANGED
@@ -45,8 +45,11 @@ import {
45
45
  BN,
46
46
  SpotMarketAccount,
47
47
  getTokenValue,
48
+ MarketType,
48
49
  getStrictTokenValue,
50
+ calculateSpotMarketMarginRatio,
49
51
  getSignedTokenAmount,
52
+ SpotBalanceType,
50
53
  } from '.';
51
54
  import {
52
55
  getTokenAmount,
@@ -370,7 +373,7 @@ export class User {
370
373
  * calculates Buying Power = free collateral / initial margin ratio
371
374
  * @returns : Precision QUOTE_PRECISION
372
375
  */
373
- public getBuyingPower(marketIndex: number): BN {
376
+ public getPerpBuyingPower(marketIndex: number): BN {
374
377
  const perpPosition = this.getPerpPosition(marketIndex);
375
378
  const worstCaseBaseAssetAmount = perpPosition
376
379
  ? calculateWorstCaseBaseAssetAmount(perpPosition)
@@ -378,14 +381,14 @@ export class User {
378
381
 
379
382
  const freeCollateral = this.getFreeCollateral();
380
383
 
381
- return this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
384
+ return this.getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount(
382
385
  marketIndex,
383
386
  freeCollateral,
384
387
  worstCaseBaseAssetAmount
385
388
  );
386
389
  }
387
390
 
388
- getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
391
+ getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount(
389
392
  marketIndex: number,
390
393
  freeCollateral: BN,
391
394
  baseAssetAmount: BN
@@ -525,91 +528,88 @@ export class User {
525
528
  }, ZERO);
526
529
  }
527
530
 
528
- public getSpotMarketLiabilityValue(
531
+ public getSpotMarketAssetAndLiabilityValue(
529
532
  marketIndex?: number,
530
533
  marginCategory?: MarginCategory,
531
534
  liquidationBuffer?: BN,
532
535
  includeOpenOrders?: boolean,
533
536
  strict = false,
534
537
  now?: BN
535
- ): BN {
538
+ ): { totalAssetValue: BN; totalLiabilityValue: BN } {
536
539
  now = now || new BN(new Date().getTime() / 1000);
537
- return this.getUserAccount().spotPositions.reduce(
538
- (totalLiabilityValue, spotPosition) => {
539
- if (
540
- isSpotPositionAvailable(spotPosition) ||
541
- (marketIndex !== undefined &&
542
- spotPosition.marketIndex !== marketIndex)
543
- ) {
544
- return totalLiabilityValue;
545
- }
540
+ let netQuoteValue = ZERO;
541
+ let totalAssetValue = ZERO;
542
+ let totalLiabilityValue = ZERO;
543
+ for (const spotPosition of this.getUserAccount().spotPositions) {
544
+ const countForBase =
545
+ marketIndex === undefined || spotPosition.marketIndex === marketIndex;
546
+
547
+ const countForQuote =
548
+ marketIndex === undefined ||
549
+ marketIndex === QUOTE_SPOT_MARKET_INDEX ||
550
+ (includeOpenOrders && spotPosition.openOrders !== 0);
551
+ if (
552
+ isSpotPositionAvailable(spotPosition) ||
553
+ (!countForBase && !countForQuote)
554
+ ) {
555
+ continue;
556
+ }
546
557
 
547
- const spotMarketAccount: SpotMarketAccount =
548
- this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
558
+ const spotMarketAccount: SpotMarketAccount =
559
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
549
560
 
550
- if (spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX) {
551
- if (isVariant(spotPosition.balanceType, 'borrow')) {
552
- const tokenAmount = getTokenAmount(
553
- spotPosition.scaledBalance,
554
- spotMarketAccount,
555
- spotPosition.balanceType
556
- );
561
+ if (
562
+ spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX &&
563
+ countForQuote
564
+ ) {
565
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
566
+ const tokenAmount = getTokenAmount(
567
+ spotPosition.scaledBalance,
568
+ spotMarketAccount,
569
+ spotPosition.balanceType
570
+ );
557
571
 
558
- let weight = SPOT_MARKET_WEIGHT_PRECISION;
559
- if (marginCategory === 'Initial') {
560
- weight = BN.max(
561
- weight,
562
- new BN(this.getUserAccount().maxMarginRatio)
563
- );
564
- }
565
-
566
- const weightedTokenValue = tokenAmount
567
- .mul(weight)
568
- .div(SPOT_MARKET_WEIGHT_PRECISION);
569
-
570
- return totalLiabilityValue.add(weightedTokenValue);
571
- } else {
572
- return totalLiabilityValue;
572
+ let weight = SPOT_MARKET_WEIGHT_PRECISION;
573
+ if (marginCategory === 'Initial') {
574
+ weight = BN.max(
575
+ weight,
576
+ new BN(this.getUserAccount().maxMarginRatio)
577
+ );
573
578
  }
574
- }
575
579
 
576
- const oraclePriceData = this.getOracleDataForSpotMarket(
577
- spotPosition.marketIndex
578
- );
580
+ const weightedTokenValue = tokenAmount
581
+ .mul(weight)
582
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
579
583
 
580
- if (!includeOpenOrders) {
581
- if (isVariant(spotPosition.balanceType, 'borrow')) {
582
- const tokenAmount = getTokenAmount(
583
- spotPosition.scaledBalance,
584
- spotMarketAccount,
585
- spotPosition.balanceType
586
- );
587
- const liabilityValue = this.getSpotLiabilityValue(
588
- tokenAmount,
589
- oraclePriceData,
590
- spotMarketAccount,
591
- marginCategory,
592
- liquidationBuffer,
593
- strict,
594
- now
595
- );
596
- return totalLiabilityValue.add(liabilityValue);
597
- } else {
598
- return totalLiabilityValue;
599
- }
600
- }
584
+ netQuoteValue = netQuoteValue.sub(weightedTokenValue);
601
585
 
602
- const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
603
- getWorstCaseTokenAmounts(
604
- spotPosition,
586
+ continue;
587
+ } else {
588
+ const tokenAmount = getTokenAmount(
589
+ spotPosition.scaledBalance,
605
590
  spotMarketAccount,
606
- this.getOracleDataForSpotMarket(spotPosition.marketIndex)
591
+ spotPosition.balanceType
607
592
  );
608
593
 
609
- let newTotalLiabilityValue = totalLiabilityValue;
610
- if (worstCaseTokenAmount.lt(ZERO)) {
611
- const baseLiabilityValue = this.getSpotLiabilityValue(
612
- worstCaseTokenAmount.abs(),
594
+ netQuoteValue = netQuoteValue.add(tokenAmount);
595
+
596
+ continue;
597
+ }
598
+ }
599
+
600
+ const oraclePriceData = this.getOracleDataForSpotMarket(
601
+ spotPosition.marketIndex
602
+ );
603
+
604
+ if (!includeOpenOrders && countForBase) {
605
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
606
+ const tokenAmount = getTokenAmount(
607
+ spotPosition.scaledBalance,
608
+ spotMarketAccount,
609
+ spotPosition.balanceType
610
+ );
611
+ const liabilityValue = this.getSpotLiabilityValue(
612
+ tokenAmount,
613
613
  oraclePriceData,
614
614
  spotMarketAccount,
615
615
  marginCategory,
@@ -617,37 +617,112 @@ export class User {
617
617
  strict,
618
618
  now
619
619
  );
620
+ totalLiabilityValue = totalLiabilityValue.add(liabilityValue);
621
+
622
+ continue;
623
+ } else {
624
+ const tokenAmount = getTokenAmount(
625
+ spotPosition.scaledBalance,
626
+ spotMarketAccount,
627
+ spotPosition.balanceType
628
+ );
629
+ const assetValue = this.getSpotAssetValue(
630
+ tokenAmount,
631
+ oraclePriceData,
632
+ spotMarketAccount,
633
+ marginCategory,
634
+ strict,
635
+ now
636
+ );
637
+ totalAssetValue = totalAssetValue.add(assetValue);
620
638
 
621
- newTotalLiabilityValue =
622
- newTotalLiabilityValue.add(baseLiabilityValue);
639
+ continue;
623
640
  }
641
+ }
624
642
 
625
- if (worstCaseQuoteTokenAmount.lt(ZERO)) {
626
- let weight = SPOT_MARKET_WEIGHT_PRECISION;
627
- if (marginCategory === 'Initial') {
628
- weight = BN.max(
629
- weight,
630
- new BN(this.getUserAccount().maxMarginRatio)
631
- );
632
- }
643
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
644
+ getWorstCaseTokenAmounts(
645
+ spotPosition,
646
+ spotMarketAccount,
647
+ this.getOracleDataForSpotMarket(spotPosition.marketIndex)
648
+ );
633
649
 
634
- const weightedTokenValue = worstCaseQuoteTokenAmount
635
- .abs()
636
- .mul(weight)
637
- .div(SPOT_MARKET_WEIGHT_PRECISION);
650
+ if (worstCaseTokenAmount.gt(ZERO) && countForBase) {
651
+ const baseAssetValue = this.getSpotAssetValue(
652
+ worstCaseTokenAmount,
653
+ oraclePriceData,
654
+ spotMarketAccount,
655
+ marginCategory,
656
+ strict,
657
+ now
658
+ );
638
659
 
639
- newTotalLiabilityValue =
640
- newTotalLiabilityValue.add(weightedTokenValue);
641
- }
660
+ totalAssetValue = totalAssetValue.add(baseAssetValue);
661
+ }
642
662
 
643
- newTotalLiabilityValue = newTotalLiabilityValue.add(
644
- new BN(spotPosition.openOrders).mul(OPEN_ORDER_MARGIN_REQUIREMENT)
663
+ if (worstCaseTokenAmount.lt(ZERO) && countForBase) {
664
+ const baseLiabilityValue = this.getSpotLiabilityValue(
665
+ worstCaseTokenAmount.abs(),
666
+ oraclePriceData,
667
+ spotMarketAccount,
668
+ marginCategory,
669
+ liquidationBuffer,
670
+ strict,
671
+ now
645
672
  );
646
673
 
647
- return newTotalLiabilityValue;
648
- },
649
- ZERO
674
+ totalLiabilityValue = totalLiabilityValue.add(baseLiabilityValue);
675
+ }
676
+
677
+ if (worstCaseQuoteTokenAmount.gt(ZERO) && countForQuote) {
678
+ netQuoteValue = netQuoteValue.add(worstCaseQuoteTokenAmount);
679
+ }
680
+
681
+ if (worstCaseQuoteTokenAmount.lt(ZERO) && countForQuote) {
682
+ let weight = SPOT_MARKET_WEIGHT_PRECISION;
683
+ if (marginCategory === 'Initial') {
684
+ weight = BN.max(weight, new BN(this.getUserAccount().maxMarginRatio));
685
+ }
686
+
687
+ const weightedTokenValue = worstCaseQuoteTokenAmount
688
+ .abs()
689
+ .mul(weight)
690
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
691
+
692
+ netQuoteValue = netQuoteValue.sub(weightedTokenValue);
693
+ }
694
+
695
+ totalLiabilityValue = totalLiabilityValue.add(
696
+ new BN(spotPosition.openOrders).mul(OPEN_ORDER_MARGIN_REQUIREMENT)
697
+ );
698
+ }
699
+
700
+ if (netQuoteValue.gt(ZERO)) {
701
+ totalAssetValue = totalAssetValue.add(netQuoteValue);
702
+ } else {
703
+ totalLiabilityValue = totalLiabilityValue.add(netQuoteValue.abs());
704
+ }
705
+
706
+ return { totalAssetValue, totalLiabilityValue };
707
+ }
708
+
709
+ public getSpotMarketLiabilityValue(
710
+ marketIndex?: number,
711
+ marginCategory?: MarginCategory,
712
+ liquidationBuffer?: BN,
713
+ includeOpenOrders?: boolean,
714
+ strict = false,
715
+ now?: BN
716
+ ): BN {
717
+ const { totalLiabilityValue } = this.getSpotMarketAssetAndLiabilityValue(
718
+ marketIndex,
719
+ marginCategory,
720
+ liquidationBuffer,
721
+ includeOpenOrders,
722
+ strict,
723
+ now
650
724
  );
725
+ return totalLiabilityValue;
651
726
  }
652
727
 
653
728
  getSpotLiabilityValue(
@@ -712,91 +787,15 @@ export class User {
712
787
  strict = false,
713
788
  now?: BN
714
789
  ): BN {
715
- now = now || new BN(new Date().getTime() / 1000);
716
- return this.getUserAccount().spotPositions.reduce(
717
- (totalAssetValue, spotPosition) => {
718
- if (
719
- isSpotPositionAvailable(spotPosition) ||
720
- (marketIndex !== undefined &&
721
- spotPosition.marketIndex !== marketIndex)
722
- ) {
723
- return totalAssetValue;
724
- }
725
-
726
- // Todo this needs to account for whether it's based on initial or maintenance requirements
727
- const spotMarketAccount: SpotMarketAccount =
728
- this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
729
-
730
- if (spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX) {
731
- if (isVariant(spotPosition.balanceType, 'deposit')) {
732
- const tokenAmount = getTokenAmount(
733
- spotPosition.scaledBalance,
734
- spotMarketAccount,
735
- spotPosition.balanceType
736
- );
737
-
738
- return totalAssetValue.add(tokenAmount);
739
- } else {
740
- return totalAssetValue;
741
- }
742
- }
743
-
744
- const oraclePriceData = this.getOracleDataForSpotMarket(
745
- spotPosition.marketIndex
746
- );
747
-
748
- if (!includeOpenOrders) {
749
- if (isVariant(spotPosition.balanceType, 'deposit')) {
750
- const tokenAmount = getTokenAmount(
751
- spotPosition.scaledBalance,
752
- spotMarketAccount,
753
- spotPosition.balanceType
754
- );
755
- const assetValue = this.getSpotAssetValue(
756
- tokenAmount,
757
- oraclePriceData,
758
- spotMarketAccount,
759
- marginCategory,
760
- strict,
761
- now
762
- );
763
- return totalAssetValue.add(assetValue);
764
- } else {
765
- return totalAssetValue;
766
- }
767
- }
768
-
769
- const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
770
- getWorstCaseTokenAmounts(
771
- spotPosition,
772
- spotMarketAccount,
773
- this.getOracleDataForSpotMarket(spotPosition.marketIndex)
774
- );
775
-
776
- let newTotalAssetValue = totalAssetValue;
777
- if (worstCaseTokenAmount.gt(ZERO)) {
778
- const baseAssetValue = this.getSpotAssetValue(
779
- worstCaseTokenAmount,
780
- oraclePriceData,
781
- spotMarketAccount,
782
- marginCategory,
783
- strict,
784
- now
785
- );
786
-
787
- newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
788
- }
789
-
790
- if (worstCaseQuoteTokenAmount.gt(ZERO)) {
791
- newTotalAssetValue = newTotalAssetValue.add(
792
- worstCaseQuoteTokenAmount
793
- );
794
- }
795
-
796
- return newTotalAssetValue;
797
- },
798
- ZERO
790
+ const { totalAssetValue } = this.getSpotMarketAssetAndLiabilityValue(
791
+ marketIndex,
792
+ marginCategory,
793
+ undefined,
794
+ includeOpenOrders,
795
+ strict,
796
+ now
799
797
  );
798
+ return totalAssetValue;
800
799
  }
801
800
 
802
801
  getSpotAssetValue(
@@ -842,15 +841,45 @@ export class User {
842
841
  return assetValue;
843
842
  }
844
843
 
845
- public getNetSpotMarketValue(withWeightMarginCategory?: MarginCategory): BN {
846
- return this.getSpotMarketAssetValue(
847
- undefined,
848
- withWeightMarginCategory
849
- ).sub(
850
- this.getSpotMarketLiabilityValue(undefined, withWeightMarginCategory)
844
+ public getSpotTokenAmount(marketIndex: number): BN {
845
+ const spotPosition = this.getSpotPosition(marketIndex);
846
+ return getTokenAmount(
847
+ spotPosition.scaledBalance,
848
+ this.driftClient.getSpotMarketAccount(marketIndex),
849
+ spotPosition.balanceType
851
850
  );
852
851
  }
853
852
 
853
+ public getSpotPositionValue(
854
+ marketIndex: number,
855
+ marginCategory?: MarginCategory,
856
+ includeOpenOrders?: boolean,
857
+ strict = false,
858
+ now?: BN
859
+ ): BN {
860
+ const { totalAssetValue, totalLiabilityValue } =
861
+ this.getSpotMarketAssetAndLiabilityValue(
862
+ marketIndex,
863
+ marginCategory,
864
+ undefined,
865
+ includeOpenOrders,
866
+ strict,
867
+ now
868
+ );
869
+
870
+ return totalAssetValue.sub(totalLiabilityValue);
871
+ }
872
+
873
+ public getNetSpotMarketValue(withWeightMarginCategory?: MarginCategory): BN {
874
+ const { totalAssetValue, totalLiabilityValue } =
875
+ this.getSpotMarketAssetAndLiabilityValue(
876
+ undefined,
877
+ withWeightMarginCategory
878
+ );
879
+
880
+ return totalAssetValue.sub(totalLiabilityValue);
881
+ }
882
+
854
883
  /**
855
884
  * calculates TotalCollateral: collateral + unrealized pnl
856
885
  * @returns : Precision QUOTE_PRECISION
@@ -1098,28 +1127,50 @@ export class User {
1098
1127
  * @returns : Precision TEN_THOUSAND
1099
1128
  */
1100
1129
  public getLeverage(): BN {
1101
- const totalPerpLiability = this.getTotalPerpPositionValue(
1130
+ // get leverage components
1131
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
1132
+ this.getLeverageComponents();
1133
+
1134
+ const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
1135
+ const totalAssetValue = spotAssetValue.add(perpPnl);
1136
+ const netAssetValue = totalAssetValue.sub(spotLiabilityValue);
1137
+
1138
+ if (netAssetValue.eq(ZERO)) {
1139
+ return ZERO;
1140
+ }
1141
+
1142
+ return totalLiabilityValue.mul(TEN_THOUSAND).div(netAssetValue);
1143
+ }
1144
+
1145
+ getLeverageComponents(): {
1146
+ perpLiabilityValue: BN;
1147
+ perpPnl: BN;
1148
+ spotAssetValue: BN;
1149
+ spotLiabilityValue: BN;
1150
+ } {
1151
+ const perpLiability = this.getTotalPerpPositionValue(
1102
1152
  undefined,
1103
1153
  undefined,
1104
1154
  true
1105
1155
  );
1106
- const totalSpotLiability = this.getSpotMarketLiabilityValue(
1156
+ const perpPnl = this.getUnrealizedPNL(true);
1157
+
1158
+ const {
1159
+ totalAssetValue: spotAssetValue,
1160
+ totalLiabilityValue: spotLiabilityValue,
1161
+ } = this.getSpotMarketAssetAndLiabilityValue(
1107
1162
  undefined,
1108
1163
  undefined,
1109
1164
  undefined,
1110
1165
  true
1111
1166
  );
1112
1167
 
1113
- const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1114
-
1115
- const totalAssetValue = this.getTotalAssetValue();
1116
- const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1117
-
1118
- if (netAssetValue.eq(ZERO)) {
1119
- return ZERO;
1120
- }
1121
-
1122
- return totalLiabilityValue.mul(TEN_THOUSAND).div(netAssetValue);
1168
+ return {
1169
+ perpLiabilityValue: perpLiability,
1170
+ perpPnl,
1171
+ spotAssetValue,
1172
+ spotLiabilityValue,
1173
+ };
1123
1174
  }
1124
1175
 
1125
1176
  getTotalLiabilityValue(marginCategory?: MarginCategory): BN {
@@ -1144,37 +1195,27 @@ export class User {
1144
1195
  * @params category {Initial, Maintenance}
1145
1196
  * @returns : Precision TEN_THOUSAND
1146
1197
  */
1147
- public getMaxLeverage(
1148
- marketIndex: number,
1198
+ public getMaxLeverageForPerp(
1199
+ perpMarketIndex: number,
1149
1200
  category: MarginCategory = 'Initial'
1150
1201
  ): BN {
1151
- const market = this.driftClient.getPerpMarketAccount(marketIndex);
1202
+ const market = this.driftClient.getPerpMarketAccount(perpMarketIndex);
1152
1203
 
1153
- const totalPerpLiability = this.getTotalPerpPositionValue(
1154
- undefined,
1155
- undefined,
1156
- true
1157
- );
1158
- const totalSpotLiability = this.getSpotMarketLiabilityValue(
1159
- undefined,
1160
- undefined,
1161
- undefined,
1162
- true
1163
- );
1204
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
1205
+ this.getLeverageComponents();
1164
1206
 
1165
- const totalAssetValue = this.getTotalAssetValue();
1207
+ const totalAssetValue = spotAssetValue.add(perpPnl);
1166
1208
 
1167
- const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1209
+ const netAssetValue = totalAssetValue.sub(spotLiabilityValue);
1168
1210
 
1169
1211
  if (netAssetValue.eq(ZERO)) {
1170
1212
  return ZERO;
1171
1213
  }
1172
1214
 
1173
- const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1215
+ const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
1174
1216
 
1175
1217
  const marginRatio = calculateMarketMarginRatio(
1176
1218
  market,
1177
- // worstCaseBaseAssetAmount.abs(),
1178
1219
  ZERO, // todo
1179
1220
  category
1180
1221
  );
@@ -1192,30 +1233,113 @@ export class User {
1192
1233
  }
1193
1234
 
1194
1235
  /**
1195
- * calculates margin ratio: total collateral / |total position value|
1236
+ * calculates max allowable leverage exceeding hitting requirement category
1237
+ * @param spotMarketIndex
1238
+ * @param direction
1196
1239
  * @returns : Precision TEN_THOUSAND
1197
1240
  */
1198
- public getMarginRatio(marginCategory?: MarginCategory): BN {
1199
- const totalPerpLiability = this.getTotalPerpPositionValue(
1200
- undefined,
1201
- undefined,
1202
- true
1241
+ public getMaxLeverageForSpot(
1242
+ spotMarketIndex: number,
1243
+ direction: PositionDirection
1244
+ ): BN {
1245
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
1246
+ this.getLeverageComponents();
1247
+
1248
+ const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
1249
+ const totalAssetValue = spotAssetValue.add(perpPnl);
1250
+
1251
+ const netAssetValue = totalAssetValue.sub(spotLiabilityValue);
1252
+
1253
+ if (netAssetValue.eq(ZERO)) {
1254
+ return ZERO;
1255
+ }
1256
+
1257
+ const currentQuoteAssetValue = this.getSpotMarketAssetValue(
1258
+ QUOTE_SPOT_MARKET_INDEX
1203
1259
  );
1204
- const totalSpotLiability = this.getSpotMarketLiabilityValue(
1205
- undefined,
1206
- undefined,
1207
- undefined,
1208
- true
1260
+ const currentQuoteLiabilityValue = this.getSpotMarketLiabilityValue(
1261
+ QUOTE_SPOT_MARKET_INDEX
1262
+ );
1263
+ const currentQuoteValue = currentQuoteAssetValue.sub(
1264
+ currentQuoteLiabilityValue
1265
+ );
1266
+
1267
+ const currentSpotMarketAssetValue =
1268
+ this.getSpotMarketAssetValue(spotMarketIndex);
1269
+ const currentSpotMarketLiabilityValue =
1270
+ this.getSpotMarketLiabilityValue(spotMarketIndex);
1271
+ const currentSpotMarketNetValue = currentSpotMarketAssetValue.sub(
1272
+ currentSpotMarketLiabilityValue
1273
+ );
1274
+
1275
+ const tradeQuoteAmount = this.getMaxTradeSizeUSDCForSpot(
1276
+ spotMarketIndex,
1277
+ direction,
1278
+ currentQuoteAssetValue,
1279
+ currentSpotMarketNetValue
1209
1280
  );
1210
1281
 
1211
- const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1282
+ let assetValueToAdd = ZERO;
1283
+ let liabilityValueToAdd = ZERO;
1284
+
1285
+ const newQuoteNetValue = isVariant(direction, 'short')
1286
+ ? currentQuoteValue.add(tradeQuoteAmount)
1287
+ : currentQuoteValue.sub(tradeQuoteAmount);
1288
+ const newQuoteAssetValue = BN.max(newQuoteNetValue, ZERO);
1289
+ const newQuoteLiabilityValue = BN.min(newQuoteNetValue, ZERO).abs();
1290
+
1291
+ assetValueToAdd = assetValueToAdd.add(
1292
+ newQuoteAssetValue.sub(currentQuoteAssetValue)
1293
+ );
1294
+ liabilityValueToAdd = liabilityValueToAdd.add(
1295
+ newQuoteLiabilityValue.sub(currentQuoteLiabilityValue)
1296
+ );
1297
+
1298
+ const newSpotMarketNetValue = isVariant(direction, 'long')
1299
+ ? currentSpotMarketNetValue.add(tradeQuoteAmount)
1300
+ : currentSpotMarketNetValue.sub(tradeQuoteAmount);
1301
+ const newSpotMarketAssetValue = BN.max(newSpotMarketNetValue, ZERO);
1302
+ const newSpotMarketLiabilityValue = BN.min(
1303
+ newSpotMarketNetValue,
1304
+ ZERO
1305
+ ).abs();
1306
+
1307
+ assetValueToAdd = assetValueToAdd.add(
1308
+ newSpotMarketAssetValue.sub(currentSpotMarketAssetValue)
1309
+ );
1310
+ liabilityValueToAdd = liabilityValueToAdd.add(
1311
+ newSpotMarketLiabilityValue.sub(currentSpotMarketLiabilityValue)
1312
+ );
1313
+
1314
+ const finalTotalAssetValue = totalAssetValue.add(assetValueToAdd);
1315
+ const finalTotalSpotLiability = spotLiabilityValue.add(liabilityValueToAdd);
1316
+
1317
+ const finalTotalLiabilityValue =
1318
+ totalLiabilityValue.add(liabilityValueToAdd);
1319
+
1320
+ const finalNetAssetValue = finalTotalAssetValue.sub(
1321
+ finalTotalSpotLiability
1322
+ );
1323
+
1324
+ return finalTotalLiabilityValue.mul(TEN_THOUSAND).div(finalNetAssetValue);
1325
+ }
1326
+
1327
+ /**
1328
+ * calculates margin ratio: 1 / leverage
1329
+ * @returns : Precision TEN_THOUSAND
1330
+ */
1331
+ public getMarginRatio(): BN {
1332
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
1333
+ this.getLeverageComponents();
1334
+
1335
+ const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
1336
+ const totalAssetValue = spotAssetValue.add(perpPnl);
1212
1337
 
1213
1338
  if (totalLiabilityValue.eq(ZERO)) {
1214
1339
  return BN_MAX;
1215
1340
  }
1216
1341
 
1217
- const totalAssetValue = this.getTotalAssetValue(marginCategory);
1218
- const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1342
+ const netAssetValue = totalAssetValue.sub(spotLiabilityValue);
1219
1343
 
1220
1344
  return netAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1221
1345
  }
@@ -1285,7 +1409,10 @@ export class User {
1285
1409
  * @param marketIndex
1286
1410
  * @returns Precision : PRICE_PRECISION
1287
1411
  */
1288
- public spotLiquidationPrice(marketIndex: number): BN {
1412
+ public spotLiquidationPrice(
1413
+ marketIndex: number,
1414
+ positionBaseSizeChange: BN = ZERO
1415
+ ): BN {
1289
1416
  const currentSpotPosition = this.getSpotPosition(marketIndex);
1290
1417
 
1291
1418
  if (!currentSpotPosition) {
@@ -1300,7 +1427,7 @@ export class User {
1300
1427
  );
1301
1428
 
1302
1429
  const market = this.driftClient.getSpotMarketAccount(marketIndex);
1303
- const signedTokenAmount = getSignedTokenAmount(
1430
+ let signedTokenAmount = getSignedTokenAmount(
1304
1431
  getTokenAmount(
1305
1432
  currentSpotPosition.scaledBalance,
1306
1433
  market,
@@ -1308,6 +1435,7 @@ export class User {
1308
1435
  ),
1309
1436
  currentSpotPosition.balanceType
1310
1437
  );
1438
+ signedTokenAmount = signedTokenAmount.add(positionBaseSizeChange);
1311
1439
 
1312
1440
  if (signedTokenAmount.eq(ZERO)) {
1313
1441
  return new BN(-1);
@@ -1570,7 +1698,7 @@ export class User {
1570
1698
  * @param tradeSide
1571
1699
  * @returns tradeSizeAllowed : Precision QUOTE_PRECISION
1572
1700
  */
1573
- public getMaxTradeSizeUSDC(
1701
+ public getMaxTradeSizeUSDCForPerp(
1574
1702
  targetMarketIndex: number,
1575
1703
  tradeSide: PositionDirection
1576
1704
  ): BN {
@@ -1595,7 +1723,7 @@ export class User {
1595
1723
  ? ZERO
1596
1724
  : this.getPerpPositionValue(targetMarketIndex, oracleData);
1597
1725
 
1598
- let maxPositionSize = this.getBuyingPower(targetMarketIndex);
1726
+ let maxPositionSize = this.getPerpBuyingPower(targetMarketIndex);
1599
1727
  if (maxPositionSize.gte(ZERO)) {
1600
1728
  if (oppositeSizeValueUSDC.eq(ZERO)) {
1601
1729
  // case 1 : Regular trade where current total position less than max, and no opposite position to account for
@@ -1629,8 +1757,9 @@ export class User {
1629
1757
  const freeCollateralAfterClose = totalCollateral.sub(
1630
1758
  marginRequirementAfterClosing
1631
1759
  );
1760
+
1632
1761
  const buyingPowerAfterClose =
1633
- this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
1762
+ this.getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount(
1634
1763
  targetMarketIndex,
1635
1764
  freeCollateralAfterClose,
1636
1765
  ZERO
@@ -1648,21 +1777,202 @@ export class User {
1648
1777
  return maxPositionSize.sub(oneMilli);
1649
1778
  }
1650
1779
 
1780
+ /**
1781
+ * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
1782
+ *
1783
+ * @param targetMarketIndex
1784
+ * @param direction
1785
+ * @param currentQuoteAssetValue
1786
+ * @param currentSpotMarketNetValue
1787
+ * @returns tradeSizeAllowed : Precision QUOTE_PRECISION
1788
+ */
1789
+ public getMaxTradeSizeUSDCForSpot(
1790
+ targetMarketIndex: number,
1791
+ direction: PositionDirection,
1792
+ currentQuoteAssetValue?: BN,
1793
+ currentSpotMarketNetValue?: BN
1794
+ ): BN {
1795
+ const market = this.driftClient.getSpotMarketAccount(targetMarketIndex);
1796
+
1797
+ currentQuoteAssetValue = this.getSpotMarketAssetValue(
1798
+ QUOTE_SPOT_MARKET_INDEX
1799
+ );
1800
+
1801
+ currentSpotMarketNetValue =
1802
+ currentSpotMarketNetValue ?? this.getSpotPositionValue(targetMarketIndex);
1803
+
1804
+ let freeCollateral = this.getFreeCollateral();
1805
+ const marginRatio = calculateSpotMarketMarginRatio(
1806
+ market,
1807
+ 'Initial',
1808
+ ZERO,
1809
+ isVariant(direction, 'long')
1810
+ ? SpotBalanceType.DEPOSIT
1811
+ : SpotBalanceType.BORROW
1812
+ );
1813
+
1814
+ let tradeAmount = ZERO;
1815
+ if (this.getUserAccount().isMarginTradingEnabled) {
1816
+ // if the user is buying/selling and already short/long, need to account for closing out short/long
1817
+ if (isVariant(direction, 'long') && currentSpotMarketNetValue.lt(ZERO)) {
1818
+ tradeAmount = currentSpotMarketNetValue.abs();
1819
+ const marginRatio = calculateSpotMarketMarginRatio(
1820
+ market,
1821
+ 'Initial',
1822
+ this.getSpotTokenAmount(targetMarketIndex),
1823
+ SpotBalanceType.BORROW
1824
+ );
1825
+ freeCollateral = freeCollateral.add(
1826
+ tradeAmount.mul(new BN(marginRatio)).div(MARGIN_PRECISION)
1827
+ );
1828
+ } else if (
1829
+ isVariant(direction, 'short') &&
1830
+ currentSpotMarketNetValue.gt(ZERO)
1831
+ ) {
1832
+ tradeAmount = currentSpotMarketNetValue;
1833
+ const marginRatio = calculateSpotMarketMarginRatio(
1834
+ market,
1835
+ 'Initial',
1836
+ this.getSpotTokenAmount(targetMarketIndex),
1837
+ SpotBalanceType.DEPOSIT
1838
+ );
1839
+ freeCollateral = freeCollateral.add(
1840
+ tradeAmount.mul(new BN(marginRatio)).div(MARGIN_PRECISION)
1841
+ );
1842
+ }
1843
+
1844
+ tradeAmount = tradeAmount.add(
1845
+ freeCollateral.mul(MARGIN_PRECISION).div(new BN(marginRatio))
1846
+ );
1847
+ } else if (isVariant(direction, 'long')) {
1848
+ tradeAmount = BN.min(
1849
+ currentQuoteAssetValue,
1850
+ freeCollateral.mul(MARGIN_PRECISION).div(new BN(marginRatio))
1851
+ );
1852
+ } else {
1853
+ tradeAmount = BN.max(ZERO, currentSpotMarketNetValue);
1854
+ }
1855
+
1856
+ return tradeAmount;
1857
+ }
1858
+
1651
1859
  // TODO - should this take the price impact of the trade into account for strict accuracy?
1652
1860
 
1653
1861
  /**
1654
1862
  * Returns the leverage ratio for the account after adding (or subtracting) the given quote size to the given position
1655
1863
  * @param targetMarketIndex
1656
- * @param positionMarketIndex
1864
+ * @param: targetMarketType
1657
1865
  * @param tradeQuoteAmount
1866
+ * @param tradeSide
1867
+ * @param includeOpenOrders
1658
1868
  * @returns leverageRatio : Precision TEN_THOUSAND
1659
1869
  */
1660
1870
  public accountLeverageRatioAfterTrade(
1661
1871
  targetMarketIndex: number,
1872
+ targetMarketType: MarketType,
1662
1873
  tradeQuoteAmount: BN,
1663
1874
  tradeSide: PositionDirection,
1664
1875
  includeOpenOrders = true
1665
1876
  ): BN {
1877
+ const tradeIsPerp = isVariant(targetMarketType, 'perp');
1878
+
1879
+ if (!tradeIsPerp) {
1880
+ // calculate new asset/liability values for base and quote market to find new account leverage
1881
+ const totalLiabilityValue = this.getTotalLiabilityValue();
1882
+ const totalAssetValue = this.getTotalAssetValue();
1883
+ const spotLiabilityValue = this.getSpotMarketLiabilityValue(
1884
+ undefined,
1885
+ undefined,
1886
+ undefined,
1887
+ includeOpenOrders
1888
+ );
1889
+
1890
+ const currentQuoteAssetValue = this.getSpotMarketAssetValue(
1891
+ QUOTE_SPOT_MARKET_INDEX,
1892
+ undefined,
1893
+ includeOpenOrders
1894
+ );
1895
+ const currentQuoteLiabilityValue = this.getSpotMarketLiabilityValue(
1896
+ QUOTE_SPOT_MARKET_INDEX,
1897
+ undefined,
1898
+ undefined,
1899
+ includeOpenOrders
1900
+ );
1901
+ const currentQuoteValue = currentQuoteAssetValue.sub(
1902
+ currentQuoteLiabilityValue
1903
+ );
1904
+
1905
+ const currentSpotMarketAssetValue = this.getSpotMarketAssetValue(
1906
+ targetMarketIndex,
1907
+ undefined,
1908
+ includeOpenOrders
1909
+ );
1910
+ const currentSpotMarketLiabilityValue = this.getSpotMarketLiabilityValue(
1911
+ targetMarketIndex,
1912
+ undefined,
1913
+ undefined,
1914
+ includeOpenOrders
1915
+ );
1916
+ const currentSpotMarketNetValue = currentSpotMarketAssetValue.sub(
1917
+ currentSpotMarketLiabilityValue
1918
+ );
1919
+
1920
+ let assetValueToAdd = ZERO;
1921
+ let liabilityValueToAdd = ZERO;
1922
+
1923
+ const newQuoteNetValue =
1924
+ tradeSide == PositionDirection.SHORT
1925
+ ? currentQuoteValue.add(tradeQuoteAmount)
1926
+ : currentQuoteValue.sub(tradeQuoteAmount);
1927
+ const newQuoteAssetValue = BN.max(newQuoteNetValue, ZERO);
1928
+ const newQuoteLiabilityValue = BN.min(newQuoteNetValue, ZERO).abs();
1929
+
1930
+ assetValueToAdd = assetValueToAdd.add(
1931
+ newQuoteAssetValue.sub(currentQuoteAssetValue)
1932
+ );
1933
+ liabilityValueToAdd = liabilityValueToAdd.add(
1934
+ newQuoteLiabilityValue.sub(currentQuoteLiabilityValue)
1935
+ );
1936
+
1937
+ const newSpotMarketNetValue =
1938
+ tradeSide == PositionDirection.LONG
1939
+ ? currentSpotMarketNetValue.add(tradeQuoteAmount)
1940
+ : currentSpotMarketNetValue.sub(tradeQuoteAmount);
1941
+ const newSpotMarketAssetValue = BN.max(newSpotMarketNetValue, ZERO);
1942
+ const newSpotMarketLiabilityValue = BN.min(
1943
+ newSpotMarketNetValue,
1944
+ ZERO
1945
+ ).abs();
1946
+
1947
+ assetValueToAdd = assetValueToAdd.add(
1948
+ newSpotMarketAssetValue.sub(currentSpotMarketAssetValue)
1949
+ );
1950
+ liabilityValueToAdd = liabilityValueToAdd.add(
1951
+ newSpotMarketLiabilityValue.sub(currentSpotMarketLiabilityValue)
1952
+ );
1953
+
1954
+ const totalAssetValueAfterTrade = totalAssetValue.add(assetValueToAdd);
1955
+ const totalSpotLiabilityValueAfterTrade =
1956
+ spotLiabilityValue.add(liabilityValueToAdd);
1957
+
1958
+ const totalLiabilityValueAfterTrade =
1959
+ totalLiabilityValue.add(liabilityValueToAdd);
1960
+
1961
+ const netAssetValueAfterTrade = totalAssetValueAfterTrade.sub(
1962
+ totalSpotLiabilityValueAfterTrade
1963
+ );
1964
+
1965
+ if (netAssetValueAfterTrade.eq(ZERO)) {
1966
+ return ZERO;
1967
+ }
1968
+
1969
+ const newLeverage = totalLiabilityValueAfterTrade
1970
+ .mul(TEN_THOUSAND)
1971
+ .div(netAssetValueAfterTrade);
1972
+
1973
+ return newLeverage;
1974
+ }
1975
+
1666
1976
  const currentPosition =
1667
1977
  this.getPerpPosition(targetMarketIndex) ||
1668
1978
  this.getEmptyPosition(targetMarketIndex);
@@ -1842,7 +2152,7 @@ export class User {
1842
2152
  const depositAmount = getTokenAmount(
1843
2153
  position.scaledBalance,
1844
2154
  spotMarket,
1845
- 'deposit'
2155
+ SpotBalanceType.DEPOSIT
1846
2156
  );
1847
2157
 
1848
2158
  if (netDeposits.lt(ZERO)) {