@drift-labs/sdk 2.0.14 → 2.0.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.
package/src/math/amm.ts CHANGED
@@ -10,6 +10,9 @@ import {
10
10
  QUOTE_PRECISION,
11
11
  MARGIN_PRECISION,
12
12
  PRICE_DIV_PEG,
13
+ PERCENTAGE_PRECISION,
14
+ BASE_PRECISION,
15
+ DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
13
16
  } from '../constants/numericConstants';
14
17
  import {
15
18
  AMM,
@@ -19,7 +22,7 @@ import {
19
22
  isVariant,
20
23
  } from '../types';
21
24
  import { assert } from '../assert/assert';
22
- import { squareRootBN, standardizeBaseAssetAmount } from '..';
25
+ import { squareRootBN, clampBN, standardizeBaseAssetAmount } from '..';
23
26
 
24
27
  import { OraclePriceData } from '../oracles/types';
25
28
  import {
@@ -28,6 +31,8 @@ import {
28
31
  calculateBudgetedPeg,
29
32
  } from './repeg';
30
33
 
34
+ import { calculateLiveOracleStd } from './oracles';
35
+
31
36
  export function calculatePegFromTargetPrice(
32
37
  targetPrice: BN,
33
38
  baseAssetReserve: BN,
@@ -63,7 +68,8 @@ export function calculateOptimalPegAndBudget(
63
68
  const totalFeeLB = amm.totalExchangeFee.div(new BN(2));
64
69
  const budget = BN.max(ZERO, amm.totalFeeMinusDistributions.sub(totalFeeLB));
65
70
  if (budget.lt(prePegCost)) {
66
- const maxPriceSpread = new BN(amm.maxSpread)
71
+ const halfMaxPriceSpread = new BN(amm.maxSpread)
72
+ .div(new BN(2))
67
73
  .mul(targetPrice)
68
74
  .div(BID_ASK_SPREAD_PRECISION);
69
75
 
@@ -72,8 +78,8 @@ export function calculateOptimalPegAndBudget(
72
78
  let newBudget: BN;
73
79
  const targetPriceGap = reservePriceBefore.sub(targetPrice);
74
80
 
75
- if (targetPriceGap.abs().gt(maxPriceSpread)) {
76
- const markAdj = targetPriceGap.abs().sub(maxPriceSpread);
81
+ if (targetPriceGap.abs().gt(halfMaxPriceSpread)) {
82
+ const markAdj = targetPriceGap.abs().sub(halfMaxPriceSpread);
77
83
 
78
84
  if (targetPriceGap.lt(new BN(0))) {
79
85
  newTargetPrice = reservePriceBefore.add(markAdj);
@@ -173,6 +179,8 @@ export function calculateUpdatedAMM(
173
179
 
174
180
  newAmm.totalFeeMinusDistributions =
175
181
  newAmm.totalFeeMinusDistributions.sub(prepegCost);
182
+ newAmm.netRevenueSinceLastFunding =
183
+ newAmm.netRevenueSinceLastFunding.sub(prepegCost);
176
184
 
177
185
  return newAmm;
178
186
  }
@@ -183,11 +191,15 @@ export function calculateUpdatedAMMSpreadReserves(
183
191
  oraclePriceData: OraclePriceData
184
192
  ): { baseAssetReserve: BN; quoteAssetReserve: BN; sqrtK: BN; newPeg: BN } {
185
193
  const newAmm = calculateUpdatedAMM(amm, oraclePriceData);
186
- const dirReserves = calculateSpreadReserves(
194
+ const [shortReserves, longReserves] = calculateSpreadReserves(
187
195
  newAmm,
188
- direction,
189
196
  oraclePriceData
190
197
  );
198
+
199
+ const dirReserves = isVariant(direction, 'long')
200
+ ? longReserves
201
+ : shortReserves;
202
+
191
203
  const result = {
192
204
  baseAssetReserve: dirReserves.baseAssetReserve,
193
205
  quoteAssetReserve: dirReserves.quoteAssetReserve,
@@ -210,14 +222,8 @@ export function calculateBidAskPrice(
210
222
  newAmm = amm;
211
223
  }
212
224
 
213
- const askReserves = calculateSpreadReserves(
225
+ const [bidReserves, askReserves] = calculateSpreadReserves(
214
226
  newAmm,
215
- PositionDirection.LONG,
216
- oraclePriceData
217
- );
218
- const bidReserves = calculateSpreadReserves(
219
- newAmm,
220
- PositionDirection.SHORT,
221
227
  oraclePriceData
222
228
  );
223
229
 
@@ -315,14 +321,14 @@ export function calculateMarketOpenBidAsk(
315
321
  ): [BN, BN] {
316
322
  // open orders
317
323
  let openAsks;
318
- if (maxBaseAssetReserve > baseAssetReserve) {
324
+ if (maxBaseAssetReserve.gt(baseAssetReserve)) {
319
325
  openAsks = maxBaseAssetReserve.sub(baseAssetReserve).mul(new BN(-1));
320
326
  } else {
321
327
  openAsks = ZERO;
322
328
  }
323
329
 
324
330
  let openBids;
325
- if (minBaseAssetReserve < baseAssetReserve) {
331
+ if (minBaseAssetReserve.lt(baseAssetReserve)) {
326
332
  openBids = baseAssetReserve.sub(minBaseAssetReserve);
327
333
  } else {
328
334
  openBids = ZERO;
@@ -331,12 +337,18 @@ export function calculateMarketOpenBidAsk(
331
337
  }
332
338
 
333
339
  export function calculateInventoryScale(
334
- netBaseAssetAmount: BN,
340
+ baseAssetAmountWithAmm: BN,
335
341
  baseAssetReserve: BN,
336
342
  minBaseAssetReserve: BN,
337
- maxBaseAssetReserve: BN
343
+ maxBaseAssetReserve: BN,
344
+ directionalSpread: number,
345
+ maxSpread: number
338
346
  ): number {
339
- const maxScale = BID_ASK_SPREAD_PRECISION.mul(new BN(10));
347
+ if (baseAssetAmountWithAmm.eq(ZERO)) {
348
+ return 0;
349
+ }
350
+
351
+ const defaultLargeBidAskFactor = BID_ASK_SPREAD_PRECISION.mul(new BN(10));
340
352
  // inventory skew
341
353
  const [openBids, openAsks] = calculateMarketOpenBidAsk(
342
354
  baseAssetReserve,
@@ -348,13 +360,27 @@ export function calculateInventoryScale(
348
360
  new BN(1),
349
361
  BN.min(openBids.abs(), openAsks.abs())
350
362
  );
351
- const inventoryScale =
352
- BN.min(
353
- maxScale,
354
- netBaseAssetAmount.mul(maxScale).div(minSideLiquidity).abs()
363
+
364
+ const inventoryScaleMax =
365
+ BN.max(
366
+ defaultLargeBidAskFactor,
367
+ new BN(maxSpread / 2)
368
+ .mul(BID_ASK_SPREAD_PRECISION)
369
+ .div(new BN(Math.max(directionalSpread, 1)))
355
370
  ).toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
356
371
 
357
- return inventoryScale;
372
+ const inventoryScale =
373
+ baseAssetAmountWithAmm
374
+ .mul(BN.max(baseAssetAmountWithAmm.abs(), BASE_PRECISION))
375
+ .div(BASE_PRECISION)
376
+ .mul(defaultLargeBidAskFactor)
377
+ .div(minSideLiquidity)
378
+ .abs()
379
+ .toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
380
+
381
+ const inventorySpreadScale = Math.min(inventoryScaleMax, 1 + inventoryScale);
382
+
383
+ return inventorySpreadScale;
358
384
  }
359
385
 
360
386
  export function calculateEffectiveLeverage(
@@ -392,6 +418,48 @@ export function calculateMaxSpread(marginRatioInitial: number): number {
392
418
  return maxTargetSpread;
393
419
  }
394
420
 
421
+ export function calculateVolSpreadBN(
422
+ lastOracleConfPct: BN,
423
+ reservePrice: BN,
424
+ markStd: BN,
425
+ oracleStd: BN,
426
+ longIntensity: BN,
427
+ shortIntensity: BN,
428
+ volume24H: BN
429
+ ): [BN, BN] {
430
+ const marketAvgStdPct = markStd
431
+ .add(oracleStd)
432
+ .mul(PERCENTAGE_PRECISION)
433
+ .div(reservePrice)
434
+ .div(new BN(2));
435
+ const volSpread = BN.max(lastOracleConfPct, marketAvgStdPct.div(new BN(2)));
436
+
437
+ const clampMin = PERCENTAGE_PRECISION.div(new BN(100));
438
+ const clampMax = PERCENTAGE_PRECISION.mul(new BN(16)).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,43 @@ 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,
474
+ netRevenueSinceLastFunding: BN,
406
475
  baseAssetReserve: BN,
407
476
  minBaseAssetReserve: BN,
408
- maxBaseAssetReserve: BN
477
+ maxBaseAssetReserve: BN,
478
+ markStd: BN,
479
+ oracleStd: BN,
480
+ longIntensity: BN,
481
+ shortIntensity: BN,
482
+ volume24H: BN
409
483
  ): [number, number] {
410
- let longSpread = baseSpread / 2;
411
- let shortSpread = baseSpread / 2;
484
+ const [longVolSpread, shortVolSpread] = calculateVolSpreadBN(
485
+ lastOracleConfPct,
486
+ reservePrice,
487
+ markStd,
488
+ oracleStd,
489
+ longIntensity,
490
+ shortIntensity,
491
+ volume24H
492
+ );
412
493
 
413
- if (lastOracleReservePriceSpreadPct.gte(ZERO)) {
494
+ let longSpread = Math.max(baseSpread / 2, longVolSpread.toNumber());
495
+ let shortSpread = Math.max(baseSpread / 2, shortVolSpread.toNumber());
496
+
497
+ if (lastOracleReservePriceSpreadPct.gt(ZERO)) {
414
498
  shortSpread = Math.max(
415
499
  shortSpread,
416
500
  lastOracleReservePriceSpreadPct.abs().toNumber() +
417
- lastOracleConfPct.toNumber()
501
+ shortVolSpread.toNumber()
418
502
  );
419
503
  } else if (lastOracleReservePriceSpreadPct.lt(ZERO)) {
420
504
  longSpread = Math.max(
421
505
  longSpread,
422
506
  lastOracleReservePriceSpreadPct.abs().toNumber() +
423
- lastOracleConfPct.toNumber()
507
+ longVolSpread.toNumber()
424
508
  );
425
509
  }
426
510
 
@@ -429,57 +513,75 @@ export function calculateSpreadBN(
429
513
  lastOracleReservePriceSpreadPct.abs().toNumber()
430
514
  );
431
515
 
432
- const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = 10;
433
-
434
- const inventoryScale = calculateInventoryScale(
435
- netBaseAssetAmount,
516
+ const inventorySpreadScale = calculateInventoryScale(
517
+ baseAssetAmountWithAmm,
436
518
  baseAssetReserve,
437
519
  minBaseAssetReserve,
438
- maxBaseAssetReserve
439
- );
440
- const inventorySpreadScale = Math.min(
441
- MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
442
- 1 + inventoryScale
520
+ maxBaseAssetReserve,
521
+ baseAssetAmountWithAmm.gt(ZERO) ? longSpread : shortSpread,
522
+ maxTargetSpread
443
523
  );
444
524
 
445
- if (netBaseAssetAmount.gt(ZERO)) {
525
+ if (baseAssetAmountWithAmm.gt(ZERO)) {
446
526
  longSpread *= inventorySpreadScale;
447
- } else if (netBaseAssetAmount.lt(ZERO)) {
527
+ } else if (baseAssetAmountWithAmm.lt(ZERO)) {
448
528
  shortSpread *= inventorySpreadScale;
449
529
  }
450
530
 
451
- const effectiveLeverage = calculateEffectiveLeverage(
452
- baseSpread,
453
- quoteAssetReserve,
454
- terminalQuoteAssetReserve,
455
- pegMultiplier,
456
- netBaseAssetAmount,
457
- reservePrice,
458
- totalFeeMinusDistributions
459
- );
460
-
531
+ const MAX_SPREAD_SCALE = 10;
461
532
  if (totalFeeMinusDistributions.gt(ZERO)) {
462
- const spreadScale = Math.min(
463
- MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
464
- 1 + effectiveLeverage
533
+ const effectiveLeverage = calculateEffectiveLeverage(
534
+ baseSpread,
535
+ quoteAssetReserve,
536
+ terminalQuoteAssetReserve,
537
+ pegMultiplier,
538
+ baseAssetAmountWithAmm,
539
+ reservePrice,
540
+ totalFeeMinusDistributions
465
541
  );
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;
551
+ }
552
+
553
+ if (
554
+ netRevenueSinceLastFunding.lt(
555
+ DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT
556
+ )
557
+ ) {
558
+ const retreatAmount = Math.min(
559
+ maxTargetSpread / 10,
560
+ Math.floor(
561
+ (baseSpread * netRevenueSinceLastFunding.abs().toNumber()) /
562
+ DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.toNumber()
563
+ )
564
+ );
565
+ const halfRetreatAmount = Math.floor(retreatAmount / 2);
566
+ if (baseAssetAmountWithAmm.gt(ZERO)) {
567
+ longSpread += retreatAmount;
568
+ shortSpread += halfRetreatAmount;
569
+ } else if (baseAssetAmountWithAmm.lt(ZERO)) {
570
+ longSpread += halfRetreatAmount;
571
+ shortSpread += retreatAmount;
572
+ } else {
573
+ longSpread += halfRetreatAmount;
574
+ shortSpread += halfRetreatAmount;
575
+ }
474
576
  }
475
577
 
476
578
  const totalSpread = longSpread + shortSpread;
477
579
  if (totalSpread > maxTargetSpread) {
478
580
  if (longSpread > shortSpread) {
479
- longSpread = Math.min(longSpread, maxTargetSpread);
581
+ longSpread = Math.ceil((longSpread * maxTargetSpread) / totalSpread);
480
582
  shortSpread = maxTargetSpread - longSpread;
481
583
  } else {
482
- shortSpread = Math.min(shortSpread, maxTargetSpread);
584
+ shortSpread = Math.ceil((shortSpread * maxTargetSpread) / totalSpread);
483
585
  longSpread = maxTargetSpread - shortSpread;
484
586
  }
485
587
  }
@@ -489,11 +591,10 @@ export function calculateSpreadBN(
489
591
 
490
592
  export function calculateSpread(
491
593
  amm: AMM,
492
- direction: PositionDirection,
493
594
  oraclePriceData: OraclePriceData
494
- ): number {
595
+ ): [number, number] {
495
596
  if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
496
- return amm.baseSpread / 2;
597
+ return [amm.baseSpread / 2, amm.baseSpread / 2];
497
598
  }
498
599
 
499
600
  const reservePrice = calculatePrice(
@@ -504,7 +605,6 @@ export function calculateSpread(
504
605
 
505
606
  const targetPrice = oraclePriceData?.price || reservePrice;
506
607
  const confInterval = oraclePriceData.confidence || ZERO;
507
-
508
608
  const targetMarkSpreadPct = reservePrice
509
609
  .sub(targetPrice)
510
610
  .mul(BID_ASK_SPREAD_PRECISION)
@@ -514,6 +614,9 @@ export function calculateSpread(
514
614
  .mul(BID_ASK_SPREAD_PRECISION)
515
615
  .div(reservePrice);
516
616
 
617
+ const now = new BN(new Date().getTime() / 1000); //todo
618
+ const liveOracleStd = calculateLiveOracleStd(amm, oraclePriceData, now);
619
+
517
620
  const [longSpread, shortSpread] = calculateSpreadBN(
518
621
  amm.baseSpread,
519
622
  targetMarkSpreadPct,
@@ -525,55 +628,70 @@ export function calculateSpread(
525
628
  amm.baseAssetAmountWithAmm,
526
629
  reservePrice,
527
630
  amm.totalFeeMinusDistributions,
631
+ amm.netRevenueSinceLastFunding,
528
632
  amm.baseAssetReserve,
529
633
  amm.minBaseAssetReserve,
530
- amm.maxBaseAssetReserve
634
+ amm.maxBaseAssetReserve,
635
+ amm.markStd,
636
+ liveOracleStd,
637
+ amm.longIntensityVolume,
638
+ amm.shortIntensityVolume,
639
+ amm.volume24H
531
640
  );
532
641
 
533
- let spread: number;
534
-
535
- if (isVariant(direction, 'long')) {
536
- spread = longSpread;
537
- } else {
538
- spread = shortSpread;
539
- }
540
-
541
- return spread;
642
+ return [longSpread, shortSpread];
542
643
  }
543
644
 
544
645
  export function calculateSpreadReserves(
545
646
  amm: AMM,
546
- direction: PositionDirection,
547
647
  oraclePriceData: OraclePriceData
548
- ): {
549
- baseAssetReserve: BN;
550
- quoteAssetReserve: BN;
551
- } {
552
- const spread = calculateSpread(amm, direction, oraclePriceData);
648
+ ) {
649
+ function calculateSpreadReserve(
650
+ spread: number,
651
+ direction: PositionDirection,
652
+ amm: AMM
653
+ ): {
654
+ baseAssetReserve;
655
+ quoteAssetReserve;
656
+ } {
657
+ if (spread === 0) {
658
+ return {
659
+ baseAssetReserve: amm.baseAssetReserve,
660
+ quoteAssetReserve: amm.quoteAssetReserve,
661
+ };
662
+ }
553
663
 
554
- if (spread === 0) {
664
+ const quoteAssetReserveDelta = amm.quoteAssetReserve.div(
665
+ BID_ASK_SPREAD_PRECISION.div(new BN(spread / 2))
666
+ );
667
+
668
+ let quoteAssetReserve;
669
+ if (isVariant(direction, 'long')) {
670
+ quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
671
+ } else {
672
+ quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
673
+ }
674
+
675
+ const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve);
555
676
  return {
556
- baseAssetReserve: amm.baseAssetReserve,
557
- quoteAssetReserve: amm.quoteAssetReserve,
677
+ baseAssetReserve,
678
+ quoteAssetReserve,
558
679
  };
559
680
  }
560
681
 
561
- const quoteAsserReserveDelta = amm.quoteAssetReserve.div(
562
- BID_ASK_SPREAD_PRECISION.div(new BN(spread / 2))
682
+ const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData);
683
+ const askReserves = calculateSpreadReserve(
684
+ longSpread,
685
+ PositionDirection.LONG,
686
+ amm
687
+ );
688
+ const bidReserves = calculateSpreadReserve(
689
+ shortSpread,
690
+ PositionDirection.SHORT,
691
+ amm
563
692
  );
564
693
 
565
- let quoteAssetReserve;
566
- if (isVariant(direction, 'long')) {
567
- quoteAssetReserve = amm.quoteAssetReserve.add(quoteAsserReserveDelta);
568
- } else {
569
- quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAsserReserveDelta);
570
- }
571
-
572
- const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve);
573
- return {
574
- baseAssetReserve,
575
- quoteAssetReserve,
576
- };
694
+ return [bidReserves, askReserves];
577
695
  }
578
696
 
579
697
  /**
@@ -665,12 +783,14 @@ export function calculateMaxBaseAssetAmountToTrade(
665
783
  .div(PEG_PRECISION);
666
784
 
667
785
  const newBaseAssetReserve = squareRootBN(newBaseAssetReserveSquared);
668
-
669
- const baseAssetReserveBefore = calculateSpreadReserves(
786
+ const [shortSpreadReserves, longSpreadReserves] = calculateSpreadReserves(
670
787
  amm,
671
- direction,
672
788
  oraclePriceData
673
- ).baseAssetReserve;
789
+ );
790
+
791
+ const baseAssetReserveBefore: BN = isVariant(direction, 'long')
792
+ ? longSpreadReserves.baseAssetReserve
793
+ : shortSpreadReserves.baseAssetReserve;
674
794
 
675
795
  if (newBaseAssetReserve.gt(baseAssetReserveBefore)) {
676
796
  return [
@@ -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,6 +1,10 @@
1
1
  import { BN } from '../';
2
2
 
3
- export const squareRootBN = (n, closeness = new BN(1)) => {
3
+ export function clampBN(x: BN, min: BN, max: BN): BN {
4
+ return BN.max(min, BN.min(x, max));
5
+ }
6
+
7
+ export const squareRootBN = (n, closeness = new BN(1)): BN => {
4
8
  // Assuming the sqrt of n as n only
5
9
  let x = n;
6
10
 
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),