@drift-labs/sdk 0.1.11 → 0.1.15

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 (83) hide show
  1. package/README.md +2 -2
  2. package/lib/accounts/defaultClearingHouseAccountSubscriber.d.ts +1 -0
  3. package/lib/accounts/defaultClearingHouseAccountSubscriber.d.ts.map +1 -1
  4. package/lib/accounts/defaultClearingHouseAccountSubscriber.js +17 -0
  5. package/lib/accounts/defaultUserAccountSubscriber.d.ts +2 -0
  6. package/lib/accounts/defaultUserAccountSubscriber.d.ts.map +1 -1
  7. package/lib/accounts/defaultUserAccountSubscriber.js +16 -0
  8. package/lib/accounts/types.d.ts +3 -21
  9. package/lib/accounts/types.d.ts.map +1 -1
  10. package/lib/accounts/webSocketAccountSubscriber.d.ts +2 -0
  11. package/lib/accounts/webSocketAccountSubscriber.d.ts.map +1 -1
  12. package/lib/accounts/webSocketAccountSubscriber.js +13 -3
  13. package/lib/clearingHouse.d.ts +4 -0
  14. package/lib/clearingHouse.d.ts.map +1 -1
  15. package/lib/clearingHouse.js +8 -0
  16. package/lib/clearingHouseUser.d.ts +33 -5
  17. package/lib/clearingHouseUser.d.ts.map +1 -1
  18. package/lib/clearingHouseUser.js +224 -78
  19. package/lib/constants/markets.d.ts.map +1 -1
  20. package/lib/constants/markets.js +21 -0
  21. package/lib/constants/numericConstants.d.ts +1 -0
  22. package/lib/constants/numericConstants.d.ts.map +1 -1
  23. package/lib/constants/numericConstants.js +2 -1
  24. package/lib/examples/makeTradeExample.d.ts.map +1 -1
  25. package/lib/examples/makeTradeExample.js +14 -13
  26. package/lib/idl/clearing_house.json +94 -42
  27. package/lib/index.d.ts +4 -1
  28. package/lib/index.d.ts.map +1 -1
  29. package/lib/index.js +4 -1
  30. package/lib/math/amm.d.ts +26 -1
  31. package/lib/math/amm.d.ts.map +1 -1
  32. package/lib/math/amm.js +85 -10
  33. package/lib/math/funding.d.ts +6 -6
  34. package/lib/math/funding.d.ts.map +1 -1
  35. package/lib/math/funding.js +69 -25
  36. package/lib/math/insuranceFund.d.ts +15 -0
  37. package/lib/math/insuranceFund.d.ts.map +1 -0
  38. package/lib/math/insuranceFund.js +36 -0
  39. package/lib/math/position.d.ts +1 -1
  40. package/lib/math/position.d.ts.map +1 -1
  41. package/lib/math/position.js +15 -23
  42. package/lib/math/trade.d.ts +1 -1
  43. package/lib/math/trade.d.ts.map +1 -1
  44. package/lib/math/trade.js +9 -4
  45. package/lib/types.d.ts +0 -50
  46. package/lib/types.d.ts.map +1 -1
  47. package/lib/util/computeUnits.d.ts +3 -0
  48. package/lib/util/computeUnits.d.ts.map +1 -0
  49. package/lib/util/computeUnits.js +27 -0
  50. package/lib/util/tps.d.ts +3 -0
  51. package/lib/util/tps.d.ts.map +1 -0
  52. package/lib/util/tps.js +27 -0
  53. package/lib/wallet.d.ts +10 -0
  54. package/lib/wallet.d.ts.map +1 -0
  55. package/lib/wallet.js +35 -0
  56. package/package.json +3 -13
  57. package/src/accounts/defaultClearingHouseAccountSubscriber.ts +18 -0
  58. package/src/accounts/defaultUserAccountSubscriber.ts +18 -0
  59. package/src/accounts/types.ts +3 -28
  60. package/src/accounts/webSocketAccountSubscriber.ts +16 -6
  61. package/src/clearingHouse.ts +9 -3
  62. package/src/clearingHouseUser.ts +325 -107
  63. package/src/constants/markets.ts +21 -0
  64. package/src/constants/numericConstants.ts +2 -0
  65. package/src/examples/makeTradeExample.ts +2 -1
  66. package/src/idl/clearing_house.json +94 -42
  67. package/src/index.ts +4 -1
  68. package/src/math/amm.ts +120 -13
  69. package/src/math/funding.ts +109 -51
  70. package/src/math/insuranceFund.ts +29 -0
  71. package/src/math/position.ts +16 -28
  72. package/src/math/trade.ts +9 -5
  73. package/src/types.ts +0 -54
  74. package/src/util/computeUnits.ts +21 -0
  75. package/src/util/tps.ts +27 -0
  76. package/src/wallet.ts +22 -0
  77. package/.eslintrc.json +0 -36
  78. package/.prettierignore +0 -1
  79. package/.prettierrc.js +0 -9
  80. package/lib/accounts/defaultHistoryAccountSubscriber.d.ts +0 -29
  81. package/lib/accounts/defaultHistoryAccountSubscriber.d.ts.map +0 -1
  82. package/lib/accounts/defaultHistoryAccountSubscriber.js +0 -110
  83. 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';
