@drift-labs/sdk 0.1.12 → 0.1.16

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 +31 -3
  17. package/lib/clearingHouseUser.d.ts.map +1 -1
  18. package/lib/clearingHouseUser.js +213 -47
  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 +41 -22
  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 -13
  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 +306 -65
  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 +47 -25
  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 -14
  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
@@ -16,6 +16,7 @@ import { EventEmitter } from 'events';
16
16
  export interface AccountSubscriber<T> {
17
17
  data?: T;
18
18
  subscribe(onChange: (data: T) => void): Promise<void>;
19
+ fetch(): Promise<void>;
19
20
  unsubscribe(): void;
20
21
  }
21
22
 
@@ -54,7 +55,7 @@ export interface ClearingHouseAccountSubscriber {
54
55
  subscribe(
55
56
  optionalSubscriptions?: ClearingHouseAccountTypes[]
56
57
  ): Promise<boolean>;
57
-
58
+ fetch(): Promise<void>;
58
59
  unsubscribe(): Promise<void>;
59
60
 
60
61
  getStateAccount(): StateAccount;
@@ -67,33 +68,6 @@ export interface ClearingHouseAccountSubscriber {
67
68
  getLiquidationHistoryAccount(): LiquidationHistoryAccount;
68
69
  }
69
70
 
70
- export interface HistoryAccountEvents {
71
- fundingPaymentHistoryAccountUpdate: (
72
- payload: FundingPaymentHistoryAccount
73
- ) => void;
74
- fundingRateHistoryAccountUpdate: (payload: FundingRateHistoryAccount) => void;
75
- tradeHistoryAccountUpdate: (payload: TradeHistoryAccount) => void;
76
- liquidationHistoryAccountUpdate: (payload: LiquidationHistoryAccount) => void;
77
- depositHistoryAccountUpdate: (payload: DepositHistoryAccount) => void;
78
- curveHistoryAccountUpdate: (payload: CurveHistoryAccount) => void;
79
- update: void;
80
- }
81
-
82
- export interface HistoryAccountSubscriber {
83
- eventEmitter: StrictEventEmitter<EventEmitter, HistoryAccountEvents>;
84
- isSubscribed: boolean;
85
-
86
- subscribe(): Promise<boolean>;
87
- unsubscribe(): Promise<void>;
88
-
89
- getTradeHistoryAccount(): TradeHistoryAccount;
90
- getDepositHistoryAccount(): DepositHistoryAccount;
91
- getFundingPaymentHistoryAccount(): FundingPaymentHistoryAccount;
92
- getFundingRateHistoryAccount(): FundingRateHistoryAccount;
93
- getCurveHistoryAccount(): CurveHistoryAccount;
94
- getLiquidationHistoryAccount(): LiquidationHistoryAccount;
95
- }
96
-
97
71
  export interface UserAccountEvents {
98
72
  userAccountData: (payload: UserAccount) => void;
99
73
  userPositionsData: (payload: UserPositionsAccount) => void;
@@ -105,6 +79,7 @@ export interface UserAccountSubscriber {
105
79
  isSubscribed: boolean;
106
80
 
107
81
  subscribe(): Promise<boolean>;
82
+ fetch(): Promise<void>;
108
83
  unsubscribe(): Promise<void>;
109
84
 
110
85
  getUserAccount(): UserAccount;
@@ -7,6 +7,7 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
7
7
  accountName: string;
8
8
  program: Program;
9
9
  accountPublicKey: PublicKey;
10
+ onChange: (data: T) => void;
10
11
 
11
12
  public constructor(
12
13
  accountName: string,
@@ -19,20 +20,29 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
19
20
  }
20
21
 
21
22
  async subscribe(onChange: (data: T) => void): Promise<void> {
22
- this.data = (await this.program.account[this.accountName].fetch(
23
- this.accountPublicKey
24
- )) as T;
25
-
26
- onChange(this.data);
23
+ this.onChange = onChange;
24
+ await this.fetch();
27
25
 
28
26
  this.program.account[this.accountName]
29
27
  .subscribe(this.accountPublicKey, this.program.provider.opts.commitment)
30
28
  .on('change', async (data: T) => {
31
29
  this.data = data;
32
- onChange(data);
30
+ this.onChange(data);
33
31
  });
34
32
  }
35
33
 
34
+ async fetch(): Promise<void> {
35
+ const newData = (await this.program.account[this.accountName].fetch(
36
+ this.accountPublicKey
37
+ )) as T;
38
+
39
+ // if data has changed trigger update
40
+ if (JSON.stringify(newData) !== JSON.stringify(this.data)) {
41
+ this.data = newData;
42
+ this.onChange(this.data);
43
+ }
44
+ }
45
+
36
46
  unsubscribe(): Promise<void> {
37
47
  return this.program.account[this.accountName].unsubscribe(
38
48
  this.accountPublicKey
@@ -16,7 +16,6 @@ import {
16
16
  PositionDirection,
17
17
  TradeHistoryAccount,
18
18
  UserAccount,
19
- UserPositionsAccount,
20
19
  Market,
21
20
  } from './types';
22
21
  import * as anchor from '@project-serum/anchor';
@@ -140,6 +139,13 @@ export class ClearingHouse {
140
139
  ]);
141
140
  }
142
141
 
142
+ /**
143
+ * Forces the accountSubscriber to fetch account updates from rpc
144
+ */
145
+ public async fetchAccounts(): Promise<void> {
146
+ await this.accountSubscriber.fetch();
147
+ }
148
+
143
149
  /**
144
150
  * Unsubscribe from all currently subscribed state accounts
145
151
  */
@@ -454,7 +460,7 @@ export class ClearingHouse {
454
460
  collateralAccountPublicKey: PublicKey
455
461
  ): Promise<TransactionInstruction> {
456
462
  const userAccountPublicKey = await this.getUserAccountPublicKey();
457
- const user: UserAccount = await this.program.account.user.fetch(
463
+ const user: any = await this.program.account.user.fetch(
458
464
  userAccountPublicKey
459
465
  );
460
466
 
@@ -659,7 +665,7 @@ export class ClearingHouse {
659
665
  const liquidateeUserAccount: any = await this.program.account.user.fetch(
660
666
  liquidateeUserAccountPublicKey
661
667
  );
662
- const liquidateePositions: UserPositionsAccount =
668
+ const liquidateePositions: any =
663
669
  await this.program.account.userPositions.fetch(
664
670
  liquidateeUserAccount.positions
665
671
  );
@@ -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;
@@ -205,7 +218,8 @@ export class ClearingHouseUser {
205
218
  * @returns : Precision QUOTE_PRECISION
206
219
  */
207
220
  public getPositionValue(marketIndex: BN): BN {
208
- const userPosition = this.getUserPosition(marketIndex);
221
+ const userPosition =
222
+ this.getUserPosition(marketIndex) || this.getEmptyPosition(marketIndex);
209
223
  const market = this.clearingHouse.getMarket(userPosition.marketIndex);
210
224
  return calculateBaseAssetValue(market, userPosition);
211
225
  }
@@ -226,15 +240,17 @@ export class ClearingHouseUser {
226
240
  * calculates average exit price for closing 100% of position
227
241
  * @returns : Precision MARK_PRICE_PRECISION
228
242
  */
229
- public getPositionEstimatedExitPrice(
243
+ public getPositionEstimatedExitPriceAndPnl(
230
244
  position: UserPosition,
231
245
  amountToClose?: BN
232
- ): BN {
246
+ ): [BN, BN] {
233
247
  const market = this.clearingHouse.getMarket(position.marketIndex);
234
248
 
249
+ const entryPrice = calculateEntryPrice(position);
250
+
235
251
  if (amountToClose) {
236
252
  if (amountToClose.eq(ZERO)) {
237
- return calculateMarkPrice(market);
253
+ return [calculateMarkPrice(market), ZERO];
238
254
  }
239
255
  position = {
240
256
  baseAssetAmount: amountToClose,
@@ -246,12 +262,21 @@ export class ClearingHouseUser {
246
262
 
247
263
  const baseAssetValue = calculateBaseAssetValue(market, position);
248
264
  if (position.baseAssetAmount.eq(ZERO)) {
249
- return ZERO;
265
+ return [ZERO, ZERO];
250
266
  }
251
- return baseAssetValue
267
+
268
+ const exitPrice = baseAssetValue
252
269
  .mul(AMM_TO_QUOTE_PRECISION_RATIO)
253
270
  .mul(MARK_PRICE_PRECISION)
254
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];
255
280
  }
256
281
 
257
282
  /**
@@ -350,7 +375,7 @@ export class ClearingHouseUser {
350
375
  * @param partial
351
376
  * @returns Precision : MARK_PRICE_PRECISION
352
377
  */
353
- public liquidationPrice(
378
+ public liquidationPriceOld(
354
379
  targetMarket: Pick<UserPosition, 'marketIndex'>,
355
380
  positionBaseSizeChange: BN = ZERO,
356
381
  partial = false
@@ -379,13 +404,11 @@ export class ClearingHouseUser {
379
404
  const totalCurrentPositionValueIgnoringTargetUSDC =
380
405
  this.getTotalPositionValueExcludingMarket(targetMarket.marketIndex);
381
406
 
382
- const currentMarketPosition = this.getUserPosition(
383
- targetMarket.marketIndex
384
- );
407
+ const currentMarketPosition =
408
+ this.getUserPosition(targetMarket.marketIndex) ||
409
+ this.getEmptyPosition(targetMarket.marketIndex);
385
410
 
386
- const currentMarketPositionBaseSize = currentMarketPosition
387
- ? currentMarketPosition.baseAssetAmount
388
- : ZERO;
411
+ const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
389
412
 
390
413
  // calculate position for current market after trade
391
414
  const proposedMarketPosition: UserPosition = {
@@ -412,9 +435,23 @@ export class ClearingHouseUser {
412
435
  proposedMarketPositionValueUSDC
413
436
  );
414
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
+
415
452
  // if the position value after the trade is less than total collateral, there is no liq price
416
453
  if (
417
- targetTotalPositionValueUSDC.lte(totalCollateralUSDC) &&
454
+ targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
418
455
  proposedMarketPosition.baseAssetAmount.gt(ZERO)
419
456
  ) {
420
457
  return new BN(-1);
@@ -456,6 +493,143 @@ export class ClearingHouseUser {
456
493
  return liqPrice;
457
494
  }
458
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
+
459
633
  /**
460
634
  * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
461
635
  * @param positionMarketIndex
@@ -466,7 +640,9 @@ export class ClearingHouseUser {
466
640
  positionMarketIndex: BN,
467
641
  closeQuoteAmount: BN
468
642
  ): BN {
469
- const currentPosition = this.getUserPosition(positionMarketIndex);
643
+ const currentPosition =
644
+ this.getUserPosition(positionMarketIndex) ||
645
+ this.getEmptyPosition(positionMarketIndex);
470
646
 
471
647
  const closeBaseAmount = currentPosition.baseAssetAmount
472
648
  .mul(closeQuoteAmount)
@@ -488,6 +664,21 @@ export class ClearingHouseUser {
488
664
 
489
665
  /**
490
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
+ *
491
682
  * @param marketIndex
492
683
  * @param tradeSide
493
684
  * @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
@@ -498,32 +689,28 @@ export class ClearingHouseUser {
498
689
  tradeSide: PositionDirection,
499
690
  userMaxLeverageSetting: BN
500
691
  ): BN {
501
- // inline function which get's the current position size on the opposite side of the target trade
502
- const getOppositePositionValueUSDC = () => {
503
- if (!currentPosition) return ZERO;
692
+ const currentPosition =
693
+ this.getUserPosition(targetMarketIndex) ||
694
+ this.getEmptyPosition(targetMarketIndex);
504
695
 
505
- const side = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
696
+ const targetSide = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
506
697
 
507
- if (side === 'long' && currentPosition?.baseAssetAmount.isNeg()) {
508
- return this.getPositionValue(targetMarketIndex);
509
- } else if (
510
- side === 'short' &&
511
- !currentPosition?.baseAssetAmount.isNeg()
512
- ) {
513
- return this.getPositionValue(targetMarketIndex);
514
- }
698
+ const currentPositionSide = currentPosition?.baseAssetAmount.isNeg()
699
+ ? 'short'
700
+ : 'long';
515
701
 
516
- return ZERO;
517
- };
702
+ const targettingSameSide = !currentPosition
703
+ ? true
704
+ : targetSide === currentPositionSide;
518
705
 
519
- 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);
520
710
 
521
711
  // get current leverage
522
712
  const currentLeverage = this.getLeverage();
523
713
 
524
- // remaining leverage
525
- // let remainingLeverage = userMaxLeverageSetting;
526
-
527
714
  const remainingLeverage = BN.max(
528
715
  userMaxLeverageSetting.sub(currentLeverage),
529
716
  ZERO
@@ -537,10 +724,57 @@ export class ClearingHouseUser {
537
724
  .mul(totalCollateral)
538
725
  .div(TEN_THOUSAND);
539
726
 
540
- // 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.
541
- 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);
747
+
748
+ const otherPositionsTotalQuoteSize = currentTotalQuoteSize.sub(
749
+ currentPositionQuoteSize
750
+ );
542
751
 
543
- maxPositionSize = maxPositionSize.add(oppositeSizeValueUSDC);
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
+ }
544
778
 
545
779
  // subtract oneMillionth of maxPositionSize
546
780
  // => to avoid rounding errors when taking max leverage
@@ -562,12 +796,16 @@ export class ClearingHouseUser {
562
796
  tradeQuoteAmount: BN,
563
797
  tradeSide: PositionDirection
564
798
  ): BN {
565
- const currentPosition = this.getUserPosition(targetMarketIndex);
566
- let currentPositionQuoteAmount = currentPosition.quoteAssetAmount;
799
+ const currentPosition =
800
+ this.getUserPosition(targetMarketIndex) ||
801
+ this.getEmptyPosition(targetMarketIndex);
802
+
803
+ let currentPositionQuoteAmount = this.getPositionValue(targetMarketIndex);
567
804
 
568
- const currentSide = currentPosition.baseAssetAmount.isNeg()
569
- ? PositionDirection.SHORT
570
- : PositionDirection.LONG;
805
+ const currentSide =
806
+ currentPosition && currentPosition.baseAssetAmount.isNeg()
807
+ ? PositionDirection.SHORT
808
+ : PositionDirection.LONG;
571
809
 
572
810
  if (currentSide === PositionDirection.SHORT)
573
811
  currentPositionQuoteAmount = currentPositionQuoteAmount.neg();
@@ -582,11 +820,18 @@ export class ClearingHouseUser {
582
820
  const totalPositionAfterTradeExcludingTargetMarket =
583
821
  this.getTotalPositionValueExcludingMarket(targetMarketIndex);
584
822
 
585
- return currentMarketPositionAfterTrade
586
- .add(totalPositionAfterTradeExcludingTargetMarket)
587
- .abs()
588
- .mul(TEN_THOUSAND)
589
- .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
+ }
590
835
  }
591
836
 
592
837
  /**
@@ -608,17 +853,13 @@ export class ClearingHouseUser {
608
853
  * @returns positionValue : Precision QUOTE_PRECISION
609
854
  */
610
855
  private getTotalPositionValueExcludingMarket(marketToIgnore: BN): BN {
611
- const currentMarketPosition = this.getUserPosition(marketToIgnore);
856
+ const currentMarketPosition =
857
+ this.getUserPosition(marketToIgnore) ||
858
+ this.getEmptyPosition(marketToIgnore);
612
859
 
613
860
  let currentMarketPositionValueUSDC = ZERO;
614
861
  if (currentMarketPosition) {
615
- const market = this.clearingHouse.getMarket(
616
- currentMarketPosition.marketIndex
617
- );
618
- currentMarketPositionValueUSDC = calculateBaseAssetValue(
619
- market,
620
- currentMarketPosition
621
- );
862
+ currentMarketPositionValueUSDC = this.getPositionValue(marketToIgnore);
622
863
  }
623
864
 
624
865
  return this.getTotalPositionValue().sub(currentMarketPositionValueUSDC);
@@ -51,4 +51,25 @@ export const Markets: Market[] = [
51
51
  devnetPythOracle: 'GwzBgrXb4PG59zjce24SF2b9JXbLEjJJTBkmytuEZj1b',
52
52
  mainnetPythOracle: '4CkQJBxhU8EZ2UjhigbtdaPbpTe6mqf811fipYBFbSYN',
53
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
+ },
68
+ {
69
+ symbol: 'DOT-PERP',
70
+ baseAssetSymbol: 'DOT',
71
+ marketIndex: new BN(8),
72
+ devnetPythOracle: '4dqq5VBpN4EwYb7wyywjjfknvMKu7m78j9mKZRXTj462',
73
+ mainnetPythOracle: 'EcV1X1gY2yb4KXxjVQtTHTbioum2gvmPnFk4zYAt7zne',
74
+ },
54
75
  ];