@drift-labs/sdk 0.1.23-master.2 → 0.1.24
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/bulkAccountLoader.d.ts +1 -0
- package/lib/accounts/bulkAccountLoader.js +14 -13
- package/lib/accounts/pollingClearingHouseAccountSubscriber.js +3 -3
- package/lib/accounts/pollingTokenAccountSubscriber.js +2 -2
- package/lib/accounts/pollingUserAccountSubscriber.js +4 -4
- package/lib/accounts/webSocketAccountSubscriber.js +2 -2
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +1 -1
- package/lib/accounts/webSocketUserAccountSubscriber.js +2 -2
- package/lib/admin.d.ts +2 -2
- package/lib/admin.js +12 -11
- package/lib/clearingHouse.js +19 -19
- package/lib/clearingHouseUser.d.ts +12 -17
- package/lib/clearingHouseUser.js +125 -228
- package/lib/constants/numericConstants.d.ts +1 -2
- package/lib/constants/numericConstants.js +2 -3
- package/lib/examples/makeTradeExample.js +6 -6
- package/lib/idl/clearing_house.json +42 -4
- package/lib/math/amm.js +12 -12
- package/lib/math/conversion.js +1 -1
- package/lib/math/funding.js +1 -1
- package/lib/math/market.js +2 -2
- package/lib/math/orders.d.ts +2 -0
- package/lib/math/orders.js +44 -4
- package/lib/math/position.js +1 -1
- package/lib/math/trade.js +18 -18
- package/lib/orders.d.ts +3 -1
- package/lib/orders.js +47 -19
- package/lib/pythClient.js +1 -1
- package/lib/tx/retryTxSender.js +1 -1
- package/lib/types.d.ts +4 -1
- package/package.json +1 -1
- package/src/accounts/bulkAccountLoader.ts +18 -13
- package/src/accounts/types.js +10 -0
- package/src/accounts/utils.js +7 -0
- package/src/accounts/webSocketAccountSubscriber.js +76 -0
- package/src/addresses.js +83 -0
- package/src/admin.ts +13 -4
- package/src/clearingHouseUser.ts +161 -330
- package/src/constants/numericConstants.ts +1 -2
- package/src/idl/clearing_house.json +42 -4
- package/src/math/orders.ts +61 -0
- package/src/mockUSDCFaucet.js +171 -0
- package/src/orders.ts +56 -3
- package/src/types.js +60 -0
- package/src/types.ts +5 -1
package/src/clearingHouseUser.ts
CHANGED
|
@@ -3,24 +3,25 @@ import { EventEmitter } from 'events';
|
|
|
3
3
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
4
4
|
import { ClearingHouse } from './clearingHouse';
|
|
5
5
|
import {
|
|
6
|
+
isVariant,
|
|
7
|
+
MarginCategory,
|
|
6
8
|
Order,
|
|
7
9
|
UserAccount,
|
|
8
10
|
UserOrdersAccount,
|
|
9
11
|
UserPosition,
|
|
10
12
|
UserPositionsAccount,
|
|
11
13
|
} from './types';
|
|
12
|
-
import { calculateEntryPrice
|
|
14
|
+
import { calculateEntryPrice } from './math/position';
|
|
13
15
|
import {
|
|
14
16
|
MARK_PRICE_PRECISION,
|
|
15
17
|
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
16
18
|
ZERO,
|
|
17
19
|
TEN_THOUSAND,
|
|
18
20
|
BN_MAX,
|
|
19
|
-
PARTIAL_LIQUIDATION_RATIO,
|
|
20
|
-
FULL_LIQUIDATION_RATIO,
|
|
21
21
|
QUOTE_PRECISION,
|
|
22
22
|
AMM_RESERVE_PRECISION,
|
|
23
23
|
PRICE_TO_QUOTE_PRECISION,
|
|
24
|
+
MARGIN_PRECISION,
|
|
24
25
|
} from './constants/numericConstants';
|
|
25
26
|
import { UserAccountSubscriber, UserAccountEvents } from './accounts/types';
|
|
26
27
|
import {
|
|
@@ -30,7 +31,6 @@ import {
|
|
|
30
31
|
calculatePositionPNL,
|
|
31
32
|
PositionDirection,
|
|
32
33
|
getUserOrdersAccountPublicKey,
|
|
33
|
-
calculateNewStateAfterOrder,
|
|
34
34
|
calculateTradeSlippage,
|
|
35
35
|
BN,
|
|
36
36
|
} from '.';
|
|
@@ -202,21 +202,51 @@ export class ClearingHouseUser {
|
|
|
202
202
|
* calculates Buying Power = FC * MAX_LEVERAGE
|
|
203
203
|
* @returns : Precision QUOTE_PRECISION
|
|
204
204
|
*/
|
|
205
|
-
public getBuyingPower(): BN {
|
|
205
|
+
public getBuyingPower(marketIndex: BN | number): BN {
|
|
206
206
|
return this.getFreeCollateral()
|
|
207
|
-
.mul(this.getMaxLeverage('Initial'))
|
|
207
|
+
.mul(this.getMaxLeverage(marketIndex, 'Initial'))
|
|
208
208
|
.div(TEN_THOUSAND);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/**
|
|
212
|
-
* calculates Free Collateral =
|
|
212
|
+
* calculates Free Collateral = Total collateral - initial margin requirement
|
|
213
213
|
* @returns : Precision QUOTE_PRECISION
|
|
214
214
|
*/
|
|
215
215
|
public getFreeCollateral(): BN {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
const totalCollateral = this.getTotalCollateral();
|
|
217
|
+
const initialMarginRequirement = this.getInitialMarginRequirement();
|
|
218
|
+
const freeCollateral = totalCollateral.sub(initialMarginRequirement);
|
|
219
|
+
return freeCollateral.gte(ZERO) ? freeCollateral : ZERO;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public getInitialMarginRequirement(): BN {
|
|
223
|
+
return this.getUserPositionsAccount().positions.reduce(
|
|
224
|
+
(marginRequirement, marketPosition) => {
|
|
225
|
+
const market = this.clearingHouse.getMarket(marketPosition.marketIndex);
|
|
226
|
+
return marginRequirement.add(
|
|
227
|
+
calculateBaseAssetValue(market, marketPosition)
|
|
228
|
+
.mul(new BN(market.marginRatioInitial))
|
|
229
|
+
.div(MARGIN_PRECISION)
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
ZERO
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @returns The partial margin requirement in USDC. : QUOTE_PRECISION
|
|
238
|
+
*/
|
|
239
|
+
public getPartialMarginRequirement(): BN {
|
|
240
|
+
return this.getUserPositionsAccount().positions.reduce(
|
|
241
|
+
(marginRequirement, marketPosition) => {
|
|
242
|
+
const market = this.clearingHouse.getMarket(marketPosition.marketIndex);
|
|
243
|
+
return marginRequirement.add(
|
|
244
|
+
calculateBaseAssetValue(market, marketPosition)
|
|
245
|
+
.mul(new BN(market.marginRatioPartial))
|
|
246
|
+
.div(MARGIN_PRECISION)
|
|
247
|
+
);
|
|
248
|
+
},
|
|
249
|
+
ZERO
|
|
220
250
|
);
|
|
221
251
|
}
|
|
222
252
|
|
|
@@ -363,25 +393,30 @@ export class ClearingHouseUser {
|
|
|
363
393
|
* @params category {Initial, Partial, Maintenance}
|
|
364
394
|
* @returns : Precision TEN_THOUSAND
|
|
365
395
|
*/
|
|
366
|
-
public getMaxLeverage(
|
|
367
|
-
|
|
368
|
-
|
|
396
|
+
public getMaxLeverage(
|
|
397
|
+
marketIndex: BN | number,
|
|
398
|
+
category: MarginCategory = 'Initial'
|
|
399
|
+
): BN {
|
|
400
|
+
const market = this.clearingHouse.getMarket(marketIndex);
|
|
401
|
+
let marginRatioCategory: number;
|
|
369
402
|
|
|
370
403
|
switch (category) {
|
|
371
404
|
case 'Initial':
|
|
372
|
-
marginRatioCategory =
|
|
405
|
+
marginRatioCategory = market.marginRatioInitial;
|
|
373
406
|
break;
|
|
374
407
|
case 'Maintenance':
|
|
375
|
-
marginRatioCategory =
|
|
408
|
+
marginRatioCategory = market.marginRatioMaintenance;
|
|
376
409
|
break;
|
|
377
410
|
case 'Partial':
|
|
378
|
-
marginRatioCategory =
|
|
411
|
+
marginRatioCategory = market.marginRatioPartial;
|
|
379
412
|
break;
|
|
380
413
|
default:
|
|
381
|
-
marginRatioCategory =
|
|
414
|
+
marginRatioCategory = market.marginRatioInitial;
|
|
382
415
|
break;
|
|
383
416
|
}
|
|
384
|
-
const maxLeverage = TEN_THOUSAND.mul(TEN_THOUSAND).div(
|
|
417
|
+
const maxLeverage = TEN_THOUSAND.mul(TEN_THOUSAND).div(
|
|
418
|
+
new BN(marginRatioCategory)
|
|
419
|
+
);
|
|
385
420
|
return maxLeverage;
|
|
386
421
|
}
|
|
387
422
|
|
|
@@ -400,8 +435,10 @@ export class ClearingHouseUser {
|
|
|
400
435
|
}
|
|
401
436
|
|
|
402
437
|
public canBeLiquidated(): [boolean, BN] {
|
|
438
|
+
const totalCollateral = this.getTotalCollateral();
|
|
439
|
+
const partialMaintenanceRequirement = this.getPartialMarginRequirement();
|
|
403
440
|
const marginRatio = this.getMarginRatio();
|
|
404
|
-
const canLiquidate =
|
|
441
|
+
const canLiquidate = totalCollateral.lt(partialMaintenanceRequirement);
|
|
405
442
|
return [canLiquidate, marginRatio];
|
|
406
443
|
}
|
|
407
444
|
|
|
@@ -436,143 +473,17 @@ export class ClearingHouseUser {
|
|
|
436
473
|
|
|
437
474
|
/**
|
|
438
475
|
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
439
|
-
* @param
|
|
440
|
-
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
441
|
-
* @param partial
|
|
442
|
-
* @returns Precision : MARK_PRICE_PRECISION
|
|
443
|
-
*/
|
|
444
|
-
public liquidationPriceOld(
|
|
445
|
-
targetMarket: Pick<UserPosition, 'marketIndex'>,
|
|
446
|
-
positionBaseSizeChange: BN = ZERO,
|
|
447
|
-
partial = false
|
|
448
|
-
): BN {
|
|
449
|
-
// +/-(margin_ratio-liq_ratio) * price_now = price_liq
|
|
450
|
-
// todo: margin_ratio is not symmetric on price action (both numer and denom change)
|
|
451
|
-
// margin_ratio = collateral / base_asset_value
|
|
452
|
-
|
|
453
|
-
/* example: assume BTC price is $40k (examine 10% up/down)
|
|
454
|
-
|
|
455
|
-
if 10k deposit and levered 10x short BTC => BTC up $400 means:
|
|
456
|
-
1. higher base_asset_value (+$4k)
|
|
457
|
-
2. lower collateral (-$4k)
|
|
458
|
-
3. (10k - 4k)/(100k + 4k) => 6k/104k => .0576
|
|
459
|
-
|
|
460
|
-
for 10x long, BTC down $400:
|
|
461
|
-
3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
|
|
462
|
-
|
|
463
|
-
const currentPrice = calculateMarkPrice(
|
|
464
|
-
this.clearingHouse.getMarket(targetMarket.marketIndex)
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
const totalCollateralUSDC = this.getTotalCollateral();
|
|
468
|
-
|
|
469
|
-
// calculate the total position value ignoring any value from the target market of the trade
|
|
470
|
-
const totalCurrentPositionValueIgnoringTargetUSDC =
|
|
471
|
-
this.getTotalPositionValueExcludingMarket(targetMarket.marketIndex);
|
|
472
|
-
|
|
473
|
-
const currentMarketPosition =
|
|
474
|
-
this.getUserPosition(targetMarket.marketIndex) ||
|
|
475
|
-
this.getEmptyPosition(targetMarket.marketIndex);
|
|
476
|
-
|
|
477
|
-
const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
|
|
478
|
-
|
|
479
|
-
// calculate position for current market after trade
|
|
480
|
-
const proposedMarketPosition: UserPosition = {
|
|
481
|
-
marketIndex: targetMarket.marketIndex,
|
|
482
|
-
baseAssetAmount: currentMarketPositionBaseSize.add(
|
|
483
|
-
positionBaseSizeChange
|
|
484
|
-
),
|
|
485
|
-
lastCumulativeFundingRate: new BN(0),
|
|
486
|
-
quoteAssetAmount: new BN(0),
|
|
487
|
-
openOrders: new BN(0),
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const market = this.clearingHouse.getMarket(
|
|
491
|
-
proposedMarketPosition.marketIndex
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
const proposedMarketPositionValueUSDC = calculateBaseAssetValue(
|
|
495
|
-
market,
|
|
496
|
-
proposedMarketPosition
|
|
497
|
-
);
|
|
498
|
-
|
|
499
|
-
// total position value after trade
|
|
500
|
-
const targetTotalPositionValueUSDC =
|
|
501
|
-
totalCurrentPositionValueIgnoringTargetUSDC.add(
|
|
502
|
-
proposedMarketPositionValueUSDC
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
let totalFreeCollateralUSDC = this.getTotalCollateral().sub(
|
|
506
|
-
this.getTotalPositionValue()
|
|
507
|
-
.mul(TEN_THOUSAND)
|
|
508
|
-
.div(this.getMaxLeverage('Maintenance'))
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
if (partial) {
|
|
512
|
-
totalFreeCollateralUSDC = this.getTotalCollateral().sub(
|
|
513
|
-
this.getTotalPositionValue()
|
|
514
|
-
.mul(TEN_THOUSAND)
|
|
515
|
-
.div(this.getMaxLeverage('Partial'))
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// if the position value after the trade is less than total collateral, there is no liq price
|
|
520
|
-
if (
|
|
521
|
-
targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
|
|
522
|
-
proposedMarketPosition.baseAssetAmount.gt(ZERO)
|
|
523
|
-
) {
|
|
524
|
-
return new BN(-1);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// get current margin ratio based on current collateral and proposed total position value
|
|
528
|
-
let marginRatio;
|
|
529
|
-
if (proposedMarketPositionValueUSDC.eq(ZERO)) {
|
|
530
|
-
marginRatio = BN_MAX;
|
|
531
|
-
} else {
|
|
532
|
-
marginRatio = totalCollateralUSDC
|
|
533
|
-
.mul(TEN_THOUSAND)
|
|
534
|
-
.div(proposedMarketPositionValueUSDC);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
let liqRatio = FULL_LIQUIDATION_RATIO;
|
|
538
|
-
if (partial) {
|
|
539
|
-
liqRatio = PARTIAL_LIQUIDATION_RATIO;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// sign of position in current market after the trade
|
|
543
|
-
const baseAssetSignIsNeg = proposedMarketPosition.baseAssetAmount.isNeg();
|
|
544
|
-
|
|
545
|
-
let pctChange = marginRatio.abs().sub(liqRatio);
|
|
546
|
-
// if user is short, higher price is liq
|
|
547
|
-
if (baseAssetSignIsNeg) {
|
|
548
|
-
pctChange = pctChange.add(TEN_THOUSAND);
|
|
549
|
-
} else {
|
|
550
|
-
if (TEN_THOUSAND.lte(pctChange)) {
|
|
551
|
-
// no liquidation price, position is a fully/over collateralized long
|
|
552
|
-
// handle as NaN on UI
|
|
553
|
-
return new BN(-1);
|
|
554
|
-
}
|
|
555
|
-
pctChange = TEN_THOUSAND.sub(pctChange);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const liqPrice = currentPrice.mul(pctChange).div(TEN_THOUSAND);
|
|
559
|
-
|
|
560
|
-
return liqPrice;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
565
|
-
* @param targetMarket
|
|
476
|
+
* @param marketPosition
|
|
566
477
|
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
567
478
|
* @param partial
|
|
568
479
|
* @returns Precision : MARK_PRICE_PRECISION
|
|
569
480
|
*/
|
|
570
481
|
public liquidationPrice(
|
|
571
|
-
|
|
482
|
+
marketPosition: Pick<UserPosition, 'marketIndex'>,
|
|
572
483
|
positionBaseSizeChange: BN = ZERO,
|
|
573
484
|
partial = false
|
|
574
485
|
): BN {
|
|
575
|
-
// solves formula for example
|
|
486
|
+
// solves formula for example canBeLiquidated below
|
|
576
487
|
|
|
577
488
|
/* example: assume BTC price is $40k (examine 10% up/down)
|
|
578
489
|
|
|
@@ -584,21 +495,15 @@ export class ClearingHouseUser {
|
|
|
584
495
|
for 10x long, BTC down $400:
|
|
585
496
|
3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
|
|
586
497
|
|
|
587
|
-
const
|
|
588
|
-
const tpv = this.getTotalPositionValue();
|
|
589
|
-
|
|
590
|
-
const partialLev = 16;
|
|
591
|
-
const maintLev = 20;
|
|
592
|
-
|
|
593
|
-
const thisLev = partial ? new BN(partialLev) : new BN(maintLev);
|
|
498
|
+
const totalCollateral = this.getTotalCollateral();
|
|
594
499
|
|
|
595
500
|
// calculate the total position value ignoring any value from the target market of the trade
|
|
596
|
-
const
|
|
597
|
-
this.getTotalPositionValueExcludingMarket(
|
|
501
|
+
const totalPositionValueExcludingTargetMarket =
|
|
502
|
+
this.getTotalPositionValueExcludingMarket(marketPosition.marketIndex);
|
|
598
503
|
|
|
599
504
|
const currentMarketPosition =
|
|
600
|
-
this.getUserPosition(
|
|
601
|
-
this.getEmptyPosition(
|
|
505
|
+
this.getUserPosition(marketPosition.marketIndex) ||
|
|
506
|
+
this.getEmptyPosition(marketPosition.marketIndex);
|
|
602
507
|
|
|
603
508
|
const currentMarketPositionBaseSize = currentMarketPosition.baseAssetAmount;
|
|
604
509
|
|
|
@@ -608,7 +513,7 @@ export class ClearingHouseUser {
|
|
|
608
513
|
|
|
609
514
|
// calculate position for current market after trade
|
|
610
515
|
const proposedMarketPosition: UserPosition = {
|
|
611
|
-
marketIndex:
|
|
516
|
+
marketIndex: marketPosition.marketIndex,
|
|
612
517
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
613
518
|
lastCumulativeFundingRate:
|
|
614
519
|
currentMarketPosition.lastCumulativeFundingRate,
|
|
@@ -616,87 +521,112 @@ export class ClearingHouseUser {
|
|
|
616
521
|
openOrders: new BN(0),
|
|
617
522
|
};
|
|
618
523
|
|
|
524
|
+
if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
|
|
525
|
+
|
|
619
526
|
const market = this.clearingHouse.getMarket(
|
|
620
527
|
proposedMarketPosition.marketIndex
|
|
621
528
|
);
|
|
622
529
|
|
|
623
|
-
const
|
|
530
|
+
const proposedMarketPositionValue = calculateBaseAssetValue(
|
|
624
531
|
market,
|
|
625
532
|
proposedMarketPosition
|
|
626
533
|
);
|
|
627
534
|
|
|
628
535
|
// total position value after trade
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
536
|
+
const totalPositionValueAfterTrade =
|
|
537
|
+
totalPositionValueExcludingTargetMarket.add(proposedMarketPositionValue);
|
|
538
|
+
|
|
539
|
+
const marginRequirementExcludingTargetMarket =
|
|
540
|
+
this.getUserPositionsAccount().positions.reduce(
|
|
541
|
+
(totalMarginRequirement, position) => {
|
|
542
|
+
if (!position.marketIndex.eq(marketPosition.marketIndex)) {
|
|
543
|
+
const market = this.clearingHouse.getMarket(position.marketIndex);
|
|
544
|
+
const positionValue = calculateBaseAssetValue(market, position);
|
|
545
|
+
const marketMarginRequirement = positionValue
|
|
546
|
+
.mul(
|
|
547
|
+
partial
|
|
548
|
+
? new BN(market.marginRatioPartial)
|
|
549
|
+
: new BN(market.marginRatioMaintenance)
|
|
550
|
+
)
|
|
551
|
+
.div(MARGIN_PRECISION);
|
|
552
|
+
totalMarginRequirement = totalMarginRequirement.add(
|
|
553
|
+
marketMarginRequirement
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return totalMarginRequirement;
|
|
557
|
+
},
|
|
558
|
+
ZERO
|
|
632
559
|
);
|
|
633
560
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
.mul(TEN_THOUSAND)
|
|
637
|
-
.div(this.getMaxLeverage('Maintenance'))
|
|
561
|
+
const freeCollateralExcludingTargetMarket = totalCollateral.sub(
|
|
562
|
+
marginRequirementExcludingTargetMarket
|
|
638
563
|
);
|
|
639
564
|
|
|
640
|
-
if
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
);
|
|
565
|
+
// if the position value after the trade is less than free collateral, there is no liq price
|
|
566
|
+
if (
|
|
567
|
+
totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
568
|
+
proposedMarketPosition.baseAssetAmount.abs().gt(ZERO)
|
|
569
|
+
) {
|
|
570
|
+
return new BN(-1);
|
|
646
571
|
}
|
|
647
572
|
|
|
648
|
-
|
|
573
|
+
const marginRequirementAfterTrade =
|
|
574
|
+
marginRequirementExcludingTargetMarket.add(
|
|
575
|
+
proposedMarketPositionValue
|
|
576
|
+
.mul(
|
|
577
|
+
partial
|
|
578
|
+
? new BN(market.marginRatioPartial)
|
|
579
|
+
: new BN(market.marginRatioMaintenance)
|
|
580
|
+
)
|
|
581
|
+
.div(MARGIN_PRECISION)
|
|
582
|
+
);
|
|
583
|
+
const freeCollateralAfterTrade = totalCollateral.sub(
|
|
584
|
+
marginRequirementAfterTrade
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const marketMaxLeverage = partial
|
|
588
|
+
? this.getMaxLeverage(proposedMarketPosition.marketIndex, 'Partial')
|
|
589
|
+
: this.getMaxLeverage(proposedMarketPosition.marketIndex, 'Maintenance');
|
|
590
|
+
|
|
591
|
+
let priceDelta;
|
|
649
592
|
if (proposedBaseAssetAmount.lt(ZERO)) {
|
|
650
|
-
|
|
651
|
-
.mul(
|
|
652
|
-
.
|
|
593
|
+
priceDelta = freeCollateralAfterTrade
|
|
594
|
+
.mul(marketMaxLeverage) // precision is TEN_THOUSAND
|
|
595
|
+
.div(marketMaxLeverage.add(TEN_THOUSAND))
|
|
653
596
|
.mul(PRICE_TO_QUOTE_PRECISION)
|
|
654
|
-
.
|
|
597
|
+
.mul(AMM_RESERVE_PRECISION)
|
|
598
|
+
.div(proposedBaseAssetAmount);
|
|
655
599
|
} else {
|
|
656
|
-
|
|
657
|
-
.mul(
|
|
658
|
-
.sub(
|
|
600
|
+
priceDelta = freeCollateralAfterTrade
|
|
601
|
+
.mul(marketMaxLeverage) // precision is TEN_THOUSAND
|
|
602
|
+
.div(marketMaxLeverage.sub(TEN_THOUSAND))
|
|
659
603
|
.mul(PRICE_TO_QUOTE_PRECISION)
|
|
660
|
-
.
|
|
604
|
+
.mul(AMM_RESERVE_PRECISION)
|
|
605
|
+
.div(proposedBaseAssetAmount);
|
|
661
606
|
}
|
|
662
607
|
|
|
663
|
-
let
|
|
608
|
+
let markPriceAfterTrade;
|
|
664
609
|
if (positionBaseSizeChange.eq(ZERO)) {
|
|
665
|
-
|
|
666
|
-
this.clearingHouse.getMarket(
|
|
610
|
+
markPriceAfterTrade = calculateMarkPrice(
|
|
611
|
+
this.clearingHouse.getMarket(marketPosition.marketIndex)
|
|
667
612
|
);
|
|
668
613
|
} else {
|
|
669
614
|
const direction = positionBaseSizeChange.gt(ZERO)
|
|
670
615
|
? PositionDirection.LONG
|
|
671
616
|
: PositionDirection.SHORT;
|
|
672
|
-
|
|
617
|
+
markPriceAfterTrade = calculateTradeSlippage(
|
|
673
618
|
direction,
|
|
674
619
|
positionBaseSizeChange.abs(),
|
|
675
|
-
this.clearingHouse.getMarket(
|
|
620
|
+
this.clearingHouse.getMarket(marketPosition.marketIndex),
|
|
676
621
|
'base'
|
|
677
622
|
)[3]; // newPrice after swap
|
|
678
623
|
}
|
|
679
624
|
|
|
680
|
-
|
|
681
|
-
if (
|
|
682
|
-
targetTotalPositionValueUSDC.lte(totalFreeCollateralUSDC) &&
|
|
683
|
-
proposedMarketPosition.baseAssetAmount.gt(ZERO)
|
|
684
|
-
) {
|
|
685
|
-
return new BN(-1);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
|
|
689
|
-
|
|
690
|
-
const eatMargin2 = priceDelt
|
|
691
|
-
.mul(AMM_RESERVE_PRECISION)
|
|
692
|
-
.div(proposedBaseAssetAmount);
|
|
693
|
-
|
|
694
|
-
if (eatMargin2.gt(currentPrice)) {
|
|
625
|
+
if (priceDelta.gt(markPriceAfterTrade)) {
|
|
695
626
|
return new BN(-1);
|
|
696
627
|
}
|
|
697
628
|
|
|
698
|
-
|
|
699
|
-
return liqPrice;
|
|
629
|
+
return markPriceAfterTrade.sub(priceDelta);
|
|
700
630
|
}
|
|
701
631
|
|
|
702
632
|
/**
|
|
@@ -750,50 +680,33 @@ export class ClearingHouseUser {
|
|
|
750
680
|
*
|
|
751
681
|
* @param targetMarketIndex
|
|
752
682
|
* @param tradeSide
|
|
753
|
-
* @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
|
|
754
683
|
* @returns tradeSizeAllowed : Precision QUOTE_PRECISION
|
|
755
684
|
*/
|
|
756
685
|
public getMaxTradeSizeUSDC(
|
|
757
686
|
targetMarketIndex: BN,
|
|
758
|
-
tradeSide: PositionDirection
|
|
759
|
-
userMaxLeverageSetting: BN
|
|
687
|
+
tradeSide: PositionDirection
|
|
760
688
|
): BN {
|
|
761
689
|
const currentPosition =
|
|
762
690
|
this.getUserPosition(targetMarketIndex) ||
|
|
763
691
|
this.getEmptyPosition(targetMarketIndex);
|
|
764
692
|
|
|
765
|
-
const targetSide = tradeSide
|
|
693
|
+
const targetSide = isVariant(tradeSide, 'short') ? 'short' : 'long';
|
|
766
694
|
|
|
767
695
|
const currentPositionSide = currentPosition?.baseAssetAmount.isNeg()
|
|
768
696
|
? 'short'
|
|
769
697
|
: 'long';
|
|
770
698
|
|
|
771
|
-
const
|
|
699
|
+
const targetingSameSide = !currentPosition
|
|
772
700
|
? true
|
|
773
701
|
: targetSide === currentPositionSide;
|
|
774
702
|
|
|
775
703
|
// 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.
|
|
776
|
-
const oppositeSizeValueUSDC =
|
|
704
|
+
const oppositeSizeValueUSDC = targetingSameSide
|
|
777
705
|
? ZERO
|
|
778
706
|
: this.getPositionValue(targetMarketIndex);
|
|
779
707
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const remainingLeverage = BN.max(
|
|
784
|
-
userMaxLeverageSetting.sub(currentLeverage),
|
|
785
|
-
ZERO
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
// get total collateral
|
|
789
|
-
const totalCollateral = this.getTotalCollateral();
|
|
790
|
-
|
|
791
|
-
// position side allowed based purely on current leverage
|
|
792
|
-
let maxPositionSize = remainingLeverage
|
|
793
|
-
.mul(totalCollateral)
|
|
794
|
-
.div(TEN_THOUSAND);
|
|
795
|
-
|
|
796
|
-
if (userMaxLeverageSetting.sub(currentLeverage).gte(ZERO)) {
|
|
708
|
+
let maxPositionSize = this.getBuyingPower(targetMarketIndex);
|
|
709
|
+
if (maxPositionSize.gte(ZERO)) {
|
|
797
710
|
if (oppositeSizeValueUSDC.eq(ZERO)) {
|
|
798
711
|
// case 1 : Regular trade where current total position less than max, and no opposite position to account for
|
|
799
712
|
// do nothing
|
|
@@ -806,39 +719,27 @@ export class ClearingHouseUser {
|
|
|
806
719
|
} else {
|
|
807
720
|
// current leverage is greater than max leverage - can only reduce position size
|
|
808
721
|
|
|
809
|
-
if (!
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
.
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
.mul(totalCollateral)
|
|
823
|
-
.div(TEN_THOUSAND);
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
otherPositionsTotalQuoteSize
|
|
827
|
-
.sub(currentPositionQuoteSize)
|
|
828
|
-
.gte(quoteValueOfMaxLeverage)
|
|
829
|
-
) {
|
|
830
|
-
// case 3: Can only reduce the current position because it will still be greater than max leverage
|
|
831
|
-
|
|
832
|
-
maxPositionSize = currentPositionQuoteSize;
|
|
722
|
+
if (!targetingSameSide) {
|
|
723
|
+
const market = this.clearingHouse.getMarket(targetMarketIndex);
|
|
724
|
+
const marketPositionValue = this.getPositionValue(targetMarketIndex);
|
|
725
|
+
const totalCollateral = this.getTotalCollateral();
|
|
726
|
+
const marginRequirement = this.getInitialMarginRequirement();
|
|
727
|
+
const marginFreedByClosing = marketPositionValue
|
|
728
|
+
.mul(new BN(market.marginRatioInitial))
|
|
729
|
+
.div(MARGIN_PRECISION);
|
|
730
|
+
const marginRequirementAfterClosing =
|
|
731
|
+
marginRequirement.sub(marginFreedByClosing);
|
|
732
|
+
|
|
733
|
+
if (marginRequirementAfterClosing.gt(totalCollateral)) {
|
|
734
|
+
maxPositionSize = marketPositionValue;
|
|
833
735
|
} else {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const allowedQuoteSizeAfterClosingCurrentPosition =
|
|
837
|
-
quoteValueOfMaxLeverage.sub(otherPositionsTotalQuoteSize);
|
|
838
|
-
|
|
839
|
-
maxPositionSize = currentPositionQuoteSize.add(
|
|
840
|
-
allowedQuoteSizeAfterClosingCurrentPosition
|
|
736
|
+
const freeCollateralAfterClose = totalCollateral.sub(
|
|
737
|
+
marginRequirementAfterClosing
|
|
841
738
|
);
|
|
739
|
+
const buyingPowerAfterClose = freeCollateralAfterClose
|
|
740
|
+
.mul(this.getMaxLeverage(targetMarketIndex))
|
|
741
|
+
.div(TEN_THOUSAND);
|
|
742
|
+
maxPositionSize = marketPositionValue.add(buyingPowerAfterClose);
|
|
842
743
|
}
|
|
843
744
|
} else {
|
|
844
745
|
// do nothing if targetting same side
|
|
@@ -932,74 +833,4 @@ export class ClearingHouseUser {
|
|
|
932
833
|
|
|
933
834
|
return this.getTotalPositionValue().sub(currentMarketPositionValueUSDC);
|
|
934
835
|
}
|
|
935
|
-
|
|
936
|
-
public canFillOrder(order: Order): boolean {
|
|
937
|
-
const userAccount = this.getUserAccount();
|
|
938
|
-
const userPositionsAccount = this.getUserPositionsAccount();
|
|
939
|
-
const userPosition = this.getUserPosition(order.marketIndex);
|
|
940
|
-
const market = this.clearingHouse.getMarket(order.marketIndex);
|
|
941
|
-
|
|
942
|
-
if (isEmptyPosition(userPosition)) {
|
|
943
|
-
return false;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const newState = calculateNewStateAfterOrder(
|
|
947
|
-
userAccount,
|
|
948
|
-
userPosition,
|
|
949
|
-
market,
|
|
950
|
-
order
|
|
951
|
-
);
|
|
952
|
-
if (newState === null) {
|
|
953
|
-
return false;
|
|
954
|
-
}
|
|
955
|
-
const [userAccountAfter, userPositionAfter, marketAfter] = newState;
|
|
956
|
-
|
|
957
|
-
const totalPositionValue = userPositionsAccount.positions.reduce(
|
|
958
|
-
(positionValue, marketPosition) => {
|
|
959
|
-
let market = this.clearingHouse.getMarket(marketPosition.marketIndex);
|
|
960
|
-
if (marketPosition.marketIndex.eq(order.marketIndex)) {
|
|
961
|
-
market = marketAfter;
|
|
962
|
-
marketPosition = userPositionAfter;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
return positionValue.add(
|
|
966
|
-
calculateBaseAssetValue(market, marketPosition)
|
|
967
|
-
);
|
|
968
|
-
},
|
|
969
|
-
ZERO
|
|
970
|
-
);
|
|
971
|
-
|
|
972
|
-
if (totalPositionValue.eq(ZERO)) {
|
|
973
|
-
return true;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
const unrealizedPnL = userPositionsAccount.positions.reduce(
|
|
977
|
-
(pnl, marketPosition) => {
|
|
978
|
-
let market = this.clearingHouse.getMarket(marketPosition.marketIndex);
|
|
979
|
-
pnl = pnl.add(
|
|
980
|
-
calculatePositionFundingPNL(market, marketPosition).div(
|
|
981
|
-
PRICE_TO_QUOTE_PRECISION
|
|
982
|
-
)
|
|
983
|
-
);
|
|
984
|
-
|
|
985
|
-
if (marketPosition.marketIndex.eq(order.marketIndex)) {
|
|
986
|
-
market = marketAfter;
|
|
987
|
-
marketPosition = userPositionAfter;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// update
|
|
991
|
-
return pnl.add(calculatePositionPNL(market, marketPosition, false));
|
|
992
|
-
},
|
|
993
|
-
ZERO
|
|
994
|
-
);
|
|
995
|
-
const totalCollateral = userAccountAfter.collateral.add(unrealizedPnL);
|
|
996
|
-
|
|
997
|
-
const marginRatioAfter = totalCollateral
|
|
998
|
-
.mul(TEN_THOUSAND)
|
|
999
|
-
.div(totalPositionValue);
|
|
1000
|
-
|
|
1001
|
-
const marginRatioInitial =
|
|
1002
|
-
this.clearingHouse.getStateAccount().marginRatioInitial;
|
|
1003
|
-
return marginRatioAfter.gte(marginRatioInitial);
|
|
1004
|
-
}
|
|
1005
836
|
}
|
|
@@ -7,8 +7,6 @@ export const TEN_THOUSAND = new BN(10000);
|
|
|
7
7
|
export const BN_MAX = new BN(Number.MAX_SAFE_INTEGER);
|
|
8
8
|
|
|
9
9
|
export const MAX_LEVERAGE = new BN(5);
|
|
10
|
-
export const FULL_LIQUIDATION_RATIO = new BN(500);
|
|
11
|
-
export const PARTIAL_LIQUIDATION_RATIO = new BN(625);
|
|
12
10
|
|
|
13
11
|
export const QUOTE_PRECISION = new BN(10 ** 6);
|
|
14
12
|
export const MARK_PRICE_PRECISION = new BN(10 ** 10);
|
|
@@ -23,3 +21,4 @@ export const PRICE_TO_QUOTE_PRECISION =
|
|
|
23
21
|
MARK_PRICE_PRECISION.div(QUOTE_PRECISION);
|
|
24
22
|
export const AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO =
|
|
25
23
|
AMM_RESERVE_PRECISION.mul(PEG_PRECISION).div(QUOTE_PRECISION); // 10^10
|
|
24
|
+
export const MARGIN_PRECISION = TEN_THOUSAND;
|