@drift-labs/sdk 0.1.9 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/lib/accounts/types.d.ts +0 -21
  2. package/lib/accounts/types.d.ts.map +1 -1
  3. package/lib/clearingHouseUser.d.ts +29 -5
  4. package/lib/clearingHouseUser.d.ts.map +1 -1
  5. package/lib/clearingHouseUser.js +206 -78
  6. package/lib/constants/markets.d.ts.map +1 -1
  7. package/lib/constants/markets.js +21 -0
  8. package/lib/constants/numericConstants.d.ts +1 -0
  9. package/lib/constants/numericConstants.d.ts.map +1 -1
  10. package/lib/constants/numericConstants.js +2 -1
  11. package/lib/examples/makeTradeExample.d.ts.map +1 -1
  12. package/lib/examples/makeTradeExample.js +14 -13
  13. package/lib/idl/clearing_house.json +94 -42
  14. package/lib/index.d.ts +2 -1
  15. package/lib/index.d.ts.map +1 -1
  16. package/lib/index.js +2 -1
  17. package/lib/math/amm.d.ts +26 -1
  18. package/lib/math/amm.d.ts.map +1 -1
  19. package/lib/math/amm.js +84 -10
  20. package/lib/math/funding.d.ts +6 -6
  21. package/lib/math/funding.d.ts.map +1 -1
  22. package/lib/math/funding.js +69 -25
  23. package/lib/math/insuranceFund.d.ts +14 -0
  24. package/lib/math/insuranceFund.d.ts.map +1 -0
  25. package/lib/math/insuranceFund.js +35 -0
  26. package/lib/math/position.d.ts +7 -1
  27. package/lib/math/position.d.ts.map +1 -1
  28. package/lib/math/position.js +32 -24
  29. package/lib/math/trade.d.ts +1 -1
  30. package/lib/math/trade.d.ts.map +1 -1
  31. package/lib/math/trade.js +9 -4
  32. package/lib/types.d.ts +0 -50
  33. package/lib/types.d.ts.map +1 -1
  34. package/lib/wallet.d.ts +10 -0
  35. package/lib/wallet.d.ts.map +1 -0
  36. package/lib/wallet.js +35 -0
  37. package/package.json +2 -2
  38. package/src/accounts/types.ts +0 -27
  39. package/src/clearingHouse.ts +2 -2
  40. package/src/clearingHouseUser.ts +301 -107
  41. package/src/constants/markets.ts +21 -0
  42. package/src/constants/numericConstants.ts +3 -0
  43. package/src/examples/makeTradeExample.ts +2 -1
  44. package/src/idl/clearing_house.json +94 -42
  45. package/src/index.ts +2 -1
  46. package/src/math/amm.ts +119 -12
  47. package/src/math/funding.ts +109 -51
  48. package/src/math/insuranceFund.ts +22 -0
  49. package/src/math/position.ts +32 -26
  50. package/src/math/trade.ts +9 -5
  51. package/src/types.ts +0 -54
  52. package/src/wallet.ts +22 -0
  53. package/lib/accounts/defaultHistoryAccountSubscriber.d.ts +0 -29
  54. package/lib/accounts/defaultHistoryAccountSubscriber.d.ts.map +0 -1
  55. package/lib/accounts/defaultHistoryAccountSubscriber.js +0 -110
  56. package/src/accounts/defaultHistoryAccountSubscriber.ts +0 -179
@@ -4,6 +4,7 @@ import { EventEmitter } from 'events';
4
4
  import StrictEventEmitter from 'strict-event-emitter-types';
5
5
  import { ClearingHouse } from './clearingHouse';
6
6
  import { UserAccount, UserPosition, UserPositionsAccount } from './types';
