@drift-labs/sdk 2.13.0-beta.0 → 2.13.0-beta.1
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/lib/constants/numericConstants.d.ts +2 -0
- package/lib/constants/numericConstants.js +3 -1
- package/lib/driftClient.d.ts +9 -0
- package/lib/driftClient.js +49 -0
- package/lib/idl/drift.json +1 -1
- package/lib/math/market.d.ts +5 -0
- package/lib/math/market.js +18 -1
- package/lib/math/oracles.d.ts +2 -2
- package/lib/math/oracles.js +20 -8
- package/lib/math/orders.js +1 -1
- package/lib/math/spotBalance.d.ts +5 -0
- package/lib/math/spotBalance.js +30 -3
- package/lib/math/trade.js +8 -2
- package/lib/user.d.ts +12 -6
- package/lib/user.js +124 -72
- package/package.json +1 -1
- package/src/constants/numericConstants.ts +2 -0
- package/src/driftClient.ts +64 -0
- package/src/idl/drift.json +1 -1
- package/src/math/market.ts +51 -0
- package/src/math/oracles.ts +30 -16
- package/src/math/orders.ts +2 -2
- package/src/math/spotBalance.ts +51 -3
- package/src/math/trade.ts +10 -2
- package/src/user.ts +267 -138
- package/tests/amm/test.ts +3 -2
package/lib/user.js
CHANGED
|
@@ -11,6 +11,7 @@ const margin_1 = require("./math/margin");
|
|
|
11
11
|
const pollingUserAccountSubscriber_1 = require("./accounts/pollingUserAccountSubscriber");
|
|
12
12
|
const webSocketUserAccountSubscriber_1 = require("./accounts/webSocketUserAccountSubscriber");
|
|
13
13
|
const spotPosition_1 = require("./math/spotPosition");
|
|
14
|
+
const oracles_1 = require("./math/oracles");
|
|
14
15
|
class User {
|
|
15
16
|
constructor(config) {
|
|
16
17
|
var _a;
|
|
@@ -259,14 +260,14 @@ class User {
|
|
|
259
260
|
/**
|
|
260
261
|
* @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
|
|
261
262
|
*/
|
|
262
|
-
getMarginRequirement(marginCategory, liquidationBuffer) {
|
|
263
|
-
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true));
|
|
263
|
+
getMarginRequirement(marginCategory, liquidationBuffer, strict = false) {
|
|
264
|
+
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true, strict));
|
|
264
265
|
}
|
|
265
266
|
/**
|
|
266
267
|
* @returns The initial margin requirement in USDC. : QUOTE_PRECISION
|
|
267
268
|
*/
|
|
268
269
|
getInitialMarginRequirement() {
|
|
269
|
-
return this.getMarginRequirement('Initial');
|
|
270
|
+
return this.getMarginRequirement('Initial', undefined, true);
|
|
270
271
|
}
|
|
271
272
|
/**
|
|
272
273
|
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
@@ -317,7 +318,8 @@ class User {
|
|
|
317
318
|
return pnl.add(_1.calculatePositionFundingPNL(market, perpPosition));
|
|
318
319
|
}, numericConstants_1.ZERO);
|
|
319
320
|
}
|
|
320
|
-
getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders) {
|
|
321
|
+
getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false, now) {
|
|
322
|
+
now = now || new _1.BN(new Date().getTime() / 1000);
|
|
321
323
|
return this.getUserAccount().spotPositions.reduce((totalLiabilityValue, spotPosition) => {
|
|
322
324
|
if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
|
|
323
325
|
(marketIndex !== undefined &&
|
|
@@ -345,7 +347,7 @@ class User {
|
|
|
345
347
|
if (!includeOpenOrders) {
|
|
346
348
|
if (types_1.isVariant(spotPosition.balanceType, 'borrow')) {
|
|
347
349
|
const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
348
|
-
const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
350
|
+
const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
|
|
349
351
|
return totalLiabilityValue.add(liabilityValue);
|
|
350
352
|
}
|
|
351
353
|
else {
|
|
@@ -355,7 +357,7 @@ class User {
|
|
|
355
357
|
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
356
358
|
let newTotalLiabilityValue = totalLiabilityValue;
|
|
357
359
|
if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
|
|
358
|
-
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
360
|
+
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
|
|
359
361
|
newTotalLiabilityValue =
|
|
360
362
|
newTotalLiabilityValue.add(baseLiabilityValue);
|
|
361
363
|
}
|
|
@@ -375,8 +377,16 @@ class User {
|
|
|
375
377
|
return newTotalLiabilityValue;
|
|
376
378
|
}, numericConstants_1.ZERO);
|
|
377
379
|
}
|
|
378
|
-
getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer) {
|
|
379
|
-
let liabilityValue =
|
|
380
|
+
getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict = false, now) {
|
|
381
|
+
let liabilityValue = null;
|
|
382
|
+
if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
383
|
+
const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
|
|
384
|
+
);
|
|
385
|
+
liabilityValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
liabilityValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
|
|
389
|
+
}
|
|
380
390
|
if (marginCategory !== undefined) {
|
|
381
391
|
let weight = spotBalance_1.calculateLiabilityWeight(tokenAmount, spotMarketAccount, marginCategory);
|
|
382
392
|
if (marginCategory === 'Initial') {
|
|
@@ -391,7 +401,8 @@ class User {
|
|
|
391
401
|
}
|
|
392
402
|
return liabilityValue;
|
|
393
403
|
}
|
|
394
|
-
getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders) {
|
|
404
|
+
getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders, strict = false, now) {
|
|
405
|
+
now = now || new _1.BN(new Date().getTime() / 1000);
|
|
395
406
|
return this.getUserAccount().spotPositions.reduce((totalAssetValue, spotPosition) => {
|
|
396
407
|
if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
|
|
397
408
|
(marketIndex !== undefined &&
|
|
@@ -413,7 +424,7 @@ class User {
|
|
|
413
424
|
if (!includeOpenOrders) {
|
|
414
425
|
if (types_1.isVariant(spotPosition.balanceType, 'deposit')) {
|
|
415
426
|
const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
416
|
-
const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
|
|
427
|
+
const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
|
|
417
428
|
return totalAssetValue.add(assetValue);
|
|
418
429
|
}
|
|
419
430
|
else {
|
|
@@ -423,7 +434,7 @@ class User {
|
|
|
423
434
|
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
424
435
|
let newTotalAssetValue = totalAssetValue;
|
|
425
436
|
if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
|
|
426
|
-
const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
|
|
437
|
+
const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
|
|
427
438
|
newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
|
|
428
439
|
}
|
|
429
440
|
if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO)) {
|
|
@@ -432,8 +443,16 @@ class User {
|
|
|
432
443
|
return newTotalAssetValue;
|
|
433
444
|
}, numericConstants_1.ZERO);
|
|
434
445
|
}
|
|
435
|
-
getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory) {
|
|
436
|
-
let assetValue =
|
|
446
|
+
getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict = false, now) {
|
|
447
|
+
let assetValue = null;
|
|
448
|
+
if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
449
|
+
const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
|
|
450
|
+
);
|
|
451
|
+
assetValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
assetValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
|
|
455
|
+
}
|
|
437
456
|
if (marginCategory !== undefined) {
|
|
438
457
|
const weight = spotBalance_1.calculateAssetWeight(tokenAmount, spotMarketAccount, marginCategory);
|
|
439
458
|
assetValue = assetValue.mul(weight).div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
@@ -470,12 +489,8 @@ class User {
|
|
|
470
489
|
health = 0;
|
|
471
490
|
}
|
|
472
491
|
else {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const marginRatio = this.getMarginRatio().toNumber() / numericConstants_1.MARGIN_PRECISION.toNumber();
|
|
476
|
-
const maintenanceRatio = (maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
477
|
-
marginRatio;
|
|
478
|
-
const healthP1 = Math.max(0, (marginRatio - maintenanceRatio) * 100) + 1;
|
|
492
|
+
const healthP1 = Math.max(0, (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
493
|
+
100) + 1;
|
|
479
494
|
health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
|
|
480
495
|
if (health > 1) {
|
|
481
496
|
health = Math.round(health);
|
|
@@ -599,16 +614,19 @@ class User {
|
|
|
599
614
|
return [exitPrice, pnl];
|
|
600
615
|
}
|
|
601
616
|
/**
|
|
602
|
-
* calculates current user leverage
|
|
617
|
+
* calculates current user leverage which is (total liability size) / (net asset value)
|
|
603
618
|
* @returns : Precision TEN_THOUSAND
|
|
604
619
|
*/
|
|
605
620
|
getLeverage() {
|
|
606
|
-
const
|
|
621
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
|
|
622
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
|
|
623
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
607
624
|
const totalAssetValue = this.getTotalAssetValue();
|
|
608
|
-
|
|
625
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
626
|
+
if (netAssetValue.eq(numericConstants_1.ZERO)) {
|
|
609
627
|
return numericConstants_1.ZERO;
|
|
610
628
|
}
|
|
611
|
-
return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(
|
|
629
|
+
return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(netAssetValue);
|
|
612
630
|
}
|
|
613
631
|
getTotalLiabilityValue(marginCategory) {
|
|
614
632
|
return this.getTotalPerpPositionValue(marginCategory, undefined, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, undefined, true));
|
|
@@ -623,11 +641,14 @@ class User {
|
|
|
623
641
|
*/
|
|
624
642
|
getMaxLeverage(marketIndex, category = 'Initial') {
|
|
625
643
|
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
644
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
|
|
645
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
|
|
626
646
|
const totalAssetValue = this.getTotalAssetValue();
|
|
627
|
-
|
|
647
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
648
|
+
if (netAssetValue.eq(numericConstants_1.ZERO)) {
|
|
628
649
|
return numericConstants_1.ZERO;
|
|
629
650
|
}
|
|
630
|
-
const totalLiabilityValue =
|
|
651
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
631
652
|
const marginRatio = _1.calculateMarketMarginRatio(market,
|
|
632
653
|
// worstCaseBaseAssetAmount.abs(),
|
|
633
654
|
numericConstants_1.ZERO, // todo
|
|
@@ -640,19 +661,22 @@ class User {
|
|
|
640
661
|
return totalLiabilityValue
|
|
641
662
|
.add(additionalLiabilities)
|
|
642
663
|
.mul(numericConstants_1.TEN_THOUSAND)
|
|
643
|
-
.div(
|
|
664
|
+
.div(netAssetValue);
|
|
644
665
|
}
|
|
645
666
|
/**
|
|
646
667
|
* calculates margin ratio: total collateral / |total position value|
|
|
647
668
|
* @returns : Precision TEN_THOUSAND
|
|
648
669
|
*/
|
|
649
670
|
getMarginRatio(marginCategory) {
|
|
650
|
-
const
|
|
671
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
|
|
672
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
|
|
673
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
651
674
|
if (totalLiabilityValue.eq(numericConstants_1.ZERO)) {
|
|
652
675
|
return numericConstants_1.BN_MAX;
|
|
653
676
|
}
|
|
654
677
|
const totalAssetValue = this.getTotalAssetValue(marginCategory);
|
|
655
|
-
|
|
678
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
679
|
+
return netAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
|
|
656
680
|
}
|
|
657
681
|
canBeLiquidated() {
|
|
658
682
|
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
@@ -753,7 +777,7 @@ class User {
|
|
|
753
777
|
|
|
754
778
|
for 10x long, BTC down $400:
|
|
755
779
|
3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
|
|
756
|
-
const totalCollateral = this.getTotalCollateral();
|
|
780
|
+
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
757
781
|
// calculate the total position value ignoring any value from the target market of the trade
|
|
758
782
|
const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
|
|
759
783
|
const currentPerpPosition = this.getPerpPosition(perpPosition.marketIndex) ||
|
|
@@ -765,7 +789,7 @@ class User {
|
|
|
765
789
|
marketIndex: perpPosition.marketIndex,
|
|
766
790
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
767
791
|
remainderBaseAssetAmount: 0,
|
|
768
|
-
quoteAssetAmount:
|
|
792
|
+
quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
|
|
769
793
|
lastCumulativeFundingRate: numericConstants_1.ZERO,
|
|
770
794
|
quoteBreakEvenAmount: new _1.BN(0),
|
|
771
795
|
quoteEntryAmount: new _1.BN(0),
|
|
@@ -783,59 +807,48 @@ class User {
|
|
|
783
807
|
const proposedPerpPositionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
|
|
784
808
|
// total position value after trade
|
|
785
809
|
const totalPositionValueAfterTrade = totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
.div(numericConstants_1.MARGIN_PRECISION);
|
|
793
|
-
totalMarginRequirement = totalMarginRequirement.add(marketMarginRequirement);
|
|
794
|
-
}
|
|
795
|
-
return totalMarginRequirement;
|
|
796
|
-
}, numericConstants_1.ZERO);
|
|
810
|
+
const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
|
|
811
|
+
const positionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
|
|
812
|
+
const marginRequirementOfTargetMarket = positionValue
|
|
813
|
+
.mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount.abs(), 'Maintenance')))
|
|
814
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
815
|
+
const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(marginRequirementOfTargetMarket);
|
|
797
816
|
const freeCollateralExcludingTargetMarket = totalCollateral.sub(marginRequirementExcludingTargetMarket);
|
|
798
817
|
// if the position value after the trade is less than free collateral, there is no liq price
|
|
799
818
|
if (totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
800
819
|
proposedPerpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
|
|
801
820
|
return new _1.BN(-1);
|
|
802
821
|
}
|
|
803
|
-
const
|
|
822
|
+
const marginRequirementTargetMarket = proposedPerpPositionValue
|
|
804
823
|
.mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount.abs(), 'Maintenance')))
|
|
805
|
-
.div(numericConstants_1.MARGIN_PRECISION)
|
|
824
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
825
|
+
const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
|
|
806
826
|
const freeCollateralAfterTrade = totalCollateral.sub(marginRequirementAfterTrade);
|
|
807
|
-
const
|
|
827
|
+
const marketMaxMaintLeverage = new _1.BN(numericConstants_1.TEN_THOUSAND.mul(numericConstants_1.TEN_THOUSAND).toNumber() /
|
|
828
|
+
_1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount, 'Maintenance'));
|
|
808
829
|
let priceDelta;
|
|
809
830
|
if (proposedBaseAssetAmount.lt(numericConstants_1.ZERO)) {
|
|
810
831
|
priceDelta = freeCollateralAfterTrade
|
|
811
|
-
.mul(
|
|
812
|
-
.div(
|
|
832
|
+
.mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
|
|
833
|
+
.div(marketMaxMaintLeverage.add(numericConstants_1.TEN_THOUSAND))
|
|
813
834
|
.mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
|
|
814
835
|
.mul(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
815
836
|
.div(proposedBaseAssetAmount);
|
|
816
837
|
}
|
|
817
838
|
else {
|
|
818
839
|
priceDelta = freeCollateralAfterTrade
|
|
819
|
-
.mul(
|
|
820
|
-
.div(
|
|
840
|
+
.mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
|
|
841
|
+
.div(marketMaxMaintLeverage.sub(numericConstants_1.TEN_THOUSAND))
|
|
821
842
|
.mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
|
|
822
843
|
.mul(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
823
844
|
.div(proposedBaseAssetAmount);
|
|
824
845
|
}
|
|
825
|
-
|
|
826
|
-
if (
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
const direction = positionBaseSizeChange.gt(numericConstants_1.ZERO)
|
|
831
|
-
? _1.PositionDirection.LONG
|
|
832
|
-
: _1.PositionDirection.SHORT;
|
|
833
|
-
markPriceAfterTrade = _1.calculateTradeSlippage(direction, positionBaseSizeChange.abs(), this.driftClient.getPerpMarketAccount(perpPosition.marketIndex), 'base', this.getOracleDataForPerpMarket(perpPosition.marketIndex))[3]; // newPrice after swap
|
|
834
|
-
}
|
|
835
|
-
if (priceDelta.gt(markPriceAfterTrade)) {
|
|
846
|
+
const currentPrice = this.getOracleDataForPerpMarket(perpPosition.marketIndex).price;
|
|
847
|
+
if (priceDelta.gt(currentPrice) &&
|
|
848
|
+
proposedPerpPosition.baseAssetAmount.gte(numericConstants_1.ZERO)) {
|
|
836
849
|
return new _1.BN(-1);
|
|
837
850
|
}
|
|
838
|
-
return
|
|
851
|
+
return currentPrice.sub(priceDelta);
|
|
839
852
|
}
|
|
840
853
|
/**
|
|
841
854
|
* Calculates the estimated liquidation price for a position after closing a quote amount of the position.
|
|
@@ -960,16 +973,18 @@ class User {
|
|
|
960
973
|
.abs();
|
|
961
974
|
const totalPositionAfterTradeExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex, undefined, undefined, includeOpenOrders);
|
|
962
975
|
const totalAssetValue = this.getTotalAssetValue();
|
|
963
|
-
const
|
|
976
|
+
const totalPerpPositionLiability = currentPerpPositionAfterTrade
|
|
964
977
|
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
965
978
|
.abs();
|
|
966
|
-
const
|
|
967
|
-
|
|
979
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, includeOpenOrders);
|
|
980
|
+
const totalLiabilitiesAfterTrade = totalPerpPositionLiability.add(totalSpotLiability);
|
|
981
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
982
|
+
if (netAssetValue.eq(numericConstants_1.ZERO)) {
|
|
968
983
|
return numericConstants_1.ZERO;
|
|
969
984
|
}
|
|
970
985
|
const newLeverage = totalLiabilitiesAfterTrade
|
|
971
986
|
.mul(numericConstants_1.TEN_THOUSAND)
|
|
972
|
-
.div(
|
|
987
|
+
.div(netAssetValue);
|
|
973
988
|
return newLeverage;
|
|
974
989
|
}
|
|
975
990
|
/**
|
|
@@ -992,28 +1007,28 @@ class User {
|
|
|
992
1007
|
getWithdrawalLimit(marketIndex, reduceOnly) {
|
|
993
1008
|
const nowTs = new _1.BN(Math.floor(Date.now() / 1000));
|
|
994
1009
|
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
995
|
-
|
|
1010
|
+
// eslint-disable-next-line prefer-const
|
|
1011
|
+
let { borrowLimit, withdrawLimit } = spotBalance_1.calculateWithdrawLimit(spotMarket, nowTs);
|
|
996
1012
|
const freeCollateral = this.getFreeCollateral();
|
|
997
1013
|
const oracleData = this.getOracleDataForSpotMarket(marketIndex);
|
|
998
1014
|
const precisionIncrease = numericConstants_1.TEN.pow(new _1.BN(spotMarket.decimals - 6));
|
|
1015
|
+
const { canBypass, depositAmount: userDepositAmount } = this.canBypassWithdrawLimits(marketIndex);
|
|
1016
|
+
if (canBypass) {
|
|
1017
|
+
withdrawLimit = _1.BN.max(withdrawLimit, userDepositAmount);
|
|
1018
|
+
}
|
|
999
1019
|
const amountWithdrawable = freeCollateral
|
|
1000
1020
|
.mul(numericConstants_1.MARGIN_PRECISION)
|
|
1001
1021
|
.div(new _1.BN(spotMarket.initialAssetWeight))
|
|
1002
1022
|
.mul(numericConstants_1.PRICE_PRECISION)
|
|
1003
1023
|
.div(oracleData.price)
|
|
1004
1024
|
.mul(precisionIncrease);
|
|
1005
|
-
const
|
|
1006
|
-
spotPosition.marketIndex == marketIndex);
|
|
1007
|
-
const userSpotBalance = userSpotPosition
|
|
1008
|
-
? spotBalance_1.getTokenAmount(userSpotPosition.scaledBalance, this.driftClient.getSpotMarketAccount(marketIndex), _1.SpotBalanceType.DEPOSIT)
|
|
1009
|
-
: numericConstants_1.ZERO;
|
|
1010
|
-
const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userSpotBalance), withdrawLimit.abs());
|
|
1025
|
+
const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userDepositAmount), withdrawLimit.abs());
|
|
1011
1026
|
if (reduceOnly) {
|
|
1012
1027
|
return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
|
|
1013
1028
|
}
|
|
1014
1029
|
else {
|
|
1015
1030
|
const weightedAssetValue = this.getSpotMarketAssetValue(marketIndex, 'Initial', false);
|
|
1016
|
-
const freeCollatAfterWithdraw =
|
|
1031
|
+
const freeCollatAfterWithdraw = userDepositAmount.gt(numericConstants_1.ZERO)
|
|
1017
1032
|
? freeCollateral.sub(weightedAssetValue)
|
|
1018
1033
|
: freeCollateral;
|
|
1019
1034
|
const maxLiabilityAllowed = freeCollatAfterWithdraw
|
|
@@ -1026,6 +1041,43 @@ class User {
|
|
|
1026
1041
|
return _1.BN.max(maxBorrowValue, numericConstants_1.ZERO);
|
|
1027
1042
|
}
|
|
1028
1043
|
}
|
|
1044
|
+
canBypassWithdrawLimits(marketIndex) {
|
|
1045
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
1046
|
+
const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new _1.BN(10));
|
|
1047
|
+
const position = this.getSpotPosition(marketIndex);
|
|
1048
|
+
const netDeposits = this.getUserAccount().totalDeposits.sub(this.getUserAccount().totalWithdraws);
|
|
1049
|
+
if (!position) {
|
|
1050
|
+
return {
|
|
1051
|
+
canBypass: false,
|
|
1052
|
+
maxDepositAmount,
|
|
1053
|
+
depositAmount: numericConstants_1.ZERO,
|
|
1054
|
+
netDeposits,
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
if (types_1.isVariant(position.balanceType, 'borrow')) {
|
|
1058
|
+
return {
|
|
1059
|
+
canBypass: false,
|
|
1060
|
+
maxDepositAmount,
|
|
1061
|
+
netDeposits,
|
|
1062
|
+
depositAmount: numericConstants_1.ZERO,
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
const depositAmount = spotBalance_1.getTokenAmount(position.scaledBalance, spotMarket, 'deposit');
|
|
1066
|
+
if (netDeposits.lt(numericConstants_1.ZERO)) {
|
|
1067
|
+
return {
|
|
1068
|
+
canBypass: false,
|
|
1069
|
+
maxDepositAmount,
|
|
1070
|
+
depositAmount: numericConstants_1.ZERO,
|
|
1071
|
+
netDeposits,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
canBypass: depositAmount.lt(maxDepositAmount),
|
|
1076
|
+
maxDepositAmount,
|
|
1077
|
+
netDeposits,
|
|
1078
|
+
depositAmount,
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1029
1081
|
/**
|
|
1030
1082
|
* Get the total position value, excluding any position coming from the given target market
|
|
1031
1083
|
* @param marketToIgnore
|
package/package.json
CHANGED
|
@@ -83,6 +83,8 @@ export const MARGIN_PRECISION = TEN_THOUSAND;
|
|
|
83
83
|
export const BID_ASK_SPREAD_PRECISION = new BN(1000000); // 10^6
|
|
84
84
|
export const LIQUIDATION_PCT_PRECISION = TEN_THOUSAND;
|
|
85
85
|
|
|
86
|
+
export const FIVE_MINUTE = new BN(60 * 5);
|
|
87
|
+
export const ONE_HOUR = new BN(60 * 60);
|
|
86
88
|
export const ONE_YEAR = new BN(31536000);
|
|
87
89
|
|
|
88
90
|
export const QUOTE_SPOT_MARKET_INDEX = 0;
|
package/src/driftClient.ts
CHANGED
|
@@ -2950,6 +2950,70 @@ export class DriftClient {
|
|
|
2950
2950
|
maxTs: openOrder.maxTs,
|
|
2951
2951
|
auctionStartPrice: openOrder.auctionStartPrice,
|
|
2952
2952
|
auctionEndPrice: openOrder.auctionEndPrice,
|
|
2953
|
+
userOrderId: openOrder.userOrderId,
|
|
2954
|
+
};
|
|
2955
|
+
const placeOrderIx = await this.getPlacePerpOrderIx(newOrderParams);
|
|
2956
|
+
|
|
2957
|
+
const tx = new Transaction();
|
|
2958
|
+
tx.add(
|
|
2959
|
+
ComputeBudgetProgram.requestUnits({
|
|
2960
|
+
units: 1_000_000,
|
|
2961
|
+
additionalFee: 0,
|
|
2962
|
+
})
|
|
2963
|
+
);
|
|
2964
|
+
tx.add(cancelOrderIx);
|
|
2965
|
+
tx.add(placeOrderIx);
|
|
2966
|
+
const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
|
|
2967
|
+
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
2968
|
+
return txSig;
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/**
|
|
2972
|
+
* Modifies an open order by closing it and replacing it with a new order.
|
|
2973
|
+
* @param userOrderId: The open order to modify
|
|
2974
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
2975
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
2976
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
2977
|
+
* @returns
|
|
2978
|
+
*/
|
|
2979
|
+
public async modifyPerpOrderByUserOrderId(
|
|
2980
|
+
userOrderId: number,
|
|
2981
|
+
newBaseAmount?: BN,
|
|
2982
|
+
newLimitPrice?: BN,
|
|
2983
|
+
newOraclePriceOffset?: number
|
|
2984
|
+
): Promise<TransactionSignature> {
|
|
2985
|
+
if (!newBaseAmount && !newLimitPrice && !newOraclePriceOffset) {
|
|
2986
|
+
throw new Error(
|
|
2987
|
+
`Must provide newBaseAmount or newLimitPrice or newOraclePriceOffset to modify order`
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
const openOrder = this.getUser().getOrderByUserOrderId(userOrderId);
|
|
2992
|
+
if (!openOrder) {
|
|
2993
|
+
throw new Error(
|
|
2994
|
+
`No open order with user order id ${userOrderId.toString()}`
|
|
2995
|
+
);
|
|
2996
|
+
}
|
|
2997
|
+
const cancelOrderIx = await this.getCancelOrderIx(openOrder.orderId);
|
|
2998
|
+
|
|
2999
|
+
const newOrderParams: OptionalOrderParams = {
|
|
3000
|
+
orderType: openOrder.orderType,
|
|
3001
|
+
marketType: openOrder.marketType,
|
|
3002
|
+
direction: openOrder.direction,
|
|
3003
|
+
baseAssetAmount: newBaseAmount || openOrder.baseAssetAmount,
|
|
3004
|
+
price: newLimitPrice || openOrder.price,
|
|
3005
|
+
marketIndex: openOrder.marketIndex,
|
|
3006
|
+
reduceOnly: openOrder.reduceOnly,
|
|
3007
|
+
postOnly: openOrder.postOnly,
|
|
3008
|
+
immediateOrCancel: openOrder.immediateOrCancel,
|
|
3009
|
+
triggerPrice: openOrder.triggerPrice,
|
|
3010
|
+
triggerCondition: openOrder.triggerCondition,
|
|
3011
|
+
oraclePriceOffset: newOraclePriceOffset || openOrder.oraclePriceOffset,
|
|
3012
|
+
auctionDuration: openOrder.auctionDuration,
|
|
3013
|
+
maxTs: openOrder.maxTs,
|
|
3014
|
+
auctionStartPrice: openOrder.auctionStartPrice,
|
|
3015
|
+
auctionEndPrice: openOrder.auctionEndPrice,
|
|
3016
|
+
userOrderId: openOrder.userOrderId,
|
|
2953
3017
|
};
|
|
2954
3018
|
const placeOrderIx = await this.getPlacePerpOrderIx(newOrderParams);
|
|
2955
3019
|
|
package/src/idl/drift.json
CHANGED
package/src/math/market.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
MarginCategory,
|
|
6
6
|
SpotMarketAccount,
|
|
7
7
|
SpotBalanceType,
|
|
8
|
+
MarketType,
|
|
8
9
|
} from '../types';
|
|
9
10
|
import {
|
|
10
11
|
calculateAmmReservesAfterSwap,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
calculateUpdatedAMMSpreadReserves,
|
|
13
14
|
getSwapDirection,
|
|
14
15
|
calculateUpdatedAMM,
|
|
16
|
+
calculateMarketOpenBidAsk,
|
|
15
17
|
} from './amm';
|
|
16
18
|
import {
|
|
17
19
|
calculateSizeDiscountAssetWeight,
|
|
@@ -25,6 +27,7 @@ import {
|
|
|
25
27
|
ZERO,
|
|
26
28
|
} from '../constants/numericConstants';
|
|
27
29
|
import { getTokenAmount } from './spotBalance';
|
|
30
|
+
import { DLOB } from '../dlob/DLOB';
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Calculates market mark price
|
|
@@ -232,3 +235,51 @@ export function calculateNetUserPnlImbalance(
|
|
|
232
235
|
|
|
233
236
|
return imbalance;
|
|
234
237
|
}
|
|
238
|
+
|
|
239
|
+
export function calculateAvailablePerpLiquidity(
|
|
240
|
+
market: PerpMarketAccount,
|
|
241
|
+
oraclePriceData: OraclePriceData,
|
|
242
|
+
dlob: DLOB,
|
|
243
|
+
slot: number
|
|
244
|
+
): { bids: BN; asks: BN } {
|
|
245
|
+
let [bids, asks] = calculateMarketOpenBidAsk(
|
|
246
|
+
market.amm.baseAssetReserve,
|
|
247
|
+
market.amm.minBaseAssetReserve,
|
|
248
|
+
market.amm.maxBaseAssetReserve,
|
|
249
|
+
market.amm.orderStepSize
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
asks = asks.abs();
|
|
253
|
+
|
|
254
|
+
const bidPrice = calculateBidPrice(market, oraclePriceData);
|
|
255
|
+
const askPrice = calculateAskPrice(market, oraclePriceData);
|
|
256
|
+
|
|
257
|
+
for (const bid of dlob.getMakerLimitBids(
|
|
258
|
+
market.marketIndex,
|
|
259
|
+
slot,
|
|
260
|
+
MarketType.PERP,
|
|
261
|
+
oraclePriceData,
|
|
262
|
+
askPrice
|
|
263
|
+
)) {
|
|
264
|
+
bids = bids.add(
|
|
265
|
+
bid.order.baseAssetAmount.sub(bid.order.baseAssetAmountFilled)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const ask of dlob.getMakerLimitAsks(
|
|
270
|
+
market.marketIndex,
|
|
271
|
+
slot,
|
|
272
|
+
MarketType.PERP,
|
|
273
|
+
oraclePriceData,
|
|
274
|
+
bidPrice
|
|
275
|
+
)) {
|
|
276
|
+
asks = asks.add(
|
|
277
|
+
ask.order.baseAssetAmount.sub(ask.order.baseAssetAmountFilled)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
bids: bids,
|
|
283
|
+
asks: asks,
|
|
284
|
+
};
|
|
285
|
+
}
|
package/src/math/oracles.ts
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
PRICE_PRECISION,
|
|
7
7
|
ONE,
|
|
8
8
|
ZERO,
|
|
9
|
+
FIVE_MINUTE,
|
|
10
|
+
ONE_HOUR,
|
|
9
11
|
} from '../constants/numericConstants';
|
|
10
|
-
import { BN, PerpMarketAccount } from '../index';
|
|
12
|
+
import { BN, HistoricalOracleData, PerpMarketAccount } from '../index';
|
|
11
13
|
import { assert } from '../assert/assert';
|
|
12
14
|
|
|
13
15
|
export function oraclePriceBands(
|
|
@@ -68,7 +70,7 @@ export function isOracleTooDivergent(
|
|
|
68
70
|
const sinceLastUpdate = now.sub(
|
|
69
71
|
amm.historicalOracleData.lastOraclePriceTwapTs
|
|
70
72
|
);
|
|
71
|
-
const sinceStart = BN.max(ZERO,
|
|
73
|
+
const sinceStart = BN.max(ZERO, FIVE_MINUTE.sub(sinceLastUpdate));
|
|
72
74
|
const oracleTwap5min = amm.historicalOracleData.lastOraclePriceTwap5Min
|
|
73
75
|
.mul(sinceStart)
|
|
74
76
|
.add(oraclePriceData.price)
|
|
@@ -90,29 +92,36 @@ export function isOracleTooDivergent(
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export function calculateLiveOracleTwap(
|
|
93
|
-
|
|
95
|
+
histOracleData: HistoricalOracleData,
|
|
94
96
|
oraclePriceData: OraclePriceData,
|
|
95
|
-
now: BN
|
|
97
|
+
now: BN,
|
|
98
|
+
period: BN
|
|
96
99
|
): BN {
|
|
100
|
+
let oracleTwap = undefined;
|
|
101
|
+
if (period.eq(ONE_HOUR)) {
|
|
102
|
+
//todo: assumes its 1hr
|
|
103
|
+
// period = amm.fundingPeriod;
|
|
104
|
+
oracleTwap = histOracleData.lastOraclePriceTwap;
|
|
105
|
+
} else if (period.eq(FIVE_MINUTE)) {
|
|
106
|
+
histOracleData.lastOraclePriceTwap5Min;
|
|
107
|
+
} else {
|
|
108
|
+
throw Error('unsupported twap period passed');
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
const sinceLastUpdate = BN.max(
|
|
98
112
|
ONE,
|
|
99
|
-
now.sub(
|
|
113
|
+
now.sub(histOracleData.lastOraclePriceTwapTs)
|
|
100
114
|
);
|
|
101
|
-
const sinceStart = BN.max(ZERO,
|
|
115
|
+
const sinceStart = BN.max(ZERO, period.sub(sinceLastUpdate));
|
|
102
116
|
|
|
103
|
-
const clampRange =
|
|
104
|
-
new BN(3)
|
|
105
|
-
);
|
|
117
|
+
const clampRange = oracleTwap.div(new BN(3));
|
|
106
118
|
|
|
107
119
|
const clampedOraclePrice = BN.min(
|
|
108
|
-
|
|
109
|
-
BN.max(
|
|
110
|
-
oraclePriceData.price,
|
|
111
|
-
amm.historicalOracleData.lastOraclePriceTwap.sub(clampRange)
|
|
112
|
-
)
|
|
120
|
+
oracleTwap.add(clampRange),
|
|
121
|
+
BN.max(oraclePriceData.price, oracleTwap.sub(clampRange))
|
|
113
122
|
);
|
|
114
123
|
|
|
115
|
-
const newOracleTwap =
|
|
124
|
+
const newOracleTwap = oracleTwap
|
|
116
125
|
.mul(sinceStart)
|
|
117
126
|
.add(clampedOraclePrice.mul(sinceLastUpdate))
|
|
118
127
|
.div(sinceStart.add(sinceLastUpdate));
|
|
@@ -131,7 +140,12 @@ export function calculateLiveOracleStd(
|
|
|
131
140
|
);
|
|
132
141
|
const sinceStart = BN.max(ZERO, amm.fundingPeriod.sub(sinceLastUpdate));
|
|
133
142
|
|
|
134
|
-
const liveOracleTwap = calculateLiveOracleTwap(
|
|
143
|
+
const liveOracleTwap = calculateLiveOracleTwap(
|
|
144
|
+
amm.historicalOracleData,
|
|
145
|
+
oraclePriceData,
|
|
146
|
+
now,
|
|
147
|
+
amm.fundingPeriod
|
|
148
|
+
);
|
|
135
149
|
|
|
136
150
|
const priceDeltaVsTwap = oraclePriceData.price.sub(liveOracleTwap).abs();
|
|
137
151
|
|