@@ -22,6 +25,7 @@ import {
22
25
  calculatePositionFundingPNL,
23
26
  calculatePositionPNL,
24
27
  PositionDirection,
28
+ calculateTradeSlippage,
25
29
  } from '.';
26
30
  import { getUserAccountPublicKey } from './addresses';
27
31
 
@@ -67,6 +71,13 @@ export class ClearingHouseUser {
67
71
  return this.isSubscribed;
68
72
  }
69
73
 
74
+ /**
75
+ * Forces the accountSubscriber to fetch account updates from rpc
76
+ */
77
+ public async fetchAccounts(): Promise<void> {
78
+ await this.accountSubscriber.fetch();
79
+ }
80
+
70
81
  public async unsubscribe(): Promise<void> {
71
82
  await this.accountSubscriber.unsubscribe();
72
83
  this.isSubscribed = false;
@@ -81,23 +92,25 @@ export class ClearingHouseUser {
81
92
  }
82
93
 
83
94
  /**
84
- * Gets the user's current position for a given market
95
+ * Gets the user's current position for a given market. If the user has no position returns undefined
85
96
  * @param marketIndex
86
97
  * @returns userPosition
87
98
  */
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
- }
99
+ public getUserPosition(marketIndex: BN): UserPosition | undefined {
100
+ return this.getUserPositionsAccount().positions.find((position) =>
101
+ position.marketIndex.eq(marketIndex)
98
102
  );
99
103
  }
100
104
 
