@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.
- package/lib/dlob/DLOB.d.ts +8 -8
- package/lib/dlob/DLOB.js +24 -17
- package/lib/idl/drift.json +1 -1
- package/lib/math/trade.d.ts +35 -3
- package/lib/math/trade.js +192 -14
- package/package.json +1 -1
- package/src/dlob/DLOB.ts +34 -26
- package/src/idl/drift.json +1 -1
- package/src/math/trade.ts +322 -16
- package/tests/dlob/test.ts +307 -38
package/lib/dlob/DLOB.d.ts
CHANGED
|
@@ -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
|
|
69
|
-
*
|
|
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
|
-
|
|
74
|
-
isRestingLimitOrder(order: Order, slot: number
|
|
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
|
|
78
|
-
*
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
540
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
552
|
-
return
|
|
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
|
|
574
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
}
|
package/lib/idl/drift.json
CHANGED
package/lib/math/trade.d.ts
CHANGED
|
@@ -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
|
|
69
|
+
* @param usersToSkip
|
|
68
70
|
*/
|
|
69
|
-
export declare function calculateEstimatedPerpEntryPrice(assetType: AssetType, amount: BN, direction: PositionDirection, market: PerpMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, slot: number,
|
|
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
|
|
261
|
+
* @param usersToSkip
|
|
262
262
|
*/
|
|
263
|
-
function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, slot,
|
|
263
|
+
function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, slot, usersToSkip = new Map()) {
|
|
264
264
|
if (amount.eq(numericConstants_1.ZERO)) {
|
|
265
|
-
return
|
|
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 ? '
|
|
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
|
|
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
|
-
|
|
285
|
-
? anchor_1.BN.min(limitOrderPrice,
|
|
286
|
-
: anchor_1.BN.max(limitOrderPrice,
|
|
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(
|
|
412
|
+
.sub(bestPrice)
|
|
378
413
|
.mul(numericConstants_1.PRICE_PRECISION)
|
|
379
|
-
.div(
|
|
414
|
+
.div(bestPrice)
|
|
380
415
|
.abs();
|
|
381
|
-
return
|
|
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
package/src/dlob/DLOB.ts
CHANGED
|
@@ -519,10 +519,11 @@ export class DLOB {
|
|
|
519
519
|
marketType,
|
|
520
520
|
oraclePriceData,
|
|
521
521
|
marketOrderGenerator,
|
|
522
|
-
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.
|
|
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
|
|
920
|
-
*
|
|
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
|
-
*
|
|
929
|
+
*getMakerLimitAsks(
|
|
925
930
|
marketIndex: number,
|
|
926
931
|
slot: number,
|
|
927
932
|
marketType: MarketType,
|
|
928
933
|
oraclePriceData: OraclePriceData,
|
|
929
|
-
|
|
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
|
|
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
|
|
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
|
|
988
|
-
*
|
|
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
|
-
*
|
|
995
|
+
*getMakerLimitBids(
|
|
993
996
|
marketIndex: number,
|
|
994
997
|
slot: number,
|
|
995
998
|
marketType: MarketType,
|
|
996
999
|
oraclePriceData: OraclePriceData,
|
|
997
|
-
|
|
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
|
|
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
|
}
|
package/src/idl/drift.json
CHANGED