@drift-labs/sdk 2.12.0-beta.3 → 2.12.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.
@@ -56,7 +56,7 @@ export declare class DLOB {
56
56
  findNodesToFill(marketIndex: number, fallbackBid: BN | undefined, fallbackAsk: BN | undefined, slot: number, ts: number, marketType: MarketType, oraclePriceData: OraclePriceData, stateAccount: StateAccount, marketAccount: PerpMarketAccount | SpotMarketAccount): NodeToFill[];
57
57
  findLimitOrderNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid: BN | undefined): NodeToFill[];
58
58
  findMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid?: BN | undefined): NodeToFill[];
59
- findMarketNodesCrossingLimitNodes(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, takerNodeGenerator: Generator<DLOBNode>, makerNodeGeneratorFn: (marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData) => Generator<DLOBNode>, doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean): NodeToFill[];
59
+ findMarketNodesCrossingLimitNodes(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, takerNodeGenerator: Generator<DLOBNode>, makerNodeGeneratorFn: (marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, fallbackPrice?: BN) => Generator<DLOBNode>, doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean, fallbackPrice?: BN): NodeToFill[];
60
60
  findNodesCrossingFallbackLiquidity(marketType: MarketType, slot: number, oraclePriceData: OraclePriceData, nodeGenerator: Generator<DLOBNode>, fallbackPrice: BN, doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean): NodeToFill[];
61
61
  findExpiredNodesToFill(marketIndex: number, ts: number, marketType: MarketType): NodeToFill[];
62
62
  findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
@@ -65,21 +65,21 @@ export declare class DLOB {
65
65
  private getBestNode;
66
66
  getLimitAsks(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
67
67
  /**
68
- * Filters the limit asks that are post only or have been place for sufficiently long
69
- * Useful for displaying order book that doesn't have taker limit orders crossing spread
68
+ * Filters the limit asks that are post only, have been place for sufficiently long or are above the fallback bid
69
+ * Market orders can only fill against orders that meet this criteria
70
70
  *
71
71
  * @returns
72
72
  */
73
- getRestingLimitAsks(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, minPerpAuctionDuration: number): Generator<DLOBNode>;
74
- isRestingLimitOrder(order: Order, slot: number, minPerpAuctionDuration: number): boolean;
73
+ getMakerLimitAsks(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, fallbackBid?: BN): Generator<DLOBNode>;
74
+ isRestingLimitOrder(order: Order, slot: number): boolean;
75
75
  getLimitBids(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
76
76
  /**
77
- * Filters the limit bids that are post only or have been place for sufficiently long
78
- * Useful for displaying order book that doesn't have taker limit orders crossing spread
77
+ * Filters the limit bids that are post only, have been place for sufficiently long or are below the fallback ask
78
+ * Market orders can only fill against orders that meet this criteria
79
79
  *
80
80
  * @returns
81
81
  */
82
- getRestingLimitBids(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, minPerpAuctionDuration: number): Generator<DLOBNode>;
82
+ getMakerLimitBids(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, fallbackAsk?: BN): Generator<DLOBNode>;
83
83
  getAsks(marketIndex: number, fallbackAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
84
84
  getBids(marketIndex: number, fallbackBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
85
85
  findCrossingLimitOrders(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, fallbackAsk: BN | undefined, fallbackBid: BN | undefined): NodeToFill[];
package/lib/dlob/DLOB.js CHANGED
@@ -288,9 +288,9 @@ class DLOB {
288
288
  findMarketNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
289
289
  const nodesToFill = new Array();
290
290
  let marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
291
- const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getLimitBids.bind(this), (takerPrice, makerPrice) => {
291
+ const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getMakerLimitBids.bind(this), (takerPrice, makerPrice) => {
292
292
  return takerPrice === undefined || takerPrice.lte(makerPrice);
293
- });
293
+ }, fallbackAsk);
294
294
  for (const marketAskCrossingBid of marketAsksCrossingBids) {
295
295
  nodesToFill.push(marketAskCrossingBid);
296
296
  }
@@ -304,9 +304,9 @@ class DLOB {
304
304
  }
305
305
  }
306
306
  marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
307
- const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getLimitAsks.bind(this), (takerPrice, fallbackPrice) => {
307
+ const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getMakerLimitAsks.bind(this), (takerPrice, fallbackPrice) => {
308
308
  return takerPrice === undefined || takerPrice.gte(fallbackPrice);
309
- });
309
+ }, fallbackBid);
310
310
  for (const marketBidToFill of marketBidsToFill) {
311
311
  nodesToFill.push(marketBidToFill);
312
312
  }
@@ -321,10 +321,10 @@ class DLOB {
321
321
  }
322
322
  return nodesToFill;
323
323
  }
