@drift-labs/sdk 2.12.0-beta.3 → 2.13.0-beta.0

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/dlob/DLOB.ts CHANGED
@@ -519,10 +519,11 @@ export class DLOB {
519
519
  marketType,
520
520
  oraclePriceData,
521
521
  marketOrderGenerator,
522
- this.getLimitBids.bind(this),
522
+ this.getMakerLimitBids.bind(this),
523
523
  (takerPrice, makerPrice) => {
524
524
  return takerPrice === undefined || takerPrice.lte(makerPrice);
525
- }
525
+ },
526
+ fallbackAsk
526
527
  );
527
528
  for (const marketAskCrossingBid of marketAsksCrossingBids) {
528
529
  nodesToFill.push(marketAskCrossingBid);
@@ -555,10 +556,11 @@ export class DLOB {
555
556
  marketType,
556
557
  oraclePriceData,
557
558
  marketOrderGenerator,
558
- this.getLimitAsks.bind(this),
559
+ this.getMakerLimitAsks.bind(this),
559
560
  (takerPrice, fallbackPrice) => {
560
561
  return takerPrice === undefined || takerPrice.gte(fallbackPrice);
561
- }
562
+ },
563
+ fallbackBid
562
564
  );
563
565
 
564
566
  for (const marketBidToFill of marketBidsToFill) {
@@ -596,9 +598,11 @@ export class DLOB {
596
598
  marketIndex: number,
597
599
  slot: number,
598
600
  marketType: MarketType,
599
- oraclePriceData: OraclePriceData
601
+ oraclePriceData: OraclePriceData,
602
+ fallbackPrice?: BN
600
603
  ) => Generator<DLOBNode>,
601
- doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean
604
+ doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean,
605
+ fallbackPrice?: BN
602
606
  ): NodeToFill[] {
603
607
  const nodesToFill = new Array<NodeToFill>();
604
608
 
@@ -607,7 +611,8 @@ export class DLOB {
607
611
  marketIndex,
608
612
  slot,
609
613
  marketType,
610
- oraclePriceData
614
+ oraclePriceData,
615
+ fallbackPrice
611
616
  );
612
617
 
613
618
  for (const makerNode of makerNodeGenerator) {
@@ -916,17 +921,17 @@ export class DLOB {
916
921
  }
917
922
 
918
923
  /**
919
- * Filters the limit asks that are post only or have been place for sufficiently long
920
- * Useful for displaying order book that doesn't have taker limit orders crossing spread
924
+ * Filters the limit asks that are post only, have been place for sufficiently long or are above the fallback bid
925
+ * Market orders can only fill against orders that meet this criteria
921
926
  *
922
927
  * @returns
923
928
  */
924
- *getRestingLimitAsks(
929
+ *getMakerLimitAsks(
925
930
  marketIndex: number,
926
931
  slot: number,
927
932
  marketType: MarketType,
928
933
  oraclePriceData: OraclePriceData,
929
- minPerpAuctionDuration: number
934
+ fallbackBid?: BN
930
935
  ): Generator<DLOBNode> {
931
936
  for (const node of this.getLimitAsks(
932
937
  marketIndex,
@@ -934,21 +939,19 @@ export class DLOB {
934
939
  marketType,
935
940
  oraclePriceData
936
941
  )) {
937
- if (this.isRestingLimitOrder(node.order, slot, minPerpAuctionDuration)) {
942
+ if (this.isRestingLimitOrder(node.order, slot)) {
943
+ yield node;
944
+ } else if (
945
+ fallbackBid &&
946
+ node.getPrice(oraclePriceData, slot).gt(fallbackBid)
947
+ ) {
938
948
  yield node;
939
949
  }
940
950
  }
941
951
  }
942
952
 
943
- isRestingLimitOrder(
944
- order: Order,
945
- slot: number,
946
- minPerpAuctionDuration: number
947
- ): boolean {
948
- return (
949
- order.postOnly ||
950
- new BN(slot).sub(order.slot).gte(new BN(minPerpAuctionDuration * 1.5))
951
- );
953
+ isRestingLimitOrder(order: Order, slot: number): boolean {
954
+ return order.postOnly || new BN(slot).sub(order.slot).gte(new BN(15));
952
955
  }
953
956
 
954
957
  *getLimitBids(
@@ -984,17 +987,17 @@ export class DLOB {
984
987
  }
985
988
 
986
989
  /**
987
- * Filters the limit bids that are post only or have been place for sufficiently long
988
- * Useful for displaying order book that doesn't have taker limit orders crossing spread
990
+ * Filters the limit bids that are post only, have been place for sufficiently long or are below the fallback ask
991
+ * Market orders can only fill against orders that meet this criteria
989
992
  *
990
993
  * @returns
991
994
  */
992
- *getRestingLimitBids(
995
+ *getMakerLimitBids(
993
996
  marketIndex: number,
994
997
  slot: number,
995
998
  marketType: MarketType,
996
999
  oraclePriceData: OraclePriceData,
997
- minPerpAuctionDuration: number
1000
+ fallbackAsk?: BN
998
1001
  ): Generator<DLOBNode> {
999
1002
  for (const node of this.getLimitBids(
1000
1003
  marketIndex,
@@ -1002,7 +1005,12 @@ export class DLOB {
1002
1005
  marketType,
1003
1006
  oraclePriceData
1004
1007
  )) {
1005
- if (this.isRestingLimitOrder(node.order, slot, minPerpAuctionDuration)) {
1008
+ if (this.isRestingLimitOrder(node.order, slot)) {
1009
+ yield node;
1010
+ } else if (
1011
+ fallbackAsk &&
1012
+ node.getPrice(oraclePriceData, slot).lt(fallbackAsk)
1013
+ ) {
1006
1014
  yield node;
1007
1015
  }
1008
1016
  }
@@ -0,0 +1,82 @@
1
+ import { AnchorProvider } from '@project-serum/anchor';
2
+ import { DLOB, UserMap, Wallet } from '..';
3
+ import { Connection, Keypair, PublicKey } from '@solana/web3.js';
4
+ import {
5
+ DriftClient,
6
+ initialize,
7
+ BulkAccountLoader,
8
+ getMarketsAndOraclesForSubscription,
9
+ } from '..';
10
+
11
+ const env = 'mainnet-beta';
12
+
13
+ const main = async () => {
14
+ // Initialize Drift SDK
15
+ const sdkConfig = initialize({ env });
16
+
17
+ // Set up the Wallet and Provider
18
+ const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
19
+ const keypair = Keypair.fromSecretKey(
20
+ Uint8Array.from(JSON.parse(privateKey))
21
+ );
22
+ const wallet = new Wallet(keypair);
23
+
24
+ // Set up the Connection
25
+ const rpcAddress = process.env.RPC_ADDRESS; // can use: https://api.devnet.solana.com for devnet; https://api.mainnet-beta.solana.com for mainnet;
26
+ const connection = new Connection(rpcAddress);
27
+
28
+ // Set up the Provider
29
+ const provider = new AnchorProvider(
30
+ connection,
31
+ wallet,
32
+ AnchorProvider.defaultOptions()
33
+ );
34
+
35
+ // Set up the Drift Clearing House
36
+ const driftPublicKey = new PublicKey(sdkConfig.DRIFT_PROGRAM_ID);
37
+ const bulkAccountLoader = new BulkAccountLoader(
38
+ connection,
39
+ 'confirmed',
40
+ 1000
41
+ );
42
+ const driftClient = new DriftClient({
43
+ connection,
44
+ wallet: provider.wallet,
45
+ programID: driftPublicKey,
46
+ ...getMarketsAndOraclesForSubscription(env),
47
+ accountSubscription: {
48
+ type: 'polling',
49
+ accountLoader: bulkAccountLoader,
50
+ },
51
+ });
52
+
53
+ console.log('Subscribing drift client...');
54
+ await driftClient.subscribe();
55
+
56
+ console.log('Loading user map...');
57
+ const userMap = new UserMap(driftClient, {
58
+ type: 'polling',
59
+ accountLoader: bulkAccountLoader,
60
+ });
61
+
62
+ // fetches all users and subscribes for updates
63
+ await userMap.fetchAllUsers();
64
+
65
+ console.log('Loading dlob from user map...');
66
+ const dlob = new DLOB();
67
+ await dlob.initFromUserMap(userMap);
68
+
69
+ console.log('number of orders', dlob.getDLOBOrders().length);
70
+
71
+ dlob.clear();
72
+
73
+ console.log('Unsubscribing users...');
74
+ for (const user of userMap.values()) {
75
+ await user.unsubscribe();
76
+ }
77
+
78
+ console.log('Unsubscribing drift client...');
79
+ await driftClient.unsubscribe();
80
+ };
81
+
82
+ main();
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.12.0-beta.3",
2
+ "version": "2.13.0-beta.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
package/src/math/trade.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { MarketType, PerpMarketAccount, PositionDirection } from '../types';
1
+ import {
2
+ MarketType,
3
+ PerpMarketAccount,
4
+ PositionDirection,
5
+ SpotMarketAccount,
6
+ } from '../types';
2
7
  import { BN } from '@project-serum/anchor';
3
8
  import { assert } from '../assert/assert';
4
9
  import {
@@ -7,6 +12,7 @@ import {
7
12
  AMM_TO_QUOTE_PRECISION_RATIO,
8
13
  ZERO,
9
14
  BASE_PRECISION,
15
+ BN_MAX,
10
16
  } from '../constants/numericConstants';
11
17
  import {
12
18
  calculateBidPrice,
@@ -20,11 +26,14 @@ import {
20
26
  AssetType,
21
27
  calculateUpdatedAMMSpreadReserves,
22
28
  calculateQuoteAssetAmountSwapped,
29
+ calculateMarketOpenBidAsk,
23
30
  } from './amm';
24
31
  import { squareRootBN } from './utils';
25
32
  import { isVariant } from '../types';
26
33
  import { OraclePriceData } from '../oracles/types';
27
34
  import { DLOB } from '../dlob/DLOB';
35
+ import { PublicKey } from '@solana/web3.js';
36
+ import { Orderbook } from '@project-serum/serum';
28
37
 
29
38
  const MAXPCT = new BN(1000); //percentage units are [0,1000] => [0,1]
30
39
 
@@ -363,7 +372,7 @@ export function calculateTargetPriceTrade(
363
372
  * @param oraclePriceData
364
373
  * @param dlob
365
374
  * @param slot
366
- * @param minPerpAuctionDuration
375
+ * @param usersToSkip
367
376
  */
368
377
  export function calculateEstimatedPerpEntryPrice(
369
378
  assetType: AssetType,
@@ -373,21 +382,37 @@ export function calculateEstimatedPerpEntryPrice(
373
382
  oraclePriceData: OraclePriceData,
374
383
  dlob: DLOB,
375
384
  slot: number,
376
- minPerpAuctionDuration: number
377
- ): [BN, BN] {
385
+ usersToSkip = new Map<PublicKey, boolean>()
386
+ ): {
387
+ entryPrice: BN;
388
+ priceImpact: BN;
389
+ bestPrice: BN;
390
+ worstPrice: BN;
391
+ baseFilled: BN;
392
+ quoteFilled: BN;
393
+ } {
378
394
  if (amount.eq(ZERO)) {
379
- return [ZERO, ZERO];
395
+ return {
396
+ entryPrice: ZERO,
397
+ priceImpact: ZERO,
398
+ bestPrice: ZERO,
399
+ worstPrice: ZERO,
400
+ baseFilled: ZERO,
401
+ quoteFilled: ZERO,
402
+ };
380
403
  }
381
404
 
382
405
  const takerIsLong = isVariant(direction, 'long');
383
406
  const limitOrders = dlob[
384
- takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'
407
+ takerIsLong ? 'getMakerLimitAsks' : 'getMakerLimitBids'
385
408
  ](
386
409
  market.marketIndex,
387
410
  slot,
388
411
  MarketType.PERP,
389
412
  oraclePriceData,
390
- minPerpAuctionDuration
413
+ takerIsLong
414
+ ? calculateBidPrice(market, oraclePriceData)
415
+ : calculateAskPrice(market, oraclePriceData)
391
416
  );
392
417
 
393
418
  const swapDirection = getSwapDirection(assetType, direction);
@@ -401,9 +426,34 @@ export function calculateEstimatedPerpEntryPrice(
401
426
  pegMultiplier: newPeg,
402
427
  };
403
428
 
429
+ const [ammBids, ammAsks] = calculateMarketOpenBidAsk(
430
+ market.amm.baseAssetReserve,
431
+ market.amm.minBaseAssetReserve,
432
+ market.amm.maxBaseAssetReserve,
433
+ market.amm.orderStepSize
434
+ );
435
+
436
+ let ammLiquidity: BN;
437
+ if (assetType === 'base') {
438
+ ammLiquidity = takerIsLong ? ammAsks.abs() : ammBids;
439
+ } else {
440
+ const [afterSwapQuoteReserves, _] = calculateAmmReservesAfterSwap(
441
+ amm,
442
+ 'base',
443
+ takerIsLong ? ammAsks.abs() : ammBids,
444
+ getSwapDirection('base', direction)
445
+ );
446
+
447
+ ammLiquidity = calculateQuoteAssetAmountSwapped(
448
+ amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
449
+ amm.pegMultiplier,
450
+ swapDirection
451
+ );
452
+ }
453
+
404
454
  const invariant = amm.sqrtK.mul(amm.sqrtK);
405
455
 
406
- let initialPrice = calculatePrice(
456
+ let bestPrice = calculatePrice(
407
457
  amm.baseAssetReserve,
408
458
  amm.quoteAssetReserve,
409
459
  amm.pegMultiplier
@@ -415,13 +465,18 @@ export function calculateEstimatedPerpEntryPrice(
415
465
  let limitOrder = limitOrders.next().value;
416
466
  if (limitOrder) {
417
467
  const limitOrderPrice = limitOrder.getPrice(oraclePriceData, slot);
418
- initialPrice = takerIsLong
419
- ? BN.min(limitOrderPrice, initialPrice)
420
- : BN.max(limitOrderPrice, initialPrice);
468
+ bestPrice = takerIsLong
469
+ ? BN.min(limitOrderPrice, bestPrice)
470
+ : BN.max(limitOrderPrice, bestPrice);
421
471
  }
422
472
 
473
+ let worstPrice = bestPrice;
474
+
423
475
  if (assetType === 'base') {
424
- while (!cumulativeBaseFilled.eq(amount)) {
476
+ while (
477
+ !cumulativeBaseFilled.eq(amount) &&
478
+ (ammLiquidity.gt(ZERO) || limitOrder)
479
+ ) {
425
480
  const limitOrderPrice = limitOrder?.getPrice(oraclePriceData, slot);
426
481
 
427
482
  let maxAmmFill: BN;
@@ -442,11 +497,15 @@ export function calculateEstimatedPerpEntryPrice(
442
497
  maxAmmFill = amount.sub(cumulativeBaseFilled);
443
498
  }
444
499
 
500
+ maxAmmFill = BN.min(maxAmmFill, ammLiquidity);
501
+
445
502
  if (maxAmmFill.gt(ZERO)) {
446
503
  const baseFilled = BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
447
504
  const [afterSwapQuoteReserves, afterSwapBaseReserves] =
448
505
  calculateAmmReservesAfterSwap(amm, 'base', baseFilled, swapDirection);
449
506
 
507
+ ammLiquidity = ammLiquidity.sub(baseFilled);
508
+
450
509
  const quoteFilled = calculateQuoteAssetAmountSwapped(
451
510
  amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
452
511
  amm.pegMultiplier,
@@ -459,11 +518,21 @@ export function calculateEstimatedPerpEntryPrice(
459
518
  amm.baseAssetReserve = afterSwapBaseReserves;
460
519
  amm.quoteAssetReserve = afterSwapQuoteReserves;
461
520
 
521
+ worstPrice = calculatePrice(
522
+ amm.baseAssetReserve,
523
+ amm.quoteAssetReserve,
524
+ amm.pegMultiplier
525
+ );
526
+
462
527
  if (cumulativeBaseFilled.eq(amount)) {
463
528
  break;
464
529
  }
465
530
  }
466
531
 
532
+ if (limitOrder && usersToSkip.has(limitOrder.userAccount)) {
533
+ continue;
534
+ }
535
+
467
536
  const baseFilled = BN.min(
468
537
  limitOrder.order.baseAssetAmount.sub(
469
538
  limitOrder.order.baseAssetAmountFilled
@@ -475,6 +544,8 @@ export function calculateEstimatedPerpEntryPrice(
475
544
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
476
545
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
477
546
 
547
+ worstPrice = limitOrderPrice;
548
+
478
549
  if (cumulativeBaseFilled.eq(amount)) {
479
550
  break;
480
551
  }
@@ -482,7 +553,10 @@ export function calculateEstimatedPerpEntryPrice(
482
553
  limitOrder = limitOrders.next().value;
483
554
  }
484
555
  } else {
485
- while (!cumulativeQuoteFilled.eq(amount)) {
556
+ while (
557
+ !cumulativeQuoteFilled.eq(amount) &&
558
+ (ammLiquidity.gt(ZERO) || limitOrder)
559
+ ) {
486
560
  const limitOrderPrice = limitOrder?.getPrice(oraclePriceData, slot);
487
561
 
488
562
  let maxAmmFill: BN;
@@ -503,6 +577,8 @@ export function calculateEstimatedPerpEntryPrice(
503
577
  maxAmmFill = amount.sub(cumulativeQuoteFilled);
504
578
  }
505
579
 
580
+ maxAmmFill = BN.min(maxAmmFill, ammLiquidity);
581
+
506
582
  if (maxAmmFill.gt(ZERO)) {
507
583
  const quoteFilled = BN.min(
508
584
  amount.sub(cumulativeQuoteFilled),
@@ -516,6 +592,8 @@ export function calculateEstimatedPerpEntryPrice(
516
592
  swapDirection
517
593
  );
518
594
 
595
+ ammLiquidity = ammLiquidity.sub(quoteFilled);
596
+
519
597
  const baseFilled = afterSwapBaseReserves
520
598
  .sub(amm.baseAssetReserve)
521
599
  .abs();
@@ -526,11 +604,21 @@ export function calculateEstimatedPerpEntryPrice(
526
604
  amm.baseAssetReserve = afterSwapBaseReserves;
527
605
  amm.quoteAssetReserve = afterSwapQuoteReserves;
528
606
 
607
+ worstPrice = calculatePrice(
608
+ amm.baseAssetReserve,
609
+ amm.quoteAssetReserve,
610
+ amm.pegMultiplier
611
+ );
612
+
529
613
  if (cumulativeQuoteFilled.eq(amount)) {
530
614
  break;
531
615
  }
532
616
  }
533
617
 
618
+ if (limitOrder && usersToSkip.has(limitOrder.userAccount)) {
619
+ continue;
620
+ }
621
+
534
622
  const quoteFilled = BN.min(
535
623
  limitOrder.order.baseAssetAmount
536
624
  .sub(limitOrder.order.baseAssetAmountFilled)
@@ -544,6 +632,8 @@ export function calculateEstimatedPerpEntryPrice(
544
632
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
545
633
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
546
634
 
635
+ worstPrice = limitOrderPrice;
636
+
547
637
  if (cumulativeQuoteFilled.eq(amount)) {
548
638
  break;
549
639
  }
@@ -557,10 +647,226 @@ export function calculateEstimatedPerpEntryPrice(
557
647
  .div(cumulativeBaseFilled);
558
648
 
559
649
  const priceImpact = entryPrice
560
- .sub(initialPrice)
650
+ .sub(bestPrice)
651
+ .mul(PRICE_PRECISION)
652
+ .div(bestPrice)
653
+ .abs();
654
+
655
+ return {
656
+ entryPrice,
657
+ priceImpact,
658
+ bestPrice,
659
+ worstPrice,
660
+ baseFilled: cumulativeBaseFilled,
661
+ quoteFilled: cumulativeQuoteFilled,
662
+ };
663
+ }
664
+
665
+ /**
666
+ * Calculates the estimated entry price and price impact of order, in base or quote
667
+ * Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or serum)
668
+ *
669
+ * @param assetType
670
+ * @param amount
671
+ * @param direction
672
+ * @param market
673
+ * @param oraclePriceData
674
+ * @param dlob
675
+ * @param serumBids
676
+ * @param serumAsks
677
+ * @param slot
678
+ * @param usersToSkip
679
+ */
680
+ export function calculateEstimatedSpotEntryPrice(
681
+ assetType: AssetType,
682
+ amount: BN,
683
+ direction: PositionDirection,
684
+ market: SpotMarketAccount,
685
+ oraclePriceData: OraclePriceData,
686
+ dlob: DLOB,
687
+ serumBids: Orderbook,
688
+ serumAsks: Orderbook,
689
+ slot: number,
690
+ usersToSkip = new Map<PublicKey, boolean>()
691
+ ): {
692
+ entryPrice: BN;
693
+ priceImpact: BN;
694
+ bestPrice: BN;
695
+ worstPrice: BN;
696
+ baseFilled: BN;
697
+ quoteFilled: BN;
698
+ } {
699
+ if (amount.eq(ZERO)) {
700
+ return {
701
+ entryPrice: ZERO,
702
+ priceImpact: ZERO,
703
+ bestPrice: ZERO,
704
+ worstPrice: ZERO,
705
+ baseFilled: ZERO,
706
+ quoteFilled: ZERO,
707
+ };
708
+ }
709
+
710
+ const basePrecision = new BN(Math.pow(10, market.decimals));
711
+
712
+ const takerIsLong = isVariant(direction, 'long');
713
+ const dlobLimitOrders = dlob[
714
+ takerIsLong ? 'getMakerLimitAsks' : 'getMakerLimitBids'
715
+ ](market.marketIndex, slot, MarketType.SPOT, oraclePriceData);
716
+ const serumLimitOrders = takerIsLong
717
+ ? serumAsks.getL2(100)
718
+ : serumBids.getL2(100);
719
+
720
+ let cumulativeBaseFilled = ZERO;
721
+ let cumulativeQuoteFilled = ZERO;
722
+
723
+ let dlobLimitOrder = dlobLimitOrders.next().value;
724
+ let serumLimitOrder = serumLimitOrders.shift();
725
+
726
+ const dlobLimitOrderPrice = dlobLimitOrder?.getPrice(oraclePriceData, slot);
727
+ const serumLimitOrderPrice = serumLimitOrder
728
+ ? new BN(serumLimitOrder[0] * PRICE_PRECISION.toNumber())
729
+ : undefined;
730
+
731
+ const bestPrice = takerIsLong
732
+ ? BN.min(serumLimitOrderPrice || BN_MAX, dlobLimitOrderPrice || BN_MAX)
733
+ : BN.max(serumLimitOrderPrice || ZERO, dlobLimitOrderPrice || ZERO);
734
+ let worstPrice = bestPrice;
735
+
736
+ if (assetType === 'base') {
737
+ while (
738
+ !cumulativeBaseFilled.eq(amount) &&
739
+ (dlobLimitOrder || serumLimitOrder)
740
+ ) {
741
+ const dlobLimitOrderPrice = dlobLimitOrder?.getPrice(
742
+ oraclePriceData,
743
+ slot
744
+ );
745
+ const serumLimitOrderPrice = serumLimitOrder
746
+ ? new BN(serumLimitOrder[0] * PRICE_PRECISION.toNumber())
747
+ : undefined;
748
+
749
+ const useSerum = takerIsLong
750
+ ? (serumLimitOrderPrice || BN_MAX).lt(dlobLimitOrderPrice || BN_MAX)
751
+ : (serumLimitOrderPrice || ZERO).gt(dlobLimitOrderPrice || ZERO);
752
+
753
+ if (!useSerum) {
754
+ if (dlobLimitOrder && usersToSkip.has(dlobLimitOrder.userAccount)) {
755
+ continue;
756
+ }
757
+
758
+ const baseFilled = BN.min(
759
+ dlobLimitOrder.order.baseAssetAmount.sub(
760
+ dlobLimitOrder.order.baseAssetAmountFilled
761
+ ),
762
+ amount.sub(cumulativeBaseFilled)
763
+ );
764
+ const quoteFilled = baseFilled
765
+ .mul(dlobLimitOrderPrice)
766
+ .div(basePrecision);
767
+
768
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
769
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
770
+
771
+ worstPrice = dlobLimitOrder;
772
+
773
+ dlobLimitOrder = dlobLimitOrders.next().value;
774
+ } else {
775
+ const baseFilled = BN.min(
776
+ new BN(serumLimitOrder[1] * basePrecision.toNumber()),
777
+ amount.sub(cumulativeBaseFilled)
778
+ );
779
+ const quoteFilled = baseFilled
780
+ .mul(serumLimitOrderPrice)
781
+ .div(basePrecision);
782
+
783
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
784
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
785
+
786
+ worstPrice = serumLimitOrderPrice;
787
+
788
+ serumLimitOrder = serumLimitOrders.shift();
789
+ }
790
+ }
791
+ } else {
792
+ while (
793
+ !cumulativeQuoteFilled.eq(amount) &&
794
+ (dlobLimitOrder || serumLimitOrder)
795
+ ) {
796
+ const dlobLimitOrderPrice = dlobLimitOrder?.getPrice(
797
+ oraclePriceData,
798
+ slot
799
+ );
800
+ const serumLimitOrderPrice = serumLimitOrder
801
+ ? new BN(serumLimitOrder[0] * PRICE_PRECISION.toNumber())
802
+ : undefined;
803
+
804
+ const useSerum = takerIsLong
805
+ ? (serumLimitOrderPrice || BN_MAX).lt(dlobLimitOrderPrice || BN_MAX)
806
+ : (serumLimitOrderPrice || ZERO).gt(dlobLimitOrderPrice || ZERO);
807
+
808
+ if (!useSerum) {
809
+ if (dlobLimitOrder && usersToSkip.has(dlobLimitOrder.userAccount)) {
810
+ continue;
811
+ }
812
+
813
+ const quoteFilled = BN.min(
814
+ dlobLimitOrder.order.baseAssetAmount
815
+ .sub(dlobLimitOrder.order.baseAssetAmountFilled)
816
+ .mul(dlobLimitOrderPrice)
817
+ .div(basePrecision),
818
+ amount.sub(cumulativeQuoteFilled)
819
+ );
820
+
821
+ const baseFilled = quoteFilled
822
+ .mul(basePrecision)
823
+ .div(dlobLimitOrderPrice);
824
+
825
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
826
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
827
+
828
+ worstPrice = dlobLimitOrderPrice;
829
+
830
+ dlobLimitOrder = dlobLimitOrders.next().value;
831
+ } else {
832
+ const serumOrderBaseAmount = new BN(
833
+ serumLimitOrder[1] * basePrecision.toNumber()
834
+ );
835
+ const quoteFilled = BN.min(
836
+ serumOrderBaseAmount.mul(serumLimitOrderPrice).div(basePrecision),
837
+ amount.sub(cumulativeQuoteFilled)
838
+ );
839
+
840
+ const baseFilled = quoteFilled
841
+ .mul(basePrecision)
842
+ .div(serumLimitOrderPrice);
843
+
844
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
845
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
846
+
847
+ worstPrice = serumLimitOrderPrice;
848
+
849
+ serumLimitOrder = serumLimitOrders.shift();
850
+ }
851
+ }
852
+ }
853
+
854
+ const entryPrice = cumulativeQuoteFilled
855
+ .mul(basePrecision)
856
+ .div(cumulativeBaseFilled);
857
+
858
+ const priceImpact = entryPrice
859
+ .sub(bestPrice)
561
860
  .mul(PRICE_PRECISION)
562
- .div(initialPrice)
861
+ .div(bestPrice)
563
862
  .abs();
564
863
 
565
- return [entryPrice, priceImpact];
864
+ return {
865
+ entryPrice,
866
+ priceImpact,
867
+ bestPrice,
868
+ worstPrice,
869
+ baseFilled: cumulativeBaseFilled,
870
+ quoteFilled: cumulativeQuoteFilled,
871
+ };
566
872
  }