@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.
- package/lib/accounts/types.d.ts +0 -21
- package/lib/accounts/types.d.ts.map +1 -1
- package/lib/clearingHouseUser.d.ts +29 -5
- package/lib/clearingHouseUser.d.ts.map +1 -1
- package/lib/clearingHouseUser.js +206 -78
- package/lib/constants/markets.d.ts.map +1 -1
- package/lib/constants/markets.js +21 -0
- package/lib/constants/numericConstants.d.ts +1 -0
- package/lib/constants/numericConstants.d.ts.map +1 -1
- package/lib/constants/numericConstants.js +2 -1
- package/lib/examples/makeTradeExample.d.ts.map +1 -1
- package/lib/examples/makeTradeExample.js +14 -13
- package/lib/idl/clearing_house.json +94 -42
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/math/amm.d.ts +26 -1
- package/lib/math/amm.d.ts.map +1 -1
- package/lib/math/amm.js +84 -10
- package/lib/math/funding.d.ts +6 -6
- package/lib/math/funding.d.ts.map +1 -1
- package/lib/math/funding.js +69 -25
- package/lib/math/insuranceFund.d.ts +14 -0
- package/lib/math/insuranceFund.d.ts.map +1 -0
- package/lib/math/insuranceFund.js +35 -0
- package/lib/math/position.d.ts +7 -1
- package/lib/math/position.d.ts.map +1 -1
- package/lib/math/position.js +32 -24
- package/lib/math/trade.d.ts +1 -1
- package/lib/math/trade.d.ts.map +1 -1
- package/lib/math/trade.js +9 -4
- package/lib/types.d.ts +0 -50
- package/lib/types.d.ts.map +1 -1
- package/lib/wallet.d.ts +10 -0
- package/lib/wallet.d.ts.map +1 -0
- package/lib/wallet.js +35 -0
- package/package.json +2 -2
- package/src/accounts/types.ts +0 -27
- package/src/clearingHouse.ts +2 -2
- package/src/clearingHouseUser.ts +301 -107
- package/src/constants/markets.ts +21 -0
- package/src/constants/numericConstants.ts +3 -0
- package/src/examples/makeTradeExample.ts +2 -1
- package/src/idl/clearing_house.json +94 -42
- package/src/index.ts +2 -1
- package/src/math/amm.ts +119 -12
- package/src/math/funding.ts +109 -51
- package/src/math/insuranceFund.ts +22 -0
- package/src/math/position.ts +32 -26
- package/src/math/trade.ts +9 -5
- package/src/types.ts +0 -54
- package/src/wallet.ts +22 -0
- package/lib/accounts/defaultHistoryAccountSubscriber.d.ts +0 -29
- package/lib/accounts/defaultHistoryAccountSubscriber.d.ts.map +0 -1
- package/lib/accounts/defaultHistoryAccountSubscriber.js +0 -110
- package/src/accounts/defaultHistoryAccountSubscriber.ts +0 -179
package/src/clearingHouseUser.ts
CHANGED
|
@@ -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
|
-
|
|
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()
|
|
150
|
-
(
|
|
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()
|
|
166
|
-
(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 (
|
|
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 (
|
|
454
|
+
if (proposedMarketPositionValueUSDC.eq(ZERO)) {
|
|
426
455
|
marginRatio = BN_MAX;
|
|
427
456
|
} else {
|
|
428
457
|
marginRatio = totalCollateralUSDC
|
|
429
458
|
.mul(TEN_THOUSAND)
|
|
430
|
-
.div(
|
|
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 =
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
668
|
+
const currentPosition =
|
|
669
|
+
this.getUserPosition(targetMarketIndex) ||
|
|
670
|
+
this.getEmptyPosition(targetMarketIndex);
|
|
527
671
|
|
|
528
|
-
|
|
672
|
+
const targetSide = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
|
|
529
673
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
540
|
-
|
|
678
|
+
const targettingSameSide = !currentPosition
|
|
679
|
+
? true
|
|
680
|
+
: targetSide === currentPositionSide;
|
|
541
681
|
|
|
542
|
-
|
|
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
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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 =
|
|
589
|
-
|
|
775
|
+
const currentPosition =
|
|
776
|
+
this.getUserPosition(targetMarketIndex) ||
|
|
777
|
+
this.getEmptyPosition(targetMarketIndex);
|
|
778
|
+
|
|
779
|
+
let currentPositionQuoteAmount = this.getPositionValue(targetMarketIndex);
|
|
590
780
|
|
|
591
|
-
const currentSide =
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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 =
|
|
832
|
+
const currentMarketPosition =
|
|
833
|
+
this.getUserPosition(marketToIgnore) ||
|
|
834
|
+
this.getEmptyPosition(marketToIgnore);
|
|
635
835
|
|
|
636
836
|
let currentMarketPositionValueUSDC = ZERO;
|
|
637
837
|
if (currentMarketPosition) {
|
|
638
|
-
|
|
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);
|
package/src/constants/markets.ts
CHANGED
|
@@ -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
|
|
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 {
|