@drift-labs/sdk 0.2.0-master.25 → 0.2.0-master.27
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/accounts/pollingClearingHouseAccountSubscriber.d.ts +14 -13
- package/lib/accounts/pollingClearingHouseAccountSubscriber.js +30 -27
- package/lib/accounts/types.d.ts +9 -9
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +15 -14
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +38 -34
- package/lib/addresses/pda.d.ts +7 -6
- package/lib/addresses/pda.js +31 -27
- package/lib/admin.d.ts +13 -7
- package/lib/admin.js +111 -44
- package/lib/clearingHouse.d.ts +71 -42
- package/lib/clearingHouse.js +767 -278
- package/lib/clearingHouseConfig.d.ts +2 -2
- package/lib/clearingHouseUser.d.ts +24 -22
- package/lib/clearingHouseUser.js +273 -177
- package/lib/config.d.ts +7 -7
- package/lib/config.js +21 -21
- package/lib/constants/numericConstants.d.ts +12 -12
- package/lib/constants/numericConstants.js +13 -13
- package/lib/constants/{markets.d.ts → perpMarkets.d.ts} +5 -5
- package/lib/constants/{markets.js → perpMarkets.js} +4 -4
- package/lib/constants/{banks.d.ts → spotMarkets.d.ts} +6 -6
- package/lib/constants/{banks.js → spotMarkets.js} +16 -16
- package/lib/dlob/DLOB.d.ts +73 -0
- package/lib/dlob/DLOB.js +557 -0
- package/lib/dlob/DLOBNode.d.ts +52 -0
- package/lib/dlob/DLOBNode.js +82 -0
- package/lib/dlob/NodeList.d.ts +26 -0
- package/lib/dlob/NodeList.js +138 -0
- package/lib/events/types.d.ts +2 -1
- package/lib/events/types.js +1 -0
- package/lib/examples/makeTradeExample.js +7 -7
- package/lib/idl/clearing_house.json +1152 -503
- package/lib/index.d.ts +10 -3
- package/lib/index.js +10 -3
- package/lib/math/amm.d.ts +2 -2
- package/lib/math/amm.js +1 -1
- package/lib/math/funding.d.ts +6 -6
- package/lib/math/funding.js +2 -1
- package/lib/math/margin.d.ts +4 -4
- package/lib/math/margin.js +18 -11
- package/lib/math/market.d.ts +11 -10
- package/lib/math/market.js +30 -7
- package/lib/math/oracles.d.ts +2 -1
- package/lib/math/oracles.js +11 -1
- package/lib/math/orders.d.ts +6 -6
- package/lib/math/orders.js +31 -16
- package/lib/math/position.d.ts +13 -13
- package/lib/math/position.js +19 -19
- package/lib/math/spotBalance.d.ts +22 -0
- package/lib/math/spotBalance.js +193 -0
- package/lib/math/spotMarket.d.ts +4 -0
- package/lib/math/spotMarket.js +8 -0
- package/lib/math/spotPosition.d.ts +6 -0
- package/lib/math/spotPosition.js +23 -0
- package/lib/math/state.js +2 -2
- package/lib/math/trade.d.ts +4 -4
- package/lib/orderParams.d.ts +4 -4
- package/lib/orderParams.js +12 -4
- package/lib/serum/serumSubscriber.d.ts +23 -0
- package/lib/serum/serumSubscriber.js +41 -0
- package/lib/serum/types.d.ts +11 -0
- package/lib/serum/types.js +2 -0
- package/lib/tx/retryTxSender.d.ts +1 -1
- package/lib/tx/retryTxSender.js +4 -2
- package/lib/tx/types.d.ts +1 -1
- package/lib/types.d.ts +148 -57
- package/lib/types.js +39 -11
- package/lib/userMap/userMap.d.ts +25 -0
- package/lib/userMap/userMap.js +73 -0
- package/lib/userMap/userStatsMap.d.ts +19 -0
- package/lib/userMap/userStatsMap.js +68 -0
- package/package.json +6 -3
- package/src/accounts/pollingClearingHouseAccountSubscriber.ts +42 -38
- package/src/accounts/types.ts +12 -9
- package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +65 -52
- package/src/addresses/pda.ts +49 -44
- package/src/admin.ts +190 -55
- package/src/clearingHouse.ts +1092 -365
- package/src/clearingHouseConfig.ts +2 -2
- package/src/clearingHouseUser.ts +518 -255
- package/src/config.ts +30 -30
- package/src/constants/numericConstants.ts +17 -15
- package/src/constants/{markets.ts → perpMarkets.ts} +5 -5
- package/src/constants/{banks.ts → spotMarkets.ts} +19 -19
- package/src/dlob/DLOB.ts +884 -0
- package/src/dlob/DLOBNode.ts +163 -0
- package/src/dlob/NodeList.ts +185 -0
- package/src/events/types.ts +3 -0
- package/src/examples/makeTradeExample.js +152 -75
- package/src/examples/makeTradeExample.ts +10 -8
- package/src/idl/clearing_house.json +1152 -503
- package/src/index.ts +10 -3
- package/src/math/amm.ts +6 -3
- package/src/math/funding.ts +7 -7
- package/src/math/margin.ts +34 -23
- package/src/math/market.ts +72 -20
- package/src/math/oracles.ts +18 -1
- package/src/math/orders.ts +33 -25
- package/src/math/position.ts +31 -31
- package/src/math/spotBalance.ts +316 -0
- package/src/math/spotMarket.ts +9 -0
- package/src/math/spotPosition.ts +47 -0
- package/src/math/state.ts +2 -2
- package/src/math/trade.ts +4 -4
- package/src/orderParams.ts +16 -8
- package/src/serum/serumSubscriber.ts +80 -0
- package/src/serum/types.ts +13 -0
- package/src/tx/retryTxSender.ts +5 -2
- package/src/tx/types.ts +2 -1
- package/src/types.ts +135 -56
- package/src/userMap/userMap.ts +100 -0
- package/src/userMap/userStatsMap.ts +110 -0
- package/tests/bn/test.ts +2 -3
- package/tests/dlob/helpers.ts +322 -0
- package/tests/dlob/test.ts +2865 -0
- package/lib/math/bankBalance.d.ts +0 -15
- package/lib/math/bankBalance.js +0 -150
- package/src/constants/numericConstants.js +0 -41
- package/src/math/bankBalance.ts +0 -258
- package/src/math/oracles.js +0 -26
- package/src/math/state.js +0 -15
- package/src/orderParams.js +0 -20
- package/src/slot/SlotSubscriber.js +0 -39
- package/src/tokenFaucet.js +0 -189
package/src/clearingHouseUser.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
MarginCategory,
|
|
8
8
|
Order,
|
|
9
9
|
UserAccount,
|
|
10
|
-
|
|
10
|
+
PerpPosition,
|
|
11
11
|
} from './types';
|
|
12
12
|
import { calculateEntryPrice } from './math/position';
|
|
13
13
|
import {
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
AMM_RESERVE_PRECISION,
|
|
21
21
|
PRICE_TO_QUOTE_PRECISION,
|
|
22
22
|
MARGIN_PRECISION,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
SPOT_MARKET_WEIGHT_PRECISION,
|
|
24
|
+
QUOTE_SPOT_MARKET_INDEX,
|
|
25
25
|
} from './constants/numericConstants';
|
|
26
26
|
import {
|
|
27
27
|
UserAccountSubscriber,
|
|
@@ -38,13 +38,14 @@ import {
|
|
|
38
38
|
PositionDirection,
|
|
39
39
|
calculateTradeSlippage,
|
|
40
40
|
BN,
|
|
41
|
-
|
|
41
|
+
SpotMarketAccount,
|
|
42
|
+
getTokenValue,
|
|
42
43
|
} from '.';
|
|
43
44
|
import {
|
|
44
45
|
getTokenAmount,
|
|
45
46
|
calculateAssetWeight,
|
|
46
47
|
calculateLiabilityWeight,
|
|
47
|
-
} from './math/
|
|
48
|
+
} from './math/spotBalance';
|
|
48
49
|
import {
|
|
49
50
|
calculateBaseAssetValueWithOracle,
|
|
50
51
|
calculateWorstCaseBaseAssetAmount,
|
|
@@ -53,6 +54,10 @@ import { OraclePriceData } from './oracles/types';
|
|
|
53
54
|
import { ClearingHouseUserConfig } from './clearingHouseUserConfig';
|
|
54
55
|
import { PollingUserAccountSubscriber } from './accounts/pollingUserAccountSubscriber';
|
|
55
56
|
import { WebSocketUserAccountSubscriber } from './accounts/webSocketUserAccountSubscriber';
|
|
57
|
+
import {
|
|
58
|
+
getWorstCaseTokenAmounts,
|
|
59
|
+
isSpotPositionAvailable,
|
|
60
|
+
} from './math/spotPosition';
|
|
56
61
|
export class ClearingHouseUser {
|
|
57
62
|
clearingHouse: ClearingHouse;
|
|
58
63
|
userAccountPublicKey: PublicKey;
|
|
@@ -120,15 +125,16 @@ export class ClearingHouseUser {
|
|
|
120
125
|
* @param marketIndex
|
|
121
126
|
* @returns userPosition
|
|
122
127
|
*/
|
|
123
|
-
public getUserPosition(marketIndex: BN):
|
|
124
|
-
return this.getUserAccount().
|
|
128
|
+
public getUserPosition(marketIndex: BN): PerpPosition | undefined {
|
|
129
|
+
return this.getUserAccount().perpPositions.find((position) =>
|
|
125
130
|
position.marketIndex.eq(marketIndex)
|
|
126
131
|
);
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
public getEmptyPosition(marketIndex: BN):
|
|
134
|
+
public getEmptyPosition(marketIndex: BN): PerpPosition {
|
|
130
135
|
return {
|
|
131
136
|
baseAssetAmount: ZERO,
|
|
137
|
+
remainderBaseAssetAmount: ZERO,
|
|
132
138
|
lastCumulativeFundingRate: ZERO,
|
|
133
139
|
marketIndex,
|
|
134
140
|
quoteAssetAmount: ZERO,
|
|
@@ -144,7 +150,7 @@ export class ClearingHouseUser {
|
|
|
144
150
|
};
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
public getClonedPosition(position:
|
|
153
|
+
public getClonedPosition(position: PerpPosition): PerpPosition {
|
|
148
154
|
const clonedPosition = Object.assign({}, position);
|
|
149
155
|
return clonedPosition;
|
|
150
156
|
}
|
|
@@ -187,11 +193,13 @@ export class ClearingHouseUser {
|
|
|
187
193
|
* @returns : the dust base asset amount (ie, < stepsize)
|
|
188
194
|
* @returns : pnl from settle
|
|
189
195
|
*/
|
|
190
|
-
public getSettledLPPosition(marketIndex: BN): [
|
|
196
|
+
public getSettledLPPosition(marketIndex: BN): [PerpPosition, BN, BN] {
|
|
191
197
|
const _position = this.getUserPosition(marketIndex);
|
|
192
198
|
const position = this.getClonedPosition(_position);
|
|
193
199
|
|
|
194
|
-
const market = this.clearingHouse.
|
|
200
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
201
|
+
position.marketIndex
|
|
202
|
+
);
|
|
195
203
|
const nShares = position.lpShares;
|
|
196
204
|
|
|
197
205
|
const deltaBaa = market.amm.marketPositionPerLp.baseAssetAmount
|
|
@@ -210,24 +218,33 @@ export class ClearingHouseUser {
|
|
|
210
218
|
return sign;
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
.abs()
|
|
215
|
-
.
|
|
216
|
-
|
|
217
|
-
const _standardizedBaa = deltaBaa.sub(remainder);
|
|
218
|
-
|
|
219
|
-
let remainderBaa;
|
|
220
|
-
if (_standardizedBaa.abs().gte(market.amm.baseAssetAmountStepSize)) {
|
|
221
|
-
remainderBaa = remainder;
|
|
222
|
-
} else {
|
|
223
|
-
remainderBaa = deltaBaa;
|
|
221
|
+
function standardize(amount, stepsize) {
|
|
222
|
+
const remainder = amount.abs().mod(stepsize).mul(sign(amount));
|
|
223
|
+
const standardizedAmount = amount.sub(remainder);
|
|
224
|
+
return [standardizedAmount, remainder];
|
|
224
225
|
}
|
|
225
|
-
const standardizedBaa = deltaBaa.sub(remainderBaa);
|
|
226
226
|
|
|
227
|
-
const
|
|
227
|
+
const [standardizedBaa, remainderBaa] = standardize(
|
|
228
|
+
deltaBaa,
|
|
229
|
+
market.amm.baseAssetAmountStepSize
|
|
230
|
+
);
|
|
228
231
|
|
|
229
|
-
position.
|
|
230
|
-
|
|
232
|
+
position.remainderBaseAssetAmount =
|
|
233
|
+
position.remainderBaseAssetAmount.add(remainderBaa);
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
position.remainderBaseAssetAmount
|
|
237
|
+
.abs()
|
|
238
|
+
.gte(market.amm.baseAssetAmountStepSize)
|
|
239
|
+
) {
|
|
240
|
+
const [newStandardizedBaa, newRemainderBaa] = standardize(
|
|
241
|
+
position.remainderBaseAssetAmount,
|
|
242
|
+
market.amm.baseAssetAmountStepSize
|
|
243
|
+
);
|
|
244
|
+
position.baseAssetAmount =
|
|
245
|
+
position.baseAssetAmount.add(newStandardizedBaa);
|
|
246
|
+
position.remainderBaseAssetAmount = newRemainderBaa;
|
|
247
|
+
}
|
|
231
248
|
|
|
232
249
|
let updateType;
|
|
233
250
|
if (position.baseAssetAmount.eq(ZERO)) {
|
|
@@ -300,75 +317,22 @@ export class ClearingHouseUser {
|
|
|
300
317
|
/**
|
|
301
318
|
* @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
|
|
302
319
|
*/
|
|
303
|
-
public getMarginRequirement(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
marketPosition.baseAssetAmount =
|
|
320
|
-
settledPosition.baseAssetAmount.add(dustBaa);
|
|
321
|
-
marketPosition.quoteAssetAmount = settledPosition.quoteAssetAmount;
|
|
322
|
-
|
|
323
|
-
// open orders
|
|
324
|
-
let openAsks;
|
|
325
|
-
if (market.amm.maxBaseAssetReserve > market.amm.baseAssetReserve) {
|
|
326
|
-
openAsks = market.amm.maxBaseAssetReserve
|
|
327
|
-
.sub(market.amm.baseAssetReserve)
|
|
328
|
-
.mul(marketPosition.lpShares)
|
|
329
|
-
.div(market.amm.sqrtK)
|
|
330
|
-
.mul(new BN(-1));
|
|
331
|
-
} else {
|
|
332
|
-
openAsks = ZERO;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
let openBids;
|
|
336
|
-
if (market.amm.minBaseAssetReserve < market.amm.baseAssetReserve) {
|
|
337
|
-
openBids = market.amm.baseAssetReserve
|
|
338
|
-
.sub(market.amm.minBaseAssetReserve)
|
|
339
|
-
.mul(marketPosition.lpShares)
|
|
340
|
-
.div(market.amm.sqrtK);
|
|
341
|
-
} else {
|
|
342
|
-
openBids = ZERO;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
marketPosition.openAsks = marketPosition.openAsks.add(openAsks);
|
|
346
|
-
marketPosition.openBids = marketPosition.openBids.add(openBids);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const worstCaseBaseAssetAmount =
|
|
350
|
-
calculateWorstCaseBaseAssetAmount(marketPosition);
|
|
351
|
-
|
|
352
|
-
const worstCaseAssetValue = worstCaseBaseAssetAmount
|
|
353
|
-
.abs()
|
|
354
|
-
.mul(this.getOracleDataForMarket(market.marketIndex).price)
|
|
355
|
-
.div(AMM_TO_QUOTE_PRECISION_RATIO.mul(MARK_PRICE_PRECISION));
|
|
356
|
-
|
|
357
|
-
return marginRequirement.add(
|
|
358
|
-
worstCaseAssetValue
|
|
359
|
-
.mul(
|
|
360
|
-
new BN(
|
|
361
|
-
calculateMarketMarginRatio(
|
|
362
|
-
market,
|
|
363
|
-
worstCaseBaseAssetAmount.abs(),
|
|
364
|
-
type
|
|
365
|
-
)
|
|
366
|
-
)
|
|
367
|
-
)
|
|
368
|
-
.div(MARGIN_PRECISION)
|
|
369
|
-
);
|
|
370
|
-
}, ZERO)
|
|
371
|
-
.add(this.getBankLiabilityValue(undefined, type));
|
|
320
|
+
public getMarginRequirement(
|
|
321
|
+
marginCategory: MarginCategory,
|
|
322
|
+
liquidationBuffer?: BN
|
|
323
|
+
): BN {
|
|
324
|
+
return this.getTotalPerpPositionValue(
|
|
325
|
+
marginCategory,
|
|
326
|
+
liquidationBuffer,
|
|
327
|
+
true
|
|
328
|
+
).add(
|
|
329
|
+
this.getSpotMarketLiabilityValue(
|
|
330
|
+
undefined,
|
|
331
|
+
marginCategory,
|
|
332
|
+
liquidationBuffer,
|
|
333
|
+
true
|
|
334
|
+
)
|
|
335
|
+
);
|
|
372
336
|
}
|
|
373
337
|
|
|
374
338
|
/**
|
|
@@ -381,8 +345,8 @@ export class ClearingHouseUser {
|
|
|
381
345
|
/**
|
|
382
346
|
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
383
347
|
*/
|
|
384
|
-
public getMaintenanceMarginRequirement(): BN {
|
|
385
|
-
return this.getMarginRequirement('Maintenance');
|
|
348
|
+
public getMaintenanceMarginRequirement(liquidationBuffer?: BN): BN {
|
|
349
|
+
return this.getMarginRequirement('Maintenance', liquidationBuffer);
|
|
386
350
|
}
|
|
387
351
|
|
|
388
352
|
/**
|
|
@@ -394,19 +358,22 @@ export class ClearingHouseUser {
|
|
|
394
358
|
marketIndex?: BN,
|
|
395
359
|
withWeightMarginCategory?: MarginCategory
|
|
396
360
|
): BN {
|
|
361
|
+
const quoteSpotMarket = this.clearingHouse.getQuoteSpotMarketAccount();
|
|
397
362
|
return this.getUserAccount()
|
|
398
|
-
.
|
|
363
|
+
.perpPositions.filter((pos) =>
|
|
399
364
|
marketIndex ? pos.marketIndex === marketIndex : true
|
|
400
365
|
)
|
|
401
|
-
.reduce((unrealizedPnl,
|
|
402
|
-
const market = this.clearingHouse.
|
|
403
|
-
|
|
366
|
+
.reduce((unrealizedPnl, perpPosition) => {
|
|
367
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
368
|
+
perpPosition.marketIndex
|
|
404
369
|
);
|
|
370
|
+
const oraclePriceData = this.getOracleDataForMarket(market.marketIndex);
|
|
371
|
+
|
|
405
372
|
let positionUnrealizedPnl = calculatePositionPNL(
|
|
406
373
|
market,
|
|
407
|
-
|
|
374
|
+
perpPosition,
|
|
408
375
|
withFunding,
|
|
409
|
-
|
|
376
|
+
oraclePriceData
|
|
410
377
|
);
|
|
411
378
|
|
|
412
379
|
if (withWeightMarginCategory !== undefined) {
|
|
@@ -415,11 +382,13 @@ export class ClearingHouseUser {
|
|
|
415
382
|
.mul(
|
|
416
383
|
calculateUnrealizedAssetWeight(
|
|
417
384
|
market,
|
|
385
|
+
quoteSpotMarket,
|
|
418
386
|
positionUnrealizedPnl,
|
|
419
|
-
withWeightMarginCategory
|
|
387
|
+
withWeightMarginCategory,
|
|
388
|
+
oraclePriceData
|
|
420
389
|
)
|
|
421
390
|
)
|
|
422
|
-
.div(new BN(
|
|
391
|
+
.div(new BN(SPOT_MARKET_WEIGHT_PRECISION));
|
|
423
392
|
}
|
|
424
393
|
}
|
|
425
394
|
|
|
@@ -433,119 +402,287 @@ export class ClearingHouseUser {
|
|
|
433
402
|
*/
|
|
434
403
|
public getUnrealizedFundingPNL(marketIndex?: BN): BN {
|
|
435
404
|
return this.getUserAccount()
|
|
436
|
-
.
|
|
405
|
+
.perpPositions.filter((pos) =>
|
|
437
406
|
marketIndex ? pos.marketIndex === marketIndex : true
|
|
438
407
|
)
|
|
439
|
-
.reduce((pnl,
|
|
440
|
-
const market = this.clearingHouse.
|
|
441
|
-
|
|
408
|
+
.reduce((pnl, perpPosition) => {
|
|
409
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
410
|
+
perpPosition.marketIndex
|
|
442
411
|
);
|
|
443
|
-
return pnl.add(calculatePositionFundingPNL(market,
|
|
412
|
+
return pnl.add(calculatePositionFundingPNL(market, perpPosition));
|
|
444
413
|
}, ZERO);
|
|
445
414
|
}
|
|
446
415
|
|
|
447
|
-
public
|
|
448
|
-
|
|
449
|
-
|
|
416
|
+
public getSpotMarketLiabilityValue(
|
|
417
|
+
marketIndex?: BN,
|
|
418
|
+
marginCategory?: MarginCategory,
|
|
419
|
+
liquidationBuffer?: BN,
|
|
420
|
+
includeOpenOrders?: boolean
|
|
450
421
|
): BN {
|
|
451
|
-
return this.getUserAccount().
|
|
452
|
-
(totalLiabilityValue,
|
|
422
|
+
return this.getUserAccount().spotPositions.reduce(
|
|
423
|
+
(totalLiabilityValue, spotPosition) => {
|
|
453
424
|
if (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
425
|
+
isSpotPositionAvailable(spotPosition) ||
|
|
426
|
+
(marketIndex !== undefined &&
|
|
427
|
+
!spotPosition.marketIndex.eq(marketIndex))
|
|
457
428
|
) {
|
|
458
429
|
return totalLiabilityValue;
|
|
459
430
|
}
|
|
460
431
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
bankBalance.bankIndex
|
|
464
|
-
);
|
|
432
|
+
const spotMarketAccount: SpotMarketAccount =
|
|
433
|
+
this.clearingHouse.getSpotMarketAccount(spotPosition.marketIndex);
|
|
465
434
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
435
|
+
if (spotPosition.marketIndex.eq(QUOTE_SPOT_MARKET_INDEX)) {
|
|
436
|
+
if (isVariant(spotPosition.balanceType, 'borrow')) {
|
|
437
|
+
const tokenAmount = getTokenAmount(
|
|
438
|
+
spotPosition.balance,
|
|
439
|
+
spotMarketAccount,
|
|
440
|
+
spotPosition.balanceType
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
let weight = SPOT_MARKET_WEIGHT_PRECISION;
|
|
444
|
+
if (marginCategory === 'Initial') {
|
|
445
|
+
weight = BN.max(
|
|
446
|
+
weight,
|
|
447
|
+
new BN(this.getUserAccount().customMarginRatio)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const weightedTokenValue = tokenAmount
|
|
452
|
+
.mul(weight)
|
|
453
|
+
.div(SPOT_MARKET_WEIGHT_PRECISION);
|
|
454
|
+
|
|
455
|
+
return totalLiabilityValue.add(weightedTokenValue);
|
|
456
|
+
} else {
|
|
457
|
+
return totalLiabilityValue;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(
|
|
462
|
+
spotPosition.marketIndex
|
|
470
463
|
);
|
|
471
464
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
)
|
|
465
|
+
if (!includeOpenOrders) {
|
|
466
|
+
if (isVariant(spotPosition.balanceType, 'borrow')) {
|
|
467
|
+
const tokenAmount = getTokenAmount(
|
|
468
|
+
spotPosition.balance,
|
|
469
|
+
spotMarketAccount,
|
|
470
|
+
spotPosition.balanceType
|
|
471
|
+
);
|
|
472
|
+
const liabilityValue = this.getSpotLiabilityValue(
|
|
473
|
+
tokenAmount,
|
|
474
|
+
oraclePriceData,
|
|
475
|
+
spotMarketAccount,
|
|
476
|
+
marginCategory,
|
|
477
|
+
liquidationBuffer
|
|
478
|
+
);
|
|
479
|
+
return totalLiabilityValue.add(liabilityValue);
|
|
480
|
+
} else {
|
|
481
|
+
return totalLiabilityValue;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
|
|
486
|
+
getWorstCaseTokenAmounts(
|
|
487
|
+
spotPosition,
|
|
488
|
+
spotMarketAccount,
|
|
489
|
+
this.getOracleDataForSpotMarket(spotPosition.marketIndex)
|
|
479
490
|
);
|
|
480
491
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
492
|
+
let newTotalLiabilityValue = totalLiabilityValue;
|
|
493
|
+
if (worstCaseTokenAmount.lt(ZERO)) {
|
|
494
|
+
const baseLiabilityValue = this.getSpotLiabilityValue(
|
|
495
|
+
worstCaseTokenAmount,
|
|
496
|
+
oraclePriceData,
|
|
497
|
+
spotMarketAccount,
|
|
498
|
+
marginCategory,
|
|
499
|
+
liquidationBuffer
|
|
486
500
|
);
|
|
487
|
-
|
|
501
|
+
|
|
502
|
+
newTotalLiabilityValue =
|
|
503
|
+
newTotalLiabilityValue.add(baseLiabilityValue);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (worstCaseQuoteTokenAmount.lt(ZERO)) {
|
|
507
|
+
let weight = SPOT_MARKET_WEIGHT_PRECISION;
|
|
508
|
+
if (marginCategory === 'Initial') {
|
|
509
|
+
weight = BN.max(
|
|
510
|
+
weight,
|
|
511
|
+
new BN(this.getUserAccount().customMarginRatio)
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const weightedTokenValue = worstCaseQuoteTokenAmount
|
|
488
516
|
.mul(weight)
|
|
489
|
-
.div(
|
|
517
|
+
.div(SPOT_MARKET_WEIGHT_PRECISION);
|
|
518
|
+
|
|
519
|
+
newTotalLiabilityValue =
|
|
520
|
+
newTotalLiabilityValue.add(weightedTokenValue);
|
|
490
521
|
}
|
|
491
522
|
|
|
492
|
-
return
|
|
523
|
+
return newTotalLiabilityValue;
|
|
493
524
|
},
|
|
494
525
|
ZERO
|
|
495
526
|
);
|
|
496
527
|
}
|
|
497
528
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
529
|
+
getSpotLiabilityValue(
|
|
530
|
+
tokenAmount: BN,
|
|
531
|
+
oraclePriceData: OraclePriceData,
|
|
532
|
+
spotMarketAccount: SpotMarketAccount,
|
|
533
|
+
marginCategory?: MarginCategory,
|
|
534
|
+
liquidationBuffer?: BN
|
|
501
535
|
): BN {
|
|
502
|
-
|
|
503
|
-
|
|
536
|
+
let liabilityValue = getTokenValue(
|
|
537
|
+
tokenAmount,
|
|
538
|
+
spotMarketAccount.decimals,
|
|
539
|
+
oraclePriceData
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
if (marginCategory !== undefined) {
|
|
543
|
+
let weight = calculateLiabilityWeight(
|
|
544
|
+
tokenAmount,
|
|
545
|
+
spotMarketAccount,
|
|
546
|
+
marginCategory
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
if (marginCategory === 'Initial') {
|
|
550
|
+
weight = BN.max(
|
|
551
|
+
weight,
|
|
552
|
+
new BN(this.getUserAccount().customMarginRatio)
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (liquidationBuffer !== undefined) {
|
|
557
|
+
weight = weight.add(liquidationBuffer);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
liabilityValue = liabilityValue
|
|
561
|
+
.mul(weight)
|
|
562
|
+
.div(SPOT_MARKET_WEIGHT_PRECISION);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return liabilityValue;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
public getSpotMarketAssetValue(
|
|
569
|
+
marketIndex?: BN,
|
|
570
|
+
marginCategory?: MarginCategory,
|
|
571
|
+
includeOpenOrders?: boolean
|
|
572
|
+
): BN {
|
|
573
|
+
return this.getUserAccount().spotPositions.reduce(
|
|
574
|
+
(totalAssetValue, spotPosition) => {
|
|
504
575
|
if (
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
576
|
+
isSpotPositionAvailable(spotPosition) ||
|
|
577
|
+
(marketIndex !== undefined &&
|
|
578
|
+
!spotPosition.marketIndex.eq(marketIndex))
|
|
508
579
|
) {
|
|
509
580
|
return totalAssetValue;
|
|
510
581
|
}
|
|
511
582
|
|
|
512
583
|
// Todo this needs to account for whether it's based on initial or maintenance requirements
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
584
|
+
const spotMarketAccount: SpotMarketAccount =
|
|
585
|
+
this.clearingHouse.getSpotMarketAccount(spotPosition.marketIndex);
|
|
586
|
+
|
|
587
|
+
if (spotPosition.marketIndex.eq(QUOTE_SPOT_MARKET_INDEX)) {
|
|
588
|
+
if (isVariant(spotPosition.balanceType, 'deposit')) {
|
|
589
|
+
const tokenAmount = getTokenAmount(
|
|
590
|
+
spotPosition.balance,
|
|
591
|
+
spotMarketAccount,
|
|
592
|
+
spotPosition.balanceType
|
|
593
|
+
);
|
|
516
594
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
595
|
+
return totalAssetValue.add(tokenAmount);
|
|
596
|
+
} else {
|
|
597
|
+
return totalAssetValue;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(
|
|
602
|
+
spotPosition.marketIndex
|
|
521
603
|
);
|
|
522
604
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
)
|
|
605
|
+
if (!includeOpenOrders) {
|
|
606
|
+
if (isVariant(spotPosition.balanceType, 'deposit')) {
|
|
607
|
+
const tokenAmount = getTokenAmount(
|
|
608
|
+
spotPosition.balance,
|
|
609
|
+
spotMarketAccount,
|
|
610
|
+
spotPosition.balanceType
|
|
611
|
+
);
|
|
612
|
+
const assetValue = this.getSpotAssetValue(
|
|
613
|
+
tokenAmount,
|
|
614
|
+
oraclePriceData,
|
|
615
|
+
spotMarketAccount,
|
|
616
|
+
marginCategory
|
|
617
|
+
);
|
|
618
|
+
return totalAssetValue.add(assetValue);
|
|
619
|
+
} else {
|
|
620
|
+
return totalAssetValue;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
|
|
625
|
+
getWorstCaseTokenAmounts(
|
|
626
|
+
spotPosition,
|
|
627
|
+
spotMarketAccount,
|
|
628
|
+
this.getOracleDataForSpotMarket(spotPosition.marketIndex)
|
|
530
629
|
);
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
630
|
+
|
|
631
|
+
let newTotalAssetValue = totalAssetValue;
|
|
632
|
+
if (worstCaseTokenAmount.gt(ZERO)) {
|
|
633
|
+
const baseAssetValue = this.getSpotAssetValue(
|
|
634
|
+
worstCaseTokenAmount,
|
|
635
|
+
oraclePriceData,
|
|
636
|
+
spotMarketAccount,
|
|
637
|
+
marginCategory
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (worstCaseQuoteTokenAmount.gt(ZERO)) {
|
|
644
|
+
newTotalAssetValue = newTotalAssetValue.add(
|
|
645
|
+
worstCaseQuoteTokenAmount
|
|
536
646
|
);
|
|
537
|
-
assetValue = assetValue.mul(weight).div(BANK_WEIGHT_PRECISION);
|
|
538
647
|
}
|
|
539
648
|
|
|
540
|
-
return
|
|
649
|
+
return newTotalAssetValue;
|
|
541
650
|
},
|
|
542
651
|
ZERO
|
|
543
652
|
);
|
|
544
653
|
}
|
|
545
654
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
655
|
+
getSpotAssetValue(
|
|
656
|
+
tokenAmount: BN,
|
|
657
|
+
oraclePriceData: OraclePriceData,
|
|
658
|
+
spotMarketAccount: SpotMarketAccount,
|
|
659
|
+
marginCategory?: MarginCategory
|
|
660
|
+
): BN {
|
|
661
|
+
let assetValue = getTokenValue(
|
|
662
|
+
tokenAmount,
|
|
663
|
+
spotMarketAccount.decimals,
|
|
664
|
+
oraclePriceData
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
if (marginCategory !== undefined) {
|
|
668
|
+
const weight = calculateAssetWeight(
|
|
669
|
+
tokenAmount,
|
|
670
|
+
spotMarketAccount,
|
|
671
|
+
marginCategory
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
assetValue = assetValue.mul(weight).div(SPOT_MARKET_WEIGHT_PRECISION);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return assetValue;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
public getNetSpotMarketValue(withWeightMarginCategory?: MarginCategory): BN {
|
|
681
|
+
return this.getSpotMarketAssetValue(
|
|
682
|
+
undefined,
|
|
683
|
+
withWeightMarginCategory
|
|
684
|
+
).sub(
|
|
685
|
+
this.getSpotMarketLiabilityValue(undefined, withWeightMarginCategory)
|
|
549
686
|
);
|
|
550
687
|
}
|
|
551
688
|
|
|
@@ -554,7 +691,7 @@ export class ClearingHouseUser {
|
|
|
554
691
|
* @returns : Precision QUOTE_PRECISION
|
|
555
692
|
*/
|
|
556
693
|
public getTotalCollateral(marginCategory: MarginCategory = 'Initial'): BN {
|
|
557
|
-
return this.
|
|
694
|
+
return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(
|
|
558
695
|
this.getUnrealizedPNL(true, undefined, marginCategory)
|
|
559
696
|
);
|
|
560
697
|
}
|
|
@@ -563,19 +700,99 @@ export class ClearingHouseUser {
|
|
|
563
700
|
* calculates sum of position value across all positions in margin system
|
|
564
701
|
* @returns : Precision QUOTE_PRECISION
|
|
565
702
|
*/
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
this.getOracleDataForMarket(market.marketIndex)
|
|
703
|
+
getTotalPerpPositionValue(
|
|
704
|
+
marginCategory?: MarginCategory,
|
|
705
|
+
liquidationBuffer?: BN,
|
|
706
|
+
includeOpenOrders?: boolean
|
|
707
|
+
): BN {
|
|
708
|
+
return this.getUserAccount().perpPositions.reduce(
|
|
709
|
+
(totalPerpValue, perpPosition) => {
|
|
710
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
711
|
+
perpPosition.marketIndex
|
|
576
712
|
);
|
|
577
713
|
|
|
578
|
-
|
|
714
|
+
if (perpPosition.lpShares.gt(ZERO)) {
|
|
715
|
+
// is an lp
|
|
716
|
+
// clone so we dont mutate the position
|
|
717
|
+
perpPosition = this.getClonedPosition(perpPosition);
|
|
718
|
+
|
|
719
|
+
// settle position
|
|
720
|
+
const [settledPosition, dustBaa, _] = this.getSettledLPPosition(
|
|
721
|
+
market.marketIndex
|
|
722
|
+
);
|
|
723
|
+
perpPosition.baseAssetAmount =
|
|
724
|
+
settledPosition.baseAssetAmount.add(dustBaa);
|
|
725
|
+
perpPosition.quoteAssetAmount = settledPosition.quoteAssetAmount;
|
|
726
|
+
|
|
727
|
+
// open orders
|
|
728
|
+
let openAsks;
|
|
729
|
+
if (market.amm.maxBaseAssetReserve > market.amm.baseAssetReserve) {
|
|
730
|
+
openAsks = market.amm.maxBaseAssetReserve
|
|
731
|
+
.sub(market.amm.baseAssetReserve)
|
|
732
|
+
.mul(perpPosition.lpShares)
|
|
733
|
+
.div(market.amm.sqrtK)
|
|
734
|
+
.mul(new BN(-1));
|
|
735
|
+
} else {
|
|
736
|
+
openAsks = ZERO;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
let openBids;
|
|
740
|
+
if (market.amm.minBaseAssetReserve < market.amm.baseAssetReserve) {
|
|
741
|
+
openBids = market.amm.baseAssetReserve
|
|
742
|
+
.sub(market.amm.minBaseAssetReserve)
|
|
743
|
+
.mul(perpPosition.lpShares)
|
|
744
|
+
.div(market.amm.sqrtK);
|
|
745
|
+
} else {
|
|
746
|
+
openBids = ZERO;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
perpPosition.openAsks = perpPosition.openAsks.add(openAsks);
|
|
750
|
+
perpPosition.openBids = perpPosition.openBids.add(openBids);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
let valuationPrice = this.getOracleDataForMarket(
|
|
754
|
+
market.marketIndex
|
|
755
|
+
).price;
|
|
756
|
+
|
|
757
|
+
if (isVariant(market.status, 'settlement')) {
|
|
758
|
+
valuationPrice = market.settlementPrice;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const baseAssetAmount = includeOpenOrders
|
|
762
|
+
? calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
763
|
+
: perpPosition.baseAssetAmount;
|
|
764
|
+
|
|
765
|
+
let baseAssetValue = baseAssetAmount
|
|
766
|
+
.abs()
|
|
767
|
+
.mul(valuationPrice)
|
|
768
|
+
.div(AMM_TO_QUOTE_PRECISION_RATIO.mul(MARK_PRICE_PRECISION));
|
|
769
|
+
|
|
770
|
+
if (marginCategory) {
|
|
771
|
+
let marginRatio = new BN(
|
|
772
|
+
calculateMarketMarginRatio(
|
|
773
|
+
market,
|
|
774
|
+
baseAssetAmount.abs(),
|
|
775
|
+
marginCategory
|
|
776
|
+
)
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
if (marginCategory === 'Initial') {
|
|
780
|
+
marginRatio = BN.max(
|
|
781
|
+
marginRatio,
|
|
782
|
+
new BN(this.getUserAccount().customMarginRatio)
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (liquidationBuffer !== undefined) {
|
|
787
|
+
marginRatio = marginRatio.add(liquidationBuffer);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
baseAssetValue = baseAssetValue
|
|
791
|
+
.mul(marginRatio)
|
|
792
|
+
.div(MARGIN_PRECISION);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return totalPerpValue.add(baseAssetValue);
|
|
579
796
|
},
|
|
580
797
|
ZERO
|
|
581
798
|
);
|
|
@@ -585,13 +802,13 @@ export class ClearingHouseUser {
|
|
|
585
802
|
* calculates position value in margin system
|
|
586
803
|
* @returns : Precision QUOTE_PRECISION
|
|
587
804
|
*/
|
|
588
|
-
public
|
|
805
|
+
public getPerpPositionValue(
|
|
589
806
|
marketIndex: BN,
|
|
590
807
|
oraclePriceData: OraclePriceData
|
|
591
808
|
): BN {
|
|
592
809
|
const userPosition =
|
|
593
810
|
this.getUserPosition(marketIndex) || this.getEmptyPosition(marketIndex);
|
|
594
|
-
const market = this.clearingHouse.
|
|
811
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
595
812
|
userPosition.marketIndex
|
|
596
813
|
);
|
|
597
814
|
return calculateBaseAssetValueWithOracle(
|
|
@@ -602,7 +819,7 @@ export class ClearingHouseUser {
|
|
|
602
819
|
}
|
|
603
820
|
|
|
604
821
|
public getPositionSide(
|
|
605
|
-
currentPosition: Pick<
|
|
822
|
+
currentPosition: Pick<PerpPosition, 'baseAssetAmount'>
|
|
606
823
|
): PositionDirection | undefined {
|
|
607
824
|
if (currentPosition.baseAssetAmount.gt(ZERO)) {
|
|
608
825
|
return PositionDirection.LONG;
|
|
@@ -618,11 +835,13 @@ export class ClearingHouseUser {
|
|
|
618
835
|
* @returns : Precision MARK_PRICE_PRECISION
|
|
619
836
|
*/
|
|
620
837
|
public getPositionEstimatedExitPriceAndPnl(
|
|
621
|
-
position:
|
|
838
|
+
position: PerpPosition,
|
|
622
839
|
amountToClose?: BN,
|
|
623
840
|
useAMMClose = false
|
|
624
841
|
): [BN, BN] {
|
|
625
|
-
const market = this.clearingHouse.
|
|
842
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
843
|
+
position.marketIndex
|
|
844
|
+
);
|
|
626
845
|
|
|
627
846
|
const entryPrice = calculateEntryPrice(position);
|
|
628
847
|
|
|
@@ -637,7 +856,7 @@ export class ClearingHouseUser {
|
|
|
637
856
|
lastCumulativeFundingRate: position.lastCumulativeFundingRate,
|
|
638
857
|
marketIndex: position.marketIndex,
|
|
639
858
|
quoteAssetAmount: position.quoteAssetAmount,
|
|
640
|
-
} as
|
|
859
|
+
} as PerpPosition;
|
|
641
860
|
}
|
|
642
861
|
|
|
643
862
|
let baseAssetValue: BN;
|
|
@@ -677,13 +896,31 @@ export class ClearingHouseUser {
|
|
|
677
896
|
* calculates current user leverage across all positions
|
|
678
897
|
* @returns : Precision TEN_THOUSAND
|
|
679
898
|
*/
|
|
680
|
-
public getLeverage(): BN {
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
899
|
+
public getLeverage(marginCategory?: MarginCategory): BN {
|
|
900
|
+
const totalLiabilityValue = this.getTotalPerpPositionValue(
|
|
901
|
+
marginCategory,
|
|
902
|
+
undefined,
|
|
903
|
+
true
|
|
904
|
+
).add(
|
|
905
|
+
this.getSpotMarketLiabilityValue(
|
|
906
|
+
undefined,
|
|
907
|
+
marginCategory,
|
|
908
|
+
undefined,
|
|
909
|
+
true
|
|
910
|
+
)
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
const totalAssetValue = this.getSpotMarketAssetValue(
|
|
914
|
+
undefined,
|
|
915
|
+
marginCategory,
|
|
916
|
+
true
|
|
917
|
+
).add(this.getUnrealizedPNL(true, undefined, marginCategory));
|
|
918
|
+
|
|
919
|
+
if (totalAssetValue.eq(ZERO) && totalLiabilityValue.eq(ZERO)) {
|
|
684
920
|
return ZERO;
|
|
685
921
|
}
|
|
686
|
-
|
|
922
|
+
|
|
923
|
+
return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
|
|
687
924
|
}
|
|
688
925
|
|
|
689
926
|
/**
|
|
@@ -695,7 +932,7 @@ export class ClearingHouseUser {
|
|
|
695
932
|
marketIndex: BN | number,
|
|
696
933
|
category: MarginCategory = 'Initial'
|
|
697
934
|
): BN {
|
|
698
|
-
const market = this.clearingHouse.
|
|
935
|
+
const market = this.clearingHouse.getPerpMarketAccount(marketIndex);
|
|
699
936
|
|
|
700
937
|
const marginRatioCategory = calculateMarketMarginRatio(
|
|
701
938
|
market,
|
|
@@ -713,23 +950,46 @@ export class ClearingHouseUser {
|
|
|
713
950
|
* calculates margin ratio: total collateral / |total position value|
|
|
714
951
|
* @returns : Precision TEN_THOUSAND
|
|
715
952
|
*/
|
|
716
|
-
public getMarginRatio(): BN {
|
|
717
|
-
const
|
|
953
|
+
public getMarginRatio(marginCategory?: MarginCategory): BN {
|
|
954
|
+
const totalLiabilityValue = this.getTotalPerpPositionValue(
|
|
955
|
+
marginCategory,
|
|
956
|
+
undefined,
|
|
957
|
+
true
|
|
958
|
+
).add(
|
|
959
|
+
this.getSpotMarketLiabilityValue(
|
|
960
|
+
undefined,
|
|
961
|
+
marginCategory,
|
|
962
|
+
undefined,
|
|
963
|
+
true
|
|
964
|
+
)
|
|
965
|
+
);
|
|
718
966
|
|
|
719
|
-
if (
|
|
967
|
+
if (totalLiabilityValue.eq(ZERO)) {
|
|
720
968
|
return BN_MAX;
|
|
721
969
|
}
|
|
722
970
|
|
|
723
|
-
|
|
971
|
+
const totalAssetValue = this.getSpotMarketAssetValue(
|
|
972
|
+
undefined,
|
|
973
|
+
marginCategory,
|
|
974
|
+
true
|
|
975
|
+
).add(this.getUnrealizedPNL(true, undefined, marginCategory));
|
|
976
|
+
|
|
977
|
+
return totalAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
|
|
724
978
|
}
|
|
725
979
|
|
|
726
|
-
public canBeLiquidated():
|
|
980
|
+
public canBeLiquidated(): boolean {
|
|
727
981
|
const totalCollateral = this.getTotalCollateral();
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
982
|
+
|
|
983
|
+
// if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
|
|
984
|
+
let liquidationBuffer = undefined;
|
|
985
|
+
if (this.getUserAccount().beingLiquidated) {
|
|
986
|
+
liquidationBuffer = new BN(
|
|
987
|
+
this.clearingHouse.getStateAccount().liquidationMarginBufferRatio
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
const maintenanceRequirement =
|
|
991
|
+
this.getMaintenanceMarginRequirement(liquidationBuffer);
|
|
992
|
+
return totalCollateral.lt(maintenanceRequirement);
|
|
733
993
|
}
|
|
734
994
|
|
|
735
995
|
/**
|
|
@@ -737,12 +997,12 @@ export class ClearingHouseUser {
|
|
|
737
997
|
* @returns
|
|
738
998
|
*/
|
|
739
999
|
public needsToSettleFundingPayment(): boolean {
|
|
740
|
-
for (const userPosition of this.getUserAccount().
|
|
1000
|
+
for (const userPosition of this.getUserAccount().perpPositions) {
|
|
741
1001
|
if (userPosition.baseAssetAmount.eq(ZERO)) {
|
|
742
1002
|
continue;
|
|
743
1003
|
}
|
|
744
1004
|
|
|
745
|
-
const market = this.clearingHouse.
|
|
1005
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
746
1006
|
userPosition.marketIndex
|
|
747
1007
|
);
|
|
748
1008
|
if (
|
|
@@ -763,13 +1023,13 @@ export class ClearingHouseUser {
|
|
|
763
1023
|
|
|
764
1024
|
/**
|
|
765
1025
|
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
766
|
-
* @param
|
|
1026
|
+
* @param PerpPosition
|
|
767
1027
|
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
768
1028
|
* @param partial
|
|
769
1029
|
* @returns Precision : MARK_PRICE_PRECISION
|
|
770
1030
|
*/
|
|
771
1031
|
public liquidationPrice(
|
|
772
|
-
|
|
1032
|
+
perpPosition: Pick<PerpPosition, 'marketIndex'>,
|
|
773
1033
|
positionBaseSizeChange: BN = ZERO
|
|
774
1034
|
): BN {
|
|
775
1035
|
// solves formula for example canBeLiquidated below
|
|
@@ -788,22 +1048,23 @@ export class ClearingHouseUser {
|
|
|
788
1048
|
|
|
789
1049
|
// calculate the total position value ignoring any value from the target market of the trade
|
|
790
1050
|
const totalPositionValueExcludingTargetMarket =
|
|
791
|
-
this.
|
|
1051
|
+
this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
|
|
792
1052
|
|
|
793
|
-
const
|
|
794
|
-
this.getUserPosition(
|
|
795
|
-
this.getEmptyPosition(
|
|
1053
|
+
const currentPerpPosition =
|
|
1054
|
+
this.getUserPosition(perpPosition.marketIndex) ||
|
|
1055
|
+
this.getEmptyPosition(perpPosition.marketIndex);
|
|
796
1056
|
|
|
797
|
-
const
|
|
1057
|
+
const currentPerpPositionBaseSize = currentPerpPosition.baseAssetAmount;
|
|
798
1058
|
|
|
799
|
-
const proposedBaseAssetAmount =
|
|
1059
|
+
const proposedBaseAssetAmount = currentPerpPositionBaseSize.add(
|
|
800
1060
|
positionBaseSizeChange
|
|
801
1061
|
);
|
|
802
1062
|
|
|
803
1063
|
// calculate position for current market after trade
|
|
804
|
-
const
|
|
805
|
-
marketIndex:
|
|
1064
|
+
const proposedPerpPosition: PerpPosition = {
|
|
1065
|
+
marketIndex: perpPosition.marketIndex,
|
|
806
1066
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
1067
|
+
remainderBaseAssetAmount: ZERO,
|
|
807
1068
|
quoteAssetAmount: new BN(0),
|
|
808
1069
|
lastCumulativeFundingRate: ZERO,
|
|
809
1070
|
quoteEntryAmount: new BN(0),
|
|
@@ -819,25 +1080,25 @@ export class ClearingHouseUser {
|
|
|
819
1080
|
|
|
820
1081
|
if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
|
|
821
1082
|
|
|
822
|
-
const market = this.clearingHouse.
|
|
823
|
-
|
|
1083
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
1084
|
+
proposedPerpPosition.marketIndex
|
|
824
1085
|
);
|
|
825
1086
|
|
|
826
|
-
const
|
|
1087
|
+
const proposedPerpPositionValue = calculateBaseAssetValueWithOracle(
|
|
827
1088
|
market,
|
|
828
|
-
|
|
1089
|
+
proposedPerpPosition,
|
|
829
1090
|
this.getOracleDataForMarket(market.marketIndex)
|
|
830
1091
|
);
|
|
831
1092
|
|
|
832
1093
|
// total position value after trade
|
|
833
1094
|
const totalPositionValueAfterTrade =
|
|
834
|
-
totalPositionValueExcludingTargetMarket.add(
|
|
1095
|
+
totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
|
|
835
1096
|
|
|
836
1097
|
const marginRequirementExcludingTargetMarket =
|
|
837
|
-
this.getUserAccount().
|
|
1098
|
+
this.getUserAccount().perpPositions.reduce(
|
|
838
1099
|
(totalMarginRequirement, position) => {
|
|
839
|
-
if (!position.marketIndex.eq(
|
|
840
|
-
const market = this.clearingHouse.
|
|
1100
|
+
if (!position.marketIndex.eq(perpPosition.marketIndex)) {
|
|
1101
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
841
1102
|
position.marketIndex
|
|
842
1103
|
);
|
|
843
1104
|
const positionValue = calculateBaseAssetValueWithOracle(
|
|
@@ -872,19 +1133,19 @@ export class ClearingHouseUser {
|
|
|
872
1133
|
// if the position value after the trade is less than free collateral, there is no liq price
|
|
873
1134
|
if (
|
|
874
1135
|
totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
875
|
-
|
|
1136
|
+
proposedPerpPosition.baseAssetAmount.abs().gt(ZERO)
|
|
876
1137
|
) {
|
|
877
1138
|
return new BN(-1);
|
|
878
1139
|
}
|
|
879
1140
|
|
|
880
1141
|
const marginRequirementAfterTrade =
|
|
881
1142
|
marginRequirementExcludingTargetMarket.add(
|
|
882
|
-
|
|
1143
|
+
proposedPerpPositionValue
|
|
883
1144
|
.mul(
|
|
884
1145
|
new BN(
|
|
885
1146
|
calculateMarketMarginRatio(
|
|
886
1147
|
market,
|
|
887
|
-
|
|
1148
|
+
proposedPerpPosition.baseAssetAmount.abs(),
|
|
888
1149
|
'Maintenance'
|
|
889
1150
|
)
|
|
890
1151
|
)
|
|
@@ -896,7 +1157,7 @@ export class ClearingHouseUser {
|
|
|
896
1157
|
);
|
|
897
1158
|
|
|
898
1159
|
const marketMaxLeverage = this.getMaxLeverage(
|
|
899
|
-
|
|
1160
|
+
proposedPerpPosition.marketIndex,
|
|
900
1161
|
'Maintenance'
|
|
901
1162
|
);
|
|
902
1163
|
|
|
@@ -920,8 +1181,8 @@ export class ClearingHouseUser {
|
|
|
920
1181
|
let markPriceAfterTrade;
|
|
921
1182
|
if (positionBaseSizeChange.eq(ZERO)) {
|
|
922
1183
|
markPriceAfterTrade = calculateMarkPrice(
|
|
923
|
-
this.clearingHouse.
|
|
924
|
-
this.getOracleDataForMarket(
|
|
1184
|
+
this.clearingHouse.getPerpMarketAccount(perpPosition.marketIndex),
|
|
1185
|
+
this.getOracleDataForMarket(perpPosition.marketIndex)
|
|
925
1186
|
);
|
|
926
1187
|
} else {
|
|
927
1188
|
const direction = positionBaseSizeChange.gt(ZERO)
|
|
@@ -930,9 +1191,9 @@ export class ClearingHouseUser {
|
|
|
930
1191
|
markPriceAfterTrade = calculateTradeSlippage(
|
|
931
1192
|
direction,
|
|
932
1193
|
positionBaseSizeChange.abs(),
|
|
933
|
-
this.clearingHouse.
|
|
1194
|
+
this.clearingHouse.getPerpMarketAccount(perpPosition.marketIndex),
|
|
934
1195
|
'base',
|
|
935
|
-
this.getOracleDataForMarket(
|
|
1196
|
+
this.getOracleDataForMarket(perpPosition.marketIndex)
|
|
936
1197
|
)[3]; // newPrice after swap
|
|
937
1198
|
}
|
|
938
1199
|
|
|
@@ -1019,7 +1280,7 @@ export class ClearingHouseUser {
|
|
|
1019
1280
|
// add any position we have on the opposite side of the current trade, because we can "flip" the size of this position without taking any extra leverage.
|
|
1020
1281
|
const oppositeSizeValueUSDC = targetingSameSide
|
|
1021
1282
|
? ZERO
|
|
1022
|
-
: this.
|
|
1283
|
+
: this.getPerpPositionValue(targetMarketIndex, oracleData);
|
|
1023
1284
|
|
|
1024
1285
|
let maxPositionSize = this.getBuyingPower(targetMarketIndex);
|
|
1025
1286
|
if (maxPositionSize.gte(ZERO)) {
|
|
@@ -1036,21 +1297,22 @@ export class ClearingHouseUser {
|
|
|
1036
1297
|
// current leverage is greater than max leverage - can only reduce position size
|
|
1037
1298
|
|
|
1038
1299
|
if (!targetingSameSide) {
|
|
1039
|
-
const market =
|
|
1040
|
-
|
|
1300
|
+
const market =
|
|
1301
|
+
this.clearingHouse.getPerpMarketAccount(targetMarketIndex);
|
|
1302
|
+
const perpPositionValue = this.getPerpPositionValue(
|
|
1041
1303
|
targetMarketIndex,
|
|
1042
1304
|
oracleData
|
|
1043
1305
|
);
|
|
1044
1306
|
const totalCollateral = this.getTotalCollateral();
|
|
1045
1307
|
const marginRequirement = this.getInitialMarginRequirement();
|
|
1046
|
-
const marginFreedByClosing =
|
|
1308
|
+
const marginFreedByClosing = perpPositionValue
|
|
1047
1309
|
.mul(new BN(market.marginRatioInitial))
|
|
1048
1310
|
.div(MARGIN_PRECISION);
|
|
1049
1311
|
const marginRequirementAfterClosing =
|
|
1050
1312
|
marginRequirement.sub(marginFreedByClosing);
|
|
1051
1313
|
|
|
1052
1314
|
if (marginRequirementAfterClosing.gt(totalCollateral)) {
|
|
1053
|
-
maxPositionSize =
|
|
1315
|
+
maxPositionSize = perpPositionValue;
|
|
1054
1316
|
} else {
|
|
1055
1317
|
const freeCollateralAfterClose = totalCollateral.sub(
|
|
1056
1318
|
marginRequirementAfterClosing
|
|
@@ -1058,7 +1320,7 @@ export class ClearingHouseUser {
|
|
|
1058
1320
|
const buyingPowerAfterClose = freeCollateralAfterClose
|
|
1059
1321
|
.mul(this.getMaxLeverage(targetMarketIndex))
|
|
1060
1322
|
.div(TEN_THOUSAND);
|
|
1061
|
-
maxPositionSize =
|
|
1323
|
+
maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
|
|
1062
1324
|
}
|
|
1063
1325
|
} else {
|
|
1064
1326
|
// do nothing if targetting same side
|
|
@@ -1091,7 +1353,7 @@ export class ClearingHouseUser {
|
|
|
1091
1353
|
|
|
1092
1354
|
const oracleData = this.getOracleDataForMarket(targetMarketIndex);
|
|
1093
1355
|
|
|
1094
|
-
let currentPositionQuoteAmount = this.
|
|
1356
|
+
let currentPositionQuoteAmount = this.getPerpPositionValue(
|
|
1095
1357
|
targetMarketIndex,
|
|
1096
1358
|
oracleData
|
|
1097
1359
|
);
|
|
@@ -1107,16 +1369,16 @@ export class ClearingHouseUser {
|
|
|
1107
1369
|
if (tradeSide === PositionDirection.SHORT)
|
|
1108
1370
|
tradeQuoteAmount = tradeQuoteAmount.neg();
|
|
1109
1371
|
|
|
1110
|
-
const
|
|
1372
|
+
const currentPerpPositionAfterTrade = currentPositionQuoteAmount
|
|
1111
1373
|
.add(tradeQuoteAmount)
|
|
1112
1374
|
.abs();
|
|
1113
1375
|
|
|
1114
1376
|
const totalPositionAfterTradeExcludingTargetMarket =
|
|
1115
|
-
this.
|
|
1377
|
+
this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex);
|
|
1116
1378
|
|
|
1117
1379
|
const totalCollateral = this.getTotalCollateral();
|
|
1118
1380
|
if (totalCollateral.gt(ZERO)) {
|
|
1119
|
-
const newLeverage =
|
|
1381
|
+
const newLeverage = currentPerpPositionAfterTrade
|
|
1120
1382
|
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
1121
1383
|
.abs()
|
|
1122
1384
|
.mul(TEN_THOUSAND)
|
|
@@ -1133,7 +1395,7 @@ export class ClearingHouseUser {
|
|
|
1133
1395
|
* @returns feeForQuote : Precision QUOTE_PRECISION
|
|
1134
1396
|
*/
|
|
1135
1397
|
public calculateFeeForQuoteAmount(quoteAmount: BN): BN {
|
|
1136
|
-
const feeStructure = this.clearingHouse.getStateAccount().
|
|
1398
|
+
const feeStructure = this.clearingHouse.getStateAccount().perpFeeStructure;
|
|
1137
1399
|
|
|
1138
1400
|
return quoteAmount
|
|
1139
1401
|
.mul(feeStructure.feeNumerator)
|
|
@@ -1145,34 +1407,35 @@ export class ClearingHouseUser {
|
|
|
1145
1407
|
* @param marketToIgnore
|
|
1146
1408
|
* @returns positionValue : Precision QUOTE_PRECISION
|
|
1147
1409
|
*/
|
|
1148
|
-
private
|
|
1149
|
-
const
|
|
1410
|
+
private getTotalPerpPositionValueExcludingMarket(marketToIgnore: BN): BN {
|
|
1411
|
+
const currentPerpPosition =
|
|
1150
1412
|
this.getUserPosition(marketToIgnore) ||
|
|
1151
1413
|
this.getEmptyPosition(marketToIgnore);
|
|
1152
1414
|
|
|
1153
1415
|
const oracleData = this.getOracleDataForMarket(marketToIgnore);
|
|
1154
1416
|
|
|
1155
|
-
let
|
|
1156
|
-
if (
|
|
1157
|
-
|
|
1417
|
+
let currentPerpPositionValueUSDC = ZERO;
|
|
1418
|
+
if (currentPerpPosition) {
|
|
1419
|
+
currentPerpPositionValueUSDC = this.getPerpPositionValue(
|
|
1158
1420
|
marketToIgnore,
|
|
1159
1421
|
oracleData
|
|
1160
1422
|
);
|
|
1161
1423
|
}
|
|
1162
1424
|
|
|
1163
|
-
return this.
|
|
1425
|
+
return this.getTotalPerpPositionValue().sub(currentPerpPositionValueUSDC);
|
|
1164
1426
|
}
|
|
1165
1427
|
|
|
1166
1428
|
private getOracleDataForMarket(marketIndex: BN): OraclePriceData {
|
|
1167
1429
|
const oracleKey =
|
|
1168
|
-
this.clearingHouse.
|
|
1430
|
+
this.clearingHouse.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
1169
1431
|
const oracleData =
|
|
1170
1432
|
this.clearingHouse.getOraclePriceDataAndSlot(oracleKey).data;
|
|
1171
1433
|
|
|
1172
1434
|
return oracleData;
|
|
1173
1435
|
}
|
|
1174
|
-
private
|
|
1175
|
-
const oracleKey =
|
|
1436
|
+
private getOracleDataForSpotMarket(marketIndex: BN): OraclePriceData {
|
|
1437
|
+
const oracleKey =
|
|
1438
|
+
this.clearingHouse.getSpotMarketAccount(marketIndex).oracle;
|
|
1176
1439
|
|
|
1177
1440
|
const oracleData =
|
|
1178
1441
|
this.clearingHouse.getOraclePriceDataAndSlot(oracleKey).data;
|