@drift-labs/sdk 2.0.14 → 2.0.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.
package/src/math/amm.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  QUOTE_PRECISION,
11
11
  MARGIN_PRECISION,
12
12
  PRICE_DIV_PEG,
13
+ PERCENTAGE_PRECISION,
14
+ BASE_PRECISION,
13
15
  } from '../constants/numericConstants';
14
16
  import {
15
17
  AMM,
@@ -19,7 +21,7 @@ import {
19
21
  isVariant,
20
22
  } from '../types';
21
23
  import { assert } from '../assert/assert';
22
- import { squareRootBN, standardizeBaseAssetAmount } from '..';
24
+ import { squareRootBN, clampBN, standardizeBaseAssetAmount } from '..';
23
25
 
24
26
  import { OraclePriceData } from '../oracles/types';
25
27
  import {
@@ -28,6 +30,8 @@ import {
28
30
  calculateBudgetedPeg,
29
31
  } from './repeg';
30
32
 
33
+ import { calculateLiveOracleStd } from './oracles';
34
+
31
35
  export function calculatePegFromTargetPrice(
32
36
  targetPrice: BN,
33
37
  baseAssetReserve: BN,
@@ -63,7 +67,8 @@ export function calculateOptimalPegAndBudget(
63
67
  const totalFeeLB = amm.totalExchangeFee.div(new BN(2));
64
68
  const budget = BN.max(ZERO, amm.totalFeeMinusDistributions.sub(totalFeeLB));
65
69
  if (budget.lt(prePegCost)) {
66
- const maxPriceSpread = new BN(amm.maxSpread)
70
+ const halfMaxPriceSpread = new BN(amm.maxSpread)
71
+ .div(new BN(2))
67
72
  .mul(targetPrice)
68
73
  .div(BID_ASK_SPREAD_PRECISION);
69
74
 
@@ -72,8 +77,8 @@ export function calculateOptimalPegAndBudget(
72
77
  let newBudget: BN;
73
78
  const targetPriceGap = reservePriceBefore.sub(targetPrice);
74
79
 
75
- if (targetPriceGap.abs().gt(maxPriceSpread)) {
76
- const markAdj = targetPriceGap.abs().sub(maxPriceSpread);
80
+ if (targetPriceGap.abs().gt(halfMaxPriceSpread)) {
81
+ const markAdj = targetPriceGap.abs().sub(halfMaxPriceSpread);
77
82
 
78
83
  if (targetPriceGap.lt(new BN(0))) {
79
84
  newTargetPrice = reservePriceBefore.add(markAdj);
@@ -173,6 +178,8 @@ export function calculateUpdatedAMM(
173
178
 
174
179
  newAmm.totalFeeMinusDistributions =
175
180
  newAmm.totalFeeMinusDistributions.sub(prepegCost);
181
+ newAmm.netRevenueSinceLastFunding =
182
+ newAmm.netRevenueSinceLastFunding.sub(prepegCost);
176
183
 
177
184
  return newAmm;
178
185
  }
@@ -315,14 +322,14 @@ export function calculateMarketOpenBidAsk(
315
322
  ): [BN, BN] {
316
323
  // open orders
317
324
  let openAsks;
318
- if (maxBaseAssetReserve > baseAssetReserve) {
325
+ if (maxBaseAssetReserve.gt(baseAssetReserve)) {
319
326
  openAsks = maxBaseAssetReserve.sub(baseAssetReserve).mul(new BN(-1));
320
327
  } else {
321
328
  openAsks = ZERO;
322
329
  }
323
330
 
324
331
  let openBids;
325
- if (minBaseAssetReserve < baseAssetReserve) {
332
+ if (minBaseAssetReserve.lt(baseAssetReserve)) {
326
333
  openBids = baseAssetReserve.sub(minBaseAssetReserve);
327
334
  } else {
328
335
  openBids = ZERO;
@@ -331,12 +338,18 @@ export function calculateMarketOpenBidAsk(
331
338
  }
332
339
 
333
340
  export function calculateInventoryScale(
334
- netBaseAssetAmount: BN,
341
+ baseAssetAmountWithAmm: BN,
335
342
  baseAssetReserve: BN,
336
343
  minBaseAssetReserve: BN,
337
- maxBaseAssetReserve: BN
344
+ maxBaseAssetReserve: BN,
345
+ directionalSpread: number,
346
+ maxSpread: number
338
347
  ): number {
339
- const maxScale = BID_ASK_SPREAD_PRECISION.mul(new BN(10));
348
+ if (baseAssetAmountWithAmm.eq(ZERO)) {
349
+ return 0;
350
+ }
351
+
352
+ const defaultLargeBidAskFactor = BID_ASK_SPREAD_PRECISION.mul(new BN(10));
340
353
  // inventory skew
341
354
  const [openBids, openAsks] = calculateMarketOpenBidAsk(
342
355
  baseAssetReserve,
@@ -348,13 +361,27 @@ export function calculateInventoryScale(
348
361
  new BN(1),
349
362
  BN.min(openBids.abs(), openAsks.abs())
350
363
  );
351
- const inventoryScale =
352
- BN.min(
353
- maxScale,
354
- netBaseAssetAmount.mul(maxScale).div(minSideLiquidity).abs()
364
+
365
+ const inventoryScaleMax =
366
+ BN.max(
367
+ defaultLargeBidAskFactor,
368
+ new BN(maxSpread / 2)
369
+ .mul(BID_ASK_SPREAD_PRECISION)
370
+ .div(new BN(Math.max(directionalSpread, 1)))
355
371
  ).toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
356
372
 
357
- return inventoryScale;
373
+ const inventoryScale =
374
+ baseAssetAmountWithAmm
375
+ .mul(BN.max(baseAssetAmountWithAmm.abs(), BASE_PRECISION))
376
+ .div(BASE_PRECISION)
377
+ .mul(defaultLargeBidAskFactor)
378
+ .div(minSideLiquidity)
379
+ .abs()
380
+ .toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
381
+
382
+ const inventorySpreadScale = Math.min(inventoryScaleMax, 1 + inventoryScale);
383
+
384
+ return inventorySpreadScale;
358
385
  }
359
386
 
360
387
  export function calculateEffectiveLeverage(
@@ -392,6 +419,47 @@ export function calculateMaxSpread(marginRatioInitial: number): number {
392
419
  return maxTargetSpread;
393
420
  }
394
421
 
422
+ export function calculateVolSpreadBN(
423
+ lastOracleConfPct: BN,
424
+ reservePrice: BN,
425
+ markStd: BN,
426
+ oracleStd: BN,
427
+ longIntensity: BN,
428
+ shortIntensity: BN,
429
+ volume24H: BN
430
+ ): [BN, BN] {
431
+ const marketAvgStdPct = markStd
432
+ .add(oracleStd)
433
+ .mul(PERCENTAGE_PRECISION)
434
+ .div(reservePrice.mul(new BN(2)));
435
+ const volSpread = BN.max(lastOracleConfPct, marketAvgStdPct.div(new BN(2)));
436
+
437
+ const clampMax = PERCENTAGE_PRECISION.mul(new BN(16)).div(new BN(10));
438
+ const clampMin = PERCENTAGE_PRECISION.div(new BN(10));
439
+
440
+ const longVolSpreadFactor = clampBN(
441
+ longIntensity.mul(PERCENTAGE_PRECISION).div(BN.max(ONE, volume24H)),
442
+ clampMin,
443
+ clampMax
444
+ );
445
+ const shortVolSpreadFactor = clampBN(
446
+ shortIntensity.mul(PERCENTAGE_PRECISION).div(BN.max(ONE, volume24H)),
447
+ clampMin,
448
+ clampMax
449
+ );
450
+
451
+ const longVolSpread = BN.max(
452
+ lastOracleConfPct,
453
+ volSpread.mul(longVolSpreadFactor).div(PERCENTAGE_PRECISION)
454
+ );
455
+ const shortVolSpread = BN.max(
456
+ lastOracleConfPct,
457
+ volSpread.mul(shortVolSpreadFactor).div(PERCENTAGE_PRECISION)
458
+ );
459
+
460
+ return [longVolSpread, shortVolSpread];
461
+ }
462
+
395
463
  export function calculateSpreadBN(
396
464
  baseSpread: number,
397
465
  lastOracleReservePriceSpreadPct: BN,
@@ -400,27 +468,42 @@ export function calculateSpreadBN(
400
468
  quoteAssetReserve: BN,
401
469
  terminalQuoteAssetReserve: BN,
402
470
  pegMultiplier: BN,
403
- netBaseAssetAmount: BN,
471
+ baseAssetAmountWithAmm: BN,
404
472
  reservePrice: BN,
405
473
  totalFeeMinusDistributions: BN,
406
474
  baseAssetReserve: BN,
407
475
  minBaseAssetReserve: BN,
408
- maxBaseAssetReserve: BN
476
+ maxBaseAssetReserve: BN,
477
+ markStd: BN,
478
+ oracleStd: BN,
479
+ longIntensity: BN,
480
+ shortIntensity: BN,
481
+ volume24H: BN
409
482
  ): [number, number] {
410
- let longSpread = baseSpread / 2;
411
- let shortSpread = baseSpread / 2;
483
+ const [longVolSpread, shortVolSpread] = calculateVolSpreadBN(
484
+ lastOracleConfPct,
485
+ reservePrice,
486
+ markStd,
487
+ oracleStd,
488
+ longIntensity,
489
+ shortIntensity,
490
+ volume24H
491
+ );
492
+
493
+ let longSpread = Math.max(baseSpread / 2, longVolSpread.toNumber());
494
+ let shortSpread = Math.max(baseSpread / 2, shortVolSpread.toNumber());
412
495
 
413
- if (lastOracleReservePriceSpreadPct.gte(ZERO)) {
496
+ if (lastOracleReservePriceSpreadPct.gt(ZERO)) {
414
497
  shortSpread = Math.max(
415
498
  shortSpread,
416
499
  lastOracleReservePriceSpreadPct.abs().toNumber() +
417
- lastOracleConfPct.toNumber()
500
+ shortVolSpread.toNumber()
418
501
  );
419
502
  } else if (lastOracleReservePriceSpreadPct.lt(ZERO)) {
420
503
  longSpread = Math.max(
421
504
  longSpread,
422
505
  lastOracleReservePriceSpreadPct.abs().toNumber() +
423
- lastOracleConfPct.toNumber()
506
+ longVolSpread.toNumber()
424
507
  );
425
508
  }
426
509
 
@@ -429,22 +512,18 @@ export function calculateSpreadBN(
429
512
  lastOracleReservePriceSpreadPct.abs().toNumber()
430
513
  );
431
514
 
432
- const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = 10;
433
-
434
- const inventoryScale = calculateInventoryScale(
435
- netBaseAssetAmount,
515
+ const inventorySpreadScale = calculateInventoryScale(
516
+ baseAssetAmountWithAmm,
436
517
  baseAssetReserve,
437
518
  minBaseAssetReserve,
438
- maxBaseAssetReserve
439
- );
440
- const inventorySpreadScale = Math.min(
441
- MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
442
- 1 + inventoryScale
519
+ maxBaseAssetReserve,
520
+ baseAssetAmountWithAmm.gt(ZERO) ? longSpread : shortSpread,
521
+ maxTargetSpread
443
522
  );
444
523
 
445
- if (netBaseAssetAmount.gt(ZERO)) {
524
+ if (baseAssetAmountWithAmm.gt(ZERO)) {
446
525
  longSpread *= inventorySpreadScale;
447
- } else if (netBaseAssetAmount.lt(ZERO)) {
526
+ } else if (baseAssetAmountWithAmm.lt(ZERO)) {
448
527
  shortSpread *= inventorySpreadScale;
449
528
  }
450
529
 
@@ -453,24 +532,22 @@ export function calculateSpreadBN(
453
532
  quoteAssetReserve,
454
533
  terminalQuoteAssetReserve,
455
534
  pegMultiplier,
456
- netBaseAssetAmount,
535
+ baseAssetAmountWithAmm,
457
536
  reservePrice,
458
537
  totalFeeMinusDistributions
459
538
  );
460
539
 
540
+ const MAX_SPREAD_SCALE = 10;
461
541
  if (totalFeeMinusDistributions.gt(ZERO)) {
462
- const spreadScale = Math.min(
463
- MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
464
- 1 + effectiveLeverage
465
- );
466
- if (netBaseAssetAmount.gt(ZERO)) {
542
+ const spreadScale = Math.min(MAX_SPREAD_SCALE, 1 + effectiveLeverage);
543
+ if (baseAssetAmountWithAmm.gt(ZERO)) {
467
544
  longSpread *= spreadScale;
468
545
  } else {
469
546
  shortSpread *= spreadScale;
470
547
  }
471
548
  } else {
472
- longSpread *= MAX_BID_ASK_INVENTORY_SKEW_FACTOR;
473
- shortSpread *= MAX_BID_ASK_INVENTORY_SKEW_FACTOR;
549
+ longSpread *= MAX_SPREAD_SCALE;
550
+ shortSpread *= MAX_SPREAD_SCALE;
474
551
  }
475
552
 
476
553
  const totalSpread = longSpread + shortSpread;
@@ -504,7 +581,6 @@ export function calculateSpread(
504
581
 
505
582
  const targetPrice = oraclePriceData?.price || reservePrice;
506
583
  const confInterval = oraclePriceData.confidence || ZERO;
507
-
508
584
  const targetMarkSpreadPct = reservePrice
509
585
  .sub(targetPrice)
510
586
  .mul(BID_ASK_SPREAD_PRECISION)
@@ -514,6 +590,9 @@ export function calculateSpread(
514
590
  .mul(BID_ASK_SPREAD_PRECISION)
515
591
  .div(reservePrice);
516
592
 
593
+ const now = new BN(new Date().getTime() / 1000); //todo
594
+ const liveOracleStd = calculateLiveOracleStd(amm, oraclePriceData, now);
595
+
517
596
  const [longSpread, shortSpread] = calculateSpreadBN(
518
597
  amm.baseSpread,
519
598
  targetMarkSpreadPct,
@@ -527,7 +606,12 @@ export function calculateSpread(
527
606
  amm.totalFeeMinusDistributions,
528
607
  amm.baseAssetReserve,
529
608
  amm.minBaseAssetReserve,
530
- amm.maxBaseAssetReserve
609
+ amm.maxBaseAssetReserve,
610
+ amm.markStd,
611
+ liveOracleStd,
612
+ amm.longIntensityVolume,
613
+ amm.shortIntensityVolume,
614
+ amm.volume24H
531
615
  );
532
616
 
533
617
  let spread: number;
@@ -88,3 +88,55 @@ export function isOracleTooDivergent(
88
88
 
89
89
  return tooDivergent;
90
90
  }
91
+
92
+ export function calculateLiveOracleTwap(
93
+ amm: AMM,
94
+ oraclePriceData: OraclePriceData,
95
+ now: BN
96
+ ): BN {
97
+ const sinceLastUpdate = now.sub(
98
+ amm.historicalOracleData.lastOraclePriceTwapTs
99
+ );
100
+ const sinceStart = BN.max(ZERO, amm.fundingPeriod.sub(sinceLastUpdate));
101
+
102
+ const clampRange = amm.historicalOracleData.lastOraclePriceTwap.div(
103
+ new BN(3)
104
+ );
105
+
106
+ const clampedOraclePrice = BN.min(
107
+ amm.historicalOracleData.lastOraclePriceTwap.add(clampRange),
108
+ BN.max(
109
+ oraclePriceData.price,
110
+ amm.historicalOracleData.lastOraclePriceTwap.sub(clampRange)
111
+ )
112
+ );
113
+
114
+ const newOracleTwap = amm.historicalOracleData.lastOraclePriceTwap
115
+ .mul(sinceStart)
116
+ .add(clampedOraclePrice)
117
+ .mul(sinceLastUpdate)
118
+ .div(sinceStart.add(sinceLastUpdate));
119
+
120
+ return newOracleTwap;
121
+ }
122
+
123
+ export function calculateLiveOracleStd(
124
+ amm: AMM,
125
+ oraclePriceData: OraclePriceData,
126
+ now: BN
127
+ ): BN {
128
+ const sinceLastUpdate = now.sub(
129
+ amm.historicalOracleData.lastOraclePriceTwapTs
130
+ );
131
+ const sinceStart = BN.max(ZERO, amm.fundingPeriod.sub(sinceLastUpdate));
132
+
133
+ const liveOracleTwap = calculateLiveOracleTwap(amm, oraclePriceData, now);
134
+
135
+ const priceDeltaVsTwap = oraclePriceData.price.sub(liveOracleTwap).abs();
136
+
137
+ const oracleStd = priceDeltaVsTwap.add(
138
+ amm.oracleStd.mul(sinceStart).div(sinceStart.add(sinceLastUpdate))
139
+ );
140
+
141
+ return oracleStd;
142
+ }
package/src/math/utils.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { BN } from '../';
2
2
 
3
+ export function clampBN(x: BN, min: BN, max: BN): BN {
4
+ return BN.max(min, BN.min(x, max));
5
+ }
6
+
3
7
  export const squareRootBN = (n, closeness = new BN(1)) => {
4
8
  // Assuming the sqrt of n as n only
5
9
  let x = n;
package/src/types.ts CHANGED
@@ -519,6 +519,8 @@ export type StateAccount = {
519
519
  perpFeeStructure: FeeStructure;
520
520
  spotFeeStructure: FeeStructure;
521
521
  lpCooldownTime: BN;
522
+ initialPctToLiquidate: number;
523
+ liquidationDuration: number;
522
524
  };
523
525
 
524
526
  export type PerpMarketAccount = {
@@ -745,8 +747,8 @@ export type PerpPosition = {
745
747
  settledPnl: BN;
746
748
  lpShares: BN;
747
749
  remainderBaseAssetAmount: number;
748
- lastNetBaseAssetAmountPerLp: BN;
749
- lastNetQuoteAssetAmountPerLp: BN;
750
+ lastBaseAssetAmountPerLp: BN;
751
+ lastQuoteAssetAmountPerLp: BN;
750
752
  };
751
753
 
752
754
  export type UserStatsAccount = {
@@ -790,6 +792,8 @@ export type UserAccount = {
790
792
  totalWithdraws: BN;
791
793
  totalSocialLoss: BN;
792
794
  cumulativePerpFunding: BN;
795
+ liquidationMarginFreed: BN;
796
+ liquidationStartSlot: BN;
793
797
  };
794
798
 
795
799
  export type SpotPosition = {
package/src/user.ts CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  SPOT_MARKET_WEIGHT_PRECISION,
26
26
  QUOTE_SPOT_MARKET_INDEX,
27
27
  TEN,
28
+ OPEN_ORDER_MARGIN_REQUIREMENT,
28
29
  } from './constants/numericConstants';
29
30
  import {
30
31
  UserAccountSubscriber,
@@ -163,8 +164,8 @@ export class User {
163
164
  openAsks: ZERO,
164
165
  settledPnl: ZERO,
165
166
  lpShares: ZERO,
166
- lastNetBaseAssetAmountPerLp: ZERO,
167
- lastNetQuoteAssetAmountPerLp: ZERO,
167
+ lastBaseAssetAmountPerLp: ZERO,
168
+ lastQuoteAssetAmountPerLp: ZERO,
168
169
  };
169
170
  }
170
171
 
@@ -267,11 +268,11 @@ export class User {
267
268
  const nShares = position.lpShares;
268
269
 
269
270
  const deltaBaa = market.amm.baseAssetAmountPerLp
270
- .sub(position.lastNetBaseAssetAmountPerLp)
271
+ .sub(position.lastBaseAssetAmountPerLp)
271
272
  .mul(nShares)
272
273
  .div(AMM_RESERVE_PRECISION);
273
274
  const deltaQaa = market.amm.quoteAssetAmountPerLp
274
- .sub(position.lastNetQuoteAssetAmountPerLp)
275
+ .sub(position.lastQuoteAssetAmountPerLp)
275
276
  .mul(nShares)
276
277
  .div(AMM_RESERVE_PRECISION);
277
278
 
@@ -593,6 +594,10 @@ export class User {
593
594
  newTotalLiabilityValue.add(weightedTokenValue);
594
595
  }
595
596
 
597
+ newTotalLiabilityValue = newTotalLiabilityValue.add(
598
+ new BN(spotPosition.openOrders).mul(OPEN_ORDER_MARGIN_REQUIREMENT)
599
+ );
600
+
596
601
  return newTotalLiabilityValue;
597
602
  },
598
603
  ZERO
@@ -893,6 +898,12 @@ export class User {
893
898
  baseAssetValue = baseAssetValue
894
899
  .mul(marginRatio)
895
900
  .div(MARGIN_PRECISION);
901
+
902
+ if (includeOpenOrders) {
903
+ baseAssetValue = baseAssetValue.add(
904
+ new BN(perpPosition.openOrders).mul(OPEN_ORDER_MARGIN_REQUIREMENT)
905
+ );
906
+ }
896
907
  }
897
908
 
898
909
  return totalPerpValue.add(baseAssetValue);
@@ -1251,8 +1262,8 @@ export class User {
1251
1262
  openAsks: new BN(0),
1252
1263
  settledPnl: ZERO,
1253
1264
  lpShares: ZERO,
1254
- lastNetBaseAssetAmountPerLp: ZERO,
1255
- lastNetQuoteAssetAmountPerLp: ZERO,
1265
+ lastBaseAssetAmountPerLp: ZERO,
1266
+ lastQuoteAssetAmountPerLp: ZERO,
1256
1267
  };
1257
1268
 
1258
1269
  if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
@@ -37,8 +37,8 @@ export const mockPerpPosition: PerpPosition = {
37
37
  settledPnl: new BN(0),
38
38
  lpShares: new BN(0),
39
39
  remainderBaseAssetAmount: 0,
40
- lastNetBaseAssetAmountPerLp: new BN(0),
41
- lastNetQuoteAssetAmountPerLp: new BN(0),
40
+ lastBaseAssetAmountPerLp: new BN(0),
41
+ lastQuoteAssetAmountPerLp: new BN(0),
42
42
  };
43
43
 
44
44
  export const mockAMM: AMM = {
@@ -486,6 +486,8 @@ export const mockStateAccount: StateAccount = {
486
486
  numberOfSpotMarkets: 0,
487
487
  numberOfSubAccounts: new BN(0),
488
488
  numberOfAuthorities: new BN(0),
489
+ initialPctToLiquidate: 0,
490
+ liquidationDuration: 0,
489
491
  oracleGuardRails: {
490
492
  priceDivergence: {
491
493
  markOracleDivergenceNumerator: new BN(0),