7
+ import { calculateEntryPrice } from './math/position';
7
8
  import {
8
9
  MARK_PRICE_PRECISION,
9
10
  AMM_TO_QUOTE_PRECISION_RATIO,
@@ -13,6 +14,8 @@ import {
13
14
  PARTIAL_LIQUIDATION_RATIO,
14
15
  FULL_LIQUIDATION_RATIO,
15
16
  QUOTE_PRECISION,
17
+ AMM_RESERVE_PRECISION,
18
+ PRICE_TO_QUOTE_PRECISION,
16
19
  } from './constants/numericConstants';
17
20
  import { UserAccountSubscriber, UserAccountEvents } from './accounts/types';
18
21
  import { DefaultUserAccountSubscriber } from './accounts/defaultUserAccountSubscriber';
@@ -81,23 +84,25 @@ export class ClearingHouseUser {
81
84
  }
82
85
 
83
86
  /**
84
- * Gets the user's current position for a given market
87
+ * Gets the user's current position for a given market. If the user has no position returns undefined
85
88
  * @param marketIndex
86
89
  * @returns userPosition
87
90
  */
88
- public getUserPosition(marketIndex: BN): UserPosition {
89
- return (
90
- this.getUserPositionsAccount().positions.find((position) =>
91
- position.marketIndex.eq(marketIndex)
92
- ) ?? {
93
- baseAssetAmount: ZERO,
94
- lastCumulativeFundingRate: ZERO,
95
- marketIndex,
96
- quoteAssetAmount: ZERO,
97
- }
91
+ public getUserPosition(marketIndex: BN): UserPosition | undefined {
92
+ return this.getUserPositionsAccount().positions.find((position) =>
93
+ position.marketIndex.eq(marketIndex)
98
94
  );
99
95
  }
100
96
 
97
+ public getEmptyPosition(marketIndex: BN): UserPosition {
98
+ return {
99
+ baseAssetAmount: ZERO,
100
+ lastCumulativeFundingRate: ZERO,
101
+ marketIndex,
102
+ quoteAssetAmount: ZERO,
103
+ };
104
+ }
105
+
101
106
  public async getUserAccountPublicKey(): Promise<PublicKey> {
102
107
  if (this.userAccountPublicKey) {
103
108
  return this.userAccountPublicKey;
@@ -145,30 +150,32 @@ export class ClearingHouseUser {
145
150
  * calculates unrealized position price pnl
146
151
  * @returns : Precision QUOTE_PRECISION
147
152
  */
148
- public getUnrealizedPNL(withFunding?: boolean): BN {
149
- return this.getUserPositionsAccount().positions.reduce(
150
- (pnl, marketPosition) => {
153
+ public getUnrealizedPNL(withFunding?: boolean, marketIndex?: BN): BN {
154
+ return this.getUserPositionsAccount()
155
+ .positions.filter((pos) =>
156
+ marketIndex ? pos.marketIndex === marketIndex : true
157
+ )
158
+ .reduce((pnl, marketPosition) => {
151
159
  const market = this.clearingHouse.getMarket(marketPosition.marketIndex);
152
160
  return pnl.add(
153
161
  calculatePositionPNL(market, marketPosition, withFunding)
154
162
  );
155
- },
156
- ZERO
157
- );
163
+ }, ZERO);
158
164
  }
159
165
 
160
166
  /**
161
167
  * calculates unrealized funding payment pnl
162
168
  * @returns : Precision QUOTE_PRECISION
163
169
  */
164
- public getUnrealizedFundingPNL(): BN {
165
- return this.getUserPositionsAccount().positions.reduce(
166
- (pnl, marketPosition) => {
170
+ public getUnrealizedFundingPNL(marketIndex?: BN): BN {
171
+ return this.getUserPositionsAccount()
172
+ .positions.filter((pos) =>
173
+ marketIndex ? pos.marketIndex === marketIndex : true
174
+ )
175
+ .reduce((pnl, marketPosition) => {
167
176
  const market = this.clearingHouse.getMarket(marketPosition.marketIndex);
168
177
  return pnl.add(calculatePositionFundingPNL(market, marketPosition));
169
- },
170
- ZERO
171
- );
178
+ }, ZERO);
172
179
  }
173
180
 
174
181
  /**
@@ -203,7 +210,8 @@ export class ClearingHouseUser {
203
210
  * @returns : Precision QUOTE_PRECISION
204
211
  */
205
212
  public getPositionValue(marketIndex: BN): BN {
206
- const userPosition = this.getUserPosition(marketIndex);
213
+ const userPosition =
214
+ this.getUserPosition(marketIndex) || this.getEmptyPosition(marketIndex);
207
215
  const market = this.clearingHouse.getMarket(userPosition.marketIndex);
208
216
  return calculateBaseAssetValue(market, userPosition);
209
217
  }
@@ -224,15 +232,17 @@ export class ClearingHouseUser {
224
232
  * calculates average exit price for closing 100% of position
225
233
  * @returns : Precision MARK_PRICE_PRECISION
226
234
  */
227
- public getPositionEstimatedExitPrice(
235
+ public getPositionEstimatedExitPriceAndPnl(
228
236
  position: UserPosition,
229
237
  amountToClose?: BN
230
- ): BN {
238
+ ): [BN, BN] {
231
239
  const market = this.clearingHouse.getMarket(position.marketIndex);
232
240
 
241
+ const entryPrice = calculateEntryPrice(position);
242
+
233
243
  if (amountToClose) {
234
244
  if (amountToClose.eq(ZERO)) {
235
- return calculateMarkPrice(market);
245
+ return [calculateMarkPrice(market), ZERO];
236
246
  }
237
247
  position = {
238
248
  baseAssetAmount: amountToClose,
@@ -244,12 +254,21 @@ export class ClearingHouseUser {
244
254
 
245
255
  const baseAssetValue = calculateBaseAssetValue(market, position);
246
256
  if (position.baseAssetAmount.eq(ZERO)) {
247
- return ZERO;
257
+ return [ZERO, ZERO];
248
258
  }
249
- return baseAssetValue
259
+
260
+ const exitPrice = baseAssetValue
250
261
  .mul(AMM_TO_QUOTE_PRECISION_RATIO)
251
262
  .mul(MARK_PRICE_PRECISION)
252
263
  .div(position.baseAssetAmount.abs());
264
+
265
+ const pnlPerBase = exitPrice.sub(entryPrice);
266
+ const pnl = pnlPerBase
267
+ .mul(position.baseAssetAmount)
268
+ .div(MARK_PRICE_PRECISION)
269
+ .div(AMM_TO_QUOTE_PRECISION_RATIO);
270
+
271
+ return [exitPrice, pnl];
253
272
  }
254
273
 
255
274
  /**
@@ -348,7 +367,7 @@ export class ClearingHouseUser {
348
367
  * @param partial
349
368
  * @returns Precision : MARK_PRICE_PRECISION
350
369
  */
351
- public liquidationPrice(
370
+ public liquidationPriceOld(
352
371
  targetMarket: Pick<UserPosition, 'marketIndex'>,
353
372
  positionBaseSizeChange: BN = ZERO,
354
373
  partial = false
@@ -377,13 +396,11 @@ export class ClearingHouseUser {
377
396
  const totalCurrentPositionValueIgnoringTargetUSDC =
378
397
  this.getTotalPositionValueExcludingMarket(targetMarket.marketIndex);
379
398
 
380
- const currentMarketPosition = this.getUserPosition(
381
- targetMarket.marketIndex
382
- );
399
+ const currentMarketPosition =
400
+ this.getUserPosition(targetMarket.marketIndex) ||
401
+ this.getEmptyPosition(targetMarket.marketIndex);
383
402
 
384
- const currentMarketPositionBaseSize = currentMarketPosition
385
- ? currentMarketPosition.baseAssetAmount
386
- : ZERO;
403
+ const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
387
404
 
388
405
  // calculate position for current market after trade
389
406
  const proposedMarketPosition: UserPosition = {
@@ -410,24 +427,36 @@ export class ClearingHouseUser {
410
427
  proposedMarketPositionValueUSDC
411
428
  );
412
429
 
430
+ let totalFreeCollateralUSDC = this.getTotalCollateral().sub(
431
+ this.getTotalPositionValue()
432
+ .mul(TEN_THOUSAND)
433
+ .div(this.getMaxLeverage('Maintenance'))
434
+ );
435
+
436
+ if (partial) {
437
+ totalFreeCollateralUSDC = this.getTotalCollateral().sub(
438
+ this.getTotalPositionValue()
439
+ .mul(TEN_THOUSAND)
440
+ .div(this.getMaxLeverage('Partial'))
441
+ );
442
+ }
443
+
413
444
  // if the position value after the trade is less than total collateral, there is no liq price
414
- if (targetTotalPositionValueUSDC.lte(totalCollateralUSDC) && proposedMarketPosition.baseAssetAmount.gt(ZERO)) {
445
+ if (
446
+ targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
447
+ proposedMarketPosition.baseAssetAmount.gt(ZERO)
448
+ ) {
415
449
  return new BN(-1);
416
450
  }
417
451
 
418
- // proportion of proposed market position to overall position
419
- // const marketProportion = proposedMarketPositionValueUSDC
420
- // .mul(TEN_THOUSAND)
421
- // .div(targetTotalPositionValueUSDC);
422
-
423
452
  // get current margin ratio based on current collateral and proposed total position value
424
453
  let marginRatio;
425
- if (targetTotalPositionValueUSDC.eq(ZERO)) {
454
+ if (proposedMarketPositionValueUSDC.eq(ZERO)) {
426
455
  marginRatio = BN_MAX;
427
456
  } else {
428
457
  marginRatio = totalCollateralUSDC
429
458
  .mul(TEN_THOUSAND)
430
- .div(targetTotalPositionValueUSDC);
459
+ .div(proposedMarketPositionValueUSDC);
431
460
  }
432
461
 
433
462
  let liqRatio = FULL_LIQUIDATION_RATIO;
@@ -438,29 +467,6 @@ export class ClearingHouseUser {
438
467
  // sign of position in current market after the trade
439
468
  const baseAssetSignIsNeg = proposedMarketPosition.baseAssetAmount.isNeg();
440
469
 
441
- // console.log(
442
- // convertToNumber(currentPrice),
443
- // convertToNumber(liqRatio),
444
- // convertToNumber(marginRatio),
445
- // convertToNumber(marketProportion),
446
- // );
447
-
448
- // // if the user is long, then the liq price is the currentPrice multiplied by liqRatio/marginRatio (how many multiples lower does the current marginRatio have to go to reach the liqRatio), multiplied by the fraction of the proposed total position value that this market will take up
449
- // if (!baseAssetSignIsNeg) {
450
- // liqPrice = currentPrice
451
- // .mul(liqRatio)
452
- // .div(marginRatio)
453
- // .mul(marketProportion)
454
- // .div(TEN_THOUSAND);
455
- // } else {
456
- // // if the user is short, it's the reciprocal of the above
457
- // liqPrice = currentPrice
458
- // .mul(marginRatio)
459
- // .div(liqRatio)
460
- // .mul(TEN_THOUSAND)
461
- // .div(marketProportion);
462
- // }
463
-
464
470
  let pctChange = marginRatio.abs().sub(liqRatio);
465
471
  // if user is short, higher price is liq
466
472
  if (baseAssetSignIsNeg) {
@@ -479,6 +485,127 @@ export class ClearingHouseUser {
479
485
  return liqPrice;
480
486
  }
481
487
 
488
+ /**
489
+ * Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
490
+ * @param targetMarket
491
+ * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
492
+ * @param partial
493
+ * @returns Precision : MARK_PRICE_PRECISION
494
+ */
495
+ public liquidationPrice(
496
+ targetMarket: Pick<UserPosition, 'marketIndex'>,
497
+ positionBaseSizeChange: BN = ZERO,
498
+ partial = false
499
+ ): BN {
500
+ // solves formula for example calc below
501
+
502
+ /* example: assume BTC price is $40k (examine 10% up/down)
503
+
504
+ if 10k deposit and levered 10x short BTC => BTC up $400 means:
505
+ 1. higher base_asset_value (+$4k)
506
+ 2. lower collateral (-$4k)
507
+ 3. (10k - 4k)/(100k + 4k) => 6k/104k => .0576
508
+
509
+ for 10x long, BTC down $400:
510
+ 3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
511
+
512
+ const tc = this.getTotalCollateral();
513
+ const tpv = this.getTotalPositionValue();
514
+
515
+ const partialLev = 16;
516
+ const maintLev = 20;
517
+
518
+ const thisLev = partial ? new BN(partialLev) : new BN(maintLev);
519
+
520
+ // calculate the total position value ignoring any value from the target market of the trade
521
+ const totalCurrentPositionValueIgnoringTargetUSDC =
522
+ this.getTotalPositionValueExcludingMarket(targetMarket.marketIndex);
523
+
524
+ const currentMarketPosition =
525
+ this.getUserPosition(targetMarket.marketIndex) ||
526
+ this.getEmptyPosition(targetMarket.marketIndex);
527
+
528
+ const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
529
+
530
+ const proposedBaseAssetAmount = currentMarketPositionBaseSize.add(
531
+ positionBaseSizeChange
532
+ );
533
+
534
+ // calculate position for current market after trade
535
+ const proposedMarketPosition: UserPosition = {
536
+ marketIndex: targetMarket.marketIndex,
537
+ baseAssetAmount: proposedBaseAssetAmount,
538
+ lastCumulativeFundingRate:
539
+ currentMarketPosition.lastCumulativeFundingRate,
540
+ quoteAssetAmount: new BN(0),
541
+ };
542
+
543
+ const market = this.clearingHouse.getMarket(
544
+ proposedMarketPosition.marketIndex
545
+ );
546
+
547
+ const proposedMarketPositionValueUSDC = calculateBaseAssetValue(
548
+ market,
549
+ proposedMarketPosition
550
+ );
551
+
552
+ // total position value after trade
553
+ const targetTotalPositionValueUSDC =
554
+ totalCurrentPositionValueIgnoringTargetUSDC.add(
555
+ proposedMarketPositionValueUSDC
556
+ );
557
+
558
+ let totalFreeCollateralUSDC = tc.sub(
559
+ totalCurrentPositionValueIgnoringTargetUSDC
560
+ .mul(TEN_THOUSAND)
561
+ .div(this.getMaxLeverage('Maintenance'))
562
+ );
563
+
564
+ if (partial) {
565
+ totalFreeCollateralUSDC = tc.sub(
566
+ totalCurrentPositionValueIgnoringTargetUSDC
567
+ .mul(TEN_THOUSAND)
568
+ .div(this.getMaxLeverage('Partial'))
569
+ );
570
+ }
571
+
572
+ let priceDelt;
573
+ if (currentMarketPositionBaseSize.lt(ZERO)) {
574
+ priceDelt = tc
575
+ .mul(thisLev)
576
+ .sub(tpv)
577
+ .mul(PRICE_TO_QUOTE_PRECISION)
578
+ .div(thisLev.add(new BN(1)));
579
+ } else {
580
+ priceDelt = tc
581
+ .mul(thisLev)
582
+ .sub(tpv)
583
+ .mul(PRICE_TO_QUOTE_PRECISION)
584
+ .div(thisLev.sub(new BN(1)));
585
+ }
586
+
587
+ const currentPrice = calculateMarkPrice(
588
+ this.clearingHouse.getMarket(targetMarket.marketIndex)
589
+ );
590
+
591
+ // if the position value after the trade is less than total collateral, there is no liq price
592
+ if (
593
+ targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
594
+ proposedMarketPosition.baseAssetAmount.gt(ZERO)
595
+ ) {
596
+ return new BN(-1);
597
+ }
598
+
599
+ if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
600
+
601
+ const eatMargin2 = priceDelt
602
+ .mul(AMM_RESERVE_PRECISION)
603
+ .div(proposedBaseAssetAmount);
604
+
605
+ const liqPrice = currentPrice.sub(eatMargin2);
606
+ return liqPrice;
607
+ }
608
+
482
609
  /**
483
610
  * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
484
611
  * @param positionMarketIndex
@@ -489,7 +616,9 @@ export class ClearingHouseUser {
489
616
  positionMarketIndex: BN,
490
617
  closeQuoteAmount: BN
491
618
  ): BN {
492
- const currentPosition = this.getUserPosition(positionMarketIndex);
619
+ const currentPosition =
620
+ this.getUserPosition(positionMarketIndex) ||
621
+ this.getEmptyPosition(positionMarketIndex);
493
622
 
494
623
  const closeBaseAmount = currentPosition.baseAssetAmount
495
624
  .mul(closeQuoteAmount)
@@ -511,6 +640,21 @@ export class ClearingHouseUser {
511
640
 
512
641
  /**
513
642
  * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
643
+ *
644
+ * To Calculate Max Quote Available:
645
+ *
646
+ * Case 1: SameSide
647
+ * => Remaining quote to get to maxLeverage
648
+ *
649
+ * Case 2: NOT SameSide && currentLeverage <= maxLeverage
650
+ * => Current opposite position x2 + remaining to get to maxLeverage
651
+ *
652
+ * Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
653
+ * => strictly reduce current position size
654
+ *
655
+ * Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
656
+ * => current position + remaining to get to maxLeverage
657
+ *
514
658
  * @param marketIndex
515
659
  * @param tradeSide
516
660
  * @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
@@ -521,32 +665,28 @@ export class ClearingHouseUser {
521
665
  tradeSide: PositionDirection,
522
666
  userMaxLeverageSetting: BN
523
667
  ): BN {
524
- // inline function which get's the current position size on the opposite side of the target trade
525
- const getOppositePositionValueUSDC = () => {
526
- if (!currentPosition) return ZERO;
668
+ const currentPosition =
669
+ this.getUserPosition(targetMarketIndex) ||
670
+ this.getEmptyPosition(targetMarketIndex);
527
671
 
528
- const side = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
672
+ const targetSide = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
529
673
 
530
- if (side === 'long' && currentPosition?.baseAssetAmount.isNeg()) {
531
- return this.getPositionValue(targetMarketIndex);
532
- } else if (
533
- side === 'short' &&
534
- !currentPosition?.baseAssetAmount.isNeg()
535
- ) {
536
- return this.getPositionValue(targetMarketIndex);
537
- }
674
+ const currentPositionSide = currentPosition?.baseAssetAmount.isNeg()
675
+ ? 'short'
676
+ : 'long';
538
677
 
539
- return ZERO;
540
- };
678
+ const targettingSameSide = !currentPosition
679
+ ? true
680
+ : targetSide === currentPositionSide;
541
681
 
542
- const currentPosition = this.getUserPosition(targetMarketIndex);
682
+ // add any position we have on the opposite side of the current trade, because we can "flip" the size of this position without taking any extra leverage.
683
+ const oppositeSizeValueUSDC = targettingSameSide
684
+ ? ZERO
685
+ : this.getPositionValue(targetMarketIndex);
543
686
 
544
687
  // get current leverage
545
688
  const currentLeverage = this.getLeverage();
546
689
 
547
- // remaining leverage
548
- // let remainingLeverage = userMaxLeverageSetting;
549
-
550
690
  const remainingLeverage = BN.max(
551
691
  userMaxLeverageSetting.sub(currentLeverage),
552
692
  ZERO
@@ -560,10 +700,57 @@ export class ClearingHouseUser {
560
700
  .mul(totalCollateral)
561
701
  .div(TEN_THOUSAND);
562
702
 
563
- // add any position we have on the opposite side of the current trade, because we can "flip" the size of this position without taking any extra leverage.
564
- const oppositeSizeValueUSDC = getOppositePositionValueUSDC();
703
+ if (userMaxLeverageSetting.sub(currentLeverage).gte(ZERO)) {
704
+ if (oppositeSizeValueUSDC.eq(ZERO)) {
705
+ // case 1 : Regular trade where current total position less than max, and no opposite position to account for
706
+ // do nothing
707
+ } else {
708
+ // case 2 : trade where current total position less than max, but need to account for flipping the current position over to the other side
709
+ maxPositionSize = maxPositionSize.add(
710
+ oppositeSizeValueUSDC.mul(new BN(2))
711
+ );
712
+ }
713
+ } else {
714
+ // current leverage is greater than max leverage - can only reduce position size
715
+
716
+ if (!targettingSameSide) {
717
+ const currentPositionQuoteSize =
718
+ this.getPositionValue(targetMarketIndex);
719
+
720
+ const currentTotalQuoteSize = currentLeverage
721
+ .mul(totalCollateral)
722
+ .div(TEN_THOUSAND);
723
+
724
+ const otherPositionsTotalQuoteSize = currentTotalQuoteSize.sub(
725
+ currentPositionQuoteSize
726
+ );
565
727
 
566
- maxPositionSize = maxPositionSize.add(oppositeSizeValueUSDC);
728
+ const quoteValueOfMaxLeverage = userMaxLeverageSetting
729
+ .mul(totalCollateral)
730
+ .div(TEN_THOUSAND);
731
+
732
+ if (
733
+ otherPositionsTotalQuoteSize
734
+ .sub(currentPositionQuoteSize)
735
+ .gte(quoteValueOfMaxLeverage)
736
+ ) {
737
+ // case 3: Can only reduce the current position because it will still be greater than max leverage
738
+
739
+ maxPositionSize = currentPositionQuoteSize;
740
+ } else {
741
+ // case 4: Can reduce the position, and then take extra remaining quote to get to max leverage
742
+
743
+ const allowedQuoteSizeAfterClosingCurrentPosition =
744
+ quoteValueOfMaxLeverage.sub(otherPositionsTotalQuoteSize);
745
+
746
+ maxPositionSize = currentPositionQuoteSize.add(
747
+ allowedQuoteSizeAfterClosingCurrentPosition
748
+ );
749
+ }
750
+ } else {
751
+ // do nothing if targetting same side
752
+ }
753
+ }
567
754
 
568
755
  // subtract oneMillionth of maxPositionSize
569
756
  // => to avoid rounding errors when taking max leverage
@@ -585,12 +772,16 @@ export class ClearingHouseUser {
585
772
  tradeQuoteAmount: BN,
586
773
  tradeSide: PositionDirection
587
774
  ): BN {
588
- const currentPosition = this.getUserPosition(targetMarketIndex);
589
- let currentPositionQuoteAmount = currentPosition.quoteAssetAmount;
775
+ const currentPosition =
776
+ this.getUserPosition(targetMarketIndex) ||
777
+ this.getEmptyPosition(targetMarketIndex);
778
+
779
+ let currentPositionQuoteAmount = this.getPositionValue(targetMarketIndex);
590
780
 
591
- const currentSide = currentPosition.baseAssetAmount.isNeg()
592
- ? PositionDirection.SHORT
593
- : PositionDirection.LONG;
781
+ const currentSide =
782
+ currentPosition && currentPosition.baseAssetAmount.isNeg()
783
+ ? PositionDirection.SHORT
784
+ : PositionDirection.LONG;
594
785
 
595
786
  if (currentSide === PositionDirection.SHORT)
596
787
  currentPositionQuoteAmount = currentPositionQuoteAmount.neg();
@@ -605,11 +796,18 @@ export class ClearingHouseUser {
605
796
  const totalPositionAfterTradeExcludingTargetMarket =
606
797
  this.getTotalPositionValueExcludingMarket(targetMarketIndex);
607
798
 
608
- return currentMarketPositionAfterTrade
609
- .add(totalPositionAfterTradeExcludingTargetMarket)
610
- .abs()
611
- .mul(TEN_THOUSAND)
612
- .div(this.getTotalCollateral());
799
+ const totalCollateral = this.getTotalCollateral();
800
+
801
+ if (totalCollateral.gt(ZERO)) {
802
+ const newLeverage = currentMarketPositionAfterTrade
803
+ .add(totalPositionAfterTradeExcludingTargetMarket)
804
+ .abs()
805
+ .mul(TEN_THOUSAND)
806
+ .div(totalCollateral);
807
+ return newLeverage;
808
+ } else {
809
+ return new BN(0);
810
+ }
613
811
  }
614
812
 
615
813
  /**
@@ -631,17 +829,13 @@ export class ClearingHouseUser {
631
829
  * @returns positionValue : Precision QUOTE_PRECISION
632
830
  */
633
831
  private getTotalPositionValueExcludingMarket(marketToIgnore: BN): BN {
634
- const currentMarketPosition = this.getUserPosition(marketToIgnore);
832
+ const currentMarketPosition =
833
+ this.getUserPosition(marketToIgnore) ||
834
+ this.getEmptyPosition(marketToIgnore);
635
835
 
636
836
  let currentMarketPositionValueUSDC = ZERO;
637
837
  if (currentMarketPosition) {
638
- const market = this.clearingHouse.getMarket(
639
- currentMarketPosition.marketIndex
640
- );
641
- currentMarketPositionValueUSDC = calculateBaseAssetValue(
642
- market,
643
- currentMarketPosition
644
- );
838
+ currentMarketPositionValueUSDC = this.getPositionValue(marketToIgnore);
645
839
  }
646
840
 
647
841
  return this.getTotalPositionValue().sub(currentMarketPositionValueUSDC);
@@ -37,4 +37,25 @@ export const Markets: Market[] = [
37
37
  devnetPythOracle: '8PugCXTAHLM9kfLSQWe2njE5pzAgUdpPk3Nx5zSm7BD3',
38
38
  mainnetPythOracle: '5bmWuR1dgP4avtGYMNKLuxumZTVKGgoN2BCMXWDNL9nY',
39
39
  },
40
+ {
41
+ symbol: 'AVAX-PERP',
42
+ baseAssetSymbol: 'AVAX',
43
+ marketIndex: new BN(4),
44
+ devnetPythOracle: 'FVb5h1VmHPfVb1RfqZckchq18GxRv4iKt8T4eVTQAqdz',
45
+ mainnetPythOracle: 'Ax9ujW5B9oqcv59N8m6f1BpTBq2rGeGaBcpKjC5UYsXU',
46
+ },
47
+ {
48
+ symbol: 'BNB-PERP',
49
+ baseAssetSymbol: 'BNB',
50
+ marketIndex: new BN(5),
51
+ devnetPythOracle: 'GwzBgrXb4PG59zjce24SF2b9JXbLEjJJTBkmytuEZj1b',
52
+ mainnetPythOracle: '4CkQJBxhU8EZ2UjhigbtdaPbpTe6mqf811fipYBFbSYN',
53
+ },
54
+ {
55
+ symbol: 'MATIC-PERP',
56
+ baseAssetSymbol: 'MATIC',
57
+ marketIndex: new BN(6),
58
+ devnetPythOracle: 'FBirwuDFuRAu4iSGc7RGxN5koHB7EJM1wbCmyPuQoGur',
59
+ mainnetPythOracle: '7KVswB9vkCgeM3SHP7aGDijvdRAHK8P5wi9JXViCrtYh',
60
+ },
40
61
  ];
@@ -19,3 +19,6 @@ export const AMM_TO_QUOTE_PRECISION_RATIO =
19
19
  AMM_RESERVE_PRECISION.div(QUOTE_PRECISION); // 10^7
20
20
  export const PRICE_TO_QUOTE_PRECISION =
21
21
  MARK_PRICE_PRECISION.div(QUOTE_PRECISION);
22
+ export const AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO =
23
+ AMM_RESERVE_PRECISION.mul(PEG_PRECISION).div(QUOTE_PRECISION); // 10^10
24
+
@@ -1,4 +1,5 @@
1
- import { BN, Provider, Wallet } from '@project-serum/anchor';
1
+ import { BN, Provider } from '@project-serum/anchor';
2
+ import {Wallet} from "..";
2
3
  import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
3
4
  import { Connection, Keypair, PublicKey } from '@solana/web3.js';
4
5
  import {