@drift-labs/sdk 2.13.0-beta.0 → 2.13.0-beta.2
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/margin.d.ts +1 -1
- package/lib/math/margin.js +7 -7
- 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 +17 -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/math/utils.js +3 -0
- package/lib/user.d.ts +15 -8
- package/lib/user.js +146 -88
- 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/margin.ts +9 -7
- package/src/math/market.ts +51 -0
- package/src/math/oracles.ts +27 -16
- package/src/math/orders.ts +2 -2
- package/src/math/spotBalance.ts +51 -3
- package/src/math/trade.ts +10 -2
- package/src/math/utils.ts +5 -1
- package/src/user.ts +321 -153
- 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;
|
|
@@ -238,13 +239,20 @@ class User {
|
|
|
238
239
|
return [position, remainderBaa, pnl];
|
|
239
240
|
}
|
|
240
241
|
/**
|
|
241
|
-
* calculates Buying Power =
|
|
242
|
+
* calculates Buying Power = free collateral / initial margin ratio
|
|
242
243
|
* @returns : Precision QUOTE_PRECISION
|
|
243
244
|
*/
|
|
244
245
|
getBuyingPower(marketIndex) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
.
|
|
246
|
+
const perpPosition = this.getPerpPosition(marketIndex);
|
|
247
|
+
const worstCaseBaseAssetAmount = perpPosition
|
|
248
|
+
? margin_1.calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
249
|
+
: numericConstants_1.ZERO;
|
|
250
|
+
const freeCollateral = this.getFreeCollateral();
|
|
251
|
+
return this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(marketIndex, freeCollateral, worstCaseBaseAssetAmount);
|
|
252
|
+
}
|
|
253
|
+
getBuyingPowerFromFreeCollateralAndBaseAssetAmount(marketIndex, freeCollateral, baseAssetAmount) {
|
|
254
|
+
const marginRatio = _1.calculateMarketMarginRatio(this.driftClient.getPerpMarketAccount(marketIndex), baseAssetAmount, 'Initial');
|
|
255
|
+
return freeCollateral.mul(numericConstants_1.MARGIN_PRECISION).div(new _1.BN(marginRatio));
|
|
248
256
|
}
|
|
249
257
|
/**
|
|
250
258
|
* calculates Free Collateral = Total collateral - initial margin requirement
|
|
@@ -259,14 +267,14 @@ class User {
|
|
|
259
267
|
/**
|
|
260
268
|
* @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
|
|
261
269
|
*/
|
|
262
|
-
getMarginRequirement(marginCategory, liquidationBuffer) {
|
|
263
|
-
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true));
|
|
270
|
+
getMarginRequirement(marginCategory, liquidationBuffer, strict = false) {
|
|
271
|
+
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true, strict));
|
|
264
272
|
}
|
|
265
273
|
/**
|
|
266
274
|
* @returns The initial margin requirement in USDC. : QUOTE_PRECISION
|
|
267
275
|
*/
|
|
268
276
|
getInitialMarginRequirement() {
|
|
269
|
-
return this.getMarginRequirement('Initial');
|
|
277
|
+
return this.getMarginRequirement('Initial', undefined, true);
|
|
270
278
|
}
|
|
271
279
|
/**
|
|
272
280
|
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
@@ -317,7 +325,8 @@ class User {
|
|
|
317
325
|
return pnl.add(_1.calculatePositionFundingPNL(market, perpPosition));
|
|
318
326
|
}, numericConstants_1.ZERO);
|
|
319
327
|
}
|
|
320
|
-
getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders) {
|
|
328
|
+
getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false, now) {
|
|
329
|
+
now = now || new _1.BN(new Date().getTime() / 1000);
|
|
321
330
|
return this.getUserAccount().spotPositions.reduce((totalLiabilityValue, spotPosition) => {
|
|
322
331
|
if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
|
|
323
332
|
(marketIndex !== undefined &&
|
|
@@ -345,7 +354,7 @@ class User {
|
|
|
345
354
|
if (!includeOpenOrders) {
|
|
346
355
|
if (types_1.isVariant(spotPosition.balanceType, 'borrow')) {
|
|
347
356
|
const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
348
|
-
const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
357
|
+
const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
|
|
349
358
|
return totalLiabilityValue.add(liabilityValue);
|
|
350
359
|
}
|
|
351
360
|
else {
|
|
@@ -355,7 +364,7 @@ class User {
|
|
|
355
364
|
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
356
365
|
let newTotalLiabilityValue = totalLiabilityValue;
|
|
357
366
|
if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
|
|
358
|
-
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
367
|
+
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
|
|
359
368
|
newTotalLiabilityValue =
|
|
360
369
|
newTotalLiabilityValue.add(baseLiabilityValue);
|
|
361
370
|
}
|
|
@@ -375,8 +384,16 @@ class User {
|
|
|
375
384
|
return newTotalLiabilityValue;
|
|
376
385
|
}, numericConstants_1.ZERO);
|
|
377
386
|
}
|
|
378
|
-
getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer) {
|
|
379
|
-
let liabilityValue =
|
|
387
|
+
getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict = false, now) {
|
|
388
|
+
let liabilityValue = null;
|
|
389
|
+
if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
390
|
+
const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
|
|
391
|
+
);
|
|
392
|
+
liabilityValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
liabilityValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
|
|
396
|
+
}
|
|
380
397
|
if (marginCategory !== undefined) {
|
|
381
398
|
let weight = spotBalance_1.calculateLiabilityWeight(tokenAmount, spotMarketAccount, marginCategory);
|
|
382
399
|
if (marginCategory === 'Initial') {
|
|
@@ -391,7 +408,8 @@ class User {
|
|
|
391
408
|
}
|
|
392
409
|
return liabilityValue;
|
|
393
410
|
}
|
|
394
|
-
getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders) {
|
|
411
|
+
getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders, strict = false, now) {
|
|
412
|
+
now = now || new _1.BN(new Date().getTime() / 1000);
|
|
395
413
|
return this.getUserAccount().spotPositions.reduce((totalAssetValue, spotPosition) => {
|
|
396
414
|
if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
|
|
397
415
|
(marketIndex !== undefined &&
|
|
@@ -413,7 +431,7 @@ class User {
|
|
|
413
431
|
if (!includeOpenOrders) {
|
|
414
432
|
if (types_1.isVariant(spotPosition.balanceType, 'deposit')) {
|
|
415
433
|
const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
416
|
-
const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
|
|
434
|
+
const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
|
|
417
435
|
return totalAssetValue.add(assetValue);
|
|
418
436
|
}
|
|
419
437
|
else {
|
|
@@ -423,7 +441,7 @@ class User {
|
|
|
423
441
|
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
424
442
|
let newTotalAssetValue = totalAssetValue;
|
|
425
443
|
if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
|
|
426
|
-
const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
|
|
444
|
+
const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
|
|
427
445
|
newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
|
|
428
446
|
}
|
|
429
447
|
if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO)) {
|
|
@@ -432,8 +450,16 @@ class User {
|
|
|
432
450
|
return newTotalAssetValue;
|
|
433
451
|
}, numericConstants_1.ZERO);
|
|
434
452
|
}
|
|
435
|
-
getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory) {
|
|
436
|
-
let assetValue =
|
|
453
|
+
getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict = false, now) {
|
|
454
|
+
let assetValue = null;
|
|
455
|
+
if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
456
|
+
const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
|
|
457
|
+
);
|
|
458
|
+
assetValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
assetValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
|
|
462
|
+
}
|
|
437
463
|
if (marginCategory !== undefined) {
|
|
438
464
|
const weight = spotBalance_1.calculateAssetWeight(tokenAmount, spotMarketAccount, marginCategory);
|
|
439
465
|
assetValue = assetValue.mul(weight).div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
@@ -470,12 +496,8 @@ class User {
|
|
|
470
496
|
health = 0;
|
|
471
497
|
}
|
|
472
498
|
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;
|
|
499
|
+
const healthP1 = Math.max(0, (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
500
|
+
100) + 1;
|
|
479
501
|
health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
|
|
480
502
|
if (health > 1) {
|
|
481
503
|
health = Math.round(health);
|
|
@@ -542,10 +564,10 @@ class User {
|
|
|
542
564
|
* calculates position value in margin system
|
|
543
565
|
* @returns : Precision QUOTE_PRECISION
|
|
544
566
|
*/
|
|
545
|
-
getPerpPositionValue(marketIndex, oraclePriceData) {
|
|
567
|
+
getPerpPositionValue(marketIndex, oraclePriceData, includeOpenOrders = false) {
|
|
546
568
|
const userPosition = this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
|
|
547
569
|
const market = this.driftClient.getPerpMarketAccount(userPosition.marketIndex);
|
|
548
|
-
return margin_1.calculateBaseAssetValueWithOracle(market, userPosition, oraclePriceData);
|
|
570
|
+
return margin_1.calculateBaseAssetValueWithOracle(market, userPosition, oraclePriceData, includeOpenOrders);
|
|
549
571
|
}
|
|
550
572
|
getPositionSide(currentPosition) {
|
|
551
573
|
if (currentPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
|
|
@@ -599,16 +621,19 @@ class User {
|
|
|
599
621
|
return [exitPrice, pnl];
|
|
600
622
|
}
|
|
601
623
|
/**
|
|
602
|
-
* calculates current user leverage
|
|
624
|
+
* calculates current user leverage which is (total liability size) / (net asset value)
|
|
603
625
|
* @returns : Precision TEN_THOUSAND
|
|
604
626
|
*/
|
|
605
627
|
getLeverage() {
|
|
606
|
-
const
|
|
628
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
|
|
629
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
|
|
630
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
607
631
|
const totalAssetValue = this.getTotalAssetValue();
|
|
608
|
-
|
|
632
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
633
|
+
if (netAssetValue.eq(numericConstants_1.ZERO)) {
|
|
609
634
|
return numericConstants_1.ZERO;
|
|
610
635
|
}
|
|
611
|
-
return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(
|
|
636
|
+
return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(netAssetValue);
|
|
612
637
|
}
|
|
613
638
|
getTotalLiabilityValue(marginCategory) {
|
|
614
639
|
return this.getTotalPerpPositionValue(marginCategory, undefined, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, undefined, true));
|
|
@@ -623,11 +648,14 @@ class User {
|
|
|
623
648
|
*/
|
|
624
649
|
getMaxLeverage(marketIndex, category = 'Initial') {
|
|
625
650
|
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
651
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
|
|
652
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
|
|
626
653
|
const totalAssetValue = this.getTotalAssetValue();
|
|
627
|
-
|
|
654
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
655
|
+
if (netAssetValue.eq(numericConstants_1.ZERO)) {
|
|
628
656
|
return numericConstants_1.ZERO;
|
|
629
657
|
}
|
|
630
|
-
const totalLiabilityValue =
|
|
658
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
631
659
|
const marginRatio = _1.calculateMarketMarginRatio(market,
|
|
632
660
|
// worstCaseBaseAssetAmount.abs(),
|
|
633
661
|
numericConstants_1.ZERO, // todo
|
|
@@ -640,19 +668,22 @@ class User {
|
|
|
640
668
|
return totalLiabilityValue
|
|
641
669
|
.add(additionalLiabilities)
|
|
642
670
|
.mul(numericConstants_1.TEN_THOUSAND)
|
|
643
|
-
.div(
|
|
671
|
+
.div(netAssetValue);
|
|
644
672
|
}
|
|
645
673
|
/**
|
|
646
674
|
* calculates margin ratio: total collateral / |total position value|
|
|
647
675
|
* @returns : Precision TEN_THOUSAND
|
|
648
676
|
*/
|
|
649
677
|
getMarginRatio(marginCategory) {
|
|
650
|
-
const
|
|
678
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
|
|
679
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
|
|
680
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
651
681
|
if (totalLiabilityValue.eq(numericConstants_1.ZERO)) {
|
|
652
682
|
return numericConstants_1.BN_MAX;
|
|
653
683
|
}
|
|
654
684
|
const totalAssetValue = this.getTotalAssetValue(marginCategory);
|
|
655
|
-
|
|
685
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
686
|
+
return netAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
|
|
656
687
|
}
|
|
657
688
|
canBeLiquidated() {
|
|
658
689
|
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
@@ -753,9 +784,9 @@ class User {
|
|
|
753
784
|
|
|
754
785
|
for 10x long, BTC down $400:
|
|
755
786
|
3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
|
|
756
|
-
const totalCollateral = this.getTotalCollateral();
|
|
787
|
+
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
757
788
|
// calculate the total position value ignoring any value from the target market of the trade
|
|
758
|
-
const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
|
|
789
|
+
const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex, undefined, undefined, true);
|
|
759
790
|
const currentPerpPosition = this.getPerpPosition(perpPosition.marketIndex) ||
|
|
760
791
|
this.getEmptyPosition(perpPosition.marketIndex);
|
|
761
792
|
const currentPerpPositionBaseSize = currentPerpPosition.baseAssetAmount;
|
|
@@ -765,13 +796,13 @@ class User {
|
|
|
765
796
|
marketIndex: perpPosition.marketIndex,
|
|
766
797
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
767
798
|
remainderBaseAssetAmount: 0,
|
|
768
|
-
quoteAssetAmount:
|
|
799
|
+
quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
|
|
769
800
|
lastCumulativeFundingRate: numericConstants_1.ZERO,
|
|
770
801
|
quoteBreakEvenAmount: new _1.BN(0),
|
|
771
802
|
quoteEntryAmount: new _1.BN(0),
|
|
772
803
|
openOrders: 0,
|
|
773
|
-
openBids:
|
|
774
|
-
openAsks:
|
|
804
|
+
openBids: currentPerpPosition.openBids,
|
|
805
|
+
openAsks: currentPerpPosition.openAsks,
|
|
775
806
|
settledPnl: numericConstants_1.ZERO,
|
|
776
807
|
lpShares: numericConstants_1.ZERO,
|
|
777
808
|
lastBaseAssetAmountPerLp: numericConstants_1.ZERO,
|
|
@@ -780,62 +811,52 @@ class User {
|
|
|
780
811
|
if (proposedBaseAssetAmount.eq(numericConstants_1.ZERO))
|
|
781
812
|
return new _1.BN(-1);
|
|
782
813
|
const market = this.driftClient.getPerpMarketAccount(proposedPerpPosition.marketIndex);
|
|
783
|
-
const proposedPerpPositionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
|
|
814
|
+
const proposedPerpPositionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex), true);
|
|
784
815
|
// total position value after trade
|
|
785
816
|
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);
|
|
817
|
+
const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
|
|
818
|
+
const positionValue = margin_1.calculateBaseAssetValueWithOracle(market, currentPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex), true);
|
|
819
|
+
const marginRequirementOfTargetMarket = positionValue
|
|
820
|
+
.mul(new _1.BN(_1.calculateMarketMarginRatio(market, margin_1.calculateWorstCaseBaseAssetAmount(currentPerpPosition).abs(), 'Maintenance')))
|
|
821
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
822
|
+
const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(marginRequirementOfTargetMarket);
|
|
797
823
|
const freeCollateralExcludingTargetMarket = totalCollateral.sub(marginRequirementExcludingTargetMarket);
|
|
798
824
|
// if the position value after the trade is less than free collateral, there is no liq price
|
|
799
825
|
if (totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
800
826
|
proposedPerpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
|
|
801
827
|
return new _1.BN(-1);
|
|
802
828
|
}
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
.
|
|
829
|
+
const proposedWorstCastBaseAssetAmount = margin_1.calculateWorstCaseBaseAssetAmount(proposedPerpPosition);
|
|
830
|
+
const marginRequirementTargetMarket = proposedPerpPositionValue
|
|
831
|
+
.mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedWorstCastBaseAssetAmount.abs(), 'Maintenance')))
|
|
832
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
833
|
+
const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
|
|
806
834
|
const freeCollateralAfterTrade = totalCollateral.sub(marginRequirementAfterTrade);
|
|
807
|
-
const
|
|
835
|
+
const marketMaxMaintLeverage = new _1.BN(numericConstants_1.TEN_THOUSAND.mul(numericConstants_1.TEN_THOUSAND).toNumber() /
|
|
836
|
+
_1.calculateMarketMarginRatio(market, proposedWorstCastBaseAssetAmount.abs(), 'Maintenance'));
|
|
808
837
|
let priceDelta;
|
|
809
838
|
if (proposedBaseAssetAmount.lt(numericConstants_1.ZERO)) {
|
|
810
839
|
priceDelta = freeCollateralAfterTrade
|
|
811
|
-
.mul(
|
|
812
|
-
.div(
|
|
840
|
+
.mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
|
|
841
|
+
.div(marketMaxMaintLeverage.add(numericConstants_1.TEN_THOUSAND))
|
|
813
842
|
.mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
|
|
814
843
|
.mul(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
815
844
|
.div(proposedBaseAssetAmount);
|
|
816
845
|
}
|
|
817
846
|
else {
|
|
818
847
|
priceDelta = freeCollateralAfterTrade
|
|
819
|
-
.mul(
|
|
820
|
-
.div(
|
|
848
|
+
.mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
|
|
849
|
+
.div(marketMaxMaintLeverage.sub(numericConstants_1.TEN_THOUSAND))
|
|
821
850
|
.mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
|
|
822
851
|
.mul(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
823
852
|
.div(proposedBaseAssetAmount);
|
|
824
853
|
}
|
|
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)) {
|
|
854
|
+
const currentPrice = this.getOracleDataForPerpMarket(perpPosition.marketIndex).price;
|
|
855
|
+
if (priceDelta.gt(currentPrice) &&
|
|
856
|
+
proposedPerpPosition.baseAssetAmount.gte(numericConstants_1.ZERO)) {
|
|
836
857
|
return new _1.BN(-1);
|
|
837
858
|
}
|
|
838
|
-
return
|
|
859
|
+
return currentPrice.sub(priceDelta);
|
|
839
860
|
}
|
|
840
861
|
/**
|
|
841
862
|
* Calculates the estimated liquidation price for a position after closing a quote amount of the position.
|
|
@@ -920,9 +941,7 @@ class User {
|
|
|
920
941
|
}
|
|
921
942
|
else {
|
|
922
943
|
const freeCollateralAfterClose = totalCollateral.sub(marginRequirementAfterClosing);
|
|
923
|
-
const buyingPowerAfterClose = freeCollateralAfterClose
|
|
924
|
-
.mul(this.getMaxLeverage(targetMarketIndex))
|
|
925
|
-
.div(numericConstants_1.TEN_THOUSAND);
|
|
944
|
+
const buyingPowerAfterClose = this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(targetMarketIndex, freeCollateralAfterClose, numericConstants_1.ZERO);
|
|
926
945
|
maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
|
|
927
946
|
}
|
|
928
947
|
}
|
|
@@ -947,7 +966,7 @@ class User {
|
|
|
947
966
|
const currentPosition = this.getPerpPosition(targetMarketIndex) ||
|
|
948
967
|
this.getEmptyPosition(targetMarketIndex);
|
|
949
968
|
const oracleData = this.getOracleDataForPerpMarket(targetMarketIndex);
|
|
950
|
-
let currentPositionQuoteAmount = this.getPerpPositionValue(targetMarketIndex, oracleData);
|
|
969
|
+
let currentPositionQuoteAmount = this.getPerpPositionValue(targetMarketIndex, oracleData, includeOpenOrders);
|
|
951
970
|
const currentSide = currentPosition && currentPosition.baseAssetAmount.isNeg()
|
|
952
971
|
? _1.PositionDirection.SHORT
|
|
953
972
|
: _1.PositionDirection.LONG;
|
|
@@ -960,16 +979,18 @@ class User {
|
|
|
960
979
|
.abs();
|
|
961
980
|
const totalPositionAfterTradeExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex, undefined, undefined, includeOpenOrders);
|
|
962
981
|
const totalAssetValue = this.getTotalAssetValue();
|
|
963
|
-
const
|
|
982
|
+
const totalPerpPositionLiability = currentPerpPositionAfterTrade
|
|
964
983
|
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
965
984
|
.abs();
|
|
966
|
-
const
|
|
967
|
-
|
|
985
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, includeOpenOrders);
|
|
986
|
+
const totalLiabilitiesAfterTrade = totalPerpPositionLiability.add(totalSpotLiability);
|
|
987
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
988
|
+
if (netAssetValue.eq(numericConstants_1.ZERO)) {
|
|
968
989
|
return numericConstants_1.ZERO;
|
|
969
990
|
}
|
|
970
991
|
const newLeverage = totalLiabilitiesAfterTrade
|
|
971
992
|
.mul(numericConstants_1.TEN_THOUSAND)
|
|
972
|
-
.div(
|
|
993
|
+
.div(netAssetValue);
|
|
973
994
|
return newLeverage;
|
|
974
995
|
}
|
|
975
996
|
/**
|
|
@@ -992,28 +1013,28 @@ class User {
|
|
|
992
1013
|
getWithdrawalLimit(marketIndex, reduceOnly) {
|
|
993
1014
|
const nowTs = new _1.BN(Math.floor(Date.now() / 1000));
|
|
994
1015
|
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
995
|
-
|
|
1016
|
+
// eslint-disable-next-line prefer-const
|
|
1017
|
+
let { borrowLimit, withdrawLimit } = spotBalance_1.calculateWithdrawLimit(spotMarket, nowTs);
|
|
996
1018
|
const freeCollateral = this.getFreeCollateral();
|
|
997
1019
|
const oracleData = this.getOracleDataForSpotMarket(marketIndex);
|
|
998
1020
|
const precisionIncrease = numericConstants_1.TEN.pow(new _1.BN(spotMarket.decimals - 6));
|
|
1021
|
+
const { canBypass, depositAmount: userDepositAmount } = this.canBypassWithdrawLimits(marketIndex);
|
|
1022
|
+
if (canBypass) {
|
|
1023
|
+
withdrawLimit = _1.BN.max(withdrawLimit, userDepositAmount);
|
|
1024
|
+
}
|
|
999
1025
|
const amountWithdrawable = freeCollateral
|
|
1000
1026
|
.mul(numericConstants_1.MARGIN_PRECISION)
|
|
1001
1027
|
.div(new _1.BN(spotMarket.initialAssetWeight))
|
|
1002
1028
|
.mul(numericConstants_1.PRICE_PRECISION)
|
|
1003
1029
|
.div(oracleData.price)
|
|
1004
1030
|
.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());
|
|
1031
|
+
const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userDepositAmount), withdrawLimit.abs());
|
|
1011
1032
|
if (reduceOnly) {
|
|
1012
1033
|
return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
|
|
1013
1034
|
}
|
|
1014
1035
|
else {
|
|
1015
1036
|
const weightedAssetValue = this.getSpotMarketAssetValue(marketIndex, 'Initial', false);
|
|
1016
|
-
const freeCollatAfterWithdraw =
|
|
1037
|
+
const freeCollatAfterWithdraw = userDepositAmount.gt(numericConstants_1.ZERO)
|
|
1017
1038
|
? freeCollateral.sub(weightedAssetValue)
|
|
1018
1039
|
: freeCollateral;
|
|
1019
1040
|
const maxLiabilityAllowed = freeCollatAfterWithdraw
|
|
@@ -1026,6 +1047,43 @@ class User {
|
|
|
1026
1047
|
return _1.BN.max(maxBorrowValue, numericConstants_1.ZERO);
|
|
1027
1048
|
}
|
|
1028
1049
|
}
|
|
1050
|
+
canBypassWithdrawLimits(marketIndex) {
|
|
1051
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
1052
|
+
const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new _1.BN(10));
|
|
1053
|
+
const position = this.getSpotPosition(marketIndex);
|
|
1054
|
+
const netDeposits = this.getUserAccount().totalDeposits.sub(this.getUserAccount().totalWithdraws);
|
|
1055
|
+
if (!position) {
|
|
1056
|
+
return {
|
|
1057
|
+
canBypass: false,
|
|
1058
|
+
maxDepositAmount,
|
|
1059
|
+
depositAmount: numericConstants_1.ZERO,
|
|
1060
|
+
netDeposits,
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
if (types_1.isVariant(position.balanceType, 'borrow')) {
|
|
1064
|
+
return {
|
|
1065
|
+
canBypass: false,
|
|
1066
|
+
maxDepositAmount,
|
|
1067
|
+
netDeposits,
|
|
1068
|
+
depositAmount: numericConstants_1.ZERO,
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
const depositAmount = spotBalance_1.getTokenAmount(position.scaledBalance, spotMarket, 'deposit');
|
|
1072
|
+
if (netDeposits.lt(numericConstants_1.ZERO)) {
|
|
1073
|
+
return {
|
|
1074
|
+
canBypass: false,
|
|
1075
|
+
maxDepositAmount,
|
|
1076
|
+
depositAmount: numericConstants_1.ZERO,
|
|
1077
|
+
netDeposits,
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
return {
|
|
1081
|
+
canBypass: depositAmount.lt(maxDepositAmount),
|
|
1082
|
+
maxDepositAmount,
|
|
1083
|
+
netDeposits,
|
|
1084
|
+
depositAmount,
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1029
1087
|
/**
|
|
1030
1088
|
* Get the total position value, excluding any position coming from the given target market
|
|
1031
1089
|
* @param marketToIgnore
|
|
@@ -1037,7 +1095,7 @@ class User {
|
|
|
1037
1095
|
const oracleData = this.getOracleDataForPerpMarket(marketToIgnore);
|
|
1038
1096
|
let currentPerpPositionValueUSDC = numericConstants_1.ZERO;
|
|
1039
1097
|
if (currentPerpPosition) {
|
|
1040
|
-
currentPerpPositionValueUSDC = this.getPerpPositionValue(marketToIgnore, oracleData);
|
|
1098
|
+
currentPerpPositionValueUSDC = this.getPerpPositionValue(marketToIgnore, oracleData, includeOpenOrders);
|
|
1041
1099
|
}
|
|
1042
1100
|
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders).sub(currentPerpPositionValueUSDC);
|
|
1043
1101
|
}
|
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/margin.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function calculateSizePremiumLiabilityWeight(
|
|
|
22
22
|
return liabilityWeight;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const sizeSqrt = squareRootBN(size.mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
|
|
25
|
+
const sizeSqrt = squareRootBN(size.abs().mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
|
|
26
26
|
|
|
27
27
|
const denom0 = BN.max(new BN(1), SPOT_MARKET_IMF_PRECISION.div(imfFactor));
|
|
28
28
|
assert(denom0.gt(ZERO));
|
|
@@ -57,7 +57,7 @@ export function calculateSizeDiscountAssetWeight(
|
|
|
57
57
|
return assetWeight;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const sizeSqrt = squareRootBN(size.mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
|
|
60
|
+
const sizeSqrt = squareRootBN(size.abs().mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
|
|
61
61
|
const imfNumerator = SPOT_MARKET_IMF_PRECISION.add(
|
|
62
62
|
SPOT_MARKET_IMF_PRECISION.div(new BN(10))
|
|
63
63
|
);
|
|
@@ -106,17 +106,19 @@ export function calculateOraclePriceForPerpMargin(
|
|
|
106
106
|
export function calculateBaseAssetValueWithOracle(
|
|
107
107
|
market: PerpMarketAccount,
|
|
108
108
|
perpPosition: PerpPosition,
|
|
109
|
-
oraclePriceData: OraclePriceData
|
|
109
|
+
oraclePriceData: OraclePriceData,
|
|
110
|
+
includeOpenOrders = false
|
|
110
111
|
): BN {
|
|
111
112
|
let price = oraclePriceData.price;
|
|
112
113
|
if (isVariant(market.status, 'settlement')) {
|
|
113
114
|
price = market.expiryPrice;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
|
|
117
|
+
const baseAssetAmount = includeOpenOrders
|
|
118
|
+
? calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
119
|
+
: perpPosition.baseAssetAmount;
|
|
120
|
+
|
|
121
|
+
return baseAssetAmount.abs().mul(price).div(AMM_RESERVE_PRECISION);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
export function calculateWorstCaseBaseAssetAmount(
|