105
+ public getEmptyPosition(marketIndex: BN): UserPosition {
106
+ return {
107
+ baseAssetAmount: ZERO,
108
+ lastCumulativeFundingRate: ZERO,
109
+ marketIndex,
110
+ quoteAssetAmount: ZERO,
111
+ };
112
+ }
113
+
101
114
  public async getUserAccountPublicKey(): Promise<PublicKey> {
102
115
  if (this.userAccountPublicKey) {
103
116
  return this.userAccountPublicKey;
@@ -145,30 +158,32 @@ export class ClearingHouseUser {
145
158
  * calculates unrealized position price pnl
146
159
  * @returns : Precision QUOTE_PRECISION
147
160
  */
148
- public getUnrealizedPNL(withFunding?: boolean): BN {
149
- return this.getUserPositionsAccount().positions.reduce(
150
- (pnl, marketPosition) => {
161
+ public getUnrealizedPNL(withFunding?: boolean, marketIndex?: BN): BN {
162
+ return this.getUserPositionsAccount()
163
+ .positions.filter((pos) =>
164
+ marketIndex ? pos.marketIndex === marketIndex : true
165
+ )
166
+ .reduce((pnl, marketPosition) => {
151
167
  const market = this.clearingHouse.getMarket(marketPosition.marketIndex);
152
168
  return pnl.add(
153
169
  calculatePositionPNL(market, marketPosition, withFunding)
154
170
  );
155
- },
156
- ZERO
157
- );
171
+ }, ZERO);
158
172
  }
159
173
 
160
174
  /**
161
175
  * calculates unrealized funding payment pnl
162
176
  * @returns : Precision QUOTE_PRECISION
163
177
  */
164
- public getUnrealizedFundingPNL(): BN {
165
- return this.getUserPositionsAccount().positions.reduce(
166
- (pnl, marketPosition) => {
178
+ public getUnrealizedFundingPNL(marketIndex?: BN): BN {
179
+ return this.getUserPositionsAccount()
180
+ .positions.filter((pos) =>
181
+ marketIndex ? pos.marketIndex === marketIndex : true
182
+ )
183
+ .reduce((pnl, marketPosition) => {
167
184
  const market = this.clearingHouse.getMarket(marketPosition.marketIndex);
168
185
  return pnl.add(calculatePositionFundingPNL(market, marketPosition));
169
- },
170
- ZERO
171
- );
186
+ }, ZERO);
172
187
  }
173
188
 
174
189
  /**
@@ -203,7 +218,8 @@ export class ClearingHouseUser {
203
218
  * @returns : Precision QUOTE_PRECISION
204
219
  */
205
220
  public getPositionValue(marketIndex: BN): BN {
206
- const userPosition = this.getUserPosition(marketIndex);
221
+ const userPosition =
222
+ this.getUserPosition(marketIndex) || this.getEmptyPosition(marketIndex);
207
223
  const market = this.clearingHouse.getMarket(userPosition.marketIndex);
208
224
  return calculateBaseAssetValue(market, userPosition);
209
225
  }
@@ -224,15 +240,17 @@ export class ClearingHouseUser {
224
240
  * calculates average exit price for closing 100% of position
225
241
  * @returns : Precision MARK_PRICE_PRECISION
226
242
  */
227
- public getPositionEstimatedExitPrice(
243
+ public getPositionEstimatedExitPriceAndPnl(
228
244
  position: UserPosition,
229
245
  amountToClose?: BN
230
- ): BN {
246
+ ): [BN, BN] {
231
247
  const market = this.clearingHouse.getMarket(position.marketIndex);
232
248
 
249
+ const entryPrice = calculateEntryPrice(position);
250
+
233
251
  if (amountToClose) {
234
252
  if (amountToClose.eq(ZERO)) {
235
- return calculateMarkPrice(market);
253
+ return [calculateMarkPrice(market), ZERO];
236
254
  }
237
255
  position = {
238
256
  baseAssetAmount: amountToClose,
@@ -244,12 +262,21 @@ export class ClearingHouseUser {
244
262
 
245
263
  const baseAssetValue = calculateBaseAssetValue(market, position);
246
264
  if (position.baseAssetAmount.eq(ZERO)) {
247
- return ZERO;
265
+ return [ZERO, ZERO];
248
266
  }
249
- return baseAssetValue
267
+
268
+ const exitPrice = baseAssetValue
250
269
  .mul(AMM_TO_QUOTE_PRECISION_RATIO)
251
270
  .mul(MARK_PRICE_PRECISION)
252
271
  .div(position.baseAssetAmount.abs());
272
+
273
+ const pnlPerBase = exitPrice.sub(entryPrice);
274
+ const pnl = pnlPerBase
275
+ .mul(position.baseAssetAmount)
276
+ .div(MARK_PRICE_PRECISION)
277
+ .div(AMM_TO_QUOTE_PRECISION_RATIO);
278
+
279
+ return [exitPrice, pnl];
253
280
  }
254
281
 
255
282
  /**
@@ -348,7 +375,7 @@ export class ClearingHouseUser {
348
375
  * @param partial
349
376
  * @returns Precision : MARK_PRICE_PRECISION
350
377
  */
351
- public liquidationPrice(
378
+ public liquidationPriceOld(
352
379
  targetMarket: Pick<UserPosition, 'marketIndex'>,
353
380
  positionBaseSizeChange: BN = ZERO,
354
381
  partial = false
@@ -377,13 +404,11 @@ export class ClearingHouseUser {
377
404
  const totalCurrentPositionValueIgnoringTargetUSDC =
378
405
  this.getTotalPositionValueExcludingMarket(targetMarket.marketIndex);
379
406
 
380
- const currentMarketPosition = this.getUserPosition(
381
- targetMarket.marketIndex
382
- );
407
+ const currentMarketPosition =
408
+ this.getUserPosition(targetMarket.marketIndex) ||
409
+ this.getEmptyPosition(targetMarket.marketIndex);
383
410
 
384
- const currentMarketPositionBaseSize = currentMarketPosition
385
- ? currentMarketPosition.baseAssetAmount
386
- : ZERO;
411
+ const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
387
412
 
388
413
  // calculate position for current market after trade
389
414
  const proposedMarketPosition: UserPosition = {
@@ -410,24 +435,36 @@ export class ClearingHouseUser {
410
435
  proposedMarketPositionValueUSDC
411
436
  );
412
437
 
438
+ let totalFreeCollateralUSDC = this.getTotalCollateral().sub(
439
+ this.getTotalPositionValue()
440
+ .mul(TEN_THOUSAND)
441
+ .div(this.getMaxLeverage('Maintenance'))
442
+ );
443
+
444
+ if (partial) {
445
+ totalFreeCollateralUSDC = this.getTotalCollateral().sub(
446
+ this.getTotalPositionValue()
447
+ .mul(TEN_THOUSAND)
448
+ .div(this.getMaxLeverage('Partial'))
449
+ );
450
+ }
451
+
413
452
  // 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)) {
453
+ if (
454
+ targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
455
+ proposedMarketPosition.baseAssetAmount.gt(ZERO)
456
+ ) {
415
457
  return new BN(-1);
416
458
  }
417
459
 
418
- // proportion of proposed market position to overall position
419
- // const marketProportion = proposedMarketPositionValueUSDC
420
- // .mul(TEN_THOUSAND)
421
- // .div(targetTotalPositionValueUSDC);
422
-
423
460
  // get current margin ratio based on current collateral and proposed total position value
424
461
  let marginRatio;
425
- if (targetTotalPositionValueUSDC.eq(ZERO)) {
462
+ if (proposedMarketPositionValueUSDC.eq(ZERO)) {
426
463
  marginRatio = BN_MAX;
427
464
  } else {
428
465
  marginRatio = totalCollateralUSDC
429
466
  .mul(TEN_THOUSAND)
430
- .div(targetTotalPositionValueUSDC);
467
+ .div(proposedMarketPositionValueUSDC);
431
468
  }
432
469
 
433
470
  let liqRatio = FULL_LIQUIDATION_RATIO;
@@ -438,29 +475,6 @@ export class ClearingHouseUser {
438
475
  // sign of position in current market after the trade
439
476
  const baseAssetSignIsNeg = proposedMarketPosition.baseAssetAmount.isNeg();
440
477
 
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
478
  let pctChange = marginRatio.abs().sub(liqRatio);
465
479
  // if user is short, higher price is liq
466
480
  if (baseAssetSignIsNeg) {
@@ -479,6 +493,143 @@ export class ClearingHouseUser {
479
493
  return liqPrice;
480
494
  }
481
495
 
496
+ /**
497
+ * Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
498
+ * @param targetMarket
499
+ * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
500
+ * @param partial
501
+ * @returns Precision : MARK_PRICE_PRECISION
502
+ */
503
+ public liquidationPrice(
504
+ targetMarket: Pick<UserPosition, 'marketIndex'>,
505
+ positionBaseSizeChange: BN = ZERO,
506
+ partial = false
507
+ ): BN {
508
+ // solves formula for example calc below
509
+
510
+ /* example: assume BTC price is $40k (examine 10% up/down)
511
+
512
+ if 10k deposit and levered 10x short BTC => BTC up $400 means:
513
+ 1. higher base_asset_value (+$4k)
514
+ 2. lower collateral (-$4k)
515
+ 3. (10k - 4k)/(100k + 4k) => 6k/104k => .0576
516
+
517
+ for 10x long, BTC down $400:
518
+ 3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
519
+
520
+ const tc = this.getTotalCollateral();
521
+ const tpv = this.getTotalPositionValue();
522
+
523
+ const partialLev = 16;
524
+ const maintLev = 20;
525
+
526
+ const thisLev = partial ? new BN(partialLev) : new BN(maintLev);
527
+
528
+ // calculate the total position value ignoring any value from the target market of the trade
529
+ const totalCurrentPositionValueIgnoringTargetUSDC =
530
+ this.getTotalPositionValueExcludingMarket(targetMarket.marketIndex);
531
+
532
+ const currentMarketPosition =
533
+ this.getUserPosition(targetMarket.marketIndex) ||
534
+ this.getEmptyPosition(targetMarket.marketIndex);
535
+
536
+ const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
537
+
538
+ const proposedBaseAssetAmount = currentMarketPositionBaseSize.add(
539
+ positionBaseSizeChange
540
+ );
541
+
542
+ // calculate position for current market after trade
543
+ const proposedMarketPosition: UserPosition = {
544
+ marketIndex: targetMarket.marketIndex,
545
+ baseAssetAmount: proposedBaseAssetAmount,
546
+ lastCumulativeFundingRate:
547
+ currentMarketPosition.lastCumulativeFundingRate,
548
+ quoteAssetAmount: new BN(0),
549
+ };
550
+
551
+ const market = this.clearingHouse.getMarket(
552
+ proposedMarketPosition.marketIndex
553
+ );
554
+
555
+ const proposedMarketPositionValueUSDC = calculateBaseAssetValue(
556
+ market,
557
+ proposedMarketPosition
558
+ );
559
+
560
+ // total position value after trade
561
+ const targetTotalPositionValueUSDC =
562
+ totalCurrentPositionValueIgnoringTargetUSDC.add(
563
+ proposedMarketPositionValueUSDC
564
+ );
565
+
566
+ let totalFreeCollateralUSDC = tc.sub(
567
+ totalCurrentPositionValueIgnoringTargetUSDC
568
+ .mul(TEN_THOUSAND)
569
+ .div(this.getMaxLeverage('Maintenance'))
570
+ );
571
+
572
+ if (partial) {
573
+ totalFreeCollateralUSDC = tc.sub(
574
+ totalCurrentPositionValueIgnoringTargetUSDC
575
+ .mul(TEN_THOUSAND)
576
+ .div(this.getMaxLeverage('Partial'))
577
+ );
578
+ }
579
+
580
+ let priceDelt;
581
+ if (proposedBaseAssetAmount.lt(ZERO)) {
582
+ priceDelt = (tc
583
+ .mul(thisLev)
584
+ .sub(tpv))
585
+ .mul(PRICE_TO_QUOTE_PRECISION)
586
+ .div(thisLev.add(new BN(1)));
587
+ } else {
588
+ priceDelt = (tc
589
+ .mul(thisLev)
590
+ .sub(tpv))
591
+ .mul(PRICE_TO_QUOTE_PRECISION)
592
+ .div(thisLev.sub(new BN(1)));
593
+ }
594
+
595
+ let currentPrice;
596
+ if(positionBaseSizeChange.eq(ZERO)){
597
+ currentPrice = calculateMarkPrice(
598
+ this.clearingHouse.getMarket(targetMarket.marketIndex)
599
+ );
600
+ } else{
601
+ const direction = positionBaseSizeChange.gt(ZERO) ? PositionDirection.LONG : PositionDirection.SHORT;
602
+ currentPrice = calculateTradeSlippage(
603
+ direction,
604
+ positionBaseSizeChange.abs(),
605
+ this.clearingHouse.getMarket(targetMarket.marketIndex),
606
+ 'base',
607
+ )[3]; // newPrice after swap
608
+ }
609
+
610
+
611
+ // if the position value after the trade is less than total collateral, there is no liq price
612
+ if (
613
+ targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
614
+ proposedMarketPosition.baseAssetAmount.gt(ZERO)
615
+ ) {
616
+ return new BN(-1);
617
+ }
618
+
619
+ if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
620
+
621
+ const eatMargin2 = priceDelt
622
+ .mul(AMM_RESERVE_PRECISION)
623
+ .div(proposedBaseAssetAmount);
624
+
625
+ if(eatMargin2.gt(currentPrice)){
626
+ return new BN(-1);
627
+ }
628
+
629
+ const liqPrice = currentPrice.sub(eatMargin2);
630
+ return liqPrice;
631
+ }
632
+
482
633
  /**
483
634
  * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
484
635
  * @param positionMarketIndex
@@ -489,7 +640,9 @@ export class ClearingHouseUser {
489
640
  positionMarketIndex: BN,
490
641
  closeQuoteAmount: BN
491
642
  ): BN {
492
- const currentPosition = this.getUserPosition(positionMarketIndex);
643
+ const currentPosition =
644
+ this.getUserPosition(positionMarketIndex) ||
645
+ this.getEmptyPosition(positionMarketIndex);
493
646
 
494
647
  const closeBaseAmount = currentPosition.baseAssetAmount
495
648
  .mul(closeQuoteAmount)
@@ -511,6 +664,21 @@ export class ClearingHouseUser {
511
664
 
512
665
  /**
513
666
  * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
667
+ *
668
+ * To Calculate Max Quote Available:
669
+ *
670
+ * Case 1: SameSide
671
+ * => Remaining quote to get to maxLeverage
672
+ *
673
+ * Case 2: NOT SameSide && currentLeverage <= maxLeverage
674
+ * => Current opposite position x2 + remaining to get to maxLeverage
675
+ *
676
+ * Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
677
+ * => strictly reduce current position size
678
+ *
679
+ * Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
680
+ * => current position + remaining to get to maxLeverage
681
+ *
514
682
  * @param marketIndex
515
683
  * @param tradeSide
516
684
  * @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
@@ -521,32 +689,28 @@ export class ClearingHouseUser {
521
689
  tradeSide: PositionDirection,
522
690
  userMaxLeverageSetting: BN
523
691
  ): 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;
692
+ const currentPosition =
693
+ this.getUserPosition(targetMarketIndex) ||
694
+ this.getEmptyPosition(targetMarketIndex);
527
695
 
528
- const side = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
696
+ const targetSide = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
529
697
 
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
- }
698
+ const currentPositionSide = currentPosition?.baseAssetAmount.isNeg()
699
+ ? 'short'
700
+ : 'long';
538
701
 
539
- return ZERO;
540
- };
702
+ const targettingSameSide = !currentPosition
703
+ ? true
704
+ : targetSide === currentPositionSide;
541
705
 
542
- const currentPosition = this.getUserPosition(targetMarketIndex);
706
+ // 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.
707
+ const oppositeSizeValueUSDC = targettingSameSide
708
+ ? ZERO
709
+ : this.getPositionValue(targetMarketIndex);
543
710
 
544
711
  // get current leverage
545
712
  const currentLeverage = this.getLeverage();
546
713
 
547
- // remaining leverage
548
- // let remainingLeverage = userMaxLeverageSetting;
549
-
550
714
  const remainingLeverage = BN.max(
551
715
  userMaxLeverageSetting.sub(currentLeverage),
552
716
  ZERO
@@ -560,10 +724,57 @@ export class ClearingHouseUser {
560
724
  .mul(totalCollateral)
561
725
  .div(TEN_THOUSAND);
562
726
 
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();
727
+ if (userMaxLeverageSetting.sub(currentLeverage).gte(ZERO)) {
728
+ if (oppositeSizeValueUSDC.eq(ZERO)) {
729
+ // case 1 : Regular trade where current total position less than max, and no opposite position to account for
730
+ // do nothing
731
+ } else {
732
+ // case 2 : trade where current total position less than max, but need to account for flipping the current position over to the other side
733
+ maxPositionSize = maxPositionSize.add(
734
+ oppositeSizeValueUSDC.mul(new BN(2))
735
+ );
736
+ }
737
+ } else {
738
+ // current leverage is greater than max leverage - can only reduce position size
739
+
740
+ if (!targettingSameSide) {
741
+ const currentPositionQuoteSize =
742
+ this.getPositionValue(targetMarketIndex);
743
+
744
+ const currentTotalQuoteSize = currentLeverage
745
+ .mul(totalCollateral)
746
+ .div(TEN_THOUSAND);
565
747
 
566
- maxPositionSize = maxPositionSize.add(oppositeSizeValueUSDC);
748
+ const otherPositionsTotalQuoteSize = currentTotalQuoteSize.sub(
749
+ currentPositionQuoteSize
750
+ );
751
+
752
+ const quoteValueOfMaxLeverage = userMaxLeverageSetting
753
+ .mul(totalCollateral)
754
+ .div(TEN_THOUSAND);
755
+
756
+ if (
757
+ otherPositionsTotalQuoteSize
758
+ .sub(currentPositionQuoteSize)
759
+ .gte(quoteValueOfMaxLeverage)
760
+ ) {
761
+ // case 3: Can only reduce the current position because it will still be greater than max leverage
762
+
763
+ maxPositionSize = currentPositionQuoteSize;
764
+ } else {
765
+ // case 4: Can reduce the position, and then take extra remaining quote to get to max leverage
766
+
767
+ const allowedQuoteSizeAfterClosingCurrentPosition =
768
+ quoteValueOfMaxLeverage.sub(otherPositionsTotalQuoteSize);
769
+
770
+ maxPositionSize = currentPositionQuoteSize.add(
771
+ allowedQuoteSizeAfterClosingCurrentPosition
772
+ );
773
+ }
774
+ } else {
775
+ // do nothing if targetting same side
776
+ }
777
+ }
567
778
 
568
779
  // subtract oneMillionth of maxPositionSize
569
780
  // => to avoid rounding errors when taking max leverage
@@ -585,12 +796,16 @@ export class ClearingHouseUser {
585
796
  tradeQuoteAmount: BN,
586
797
  tradeSide: PositionDirection
587
798
  ): BN {
588
- const currentPosition = this.getUserPosition(targetMarketIndex);
589
- let currentPositionQuoteAmount = currentPosition.quoteAssetAmount;
799
+ const currentPosition =
800
+ this.getUserPosition(targetMarketIndex) ||
801
+ this.getEmptyPosition(targetMarketIndex);
590
802
 
591
- const currentSide = currentPosition.baseAssetAmount.isNeg()
592
- ? PositionDirection.SHORT
593
- : PositionDirection.LONG;
803
+ let currentPositionQuoteAmount = this.getPositionValue(targetMarketIndex);
804
+
805
+ const currentSide =
806
+ currentPosition && currentPosition.baseAssetAmount.isNeg()
807
+ ? PositionDirection.SHORT
808
+ : PositionDirection.LONG;
594
809
 
595
810
  if (currentSide === PositionDirection.SHORT)
596
811
  currentPositionQuoteAmount = currentPositionQuoteAmount.neg();
@@ -605,11 +820,18 @@ export class ClearingHouseUser {
605
820
  const totalPositionAfterTradeExcludingTargetMarket =
606
821
  this.getTotalPositionValueExcludingMarket(targetMarketIndex);
607
822
 
608
- return currentMarketPositionAfterTrade
609
- .add(totalPositionAfterTradeExcludingTargetMarket)
610
- .abs()
611
- .mul(TEN_THOUSAND)
612
- .div(this.getTotalCollateral());
823
+ const totalCollateral = this.getTotalCollateral();
824
+
825
+ if (totalCollateral.gt(ZERO)) {
826
+ const newLeverage = currentMarketPositionAfterTrade
827
+ .add(totalPositionAfterTradeExcludingTargetMarket)
828
+ .abs()
829
+ .mul(TEN_THOUSAND)
830
+ .div(totalCollateral);
831
+ return newLeverage;
832
+ } else {
833
+ return new BN(0);
834
+ }
613
835
  }
614
836
 
615
837
  /**
@@ -631,17 +853,13 @@ export class ClearingHouseUser {
631
853
  * @returns positionValue : Precision QUOTE_PRECISION
632
854
  */
633
855
  private getTotalPositionValueExcludingMarket(marketToIgnore: BN): BN {
634
- const currentMarketPosition = this.getUserPosition(marketToIgnore);
856
+ const currentMarketPosition =
857
+ this.getUserPosition(marketToIgnore) ||
858
+ this.getEmptyPosition(marketToIgnore);
635
859
 
636
860
  let currentMarketPositionValueUSDC = ZERO;
637
861
  if (currentMarketPosition) {
638
- const market = this.clearingHouse.getMarket(
639
- currentMarketPosition.marketIndex
640
- );
641
- currentMarketPositionValueUSDC = calculateBaseAssetValue(
642
- market,
643
- currentMarketPosition
644
- );
862
+ currentMarketPositionValueUSDC = this.getPositionValue(marketToIgnore);
645
863
  }
646
864
 
647
865
  return this.getTotalPositionValue().sub(currentMarketPositionValueUSDC);
@@ -44,4 +44,25 @@ export const Markets: Market[] = [
44
44
  devnetPythOracle: 'FVb5h1VmHPfVb1RfqZckchq18GxRv4iKt8T4eVTQAqdz',
45
45
  mainnetPythOracle: 'Ax9ujW5B9oqcv59N8m6f1BpTBq2rGeGaBcpKjC5UYsXU',
46
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
+ },
61
+ {
62
+ symbol: 'ATOM-PERP',
63
+ baseAssetSymbol: 'ATOM',
64
+ marketIndex: new BN(7),
65
+ devnetPythOracle: '7YAze8qFUMkBnyLVdKT4TFUUFui99EwS5gfRArMcrvFk',
66
+ mainnetPythOracle: 'CrCpTerNqtZvqLcKqz1k13oVeXV9WkMD2zA9hBKXrsbN',
67
+ },
47
68
  ];
@@ -19,3 +19,5 @@ 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
@@ -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 {