@drift-labs/sdk 2.12.0-beta.3 → 2.13.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dlob/DLOB.d.ts +8 -8
- package/lib/dlob/DLOB.js +24 -17
- package/lib/examples/loadDlob.d.ts +1 -0
- package/lib/examples/loadDlob.js +54 -0
- 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/examples/loadDlob.ts +82 -0
- package/src/idl/drift.json +1 -1
- package/src/math/trade.ts +322 -16
- package/tests/dlob/test.ts +307 -38
- package/src/examples/makeTradeExample.js +0 -157
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
|
}
|
|
@@ -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();
|
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;
|