@drift-labs/sdk 2.74.0-beta.6 → 2.74.0-beta.8
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/VERSION +1 -1
- package/lib/dlob/orderBookLevels.js +41 -12
- package/lib/user.d.ts +6 -4
- package/lib/user.js +24 -21
- package/package.json +1 -1
- package/src/dlob/orderBookLevels.ts +45 -15
- package/src/user.ts +32 -30
- package/tests/dlob/test.ts +57 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.74.0-beta.
|
|
1
|
+
2.74.0-beta.8
|
|
@@ -261,6 +261,31 @@ function groupL2Levels(levels, grouping, direction, depth) {
|
|
|
261
261
|
}
|
|
262
262
|
return groupedLevels;
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Method to merge bids or asks by price
|
|
266
|
+
*/
|
|
267
|
+
const mergeByPrice = (bidsOrAsks) => {
|
|
268
|
+
const merged = new Map();
|
|
269
|
+
for (const level of bidsOrAsks) {
|
|
270
|
+
const key = level.price.toString();
|
|
271
|
+
if (merged.has(key)) {
|
|
272
|
+
const existing = merged.get(key);
|
|
273
|
+
existing.size = existing.size.add(level.size);
|
|
274
|
+
for (const [source, size] of Object.entries(level.sources)) {
|
|
275
|
+
if (existing.sources[source]) {
|
|
276
|
+
existing.sources[source] = existing.sources[source].add(size);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
existing.sources[source] = size;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
merged.set(key, cloneL2Level(level));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return Array.from(merged.values());
|
|
288
|
+
};
|
|
264
289
|
/**
|
|
265
290
|
* The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
|
|
266
291
|
* This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
|
|
@@ -342,17 +367,17 @@ function uncrossL2(bids, asks, oraclePrice, oracleTwap5Min, markTwap5Min, groupi
|
|
|
342
367
|
bidIndex++;
|
|
343
368
|
continue;
|
|
344
369
|
}
|
|
370
|
+
if (userBids.has(nextBid.price.toString())) {
|
|
371
|
+
newBids.push(nextBid);
|
|
372
|
+
bidIndex++;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (userAsks.has(nextAsk.price.toString())) {
|
|
376
|
+
newAsks.push(nextAsk);
|
|
377
|
+
askIndex++;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
345
380
|
if (nextBid.price.gte(nextAsk.price)) {
|
|
346
|
-
if (userBids.has(nextBid.price.toString())) {
|
|
347
|
-
newBids.push(nextBid);
|
|
348
|
-
bidIndex++;
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
if (userAsks.has(nextAsk.price.toString())) {
|
|
352
|
-
newAsks.push(nextAsk);
|
|
353
|
-
askIndex++;
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
381
|
if (nextBid.price.gt(referencePrice) &&
|
|
357
382
|
nextAsk.price.gt(referencePrice)) {
|
|
358
383
|
let newBidPrice = nextAsk.price.sub(grouping);
|
|
@@ -397,9 +422,13 @@ function uncrossL2(bids, asks, oraclePrice, oracleTwap5Min, markTwap5Min, groupi
|
|
|
397
422
|
bidIndex++;
|
|
398
423
|
}
|
|
399
424
|
}
|
|
425
|
+
newBids.sort((a, b) => b.price.cmp(a.price));
|
|
426
|
+
newAsks.sort((a, b) => a.price.cmp(b.price));
|
|
427
|
+
const finalNewBids = mergeByPrice(newBids);
|
|
428
|
+
const finalNewAsks = mergeByPrice(newAsks);
|
|
400
429
|
return {
|
|
401
|
-
bids:
|
|
402
|
-
asks:
|
|
430
|
+
bids: finalNewBids,
|
|
431
|
+
asks: finalNewAsks,
|
|
403
432
|
};
|
|
404
433
|
}
|
|
405
434
|
exports.uncrossL2 = uncrossL2;
|
package/lib/user.d.ts
CHANGED
|
@@ -246,13 +246,15 @@ export declare class User {
|
|
|
246
246
|
/**
|
|
247
247
|
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
248
248
|
* @param marketIndex
|
|
249
|
-
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^
|
|
249
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^9
|
|
250
|
+
* @param estimatedEntryPrice
|
|
250
251
|
* @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
|
|
252
|
+
* @param includeOpenOrders
|
|
251
253
|
* @returns Precision : PRICE_PRECISION
|
|
252
254
|
*/
|
|
253
|
-
liquidationPrice(marketIndex: number, positionBaseSizeChange?: BN, estimatedEntryPrice?: BN, marginCategory?: MarginCategory): BN;
|
|
254
|
-
calculateEntriesEffectOnFreeCollateral(market: PerpMarketAccount, oraclePrice: BN, perpPosition: PerpPosition, positionBaseSizeChange: BN, estimatedEntryPrice: BN): BN;
|
|
255
|
-
calculateFreeCollateralDeltaForPerp(market: PerpMarketAccount, perpPosition: PerpPosition, positionBaseSizeChange: BN, marginCategory?: MarginCategory): BN | undefined;
|
|
255
|
+
liquidationPrice(marketIndex: number, positionBaseSizeChange?: BN, estimatedEntryPrice?: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean): BN;
|
|
256
|
+
calculateEntriesEffectOnFreeCollateral(market: PerpMarketAccount, oraclePrice: BN, perpPosition: PerpPosition, positionBaseSizeChange: BN, estimatedEntryPrice: BN, includeOpenOrders: boolean): BN;
|
|
257
|
+
calculateFreeCollateralDeltaForPerp(market: PerpMarketAccount, perpPosition: PerpPosition, positionBaseSizeChange: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean): BN | undefined;
|
|
256
258
|
calculateFreeCollateralDeltaForSpot(market: SpotMarketAccount, signedTokenAmount: BN, marginCategory?: MarginCategory): BN;
|
|
257
259
|
/**
|
|
258
260
|
* Calculates the estimated liquidation price for a position after closing a quote amount of the position.
|
package/lib/user.js
CHANGED
|
@@ -1082,13 +1082,15 @@ class User {
|
|
|
1082
1082
|
/**
|
|
1083
1083
|
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
1084
1084
|
* @param marketIndex
|
|
1085
|
-
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^
|
|
1085
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^9
|
|
1086
|
+
* @param estimatedEntryPrice
|
|
1086
1087
|
* @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
|
|
1088
|
+
* @param includeOpenOrders
|
|
1087
1089
|
* @returns Precision : PRICE_PRECISION
|
|
1088
1090
|
*/
|
|
1089
|
-
liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance') {
|
|
1091
|
+
liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false) {
|
|
1090
1092
|
const totalCollateral = this.getTotalCollateral(marginCategory);
|
|
1091
|
-
const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false);
|
|
1093
|
+
const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders);
|
|
1092
1094
|
let freeCollateral = _1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement));
|
|
1093
1095
|
const oracle = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
1094
1096
|
const oraclePrice = this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
|
|
@@ -1096,9 +1098,9 @@ class User {
|
|
|
1096
1098
|
const currentPerpPosition = this.getPerpPositionWithLPSettle(marketIndex, undefined, true)[0] ||
|
|
1097
1099
|
this.getEmptyPosition(marketIndex);
|
|
1098
1100
|
positionBaseSizeChange = (0, _1.standardizeBaseAssetAmount)(positionBaseSizeChange, market.amm.orderStepSize);
|
|
1099
|
-
const freeCollateralChangeFromNewPosition = this.calculateEntriesEffectOnFreeCollateral(market, oraclePrice, currentPerpPosition, positionBaseSizeChange, estimatedEntryPrice);
|
|
1101
|
+
const freeCollateralChangeFromNewPosition = this.calculateEntriesEffectOnFreeCollateral(market, oraclePrice, currentPerpPosition, positionBaseSizeChange, estimatedEntryPrice, includeOpenOrders);
|
|
1100
1102
|
freeCollateral = freeCollateral.add(freeCollateralChangeFromNewPosition);
|
|
1101
|
-
let freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(market, currentPerpPosition, positionBaseSizeChange, marginCategory);
|
|
1103
|
+
let freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(market, currentPerpPosition, positionBaseSizeChange, marginCategory, includeOpenOrders);
|
|
1102
1104
|
if (!freeCollateralDelta) {
|
|
1103
1105
|
return new _1.BN(-1);
|
|
1104
1106
|
}
|
|
@@ -1125,7 +1127,7 @@ class User {
|
|
|
1125
1127
|
}
|
|
1126
1128
|
return liqPrice;
|
|
1127
1129
|
}
|
|
1128
|
-
calculateEntriesEffectOnFreeCollateral(market, oraclePrice, perpPosition, positionBaseSizeChange, estimatedEntryPrice) {
|
|
1130
|
+
calculateEntriesEffectOnFreeCollateral(market, oraclePrice, perpPosition, positionBaseSizeChange, estimatedEntryPrice, includeOpenOrders) {
|
|
1129
1131
|
let freeCollateralChange = numericConstants_1.ZERO;
|
|
1130
1132
|
// update free collateral to account for change in pnl from new position
|
|
1131
1133
|
if (!estimatedEntryPrice.eq(numericConstants_1.ZERO) && !positionBaseSizeChange.eq(numericConstants_1.ZERO)) {
|
|
@@ -1139,8 +1141,6 @@ class User {
|
|
|
1139
1141
|
freeCollateralChange = costBasis.sub(newPositionValue);
|
|
1140
1142
|
}
|
|
1141
1143
|
else {
|
|
1142
|
-
console.log('newPositionValue', newPositionValue.toString());
|
|
1143
|
-
console.log('costBasis', costBasis.toString());
|
|
1144
1144
|
freeCollateralChange = newPositionValue.sub(costBasis);
|
|
1145
1145
|
}
|
|
1146
1146
|
// assume worst fee tier
|
|
@@ -1150,30 +1150,33 @@ class User {
|
|
|
1150
1150
|
.divn(takerFeeTier.feeDenominator);
|
|
1151
1151
|
freeCollateralChange = freeCollateralChange.sub(takerFee);
|
|
1152
1152
|
}
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1153
|
+
const baseAssetAmount = includeOpenOrders
|
|
1154
|
+
? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
|
|
1155
|
+
: perpPosition.baseAssetAmount;
|
|
1156
|
+
const newBaseAssetAmount = baseAssetAmount.add(positionBaseSizeChange);
|
|
1157
|
+
const newMarginRatio = (0, _1.calculateMarketMarginRatio)(market, newBaseAssetAmount.abs(), 'Maintenance');
|
|
1156
1158
|
// update free collateral to account for new margin requirement from position change
|
|
1157
|
-
freeCollateralChange = freeCollateralChange.sub(
|
|
1159
|
+
freeCollateralChange = freeCollateralChange.sub(newBaseAssetAmount
|
|
1158
1160
|
.abs()
|
|
1159
|
-
.sub(
|
|
1161
|
+
.sub(baseAssetAmount.abs())
|
|
1160
1162
|
.mul(oraclePrice)
|
|
1161
1163
|
.div(numericConstants_1.BASE_PRECISION)
|
|
1162
1164
|
.mul(new _1.BN(newMarginRatio))
|
|
1163
1165
|
.div(numericConstants_1.MARGIN_PRECISION));
|
|
1164
1166
|
return freeCollateralChange;
|
|
1165
1167
|
}
|
|
1166
|
-
calculateFreeCollateralDeltaForPerp(market, perpPosition, positionBaseSizeChange, marginCategory = 'Maintenance') {
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1168
|
+
calculateFreeCollateralDeltaForPerp(market, perpPosition, positionBaseSizeChange, marginCategory = 'Maintenance', includeOpenOrders = false) {
|
|
1169
|
+
const baseAssetAmount = includeOpenOrders
|
|
1170
|
+
? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
|
|
1171
|
+
: perpPosition.baseAssetAmount;
|
|
1172
|
+
// zero if include orders == false
|
|
1173
|
+
const orderBaseAssetAmount = baseAssetAmount.sub(perpPosition.baseAssetAmount);
|
|
1174
|
+
const proposedBaseAssetAmount = baseAssetAmount.add(positionBaseSizeChange);
|
|
1175
|
+
const marginRatio = (0, _1.calculateMarketMarginRatio)(market, proposedBaseAssetAmount.abs(), marginCategory, this.getUserAccount().maxMarginRatio);
|
|
1173
1176
|
const marginRatioQuotePrecision = new _1.BN(marginRatio)
|
|
1174
1177
|
.mul(numericConstants_1.QUOTE_PRECISION)
|
|
1175
1178
|
.div(numericConstants_1.MARGIN_PRECISION);
|
|
1176
|
-
if (
|
|
1179
|
+
if (proposedBaseAssetAmount.eq(numericConstants_1.ZERO)) {
|
|
1177
1180
|
return undefined;
|
|
1178
1181
|
}
|
|
1179
1182
|
let freeCollateralDelta = numericConstants_1.ZERO;
|
package/package.json
CHANGED
|
@@ -429,6 +429,30 @@ function groupL2Levels(
|
|
|
429
429
|
return groupedLevels;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Method to merge bids or asks by price
|
|
434
|
+
*/
|
|
435
|
+
const mergeByPrice = (bidsOrAsks: L2Level[]) => {
|
|
436
|
+
const merged = new Map<string, L2Level>();
|
|
437
|
+
for (const level of bidsOrAsks) {
|
|
438
|
+
const key = level.price.toString();
|
|
439
|
+
if (merged.has(key)) {
|
|
440
|
+
const existing = merged.get(key);
|
|
441
|
+
existing.size = existing.size.add(level.size);
|
|
442
|
+
for (const [source, size] of Object.entries(level.sources)) {
|
|
443
|
+
if (existing.sources[source]) {
|
|
444
|
+
existing.sources[source] = existing.sources[source].add(size);
|
|
445
|
+
} else {
|
|
446
|
+
existing.sources[source] = size;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} else {
|
|
450
|
+
merged.set(key, cloneL2Level(level));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return Array.from(merged.values());
|
|
454
|
+
};
|
|
455
|
+
|
|
432
456
|
/**
|
|
433
457
|
* The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
|
|
434
458
|
* This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
|
|
@@ -467,8 +491,8 @@ export function uncrossL2(
|
|
|
467
491
|
return { bids, asks };
|
|
468
492
|
}
|
|
469
493
|
|
|
470
|
-
const newBids = [];
|
|
471
|
-
const newAsks = [];
|
|
494
|
+
const newBids: L2Level[] = [];
|
|
495
|
+
const newAsks: L2Level[] = [];
|
|
472
496
|
|
|
473
497
|
const updateLevels = (newPrice: BN, oldLevel: L2Level, levels: L2Level[]) => {
|
|
474
498
|
if (levels.length > 0 && levels[levels.length - 1].price.eq(newPrice)) {
|
|
@@ -528,19 +552,19 @@ export function uncrossL2(
|
|
|
528
552
|
continue;
|
|
529
553
|
}
|
|
530
554
|
|
|
531
|
-
if (nextBid.price.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
555
|
+
if (userBids.has(nextBid.price.toString())) {
|
|
556
|
+
newBids.push(nextBid);
|
|
557
|
+
bidIndex++;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
537
560
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
561
|
+
if (userAsks.has(nextAsk.price.toString())) {
|
|
562
|
+
newAsks.push(nextAsk);
|
|
563
|
+
askIndex++;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
543
566
|
|
|
567
|
+
if (nextBid.price.gte(nextAsk.price)) {
|
|
544
568
|
if (
|
|
545
569
|
nextBid.price.gt(referencePrice) &&
|
|
546
570
|
nextAsk.price.gt(referencePrice)
|
|
@@ -594,8 +618,14 @@ export function uncrossL2(
|
|
|
594
618
|
}
|
|
595
619
|
}
|
|
596
620
|
|
|
621
|
+
newBids.sort((a, b) => b.price.cmp(a.price));
|
|
622
|
+
newAsks.sort((a, b) => a.price.cmp(b.price));
|
|
623
|
+
|
|
624
|
+
const finalNewBids = mergeByPrice(newBids);
|
|
625
|
+
const finalNewAsks = mergeByPrice(newAsks);
|
|
626
|
+
|
|
597
627
|
return {
|
|
598
|
-
bids:
|
|
599
|
-
asks:
|
|
628
|
+
bids: finalNewBids,
|
|
629
|
+
asks: finalNewAsks,
|
|
600
630
|
};
|
|
601
631
|
}
|
package/src/user.ts
CHANGED
|
@@ -1975,21 +1975,25 @@ export class User {
|
|
|
1975
1975
|
/**
|
|
1976
1976
|
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
1977
1977
|
* @param marketIndex
|
|
1978
|
-
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^
|
|
1978
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^9
|
|
1979
|
+
* @param estimatedEntryPrice
|
|
1979
1980
|
* @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
|
|
1981
|
+
* @param includeOpenOrders
|
|
1980
1982
|
* @returns Precision : PRICE_PRECISION
|
|
1981
1983
|
*/
|
|
1982
1984
|
public liquidationPrice(
|
|
1983
1985
|
marketIndex: number,
|
|
1984
1986
|
positionBaseSizeChange: BN = ZERO,
|
|
1985
1987
|
estimatedEntryPrice: BN = ZERO,
|
|
1986
|
-
marginCategory: MarginCategory = 'Maintenance'
|
|
1988
|
+
marginCategory: MarginCategory = 'Maintenance',
|
|
1989
|
+
includeOpenOrders = false
|
|
1987
1990
|
): BN {
|
|
1988
1991
|
const totalCollateral = this.getTotalCollateral(marginCategory);
|
|
1989
1992
|
const marginRequirement = this.getMarginRequirement(
|
|
1990
1993
|
marginCategory,
|
|
1991
1994
|
undefined,
|
|
1992
|
-
false
|
|
1995
|
+
false,
|
|
1996
|
+
includeOpenOrders
|
|
1993
1997
|
);
|
|
1994
1998
|
let freeCollateral = BN.max(ZERO, totalCollateral.sub(marginRequirement));
|
|
1995
1999
|
|
|
@@ -2015,7 +2019,8 @@ export class User {
|
|
|
2015
2019
|
oraclePrice,
|
|
2016
2020
|
currentPerpPosition,
|
|
2017
2021
|
positionBaseSizeChange,
|
|
2018
|
-
estimatedEntryPrice
|
|
2022
|
+
estimatedEntryPrice,
|
|
2023
|
+
includeOpenOrders
|
|
2019
2024
|
);
|
|
2020
2025
|
|
|
2021
2026
|
freeCollateral = freeCollateral.add(freeCollateralChangeFromNewPosition);
|
|
@@ -2024,7 +2029,8 @@ export class User {
|
|
|
2024
2029
|
market,
|
|
2025
2030
|
currentPerpPosition,
|
|
2026
2031
|
positionBaseSizeChange,
|
|
2027
|
-
marginCategory
|
|
2032
|
+
marginCategory,
|
|
2033
|
+
includeOpenOrders
|
|
2028
2034
|
);
|
|
2029
2035
|
|
|
2030
2036
|
if (!freeCollateralDelta) {
|
|
@@ -2082,7 +2088,8 @@ export class User {
|
|
|
2082
2088
|
oraclePrice: BN,
|
|
2083
2089
|
perpPosition: PerpPosition,
|
|
2084
2090
|
positionBaseSizeChange: BN,
|
|
2085
|
-
estimatedEntryPrice: BN
|
|
2091
|
+
estimatedEntryPrice: BN,
|
|
2092
|
+
includeOpenOrders: boolean
|
|
2086
2093
|
): BN {
|
|
2087
2094
|
let freeCollateralChange = ZERO;
|
|
2088
2095
|
|
|
@@ -2097,8 +2104,6 @@ export class User {
|
|
|
2097
2104
|
if (positionBaseSizeChange.gt(ZERO)) {
|
|
2098
2105
|
freeCollateralChange = costBasis.sub(newPositionValue);
|
|
2099
2106
|
} else {
|
|
2100
|
-
console.log('newPositionValue', newPositionValue.toString());
|
|
2101
|
-
console.log('costBasis', costBasis.toString());
|
|
2102
2107
|
freeCollateralChange = newPositionValue.sub(costBasis);
|
|
2103
2108
|
}
|
|
2104
2109
|
|
|
@@ -2111,24 +2116,23 @@ export class User {
|
|
|
2111
2116
|
freeCollateralChange = freeCollateralChange.sub(takerFee);
|
|
2112
2117
|
}
|
|
2113
2118
|
|
|
2114
|
-
const
|
|
2115
|
-
calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
2119
|
+
const baseAssetAmount = includeOpenOrders
|
|
2120
|
+
? calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
2121
|
+
: perpPosition.baseAssetAmount;
|
|
2116
2122
|
|
|
2117
|
-
const
|
|
2118
|
-
positionBaseSizeChange
|
|
2119
|
-
);
|
|
2123
|
+
const newBaseAssetAmount = baseAssetAmount.add(positionBaseSizeChange);
|
|
2120
2124
|
|
|
2121
2125
|
const newMarginRatio = calculateMarketMarginRatio(
|
|
2122
2126
|
market,
|
|
2123
|
-
|
|
2127
|
+
newBaseAssetAmount.abs(),
|
|
2124
2128
|
'Maintenance'
|
|
2125
2129
|
);
|
|
2126
2130
|
|
|
2127
2131
|
// update free collateral to account for new margin requirement from position change
|
|
2128
2132
|
freeCollateralChange = freeCollateralChange.sub(
|
|
2129
|
-
|
|
2133
|
+
newBaseAssetAmount
|
|
2130
2134
|
.abs()
|
|
2131
|
-
.sub(
|
|
2135
|
+
.sub(baseAssetAmount.abs())
|
|
2132
2136
|
.mul(oraclePrice)
|
|
2133
2137
|
.div(BASE_PRECISION)
|
|
2134
2138
|
.mul(new BN(newMarginRatio))
|
|
@@ -2142,25 +2146,23 @@ export class User {
|
|
|
2142
2146
|
market: PerpMarketAccount,
|
|
2143
2147
|
perpPosition: PerpPosition,
|
|
2144
2148
|
positionBaseSizeChange: BN,
|
|
2145
|
-
marginCategory: MarginCategory = 'Maintenance'
|
|
2149
|
+
marginCategory: MarginCategory = 'Maintenance',
|
|
2150
|
+
includeOpenOrders = false
|
|
2146
2151
|
): BN | undefined {
|
|
2147
|
-
const
|
|
2152
|
+
const baseAssetAmount = includeOpenOrders
|
|
2153
|
+
? calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
2154
|
+
: perpPosition.baseAssetAmount;
|
|
2148
2155
|
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
currentBaseAssetAmount
|
|
2153
|
-
);
|
|
2154
|
-
const proposedBaseAssetAmount = currentBaseAssetAmount.add(
|
|
2155
|
-
positionBaseSizeChange
|
|
2156
|
-
);
|
|
2157
|
-
const proposedWorstCaseBaseAssetAmount = worstCaseBaseAssetAmount.add(
|
|
2158
|
-
positionBaseSizeChange
|
|
2156
|
+
// zero if include orders == false
|
|
2157
|
+
const orderBaseAssetAmount = baseAssetAmount.sub(
|
|
2158
|
+
perpPosition.baseAssetAmount
|
|
2159
2159
|
);
|
|
2160
2160
|
|
|
2161
|
+
const proposedBaseAssetAmount = baseAssetAmount.add(positionBaseSizeChange);
|
|
2162
|
+
|
|
2161
2163
|
const marginRatio = calculateMarketMarginRatio(
|
|
2162
2164
|
market,
|
|
2163
|
-
|
|
2165
|
+
proposedBaseAssetAmount.abs(),
|
|
2164
2166
|
marginCategory,
|
|
2165
2167
|
this.getUserAccount().maxMarginRatio
|
|
2166
2168
|
);
|
|
@@ -2168,7 +2170,7 @@ export class User {
|
|
|
2168
2170
|
.mul(QUOTE_PRECISION)
|
|
2169
2171
|
.div(MARGIN_PRECISION);
|
|
2170
2172
|
|
|
2171
|
-
if (
|
|
2173
|
+
if (proposedBaseAssetAmount.eq(ZERO)) {
|
|
2172
2174
|
return undefined;
|
|
2173
2175
|
}
|
|
2174
2176
|
|
package/tests/dlob/test.ts
CHANGED
|
@@ -6614,6 +6614,63 @@ describe('Uncross L2', () => {
|
|
|
6614
6614
|
);
|
|
6615
6615
|
});
|
|
6616
6616
|
|
|
6617
|
+
it('Handles user crossing bid in second level', () => {
|
|
6618
|
+
const oraclePrice = new BN(190.3843 * PRICE_PRECISION.toNumber());
|
|
6619
|
+
const bids = [
|
|
6620
|
+
[190.59, 2],
|
|
6621
|
+
[190.588, 58.3],
|
|
6622
|
+
[190.5557, 5],
|
|
6623
|
+
[190.5547, 5],
|
|
6624
|
+
[190.5508, 5],
|
|
6625
|
+
[190.541, 2],
|
|
6626
|
+
[190.5099, 49.1],
|
|
6627
|
+
[190.5, 60],
|
|
6628
|
+
].map(([price, size]) => ({
|
|
6629
|
+
price: new BN(price * PRICE_PRECISION.toNumber()),
|
|
6630
|
+
size: new BN(size * BASE_PRECISION.toNumber()),
|
|
6631
|
+
sources: { vamm: new BN(size * BASE_PRECISION.toNumber()) },
|
|
6632
|
+
}));
|
|
6633
|
+
|
|
6634
|
+
const asks = [
|
|
6635
|
+
[190.5, 86.5],
|
|
6636
|
+
[190.6159, 1],
|
|
6637
|
+
[190.656, 10.5],
|
|
6638
|
+
[190.6561, 1],
|
|
6639
|
+
[190.6585, 5],
|
|
6640
|
+
[190.6595, 5],
|
|
6641
|
+
[190.6596, 5],
|
|
6642
|
+
].map(([price, size]) => ({
|
|
6643
|
+
price: new BN(price * PRICE_PRECISION.toNumber()),
|
|
6644
|
+
size: new BN(size * BASE_PRECISION.toNumber()),
|
|
6645
|
+
sources: { vamm: new BN(size * BASE_PRECISION.toNumber()) },
|
|
6646
|
+
}));
|
|
6647
|
+
|
|
6648
|
+
expect(asksAreSortedAsc(asks), 'Input asks are ascending').to.be.true;
|
|
6649
|
+
expect(bidsAreSortedDesc(bids), 'Input bids are descending').to.be.true;
|
|
6650
|
+
|
|
6651
|
+
const groupingSize = new BN('100');
|
|
6652
|
+
|
|
6653
|
+
const userBidPrice = new BN(190.588 * PRICE_PRECISION.toNumber());
|
|
6654
|
+
const userBids = new Set<string>([userBidPrice.toString()]);
|
|
6655
|
+
|
|
6656
|
+
const { bids: newBids, asks: newAsks } = uncrossL2(
|
|
6657
|
+
bids,
|
|
6658
|
+
asks,
|
|
6659
|
+
oraclePrice,
|
|
6660
|
+
oraclePrice,
|
|
6661
|
+
oraclePrice,
|
|
6662
|
+
groupingSize,
|
|
6663
|
+
userBids,
|
|
6664
|
+
new Set<string>()
|
|
6665
|
+
);
|
|
6666
|
+
|
|
6667
|
+
expect(asksAreSortedAsc(newAsks), 'Uncrossed asks are ascending').to.be
|
|
6668
|
+
.true;
|
|
6669
|
+
expect(bidsAreSortedDesc(newBids), 'Uncrossed bids are descending').to.be
|
|
6670
|
+
.true;
|
|
6671
|
+
expect(newBids[0].price.toString()).to.equal(userBidPrice.toString());
|
|
6672
|
+
});
|
|
6673
|
+
|
|
6617
6674
|
it('Handles edge case bide and asks with large cross and an overlapping level', () => {
|
|
6618
6675
|
const bids = [
|
|
6619
6676
|
'104411000',
|