324
- findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, takerNodeGenerator, makerNodeGeneratorFn, doesCross) {
324
+ findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, takerNodeGenerator, makerNodeGeneratorFn, doesCross, fallbackPrice) {
325
325
  const nodesToFill = new Array();
326
326
  for (const takerNode of takerNodeGenerator) {
327
- const makerNodeGenerator = makerNodeGeneratorFn(marketIndex, slot, marketType, oraclePriceData);
327
+ const makerNodeGenerator = makerNodeGeneratorFn(marketIndex, slot, marketType, oraclePriceData, fallbackPrice);
328
328
  for (const makerNode of makerNodeGenerator) {
329
329
  // Can't match orders from the same user
330
330
  const sameUser = takerNode.userAccount.equals(makerNode.userAccount);
@@ -536,21 +536,24 @@ class DLOB {
536
536
  });
537
537
  }
538
538
  /**
539
- * Filters the limit asks that are post only or have been place for sufficiently long
540
- * Useful for displaying order book that doesn't have taker limit orders crossing spread
539
+ * Filters the limit asks that are post only, have been place for sufficiently long or are above the fallback bid
540
+ * Market orders can only fill against orders that meet this criteria
541
541
  *
542
542
  * @returns
543
543
  */
544
- *getRestingLimitAsks(marketIndex, slot, marketType, oraclePriceData, minPerpAuctionDuration) {
544
+ *getMakerLimitAsks(marketIndex, slot, marketType, oraclePriceData, fallbackBid) {
545
545
  for (const node of this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData)) {
546
- if (this.isRestingLimitOrder(node.order, slot, minPerpAuctionDuration)) {
546
+ if (this.isRestingLimitOrder(node.order, slot)) {
547
+ yield node;
548
+ }
549
+ else if (fallbackBid &&
550
+ node.getPrice(oraclePriceData, slot).gt(fallbackBid)) {
547
551
  yield node;
548
552
  }
549
553
  }
550
554
  }
551
- isRestingLimitOrder(order, slot, minPerpAuctionDuration) {
552
- return (order.postOnly ||
553
- new __1.BN(slot).sub(order.slot).gte(new __1.BN(minPerpAuctionDuration * 1.5)));
555
+ isRestingLimitOrder(order, slot) {
556
+ return order.postOnly || new __1.BN(slot).sub(order.slot).gte(new __1.BN(15));
554
557
  }
555
558
  *getLimitBids(marketIndex, slot, marketType, oraclePriceData) {
556
559
  if (__1.isVariant(marketType, 'spot') && !oraclePriceData) {
@@ -570,14 +573,18 @@ class DLOB {
570
573
  });
571
574
  }
572
575
  /**
573
- * Filters the limit bids that are post only or have been place for sufficiently long
574
- * Useful for displaying order book that doesn't have taker limit orders crossing spread
576
+ * Filters the limit bids that are post only, have been place for sufficiently long or are below the fallback ask
577
+ * Market orders can only fill against orders that meet this criteria
575
578
  *
576
579
  * @returns
577
580
  */
578
- *getRestingLimitBids(marketIndex, slot, marketType, oraclePriceData, minPerpAuctionDuration) {
581
+ *getMakerLimitBids(marketIndex, slot, marketType, oraclePriceData, fallbackAsk) {
579
582
  for (const node of this.getLimitBids(marketIndex, slot, marketType, oraclePriceData)) {
580
- if (this.isRestingLimitOrder(node.order, slot, minPerpAuctionDuration)) {
583
+ if (this.isRestingLimitOrder(node.order, slot)) {
584
+ yield node;
585
+ }
586
+ else if (fallbackAsk &&
587
+ node.getPrice(oraclePriceData, slot).lt(fallbackAsk)) {
581
588
  yield node;
582
589
  }
583
590
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.12.0-beta.3",
2
+ "version": "2.12.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -1,9 +1,11 @@
1
1
  /// <reference types="bn.js" />
2
- import { PerpMarketAccount, PositionDirection } from '../types';
2
+ import { PerpMarketAccount, PositionDirection, SpotMarketAccount } from '../types';
3
3
  import { BN } from '@project-serum/anchor';
4
4
  import { AssetType } from './amm';
5
5
  import { OraclePriceData } from '../oracles/types';
6
6
  import { DLOB } from '../dlob/DLOB';
7
+ import { PublicKey } from '@solana/web3.js';
8
+ import { Orderbook } from '@project-serum/serum';
7
9
  export declare type PriceImpactUnit = 'entryPrice' | 'maxPrice' | 'priceDelta' | 'priceDeltaAsNumber' | 'pctAvg' | 'pctMax' | 'quoteAssetAmount' | 'quoteAssetAmountPeg' | 'acquiredBaseAssetAmount' | 'acquiredQuoteAssetAmount' | 'all';
8
10
  /**
9
11
  * Calculates avg/max slippage (price impact) for candidate trade
@@ -64,6 +66,36 @@ export declare function calculateTargetPriceTrade(market: PerpMarketAccount, tar
64
66
  * @param oraclePriceData
65
67
  * @param dlob
66
68
  * @param slot
67
- * @param minPerpAuctionDuration
69
+ * @param usersToSkip
68
70
  */
69
- export declare function calculateEstimatedPerpEntryPrice(assetType: AssetType, amount: BN, direction: PositionDirection, market: PerpMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, slot: number, minPerpAuctionDuration: number): [BN, BN];
71
+ export declare function calculateEstimatedPerpEntryPrice(assetType: AssetType, amount: BN, direction: PositionDirection, market: PerpMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, slot: number, usersToSkip?: Map<PublicKey, boolean>): {
72
+ entryPrice: BN;
73
+ priceImpact: BN;
74
+ bestPrice: BN;
75
+ worstPrice: BN;
76
+ baseFilled: BN;
77
+ quoteFilled: BN;
78
+ };
79
+ /**
80
+ * Calculates the estimated entry price and price impact of order, in base or quote
81
+ * Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or serum)
82
+ *
83
+ * @param assetType
84
+ * @param amount
85
+ * @param direction
86
+ * @param market
87
+ * @param oraclePriceData
88
+ * @param dlob
89
+ * @param serumBids
90
+ * @param serumAsks
91
+ * @param slot
92
+ * @param usersToSkip
93
+ */
94
+ export declare function calculateEstimatedSpotEntryPrice(assetType: AssetType, amount: BN, direction: PositionDirection, market: SpotMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, serumBids: Orderbook, serumAsks: Orderbook, slot: number, usersToSkip?: Map<PublicKey, boolean>): {
95
+ entryPrice: BN;
96
+ priceImpact: BN;
97
+ bestPrice: BN;
98
+ worstPrice: BN;
99
+ baseFilled: BN;
100
+ quoteFilled: BN;
101
+ };
package/lib/math/trade.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateEstimatedPerpEntryPrice = exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
3
+ exports.calculateEstimatedSpotEntryPrice = exports.calculateEstimatedPerpEntryPrice = exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
4
4
  const types_1 = require("../types");
5
5
  const anchor_1 = require("@project-serum/anchor");
6
6
  const assert_1 = require("../assert/assert");
@@ -258,14 +258,23 @@ exports.calculateTargetPriceTrade = calculateTargetPriceTrade;
258
258
  * @param oraclePriceData
259
259
  * @param dlob
260
260
  * @param slot
261
- * @param minPerpAuctionDuration
261
+ * @param usersToSkip
262
262
  */
263
- function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, slot, minPerpAuctionDuration) {
263
+ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, slot, usersToSkip = new Map()) {
264
264
  if (amount.eq(numericConstants_1.ZERO)) {
265
- return [numericConstants_1.ZERO, numericConstants_1.ZERO];
265
+ return {
266
+ entryPrice: numericConstants_1.ZERO,
267
+ priceImpact: numericConstants_1.ZERO,
268
+ bestPrice: numericConstants_1.ZERO,
269
+ worstPrice: numericConstants_1.ZERO,
270
+ baseFilled: numericConstants_1.ZERO,
271
+ quoteFilled: numericConstants_1.ZERO,
272
+ };
266
273
  }
267
274
  const takerIsLong = types_2.isVariant(direction, 'long');
268
- const limitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, minPerpAuctionDuration);
275
+ const limitOrders = dlob[takerIsLong ? 'getMakerLimitAsks' : 'getMakerLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, takerIsLong
276
+ ? market_1.calculateBidPrice(market, oraclePriceData)
277
+ : market_1.calculateAskPrice(market, oraclePriceData));
269
278
  const swapDirection = amm_1.getSwapDirection(assetType, direction);
270
279
  const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = amm_1.calculateUpdatedAMMSpreadReserves(market.amm, direction, oraclePriceData);
271
280
  const amm = {
@@ -274,19 +283,30 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
274
283
  sqrtK: sqrtK,
275
284
  pegMultiplier: newPeg,
276
285
  };
286
+ const [ammBids, ammAsks] = amm_1.calculateMarketOpenBidAsk(market.amm.baseAssetReserve, market.amm.minBaseAssetReserve, market.amm.maxBaseAssetReserve, market.amm.orderStepSize);
287
+ let ammLiquidity;
288
+ if (assetType === 'base') {
289
+ ammLiquidity = takerIsLong ? ammAsks.abs() : ammBids;
290
+ }
291
+ else {
292
+ const [afterSwapQuoteReserves, _] = amm_1.calculateAmmReservesAfterSwap(amm, 'base', takerIsLong ? ammAsks.abs() : ammBids, amm_1.getSwapDirection('base', direction));
293
+ ammLiquidity = amm_1.calculateQuoteAssetAmountSwapped(amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), amm.pegMultiplier, swapDirection);
294
+ }
277
295
  const invariant = amm.sqrtK.mul(amm.sqrtK);
278
- let initialPrice = amm_1.calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
296
+ let bestPrice = amm_1.calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
279
297
  let cumulativeBaseFilled = numericConstants_1.ZERO;
280
298
  let cumulativeQuoteFilled = numericConstants_1.ZERO;
281
299
  let limitOrder = limitOrders.next().value;
282
300
  if (limitOrder) {
283
301
  const limitOrderPrice = limitOrder.getPrice(oraclePriceData, slot);
284
- initialPrice = takerIsLong
285
- ? anchor_1.BN.min(limitOrderPrice, initialPrice)
286
- : anchor_1.BN.max(limitOrderPrice, initialPrice);
302
+ bestPrice = takerIsLong
303
+ ? anchor_1.BN.min(limitOrderPrice, bestPrice)
304
+ : anchor_1.BN.max(limitOrderPrice, bestPrice);
287
305
  }
306
+ let worstPrice = bestPrice;
288
307
  if (assetType === 'base') {
289
- while (!cumulativeBaseFilled.eq(amount)) {
308
+ while (!cumulativeBaseFilled.eq(amount) &&
309
+ (ammLiquidity.gt(numericConstants_1.ZERO) || limitOrder)) {
290
310
  const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(oraclePriceData, slot);
291
311
  let maxAmmFill;
292
312
  if (limitOrderPrice) {
@@ -303,22 +323,29 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
303
323
  else {
304
324
  maxAmmFill = amount.sub(cumulativeBaseFilled);
305
325
  }
326
+ maxAmmFill = anchor_1.BN.min(maxAmmFill, ammLiquidity);
306
327
  if (maxAmmFill.gt(numericConstants_1.ZERO)) {
307
328
  const baseFilled = anchor_1.BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
308
329
  const [afterSwapQuoteReserves, afterSwapBaseReserves] = amm_1.calculateAmmReservesAfterSwap(amm, 'base', baseFilled, swapDirection);
330
+ ammLiquidity = ammLiquidity.sub(baseFilled);
309
331
  const quoteFilled = amm_1.calculateQuoteAssetAmountSwapped(amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), amm.pegMultiplier, swapDirection);
310
332
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
311
333
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
312
334
  amm.baseAssetReserve = afterSwapBaseReserves;
313
335
  amm.quoteAssetReserve = afterSwapQuoteReserves;
336
+ worstPrice = amm_1.calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
314
337
  if (cumulativeBaseFilled.eq(amount)) {
315
338
  break;
316
339
  }
317
340
  }
341
+ if (limitOrder && usersToSkip.has(limitOrder.userAccount)) {
342
+ continue;
343
+ }
318
344
  const baseFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount.sub(limitOrder.order.baseAssetAmountFilled), amount.sub(cumulativeBaseFilled));
319
345
  const quoteFilled = baseFilled.mul(limitOrderPrice).div(numericConstants_1.BASE_PRECISION);
320
346
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
321
347
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
348
+ worstPrice = limitOrderPrice;
322
349
  if (cumulativeBaseFilled.eq(amount)) {
323
350
  break;
324
351
  }
@@ -326,7 +353,8 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
326
353
  }
327
354
  }
328
355
  else {
329
- while (!cumulativeQuoteFilled.eq(amount)) {
356
+ while (!cumulativeQuoteFilled.eq(amount) &&
357
+ (ammLiquidity.gt(numericConstants_1.ZERO) || limitOrder)) {
330
358
  const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(oraclePriceData, slot);
331
359
  let maxAmmFill;
332
360
  if (limitOrderPrice) {
@@ -343,9 +371,11 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
343
371
  else {
344
372
  maxAmmFill = amount.sub(cumulativeQuoteFilled);
345
373
  }
374
+ maxAmmFill = anchor_1.BN.min(maxAmmFill, ammLiquidity);
346
375
  if (maxAmmFill.gt(numericConstants_1.ZERO)) {
347
376
  const quoteFilled = anchor_1.BN.min(amount.sub(cumulativeQuoteFilled), maxAmmFill);
348
377
  const [afterSwapQuoteReserves, afterSwapBaseReserves] = amm_1.calculateAmmReservesAfterSwap(amm, 'quote', quoteFilled, swapDirection);
378
+ ammLiquidity = ammLiquidity.sub(quoteFilled);
349
379
  const baseFilled = afterSwapBaseReserves
350
380
  .sub(amm.baseAssetReserve)
351
381
  .abs();
@@ -353,10 +383,14 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
353
383
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
354
384
  amm.baseAssetReserve = afterSwapBaseReserves;
355
385
  amm.quoteAssetReserve = afterSwapQuoteReserves;
386
+ worstPrice = amm_1.calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
356
387
  if (cumulativeQuoteFilled.eq(amount)) {
357
388
  break;
358
389
  }
359
390
  }
391
+ if (limitOrder && usersToSkip.has(limitOrder.userAccount)) {
392
+ continue;
393
+ }
360
394
  const quoteFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount
361
395
  .sub(limitOrder.order.baseAssetAmountFilled)
362
396
  .mul(limitOrderPrice)
@@ -364,6 +398,7 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
364
398
  const baseFilled = quoteFilled.mul(numericConstants_1.BASE_PRECISION).div(limitOrderPrice);
365
399
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
366
400
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
401
+ worstPrice = limitOrderPrice;
367
402
  if (cumulativeQuoteFilled.eq(amount)) {
368
403
  break;
369
404
  }
@@ -374,10 +409,153 @@ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market,
374
409
  .mul(numericConstants_1.BASE_PRECISION)
375
410
  .div(cumulativeBaseFilled);
376
411
  const priceImpact = entryPrice
377
- .sub(initialPrice)
412
+ .sub(bestPrice)
378
413
  .mul(numericConstants_1.PRICE_PRECISION)
379
- .div(initialPrice)
414
+ .div(bestPrice)
380
415
  .abs();
381
- return [entryPrice, priceImpact];
416
+ return {
417
+ entryPrice,
418
+ priceImpact,
419
+ bestPrice,
420
+ worstPrice,
421
+ baseFilled: cumulativeBaseFilled,
422
+ quoteFilled: cumulativeQuoteFilled,
423
+ };
382
424
  }
383
425
  exports.calculateEstimatedPerpEntryPrice = calculateEstimatedPerpEntryPrice;
426
+ /**
427
+ * Calculates the estimated entry price and price impact of order, in base or quote
428
+ * Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or serum)
429
+ *
430
+ * @param assetType
431
+ * @param amount
432
+ * @param direction
433
+ * @param market
434
+ * @param oraclePriceData
435
+ * @param dlob
436
+ * @param serumBids
437
+ * @param serumAsks
438
+ * @param slot
439
+ * @param usersToSkip
440
+ */
441
+ function calculateEstimatedSpotEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, serumBids, serumAsks, slot, usersToSkip = new Map()) {
442
+ if (amount.eq(numericConstants_1.ZERO)) {
443
+ return {
444
+ entryPrice: numericConstants_1.ZERO,
445
+ priceImpact: numericConstants_1.ZERO,
446
+ bestPrice: numericConstants_1.ZERO,
447
+ worstPrice: numericConstants_1.ZERO,
448
+ baseFilled: numericConstants_1.ZERO,
449
+ quoteFilled: numericConstants_1.ZERO,
450
+ };
451
+ }
452
+ const basePrecision = new anchor_1.BN(Math.pow(10, market.decimals));
453
+ const takerIsLong = types_2.isVariant(direction, 'long');
454
+ const dlobLimitOrders = dlob[takerIsLong ? 'getMakerLimitAsks' : 'getMakerLimitBids'](market.marketIndex, slot, types_1.MarketType.SPOT, oraclePriceData);
455
+ const serumLimitOrders = takerIsLong
456
+ ? serumAsks.getL2(100)
457
+ : serumBids.getL2(100);
458
+ let cumulativeBaseFilled = numericConstants_1.ZERO;
459
+ let cumulativeQuoteFilled = numericConstants_1.ZERO;
460
+ let dlobLimitOrder = dlobLimitOrders.next().value;
461
+ let serumLimitOrder = serumLimitOrders.shift();
462
+ const dlobLimitOrderPrice = dlobLimitOrder === null || dlobLimitOrder === void 0 ? void 0 : dlobLimitOrder.getPrice(oraclePriceData, slot);
463
+ const serumLimitOrderPrice = serumLimitOrder
464
+ ? new anchor_1.BN(serumLimitOrder[0] * numericConstants_1.PRICE_PRECISION.toNumber())
465
+ : undefined;
466
+ const bestPrice = takerIsLong
467
+ ? anchor_1.BN.min(serumLimitOrderPrice || numericConstants_1.BN_MAX, dlobLimitOrderPrice || numericConstants_1.BN_MAX)
468
+ : anchor_1.BN.max(serumLimitOrderPrice || numericConstants_1.ZERO, dlobLimitOrderPrice || numericConstants_1.ZERO);
469
+ let worstPrice = bestPrice;
470
+ if (assetType === 'base') {
471
+ while (!cumulativeBaseFilled.eq(amount) &&
472
+ (dlobLimitOrder || serumLimitOrder)) {
473
+ const dlobLimitOrderPrice = dlobLimitOrder === null || dlobLimitOrder === void 0 ? void 0 : dlobLimitOrder.getPrice(oraclePriceData, slot);
474
+ const serumLimitOrderPrice = serumLimitOrder
475
+ ? new anchor_1.BN(serumLimitOrder[0] * numericConstants_1.PRICE_PRECISION.toNumber())
476
+ : undefined;
477
+ const useSerum = takerIsLong
478
+ ? (serumLimitOrderPrice || numericConstants_1.BN_MAX).lt(dlobLimitOrderPrice || numericConstants_1.BN_MAX)
479
+ : (serumLimitOrderPrice || numericConstants_1.ZERO).gt(dlobLimitOrderPrice || numericConstants_1.ZERO);
480
+ if (!useSerum) {
481
+ if (dlobLimitOrder && usersToSkip.has(dlobLimitOrder.userAccount)) {
482
+ continue;
483
+ }
484
+ const baseFilled = anchor_1.BN.min(dlobLimitOrder.order.baseAssetAmount.sub(dlobLimitOrder.order.baseAssetAmountFilled), amount.sub(cumulativeBaseFilled));
485
+ const quoteFilled = baseFilled
486
+ .mul(dlobLimitOrderPrice)
487
+ .div(basePrecision);
488
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
489
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
490
+ worstPrice = dlobLimitOrder;
491
+ dlobLimitOrder = dlobLimitOrders.next().value;
492
+ }
493
+ else {
494
+ const baseFilled = anchor_1.BN.min(new anchor_1.BN(serumLimitOrder[1] * basePrecision.toNumber()), amount.sub(cumulativeBaseFilled));
495
+ const quoteFilled = baseFilled
496
+ .mul(serumLimitOrderPrice)
497
+ .div(basePrecision);
498
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
499
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
500
+ worstPrice = serumLimitOrderPrice;
501
+ serumLimitOrder = serumLimitOrders.shift();
502
+ }
503
+ }
504
+ }
505
+ else {
506
+ while (!cumulativeQuoteFilled.eq(amount) &&
507
+ (dlobLimitOrder || serumLimitOrder)) {
508
+ const dlobLimitOrderPrice = dlobLimitOrder === null || dlobLimitOrder === void 0 ? void 0 : dlobLimitOrder.getPrice(oraclePriceData, slot);
509
+ const serumLimitOrderPrice = serumLimitOrder
510
+ ? new anchor_1.BN(serumLimitOrder[0] * numericConstants_1.PRICE_PRECISION.toNumber())
511
+ : undefined;
512
+ const useSerum = takerIsLong
513
+ ? (serumLimitOrderPrice || numericConstants_1.BN_MAX).lt(dlobLimitOrderPrice || numericConstants_1.BN_MAX)
514
+ : (serumLimitOrderPrice || numericConstants_1.ZERO).gt(dlobLimitOrderPrice || numericConstants_1.ZERO);
515
+ if (!useSerum) {
516
+ if (dlobLimitOrder && usersToSkip.has(dlobLimitOrder.userAccount)) {
517
+ continue;
518
+ }
519
+ const quoteFilled = anchor_1.BN.min(dlobLimitOrder.order.baseAssetAmount
520
+ .sub(dlobLimitOrder.order.baseAssetAmountFilled)
521
+ .mul(dlobLimitOrderPrice)
522
+ .div(basePrecision), amount.sub(cumulativeQuoteFilled));
523
+ const baseFilled = quoteFilled
524
+ .mul(basePrecision)
525
+ .div(dlobLimitOrderPrice);
526
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
527
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
528
+ worstPrice = dlobLimitOrderPrice;
529
+ dlobLimitOrder = dlobLimitOrders.next().value;
530
+ }
531
+ else {
532
+ const serumOrderBaseAmount = new anchor_1.BN(serumLimitOrder[1] * basePrecision.toNumber());
533
+ const quoteFilled = anchor_1.BN.min(serumOrderBaseAmount.mul(serumLimitOrderPrice).div(basePrecision), amount.sub(cumulativeQuoteFilled));
534
+ const baseFilled = quoteFilled
535
+ .mul(basePrecision)
536
+ .div(serumLimitOrderPrice);
537
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
538
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
539
+ worstPrice = serumLimitOrderPrice;
540
+ serumLimitOrder = serumLimitOrders.shift();
541
+ }
542
+ }
543
+ }
544
+ const entryPrice = cumulativeQuoteFilled
545
+ .mul(basePrecision)
546
+ .div(cumulativeBaseFilled);
547
+ const priceImpact = entryPrice
548
+ .sub(bestPrice)
549
+ .mul(numericConstants_1.PRICE_PRECISION)
550
+ .div(bestPrice)
551
+ .abs();
552
+ return {
553
+ entryPrice,
554
+ priceImpact,
555
+ bestPrice,
556
+ worstPrice,
557
+ baseFilled: cumulativeBaseFilled,
558
+ quoteFilled: cumulativeQuoteFilled,
559
+ };
560
+ }
561
+ exports.calculateEstimatedSpotEntryPrice = calculateEstimatedSpotEntryPrice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.12.0-beta.3",
3
+ "version": "2.12.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
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
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.12.0-beta.3",
2
+ "version": "2.12.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {