@drift-labs/sdk 2.0.12 → 2.0.14
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 -3
- package/lib/dlob/DLOB.js +209 -146
- package/lib/driftClient.d.ts +19 -0
- package/lib/driftClient.js +32 -0
- package/lib/math/orders.d.ts +1 -2
- package/lib/math/orders.js +4 -20
- package/lib/userMap/userMap.d.ts +2 -1
- package/lib/userMap/userMap.js +40 -0
- package/lib/userMap/userStatsMap.d.ts +2 -1
- package/lib/userMap/userStatsMap.js +65 -0
- package/package.json +1 -1
- package/src/dlob/DLOB.ts +381 -197
- package/src/driftClient.ts +41 -0
- package/src/math/orders.ts +5 -22
- package/src/userMap/userMap.ts +44 -0
- package/src/userMap/userStatsMap.ts +76 -0
- package/tests/dlob/test.ts +295 -63
package/lib/dlob/DLOB.d.ts
CHANGED
|
@@ -57,15 +57,20 @@ export declare class DLOB {
|
|
|
57
57
|
trigger(order: Order, userAccount: PublicKey, onTrigger?: OrderBookCallback): void;
|
|
58
58
|
getListForOrder(order: Order): NodeList<any> | undefined;
|
|
59
59
|
findNodesToFill(marketIndex: number, fallbackBid: BN | undefined, fallbackAsk: BN | undefined, slot: number, ts: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
findLimitOrderNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid: BN | undefined): NodeToFill[];
|
|
61
|
+
findMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid?: BN | undefined): NodeToFill[];
|
|
62
|
+
findMarketNodesCrossingLimitNodes(slot: number, oraclePriceData: OraclePriceData, takerNodeGenerator: Generator<DLOBNode>, makerNodeGenerator: Generator<DLOBNode>, doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean): NodeToFill[];
|
|
63
|
+
findNodesCrossingFallbackLiquidity(marketType: MarketType, slot: number, oraclePriceData: OraclePriceData, nodeGenerator: Generator<DLOBNode>, fallbackPrice: BN, doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean): NodeToFill[];
|
|
62
64
|
findExpiredNodesToFill(marketIndex: number, ts: number, marketType: MarketType): NodeToFill[];
|
|
63
65
|
findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
|
|
64
66
|
getMarketBids(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
|
|
65
67
|
getMarketAsks(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
|
|
68
|
+
private getBestNode;
|
|
69
|
+
getLimitAsks(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
|
|
70
|
+
getLimitBids(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
|
|
66
71
|
getAsks(marketIndex: number, fallbackAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
|
|
67
72
|
getBids(marketIndex: number, fallbackBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
|
|
68
|
-
|
|
73
|
+
findCrossingLimitOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
|
|
69
74
|
crossingNodes: NodeToFill[];
|
|
70
75
|
exhaustedSide: Side;
|
|
71
76
|
};
|
package/lib/dlob/DLOB.js
CHANGED
|
@@ -180,27 +180,22 @@ class DLOB {
|
|
|
180
180
|
if ((0, exchangeStatus_1.fillPaused)(this.stateAccount, marketAccount)) {
|
|
181
181
|
return [];
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
if (!(0, exchangeStatus_1.ammPaused)(this.stateAccount, marketAccount)) {
|
|
187
|
-
fallbackCrossingNodesToFill = this.findFallbackCrossingNodesToFill(marketIndex, fallbackBid, fallbackAsk, slot, marketType, oraclePriceData);
|
|
188
|
-
}
|
|
183
|
+
const isAmmPaused = (0, exchangeStatus_1.ammPaused)(this.stateAccount, marketAccount);
|
|
184
|
+
const marketOrderNodesToFill = this.findMarketNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid);
|
|
185
|
+
const limitOrderNodesToFill = this.findLimitOrderNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid);
|
|
189
186
|
// get expired market nodes
|
|
190
187
|
const expiredNodesToFill = this.findExpiredNodesToFill(marketIndex, ts, marketType);
|
|
191
|
-
return
|
|
188
|
+
return marketOrderNodesToFill.concat(limitOrderNodesToFill, expiredNodesToFill);
|
|
192
189
|
}
|
|
193
|
-
|
|
190
|
+
findLimitOrderNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
|
|
194
191
|
const nodesToFill = new Array();
|
|
195
|
-
|
|
196
|
-
slot, marketType, oraclePriceData);
|
|
197
|
-
const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
|
|
198
|
-
slot, marketType, oraclePriceData);
|
|
192
|
+
let askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
|
|
193
|
+
let bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
|
|
199
194
|
let nextAsk = askGenerator.next();
|
|
200
195
|
let nextBid = bidGenerator.next();
|
|
201
196
|
// First try to find orders that cross
|
|
202
197
|
while (!nextAsk.done && !nextBid.done) {
|
|
203
|
-
const { crossingNodes, exhaustedSide } = this.
|
|
198
|
+
const { crossingNodes, exhaustedSide } = this.findCrossingLimitOrders(nextAsk.value, nextBid.value, oraclePriceData, slot);
|
|
204
199
|
if (exhaustedSide === 'bid') {
|
|
205
200
|
nextBid = bidGenerator.next();
|
|
206
201
|
}
|
|
@@ -222,56 +217,141 @@ class DLOB {
|
|
|
222
217
|
nodesToFill.push(crossingNode);
|
|
223
218
|
}
|
|
224
219
|
}
|
|
220
|
+
if (fallbackBid && !isAmmPaused) {
|
|
221
|
+
askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
|
|
222
|
+
const asksCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, askGenerator, fallbackBid, (askPrice, fallbackPrice) => {
|
|
223
|
+
return askPrice.lte(fallbackPrice);
|
|
224
|
+
});
|
|
225
|
+
for (const askCrossingFallback of asksCrossingFallback) {
|
|
226
|
+
nodesToFill.push(askCrossingFallback);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (fallbackAsk && !isAmmPaused) {
|
|
230
|
+
bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
|
|
231
|
+
const bidsCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, bidGenerator, fallbackAsk, (bidPrice, fallbackPrice) => {
|
|
232
|
+
return bidPrice.gte(fallbackPrice);
|
|
233
|
+
});
|
|
234
|
+
for (const bidCrossingFallback of bidsCrossingFallback) {
|
|
235
|
+
nodesToFill.push(bidCrossingFallback);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
225
238
|
return nodesToFill;
|
|
226
239
|
}
|
|
227
|
-
|
|
228
|
-
var _a, _b;
|
|
240
|
+
findMarketNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
|
|
229
241
|
const nodesToFill = new Array();
|
|
230
|
-
|
|
231
|
-
slot, marketType, oraclePriceData);
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
242
|
+
let marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
|
|
243
|
+
let limitOrderGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
|
|
244
|
+
const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(slot, oraclePriceData, marketOrderGenerator, limitOrderGenerator, (takerPrice, makerPrice) => {
|
|
245
|
+
return takerPrice === undefined || takerPrice.lte(makerPrice);
|
|
246
|
+
});
|
|
247
|
+
for (const marketAskCrossingBid of marketAsksCrossingBids) {
|
|
248
|
+
nodesToFill.push(marketAskCrossingBid);
|
|
249
|
+
}
|
|
250
|
+
if (fallbackBid && !isAmmPaused) {
|
|
251
|
+
marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
|
|
252
|
+
const marketAsksCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, marketOrderGenerator, fallbackBid, (takerPrice, fallbackPrice) => {
|
|
253
|
+
return takerPrice === undefined || takerPrice.lte(fallbackPrice);
|
|
254
|
+
});
|
|
255
|
+
for (const marketAskCrossingFallback of marketAsksCrossingFallback) {
|
|
256
|
+
nodesToFill.push(marketAskCrossingFallback);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
|
|
260
|
+
limitOrderGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
|
|
261
|
+
const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(slot, oraclePriceData, marketOrderGenerator, limitOrderGenerator, (takerPrice, fallbackPrice) => {
|
|
262
|
+
return takerPrice === undefined || takerPrice.gte(fallbackPrice);
|
|
263
|
+
});
|
|
264
|
+
for (const marketBidToFill of marketBidsToFill) {
|
|
265
|
+
nodesToFill.push(marketBidToFill);
|
|
266
|
+
}
|
|
267
|
+
if (fallbackAsk && !isAmmPaused) {
|
|
268
|
+
marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
|
|
269
|
+
const marketBidsCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, marketOrderGenerator, fallbackAsk, (takerPrice, fallbackPrice) => {
|
|
270
|
+
return takerPrice === undefined || takerPrice.gte(fallbackPrice);
|
|
271
|
+
});
|
|
272
|
+
for (const marketBidCrossingFallback of marketBidsCrossingFallback) {
|
|
273
|
+
nodesToFill.push(marketBidCrossingFallback);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return nodesToFill;
|
|
277
|
+
}
|
|
278
|
+
findMarketNodesCrossingLimitNodes(slot, oraclePriceData, takerNodeGenerator, makerNodeGenerator, doesCross) {
|
|
279
|
+
const nodesToFill = new Array();
|
|
280
|
+
let nextTakerNode = takerNodeGenerator.next();
|
|
281
|
+
let nextMakerNode = makerNodeGenerator.next();
|
|
282
|
+
while (!nextTakerNode.done && !nextMakerNode.done) {
|
|
283
|
+
const takerNode = nextTakerNode.value;
|
|
284
|
+
const makerNode = nextMakerNode.value;
|
|
285
|
+
const bidUserAuthority = this.userMap.getUserAuthority(makerNode.userAccount.toString());
|
|
286
|
+
const askUserAuthority = this.userMap.getUserAuthority(takerNode.userAccount.toString());
|
|
287
|
+
// Can't match orders from the same authority
|
|
288
|
+
const sameAuthority = bidUserAuthority.equals(askUserAuthority);
|
|
289
|
+
if (sameAuthority) {
|
|
290
|
+
if (Math.random() < 0.5) {
|
|
291
|
+
nextTakerNode = takerNodeGenerator.next();
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
nextMakerNode = makerNodeGenerator.next();
|
|
295
|
+
}
|
|
241
296
|
continue;
|
|
242
297
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
298
|
+
const makerPrice = makerNode.getPrice(oraclePriceData, slot);
|
|
299
|
+
const takerPrice = takerNode.getPrice(oraclePriceData, slot);
|
|
300
|
+
const ordersCross = doesCross(takerPrice, makerPrice);
|
|
301
|
+
if (!ordersCross) {
|
|
302
|
+
// market orders aren't sorted by price, they are sorted by time, so we need to traverse
|
|
303
|
+
// through all of em
|
|
304
|
+
nextTakerNode = takerNodeGenerator.next();
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
nodesToFill.push({
|
|
308
|
+
node: takerNode,
|
|
309
|
+
makerNode: makerNode,
|
|
310
|
+
});
|
|
311
|
+
const makerOrder = makerNode.order;
|
|
312
|
+
const takerOrder = takerNode.order;
|
|
313
|
+
const makerBaseRemaining = makerOrder.baseAssetAmount.sub(makerOrder.baseAssetAmountFilled);
|
|
314
|
+
const takerBaseRemaining = takerOrder.baseAssetAmount.sub(takerOrder.baseAssetAmountFilled);
|
|
315
|
+
const baseFilled = __1.BN.min(makerBaseRemaining, takerBaseRemaining);
|
|
316
|
+
const newMakerOrder = { ...makerOrder };
|
|
317
|
+
newMakerOrder.baseAssetAmountFilled =
|
|
318
|
+
makerOrder.baseAssetAmountFilled.add(baseFilled);
|
|
319
|
+
this.getListForOrder(newMakerOrder).update(newMakerOrder, makerNode.userAccount);
|
|
320
|
+
if (newMakerOrder.baseAssetAmountFilled.eq(makerOrder.baseAssetAmount)) {
|
|
321
|
+
nextMakerNode = makerNodeGenerator.next();
|
|
322
|
+
}
|
|
323
|
+
const newTakerOrder = { ...takerOrder };
|
|
324
|
+
newTakerOrder.baseAssetAmountFilled =
|
|
325
|
+
takerOrder.baseAssetAmountFilled.add(baseFilled);
|
|
326
|
+
this.getListForOrder(newTakerOrder).update(newTakerOrder, takerNode.userAccount);
|
|
327
|
+
if (newTakerOrder.baseAssetAmountFilled.eq(takerOrder.baseAssetAmount)) {
|
|
328
|
+
nextTakerNode = takerNodeGenerator.next();
|
|
253
329
|
}
|
|
254
|
-
nextAsk = askGenerator.next();
|
|
255
330
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
331
|
+
return nodesToFill;
|
|
332
|
+
}
|
|
333
|
+
findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, nodeGenerator, fallbackPrice, doesCross) {
|
|
334
|
+
var _a;
|
|
335
|
+
const nodesToFill = new Array();
|
|
336
|
+
let nextNode = nodeGenerator.next();
|
|
337
|
+
while (!nextNode.done) {
|
|
338
|
+
const node = nextNode.value;
|
|
339
|
+
if ((0, __1.isVariant)(marketType, 'spot') && ((_a = node.order) === null || _a === void 0 ? void 0 : _a.postOnly)) {
|
|
340
|
+
nextNode = nodeGenerator.next();
|
|
261
341
|
continue;
|
|
262
342
|
}
|
|
263
|
-
const
|
|
343
|
+
const nodePrice = (0, __1.getLimitPrice)(node.order, oraclePriceData, slot);
|
|
264
344
|
// order crosses if there is no limit price or it crosses fallback price
|
|
265
|
-
const crosses =
|
|
345
|
+
const crosses = doesCross(nodePrice, fallbackPrice);
|
|
266
346
|
// fallback is available if auction is complete or it's a spot order
|
|
267
|
-
const fallbackAvailable = (0, __1.isVariant)(marketType, 'spot') || (0, __1.isAuctionComplete)(
|
|
347
|
+
const fallbackAvailable = (0, __1.isVariant)(marketType, 'spot') || (0, __1.isAuctionComplete)(node.order, slot);
|
|
268
348
|
if (crosses && fallbackAvailable) {
|
|
269
349
|
nodesToFill.push({
|
|
270
|
-
node:
|
|
350
|
+
node: node,
|
|
271
351
|
makerNode: undefined, // filled by fallback
|
|
272
352
|
});
|
|
273
353
|
}
|
|
274
|
-
|
|
354
|
+
nextNode = nodeGenerator.next();
|
|
275
355
|
}
|
|
276
356
|
return nodesToFill;
|
|
277
357
|
}
|
|
@@ -329,55 +409,60 @@ class DLOB {
|
|
|
329
409
|
}
|
|
330
410
|
return nodesToFill;
|
|
331
411
|
}
|
|
332
|
-
getMarketBids(marketIndex, marketType) {
|
|
412
|
+
*getMarketBids(marketIndex, marketType) {
|
|
333
413
|
const marketTypeStr = (0, __1.getVariant)(marketType);
|
|
334
|
-
|
|
414
|
+
const generator = this.orderLists
|
|
335
415
|
.get(marketTypeStr)
|
|
336
416
|
.get(marketIndex)
|
|
337
417
|
.market.bid.getGenerator();
|
|
418
|
+
for (const marketBidNode of generator) {
|
|
419
|
+
if (marketBidNode.isBaseFilled()) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
yield marketBidNode;
|
|
423
|
+
}
|
|
338
424
|
}
|
|
339
|
-
getMarketAsks(marketIndex, marketType) {
|
|
425
|
+
*getMarketAsks(marketIndex, marketType) {
|
|
340
426
|
const marketTypeStr = (0, __1.getVariant)(marketType);
|
|
341
|
-
|
|
427
|
+
const generator = this.orderLists
|
|
342
428
|
.get(marketTypeStr)
|
|
343
429
|
.get(marketIndex)
|
|
344
430
|
.market.ask.getGenerator();
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const marketTypeStr = (0, __1.getVariant)(marketType);
|
|
351
|
-
const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
|
|
352
|
-
const generatorList = [
|
|
353
|
-
nodeLists.limit.ask.getGenerator(),
|
|
354
|
-
nodeLists.floatingLimit.ask.getGenerator(),
|
|
355
|
-
nodeLists.market.ask.getGenerator(),
|
|
356
|
-
];
|
|
357
|
-
if (marketTypeStr === 'perp' && fallbackAsk) {
|
|
358
|
-
generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackAsk));
|
|
359
|
-
}
|
|
360
|
-
if (generatorList.length === 0) {
|
|
361
|
-
throw new Error('No ask generators found');
|
|
431
|
+
for (const marketAskNode of generator) {
|
|
432
|
+
if (marketAskNode.isBaseFilled()) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
yield marketAskNode;
|
|
362
436
|
}
|
|
363
|
-
|
|
437
|
+
}
|
|
438
|
+
*getBestNode(generatorList, oraclePriceData, slot, compareFcn) {
|
|
439
|
+
const generators = generatorList.map((generator) => {
|
|
364
440
|
return {
|
|
365
441
|
next: generator.next(),
|
|
366
442
|
generator,
|
|
367
443
|
};
|
|
368
444
|
});
|
|
369
|
-
let
|
|
370
|
-
while (!
|
|
371
|
-
const bestGenerator =
|
|
445
|
+
let sideExhausted = false;
|
|
446
|
+
while (!sideExhausted) {
|
|
447
|
+
const bestGenerator = generators.reduce((bestGenerator, currentGenerator) => {
|
|
372
448
|
if (currentGenerator.next.done) {
|
|
373
449
|
return bestGenerator;
|
|
374
450
|
}
|
|
375
451
|
if (bestGenerator.next.done) {
|
|
376
452
|
return currentGenerator;
|
|
377
453
|
}
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
return
|
|
454
|
+
const bestValue = bestGenerator.next.value;
|
|
455
|
+
const currentValue = currentGenerator.next.value;
|
|
456
|
+
// always return the market orders first
|
|
457
|
+
if (bestValue.order && (0, __1.isMarketOrder)(bestValue.order)) {
|
|
458
|
+
return bestGenerator;
|
|
459
|
+
}
|
|
460
|
+
if (currentValue.order && (0, __1.isMarketOrder)(currentValue.order)) {
|
|
461
|
+
return currentGenerator;
|
|
462
|
+
}
|
|
463
|
+
const bestPrice = bestValue.getPrice(oraclePriceData, slot);
|
|
464
|
+
const currentPrice = currentValue.getPrice(oraclePriceData, slot);
|
|
465
|
+
return compareFcn(bestPrice, currentPrice)
|
|
381
466
|
? bestGenerator
|
|
382
467
|
: currentGenerator;
|
|
383
468
|
});
|
|
@@ -399,11 +484,25 @@ class DLOB {
|
|
|
399
484
|
bestGenerator.next = bestGenerator.generator.next();
|
|
400
485
|
}
|
|
401
486
|
else {
|
|
402
|
-
|
|
487
|
+
sideExhausted = true;
|
|
403
488
|
}
|
|
404
489
|
}
|
|
405
490
|
}
|
|
406
|
-
*
|
|
491
|
+
*getLimitAsks(marketIndex, slot, marketType, oraclePriceData) {
|
|
492
|
+
if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
|
|
493
|
+
throw new Error('Must provide OraclePriceData to get spot asks');
|
|
494
|
+
}
|
|
495
|
+
const marketTypeStr = (0, __1.getVariant)(marketType);
|
|
496
|
+
const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
|
|
497
|
+
const generatorList = [
|
|
498
|
+
nodeLists.limit.ask.getGenerator(),
|
|
499
|
+
nodeLists.floatingLimit.ask.getGenerator(),
|
|
500
|
+
];
|
|
501
|
+
yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
|
|
502
|
+
return bestPrice.lt(currentPrice);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
*getLimitBids(marketIndex, slot, marketType, oraclePriceData) {
|
|
407
506
|
if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
|
|
408
507
|
throw new Error('Must provide OraclePriceData to get spot bids');
|
|
409
508
|
}
|
|
@@ -412,58 +511,44 @@ class DLOB {
|
|
|
412
511
|
const generatorList = [
|
|
413
512
|
nodeLists.limit.bid.getGenerator(),
|
|
414
513
|
nodeLists.floatingLimit.bid.getGenerator(),
|
|
415
|
-
nodeLists.market.bid.getGenerator(),
|
|
416
514
|
];
|
|
417
|
-
|
|
418
|
-
|
|
515
|
+
yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
|
|
516
|
+
return bestPrice.gt(currentPrice);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
*getAsks(marketIndex, fallbackAsk, slot, marketType, oraclePriceData) {
|
|
520
|
+
if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
|
|
521
|
+
throw new Error('Must provide OraclePriceData to get spot asks');
|
|
419
522
|
}
|
|
420
|
-
|
|
421
|
-
|
|
523
|
+
const generatorList = [
|
|
524
|
+
this.getMarketAsks(marketIndex, marketType),
|
|
525
|
+
this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData),
|
|
526
|
+
];
|
|
527
|
+
const marketTypeStr = (0, __1.getVariant)(marketType);
|
|
528
|
+
if (marketTypeStr === 'perp' && fallbackAsk) {
|
|
529
|
+
generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackAsk));
|
|
422
530
|
}
|
|
423
|
-
|
|
424
|
-
return
|
|
425
|
-
next: generator.next(),
|
|
426
|
-
generator,
|
|
427
|
-
};
|
|
531
|
+
yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
|
|
532
|
+
return bestPrice.lt(currentPrice);
|
|
428
533
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return bestGenerator;
|
|
434
|
-
}
|
|
435
|
-
if (bestGenerator.next.done) {
|
|
436
|
-
return currentGenerator;
|
|
437
|
-
}
|
|
438
|
-
const bestBidPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
|
|
439
|
-
const currentBidPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
|
|
440
|
-
return bestBidPrice.gt(currentBidPrice)
|
|
441
|
-
? bestGenerator
|
|
442
|
-
: currentGenerator;
|
|
443
|
-
});
|
|
444
|
-
if (!bestGenerator.next.done) {
|
|
445
|
-
// skip this node if it's already completely filled
|
|
446
|
-
if (bestGenerator.next.value.isBaseFilled()) {
|
|
447
|
-
bestGenerator.next = bestGenerator.generator.next();
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
// skip order if user is being liquidated/bankrupt
|
|
451
|
-
if (bestGenerator.next.value.userAccount !== undefined) {
|
|
452
|
-
const user = this.userMap.get(bestGenerator.next.value.userAccount.toString());
|
|
453
|
-
if (user === null || user === void 0 ? void 0 : user.isBeingLiquidated()) {
|
|
454
|
-
bestGenerator.next = bestGenerator.generator.next();
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
yield bestGenerator.next.value;
|
|
459
|
-
bestGenerator.next = bestGenerator.generator.next();
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
bidsExhausted = true;
|
|
463
|
-
}
|
|
534
|
+
}
|
|
535
|
+
*getBids(marketIndex, fallbackBid, slot, marketType, oraclePriceData) {
|
|
536
|
+
if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
|
|
537
|
+
throw new Error('Must provide OraclePriceData to get spot bids');
|
|
464
538
|
}
|
|
539
|
+
const generatorList = [
|
|
540
|
+
this.getMarketBids(marketIndex, marketType),
|
|
541
|
+
this.getLimitBids(marketIndex, slot, marketType, oraclePriceData),
|
|
542
|
+
];
|
|
543
|
+
const marketTypeStr = (0, __1.getVariant)(marketType);
|
|
544
|
+
if (marketTypeStr === 'perp' && fallbackBid) {
|
|
545
|
+
generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackBid));
|
|
546
|
+
}
|
|
547
|
+
yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
|
|
548
|
+
return bestPrice.gt(currentPrice);
|
|
549
|
+
});
|
|
465
550
|
}
|
|
466
|
-
|
|
551
|
+
findCrossingLimitOrders(askNode, bidNode, oraclePriceData, slot) {
|
|
467
552
|
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
468
553
|
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
469
554
|
// orders don't cross - we're done walkin gup the book
|
|
@@ -488,15 +573,7 @@ class DLOB {
|
|
|
488
573
|
exhaustedSide,
|
|
489
574
|
};
|
|
490
575
|
}
|
|
491
|
-
const { takerNode, makerNode
|
|
492
|
-
// If maker is market order and auction is complete, cant be maker
|
|
493
|
-
if ((0, __1.isMarketOrder)(makerNode.order) &&
|
|
494
|
-
(0, __1.isAuctionComplete)(makerNode.order, slot)) {
|
|
495
|
-
return {
|
|
496
|
-
crossingNodes: [],
|
|
497
|
-
exhaustedSide: makerSide,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
576
|
+
const { takerNode, makerNode } = this.determineMakerAndTaker(askNode, bidNode);
|
|
500
577
|
const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
|
|
501
578
|
const askBaseRemaining = askOrder.baseAssetAmount.sub(askOrder.baseAssetAmountFilled);
|
|
502
579
|
let exhaustedSide;
|
|
@@ -570,20 +647,6 @@ class DLOB {
|
|
|
570
647
|
makerSide: 'ask',
|
|
571
648
|
};
|
|
572
649
|
}
|
|
573
|
-
else if ((0, __1.isMarketOrder)(bidNode.order) && (0, __1.isLimitOrder)(askNode.order)) {
|
|
574
|
-
return {
|
|
575
|
-
takerNode: bidNode,
|
|
576
|
-
makerNode: askNode,
|
|
577
|
-
makerSide: 'ask',
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
else if ((0, __1.isMarketOrder)(askNode.order) && (0, __1.isLimitOrder)(bidNode.order)) {
|
|
581
|
-
return {
|
|
582
|
-
takerNode: askNode,
|
|
583
|
-
makerNode: bidNode,
|
|
584
|
-
makerSide: 'bid',
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
650
|
else if (askNode.order.slot.lt(bidNode.order.slot)) {
|
|
588
651
|
return {
|
|
589
652
|
takerNode: bidNode,
|
package/lib/driftClient.d.ts
CHANGED
|
@@ -61,9 +61,23 @@ export declare class DriftClient {
|
|
|
61
61
|
signerPublicKey?: PublicKey;
|
|
62
62
|
getSignerPublicKey(): PublicKey;
|
|
63
63
|
getStateAccount(): StateAccount;
|
|
64
|
+
/**
|
|
65
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
66
|
+
*/
|
|
67
|
+
forceGetStateAccount(): Promise<StateAccount>;
|
|
64
68
|
getPerpMarketAccount(marketIndex: number): PerpMarketAccount | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
71
|
+
* @param marketIndex
|
|
72
|
+
*/
|
|
73
|
+
forceGetPerpMarketAccount(marketIndex: number): Promise<PerpMarketAccount | undefined>;
|
|
65
74
|
getPerpMarketAccounts(): PerpMarketAccount[];
|
|
66
75
|
getSpotMarketAccount(marketIndex: number): SpotMarketAccount | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
78
|
+
* @param marketIndex
|
|
79
|
+
*/
|
|
80
|
+
forceGetSpotMarketAccount(marketIndex: number): Promise<SpotMarketAccount | undefined>;
|
|
67
81
|
getSpotMarketAccounts(): SpotMarketAccount[];
|
|
68
82
|
getQuoteSpotMarketAccount(): SpotMarketAccount;
|
|
69
83
|
getOraclePriceDataAndSlot(oraclePublicKey: PublicKey): DataAndSlot<OraclePriceData> | undefined;
|
|
@@ -94,6 +108,11 @@ export declare class DriftClient {
|
|
|
94
108
|
getUserStatsAccountPublicKey(): PublicKey;
|
|
95
109
|
getUserAccountPublicKey(): Promise<PublicKey>;
|
|
96
110
|
getUserAccount(subAccountId?: number): UserAccount | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
113
|
+
* @param subAccountId
|
|
114
|
+
*/
|
|
115
|
+
forceGetUserAccount(subAccountId?: number): Promise<UserAccount | undefined>;
|
|
97
116
|
getUserAccountAndSlot(subAccountId?: number): DataAndSlot<UserAccount> | undefined;
|
|
98
117
|
getSpotPosition(marketIndex: number): SpotPosition | undefined;
|
|
99
118
|
getQuoteAssetTokenAmount(): BN;
|
package/lib/driftClient.js
CHANGED
|
@@ -177,10 +177,26 @@ class DriftClient {
|
|
|
177
177
|
getStateAccount() {
|
|
178
178
|
return this.accountSubscriber.getStateAccountAndSlot().data;
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
182
|
+
*/
|
|
183
|
+
async forceGetStateAccount() {
|
|
184
|
+
await this.accountSubscriber.fetch();
|
|
185
|
+
return this.accountSubscriber.getStateAccountAndSlot().data;
|
|
186
|
+
}
|
|
180
187
|
getPerpMarketAccount(marketIndex) {
|
|
181
188
|
var _a;
|
|
182
189
|
return (_a = this.accountSubscriber.getMarketAccountAndSlot(marketIndex)) === null || _a === void 0 ? void 0 : _a.data;
|
|
183
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
193
|
+
* @param marketIndex
|
|
194
|
+
*/
|
|
195
|
+
async forceGetPerpMarketAccount(marketIndex) {
|
|
196
|
+
var _a;
|
|
197
|
+
await this.accountSubscriber.fetch();
|
|
198
|
+
return (_a = this.accountSubscriber.getMarketAccountAndSlot(marketIndex)) === null || _a === void 0 ? void 0 : _a.data;
|
|
199
|
+
}
|
|
184
200
|
getPerpMarketAccounts() {
|
|
185
201
|
return this.accountSubscriber
|
|
186
202
|
.getMarketAccountsAndSlots()
|
|
@@ -189,6 +205,14 @@ class DriftClient {
|
|
|
189
205
|
getSpotMarketAccount(marketIndex) {
|
|
190
206
|
return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex).data;
|
|
191
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
210
|
+
* @param marketIndex
|
|
211
|
+
*/
|
|
212
|
+
async forceGetSpotMarketAccount(marketIndex) {
|
|
213
|
+
await this.accountSubscriber.fetch();
|
|
214
|
+
return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex).data;
|
|
215
|
+
}
|
|
192
216
|
getSpotMarketAccounts() {
|
|
193
217
|
return this.accountSubscriber
|
|
194
218
|
.getSpotMarketAccountsAndSlots()
|
|
@@ -414,6 +438,14 @@ class DriftClient {
|
|
|
414
438
|
getUserAccount(subAccountId) {
|
|
415
439
|
return this.getUser(subAccountId).getUserAccount();
|
|
416
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Forces a fetch to rpc before returning accounts. Useful for anchor tests.
|
|
443
|
+
* @param subAccountId
|
|
444
|
+
*/
|
|
445
|
+
async forceGetUserAccount(subAccountId) {
|
|
446
|
+
await this.getUser(subAccountId).fetchAccounts();
|
|
447
|
+
return this.getUser(subAccountId).getUserAccount();
|
|
448
|
+
}
|
|
417
449
|
getUserAccountAndSlot(subAccountId) {
|
|
418
450
|
return this.getUser(subAccountId).getUserAccountAndSlot();
|
|
419
451
|
}
|
package/lib/math/orders.d.ts
CHANGED
|
@@ -7,8 +7,7 @@ export declare function isOrderRiskIncreasing(user: User, order: Order): boolean
|
|
|
7
7
|
export declare function isOrderRiskIncreasingInSameDirection(user: User, order: Order): boolean;
|
|
8
8
|
export declare function isOrderReduceOnly(user: User, order: Order): boolean;
|
|
9
9
|
export declare function standardizeBaseAssetAmount(baseAssetAmount: BN, stepSize: BN): BN;
|
|
10
|
-
export declare function getLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number): BN;
|
|
11
|
-
export declare function getOptionalLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number): BN | undefined;
|
|
10
|
+
export declare function getLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number, fallbackPrice?: BN): BN | undefined;
|
|
12
11
|
export declare function hasLimitPrice(order: Order, slot: number): boolean;
|
|
13
12
|
export declare function isFillableByVAMM(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number, ts: number): boolean;
|
|
14
13
|
export declare function calculateBaseAssetAmountForAmmToFulfill(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number): BN;
|
package/lib/math/orders.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isTriggered = exports.mustBeTriggered = exports.isLimitOrder = exports.isMarketOrder = exports.isOrderExpired = exports.calculateBaseAssetAmountToFillUpToLimitPrice = exports.calculateBaseAssetAmountForAmmToFulfill = exports.isFillableByVAMM = exports.hasLimitPrice = exports.
|
|
3
|
+
exports.isTriggered = exports.mustBeTriggered = exports.isLimitOrder = exports.isMarketOrder = exports.isOrderExpired = exports.calculateBaseAssetAmountToFillUpToLimitPrice = exports.calculateBaseAssetAmountForAmmToFulfill = exports.isFillableByVAMM = exports.hasLimitPrice = exports.getLimitPrice = exports.standardizeBaseAssetAmount = exports.isOrderReduceOnly = exports.isOrderRiskIncreasingInSameDirection = exports.isOrderRiskIncreasing = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
6
6
|
const anchor_1 = require("@project-serum/anchor");
|
|
@@ -79,7 +79,7 @@ function standardizeBaseAssetAmount(baseAssetAmount, stepSize) {
|
|
|
79
79
|
return baseAssetAmount.sub(remainder);
|
|
80
80
|
}
|
|
81
81
|
exports.standardizeBaseAssetAmount = standardizeBaseAssetAmount;
|
|
82
|
-
function getLimitPrice(order, oraclePriceData, slot) {
|
|
82
|
+
function getLimitPrice(order, oraclePriceData, slot, fallbackPrice) {
|
|
83
83
|
let limitPrice;
|
|
84
84
|
if (order.oraclePriceOffset !== 0) {
|
|
85
85
|
limitPrice = oraclePriceData.price.add(new anchor_1.BN(order.oraclePriceOffset));
|
|
@@ -92,14 +92,7 @@ function getLimitPrice(order, oraclePriceData, slot) {
|
|
|
92
92
|
limitPrice = order.price;
|
|
93
93
|
}
|
|
94
94
|
else {
|
|
95
|
-
|
|
96
|
-
const oraclePrice1Pct = oraclePriceData.price.div(new anchor_1.BN(100));
|
|
97
|
-
if ((0, types_1.isVariant)(order.direction, 'long')) {
|
|
98
|
-
limitPrice = oraclePriceData.price.add(oraclePrice1Pct);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
limitPrice = oraclePriceData.price.sub(oraclePrice1Pct);
|
|
102
|
-
}
|
|
95
|
+
limitPrice = fallbackPrice;
|
|
103
96
|
}
|
|
104
97
|
}
|
|
105
98
|
else {
|
|
@@ -108,15 +101,6 @@ function getLimitPrice(order, oraclePriceData, slot) {
|
|
|
108
101
|
return limitPrice;
|
|
109
102
|
}
|
|
110
103
|
exports.getLimitPrice = getLimitPrice;
|
|
111
|
-
function getOptionalLimitPrice(order, oraclePriceData, slot) {
|
|
112
|
-
if (hasLimitPrice(order, slot)) {
|
|
113
|
-
return getLimitPrice(order, oraclePriceData, slot);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
exports.getOptionalLimitPrice = getOptionalLimitPrice;
|
|
120
104
|
function hasLimitPrice(order, slot) {
|
|
121
105
|
return (order.price.gt(numericConstants_1.ZERO) ||
|
|
122
106
|
order.oraclePriceOffset != 0 ||
|
|
@@ -133,7 +117,7 @@ function calculateBaseAssetAmountForAmmToFulfill(order, market, oraclePriceData,
|
|
|
133
117
|
if (mustBeTriggered(order) && !isTriggered(order)) {
|
|
134
118
|
return numericConstants_1.ZERO;
|
|
135
119
|
}
|
|
136
|
-
const limitPrice =
|
|
120
|
+
const limitPrice = getLimitPrice(order, oraclePriceData, slot);
|
|
137
121
|
let baseAssetAmount;
|
|
138
122
|
const updatedAMM = (0, amm_1.calculateUpdatedAMM)(market.amm, oraclePriceData);
|
|
139
123
|
if (limitPrice !== undefined) {
|