@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.
@@ -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
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const anchor_1 = require("@project-serum/anchor");
4
+ const __1 = require("..");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ const __2 = require("..");
7
+ const env = 'mainnet-beta';
8
+ const main = async () => {
9
+ // Initialize Drift SDK
10
+ const sdkConfig = __2.initialize({ env });
11
+ // Set up the Wallet and Provider
12
+ const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
13
+ const keypair = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(JSON.parse(privateKey)));
14
+ const wallet = new __1.Wallet(keypair);
15
+ // Set up the Connection
16
+ const rpcAddress = process.env.RPC_ADDRESS; // can use: https://api.devnet.solana.com for devnet; https://api.mainnet-beta.solana.com for mainnet;
17
+ const connection = new web3_js_1.Connection(rpcAddress);
18
+ // Set up the Provider
19
+ const provider = new anchor_1.AnchorProvider(connection, wallet, anchor_1.AnchorProvider.defaultOptions());
20
+ // Set up the Drift Clearing House
21
+ const driftPublicKey = new web3_js_1.PublicKey(sdkConfig.DRIFT_PROGRAM_ID);
22
+ const bulkAccountLoader = new __2.BulkAccountLoader(connection, 'confirmed', 1000);
23
+ const driftClient = new __2.DriftClient({
24
+ connection,
25
+ wallet: provider.wallet,
26
+ programID: driftPublicKey,
27
+ ...__2.getMarketsAndOraclesForSubscription(env),
28
+ accountSubscription: {
29
+ type: 'polling',
30
+ accountLoader: bulkAccountLoader,
31
+ },
32
+ });
33
+ console.log('Subscribing drift client...');
34
+ await driftClient.subscribe();
35
+ console.log('Loading user map...');
36
+ const userMap = new __1.UserMap(driftClient, {
37
+ type: 'polling',
38
+ accountLoader: bulkAccountLoader,
39
+ });
40
+ // fetches all users and subscribes for updates
41
+ await userMap.fetchAllUsers();
42
+ console.log('Loading dlob from user map...');
43
+ const dlob = new __1.DLOB();
44
+ await dlob.initFromUserMap(userMap);
45
+ console.log('number of orders', dlob.getDLOBOrders().length);
46
+ dlob.clear();
47
+ console.log('Unsubscribing users...');
48
+ for (const user of userMap.values()) {
49
+ await user.unsubscribe();
50
+ }
51
+ console.log('Unsubscribing drift client...');
52
+ await driftClient.unsubscribe();
53
+ };
54
+ 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
  {
@@ -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.13.0-beta.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",