@drift-labs/sdk 0.2.0-master.3 → 0.2.0-master.30
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/README.md +13 -13
- package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
- package/lib/accounts/bulkUserStatsSubscription.js +21 -0
- package/lib/accounts/bulkUserSubscription.js +0 -1
- package/lib/accounts/fetch.d.ts +2 -1
- package/lib/accounts/fetch.js +9 -1
- package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +16 -16
- package/lib/accounts/pollingClearingHouseAccountSubscriber.js +31 -28
- package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
- package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
- package/lib/accounts/types.d.ts +22 -11
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +17 -17
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +37 -35
- package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
- package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
- package/lib/addresses/marketAddresses.d.ts +1 -3
- package/lib/addresses/pda.d.ts +10 -4
- package/lib/addresses/pda.js +51 -14
- package/lib/admin.d.ts +34 -22
- package/lib/admin.js +182 -73
- package/lib/clearingHouse.d.ts +120 -42
- package/lib/clearingHouse.js +1505 -254
- package/lib/clearingHouseConfig.d.ts +4 -4
- package/lib/clearingHouseUser.d.ts +50 -38
- package/lib/clearingHouseUser.js +410 -190
- package/lib/clearingHouseUserStats.d.ts +18 -0
- package/lib/clearingHouseUserStats.js +49 -0
- package/lib/clearingHouseUserStatsConfig.d.ts +14 -0
- package/lib/clearingHouseUserStatsConfig.js +2 -0
- package/lib/config.d.ts +7 -9
- package/lib/config.js +21 -21
- package/lib/constants/numericConstants.d.ts +18 -12
- package/lib/constants/numericConstants.js +28 -21
- package/lib/constants/perpMarkets.d.ts +18 -0
- package/lib/constants/{markets.js → perpMarkets.js} +7 -7
- package/lib/constants/spotMarkets.d.ts +19 -0
- package/lib/constants/spotMarkets.js +53 -0
- package/lib/dlob/DLOB.d.ts +73 -0
- package/lib/dlob/DLOB.js +553 -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/eventList.js +3 -0
- package/lib/events/eventSubscriber.d.ts +4 -2
- package/lib/events/eventSubscriber.js +16 -9
- package/lib/events/fetchLogs.d.ts +10 -1
- package/lib/events/fetchLogs.js +27 -7
- package/lib/events/pollingLogProvider.d.ts +2 -1
- package/lib/events/pollingLogProvider.js +6 -2
- package/lib/events/sort.js +8 -11
- package/lib/events/types.d.ts +7 -2
- package/lib/events/types.js +5 -0
- package/lib/examples/makeTradeExample.js +20 -8
- package/lib/factory/bigNum.d.ts +1 -0
- package/lib/factory/bigNum.js +34 -10
- package/lib/idl/clearing_house.json +4313 -1433
- package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
- package/lib/index.d.ts +21 -6
- package/lib/index.js +25 -6
- package/lib/math/amm.d.ts +8 -5
- package/lib/math/amm.js +68 -46
- package/lib/math/auction.js +4 -1
- package/lib/math/conversion.js +1 -1
- package/lib/math/funding.d.ts +6 -6
- package/lib/math/funding.js +11 -10
- package/lib/math/insurance.d.ts +4 -0
- package/lib/math/insurance.js +27 -0
- package/lib/math/margin.d.ts +11 -0
- package/lib/math/margin.js +77 -0
- package/lib/math/market.d.ts +14 -9
- package/lib/math/market.js +69 -7
- package/lib/math/oracles.d.ts +4 -0
- package/lib/math/oracles.js +36 -8
- package/lib/math/orders.d.ts +6 -2
- package/lib/math/orders.js +78 -3
- package/lib/math/position.d.ts +21 -13
- package/lib/math/position.js +76 -36
- package/lib/math/repeg.js +14 -5
- 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/trade.d.ts +10 -10
- package/lib/math/trade.js +22 -29
- package/lib/oracles/pythClient.js +1 -1
- package/lib/oracles/quoteAssetOracleClient.js +1 -1
- package/lib/oracles/switchboardClient.js +1 -1
- package/lib/orderParams.d.ts +14 -5
- package/lib/orderParams.js +12 -92
- 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/slot/SlotSubscriber.d.ts +7 -0
- package/lib/slot/SlotSubscriber.js +3 -0
- package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
- package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
- package/lib/tx/retryTxSender.d.ts +1 -1
- package/lib/tx/retryTxSender.js +13 -4
- package/lib/tx/types.d.ts +1 -1
- package/lib/tx/utils.js +1 -1
- package/lib/types.d.ts +474 -123
- package/lib/types.js +99 -5
- 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/lib/util/computeUnits.js +1 -1
- package/lib/util/getTokenAddress.d.ts +2 -0
- package/lib/util/getTokenAddress.js +9 -0
- package/package.json +9 -5
- package/src/accounts/bulkUserStatsSubscription.ts +33 -0
- package/src/accounts/bulkUserSubscription.ts +0 -1
- package/src/accounts/fetch.ts +27 -2
- package/src/accounts/pollingClearingHouseAccountSubscriber.ts +46 -42
- package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
- package/src/accounts/types.ts +31 -11
- package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +64 -59
- package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
- package/src/addresses/marketAddresses.ts +1 -2
- package/src/addresses/pda.ts +88 -14
- package/src/admin.ts +333 -128
- package/src/assert/assert.js +9 -0
- package/src/clearingHouse.ts +2464 -458
- package/src/clearingHouseConfig.ts +4 -3
- package/src/clearingHouseUser.ts +747 -291
- package/src/clearingHouseUserStats.ts +75 -0
- package/src/clearingHouseUserStatsConfig.ts +18 -0
- package/src/config.ts +30 -31
- package/src/constants/numericConstants.ts +41 -25
- package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
- package/src/constants/spotMarkets.ts +72 -0
- package/src/dlob/DLOB.ts +868 -0
- package/src/dlob/DLOBNode.ts +162 -0
- package/src/dlob/NodeList.ts +185 -0
- package/src/events/eventList.js +77 -0
- package/src/events/eventList.ts +3 -0
- package/src/events/eventSubscriber.ts +20 -12
- package/src/events/fetchLogs.ts +35 -8
- package/src/events/pollingLogProvider.ts +10 -2
- package/src/events/sort.ts +11 -15
- package/src/events/types.ts +16 -1
- package/src/examples/makeTradeExample.js +157 -0
- package/src/examples/makeTradeExample.ts +32 -14
- package/src/factory/bigNum.ts +42 -13
- package/src/idl/clearing_house.json +4313 -1433
- package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
- package/src/index.ts +21 -6
- package/src/math/amm.ts +136 -66
- package/src/math/auction.ts +5 -1
- package/src/math/conversion.ts +2 -2
- package/src/math/funding.ts +20 -18
- package/src/math/insurance.ts +35 -0
- package/src/math/margin.ts +127 -0
- package/src/math/market.ts +138 -12
- package/src/math/oracles.ts +63 -9
- package/src/math/orders.ts +138 -4
- package/src/math/position.ts +119 -58
- package/src/math/repeg.ts +16 -6
- package/src/math/spotBalance.ts +316 -0
- package/src/math/spotMarket.ts +9 -0
- package/src/math/spotPosition.ts +47 -0
- package/src/math/trade.ts +43 -49
- package/src/oracles/pythClient.ts +2 -2
- package/src/oracles/quoteAssetOracleClient.ts +2 -2
- package/src/oracles/switchboardClient.ts +2 -2
- package/src/orderParams.ts +24 -137
- package/src/serum/serumSubscriber.ts +80 -0
- package/src/serum/types.ts +13 -0
- package/src/slot/SlotSubscriber.ts +11 -1
- package/src/token/index.js +38 -0
- package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
- package/src/tx/retryTxSender.ts +16 -5
- package/src/tx/types.js +2 -0
- package/src/tx/types.ts +2 -1
- package/src/tx/utils.js +17 -0
- package/src/tx/utils.ts +1 -1
- package/src/types.ts +477 -125
- package/src/userMap/userMap.ts +100 -0
- package/src/userMap/userStatsMap.ts +110 -0
- package/src/util/computeUnits.js +21 -11
- package/src/util/computeUnits.ts +1 -1
- package/src/util/getTokenAddress.js +9 -0
- package/src/util/getTokenAddress.ts +18 -0
- package/src/util/promiseTimeout.js +14 -0
- package/src/util/tps.js +27 -0
- package/tests/bn/test.ts +12 -3
- package/tests/dlob/helpers.ts +374 -0
- package/tests/dlob/test.ts +2865 -0
- package/lib/constants/banks.d.ts +0 -16
- package/lib/constants/banks.js +0 -34
- package/lib/constants/markets.d.ts +0 -19
- package/lib/math/bankBalance.d.ts +0 -9
- package/lib/math/bankBalance.js +0 -75
- package/lib/math/state.d.ts +0 -8
- package/lib/math/state.js +0 -15
- package/lib/orders.d.ts +0 -8
- package/lib/orders.js +0 -134
- package/src/constants/banks.ts +0 -43
- package/src/math/bankBalance.ts +0 -112
- package/src/math/state.ts +0 -14
- package/src/math/utils.js +0 -27
- package/src/math/utils.js.map +0 -1
- package/src/orders.ts +0 -244
- package/src/util/computeUnits.js.map +0 -1
package/src/clearingHouseUser.ts
CHANGED
|
@@ -7,11 +7,11 @@ 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 {
|
|
14
|
-
|
|
14
|
+
PRICE_PRECISION,
|
|
15
15
|
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
16
16
|
ZERO,
|
|
17
17
|
TEN_THOUSAND,
|
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
AMM_RESERVE_PRECISION,
|
|
21
21
|
PRICE_TO_QUOTE_PRECISION,
|
|
22
22
|
MARGIN_PRECISION,
|
|
23
|
-
|
|
23
|
+
SPOT_MARKET_WEIGHT_PRECISION,
|
|
24
|
+
QUOTE_SPOT_MARKET_INDEX,
|
|
24
25
|
} from './constants/numericConstants';
|
|
25
26
|
import {
|
|
26
27
|
UserAccountSubscriber,
|
|
@@ -28,21 +29,35 @@ import {
|
|
|
28
29
|
DataAndSlot,
|
|
29
30
|
} from './accounts/types';
|
|
30
31
|
import {
|
|
31
|
-
|
|
32
|
+
calculateReservePrice,
|
|
32
33
|
calculateBaseAssetValue,
|
|
33
34
|
calculatePositionFundingPNL,
|
|
34
35
|
calculatePositionPNL,
|
|
36
|
+
calculateUnrealizedAssetWeight,
|
|
37
|
+
calculateMarketMarginRatio,
|
|
35
38
|
PositionDirection,
|
|
36
39
|
calculateTradeSlippage,
|
|
37
40
|
BN,
|
|
38
|
-
|
|
41
|
+
SpotMarketAccount,
|
|
42
|
+
getTokenValue,
|
|
39
43
|
} from '.';
|
|
40
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
getTokenAmount,
|
|
46
|
+
calculateAssetWeight,
|
|
47
|
+
calculateLiabilityWeight,
|
|
48
|
+
} from './math/spotBalance';
|
|
49
|
+
import {
|
|
50
|
+
calculateBaseAssetValueWithOracle,
|
|
51
|
+
calculateWorstCaseBaseAssetAmount,
|
|
52
|
+
} from './math/margin';
|
|
41
53
|
import { OraclePriceData } from './oracles/types';
|
|
42
54
|
import { ClearingHouseUserConfig } from './clearingHouseUserConfig';
|
|
43
55
|
import { PollingUserAccountSubscriber } from './accounts/pollingUserAccountSubscriber';
|
|
44
56
|
import { WebSocketUserAccountSubscriber } from './accounts/webSocketUserAccountSubscriber';
|
|
45
|
-
|
|
57
|
+
import {
|
|
58
|
+
getWorstCaseTokenAmounts,
|
|
59
|
+
isSpotPositionAvailable,
|
|
60
|
+
} from './math/spotPosition';
|
|
46
61
|
export class ClearingHouseUser {
|
|
47
62
|
clearingHouse: ClearingHouse;
|
|
48
63
|
userAccountPublicKey: PublicKey;
|
|
@@ -110,26 +125,36 @@ export class ClearingHouseUser {
|
|
|
110
125
|
* @param marketIndex
|
|
111
126
|
* @returns userPosition
|
|
112
127
|
*/
|
|
113
|
-
public getUserPosition(marketIndex:
|
|
114
|
-
return this.getUserAccount().
|
|
115
|
-
position.marketIndex
|
|
128
|
+
public getUserPosition(marketIndex: number): PerpPosition | undefined {
|
|
129
|
+
return this.getUserAccount().perpPositions.find(
|
|
130
|
+
(position) => position.marketIndex === marketIndex
|
|
116
131
|
);
|
|
117
132
|
}
|
|
118
133
|
|
|
119
|
-
public getEmptyPosition(marketIndex:
|
|
134
|
+
public getEmptyPosition(marketIndex: number): PerpPosition {
|
|
120
135
|
return {
|
|
121
136
|
baseAssetAmount: ZERO,
|
|
137
|
+
remainderBaseAssetAmount: ZERO,
|
|
122
138
|
lastCumulativeFundingRate: ZERO,
|
|
123
139
|
marketIndex,
|
|
124
140
|
quoteAssetAmount: ZERO,
|
|
125
141
|
quoteEntryAmount: ZERO,
|
|
126
|
-
openOrders:
|
|
127
|
-
unsettledPnl: ZERO,
|
|
142
|
+
openOrders: 0,
|
|
128
143
|
openBids: ZERO,
|
|
129
144
|
openAsks: ZERO,
|
|
145
|
+
settledPnl: ZERO,
|
|
146
|
+
lpShares: ZERO,
|
|
147
|
+
lastFeePerLp: ZERO,
|
|
148
|
+
lastNetBaseAssetAmountPerLp: ZERO,
|
|
149
|
+
lastNetQuoteAssetAmountPerLp: ZERO,
|
|
130
150
|
};
|
|
131
151
|
}
|
|
132
152
|
|
|
153
|
+
public getClonedPosition(position: PerpPosition): PerpPosition {
|
|
154
|
+
const clonedPosition = Object.assign({}, position);
|
|
155
|
+
return clonedPosition;
|
|
156
|
+
}
|
|
157
|
+
|
|
133
158
|
/**
|
|
134
159
|
* @param orderId
|
|
135
160
|
* @returns Order
|
|
@@ -162,11 +187,117 @@ export class ClearingHouseUser {
|
|
|
162
187
|
return userAccountRPCResponse.value !== null;
|
|
163
188
|
}
|
|
164
189
|
|
|
190
|
+
/**
|
|
191
|
+
* calculates the market position if the lp position was settled
|
|
192
|
+
* @returns : the settled userPosition
|
|
193
|
+
* @returns : the dust base asset amount (ie, < stepsize)
|
|
194
|
+
* @returns : pnl from settle
|
|
195
|
+
*/
|
|
196
|
+
public getSettledLPPosition(marketIndex: number): [PerpPosition, BN, BN] {
|
|
197
|
+
const _position = this.getUserPosition(marketIndex);
|
|
198
|
+
const position = this.getClonedPosition(_position);
|
|
199
|
+
|
|
200
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
201
|
+
position.marketIndex
|
|
202
|
+
);
|
|
203
|
+
const nShares = position.lpShares;
|
|
204
|
+
|
|
205
|
+
const deltaBaa = market.amm.marketPositionPerLp.baseAssetAmount
|
|
206
|
+
.sub(position.lastNetBaseAssetAmountPerLp)
|
|
207
|
+
.mul(nShares)
|
|
208
|
+
.div(AMM_RESERVE_PRECISION);
|
|
209
|
+
const deltaQaa = market.amm.marketPositionPerLp.quoteAssetAmount
|
|
210
|
+
.sub(position.lastNetQuoteAssetAmountPerLp)
|
|
211
|
+
.mul(nShares)
|
|
212
|
+
.div(AMM_RESERVE_PRECISION);
|
|
213
|
+
|
|
214
|
+
function sign(v: BN) {
|
|
215
|
+
const sign = { true: new BN(1), false: new BN(-1) }[
|
|
216
|
+
v.gte(ZERO).toString()
|
|
217
|
+
];
|
|
218
|
+
return sign;
|
|
219
|
+
}
|
|
220
|
+
|
|
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];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const [standardizedBaa, remainderBaa] = standardize(
|
|
228
|
+
deltaBaa,
|
|
229
|
+
market.amm.baseAssetAmountStepSize
|
|
230
|
+
);
|
|
231
|
+
|
|
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
|
+
}
|
|
248
|
+
|
|
249
|
+
let updateType;
|
|
250
|
+
if (position.baseAssetAmount.eq(ZERO)) {
|
|
251
|
+
updateType = 'open';
|
|
252
|
+
} else if (sign(position.baseAssetAmount).eq(sign(deltaBaa))) {
|
|
253
|
+
updateType = 'increase';
|
|
254
|
+
} else if (position.baseAssetAmount.abs().gt(deltaBaa.abs())) {
|
|
255
|
+
updateType = 'reduce';
|
|
256
|
+
} else if (position.baseAssetAmount.abs().eq(deltaBaa.abs())) {
|
|
257
|
+
updateType = 'close';
|
|
258
|
+
} else {
|
|
259
|
+
updateType = 'flip';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let newQuoteEntry;
|
|
263
|
+
let pnl;
|
|
264
|
+
if (updateType == 'open' || updateType == 'increase') {
|
|
265
|
+
newQuoteEntry = position.quoteEntryAmount.add(deltaQaa);
|
|
266
|
+
pnl = 0;
|
|
267
|
+
} else if (updateType == 'reduce' || updateType == 'close') {
|
|
268
|
+
newQuoteEntry = position.quoteEntryAmount.sub(
|
|
269
|
+
position.quoteEntryAmount
|
|
270
|
+
.mul(deltaBaa.abs())
|
|
271
|
+
.div(position.baseAssetAmount.abs())
|
|
272
|
+
);
|
|
273
|
+
pnl = position.quoteEntryAmount.sub(newQuoteEntry).add(deltaQaa);
|
|
274
|
+
} else {
|
|
275
|
+
newQuoteEntry = deltaQaa.sub(
|
|
276
|
+
deltaQaa.mul(position.baseAssetAmount.abs()).div(deltaBaa.abs())
|
|
277
|
+
);
|
|
278
|
+
pnl = position.quoteEntryAmount.add(deltaQaa.sub(newQuoteEntry));
|
|
279
|
+
}
|
|
280
|
+
position.quoteEntryAmount = newQuoteEntry;
|
|
281
|
+
position.baseAssetAmount = position.baseAssetAmount.add(standardizedBaa);
|
|
282
|
+
position.quoteAssetAmount = position.quoteAssetAmount.add(deltaQaa);
|
|
283
|
+
|
|
284
|
+
if (position.baseAssetAmount.gt(ZERO)) {
|
|
285
|
+
position.lastCumulativeFundingRate = market.amm.cumulativeFundingRateLong;
|
|
286
|
+
} else if (position.baseAssetAmount.lt(ZERO)) {
|
|
287
|
+
position.lastCumulativeFundingRate =
|
|
288
|
+
market.amm.cumulativeFundingRateShort;
|
|
289
|
+
} else {
|
|
290
|
+
position.lastCumulativeFundingRate = ZERO;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return [position, remainderBaa, pnl];
|
|
294
|
+
}
|
|
295
|
+
|
|
165
296
|
/**
|
|
166
297
|
* calculates Buying Power = FC * MAX_LEVERAGE
|
|
167
298
|
* @returns : Precision QUOTE_PRECISION
|
|
168
299
|
*/
|
|
169
|
-
public getBuyingPower(marketIndex:
|
|
300
|
+
public getBuyingPower(marketIndex: number): BN {
|
|
170
301
|
return this.getFreeCollateral()
|
|
171
302
|
.mul(this.getMaxLeverage(marketIndex, 'Initial'))
|
|
172
303
|
.div(TEN_THOUSAND);
|
|
@@ -183,82 +314,85 @@ export class ClearingHouseUser {
|
|
|
183
314
|
return freeCollateral.gte(ZERO) ? freeCollateral : ZERO;
|
|
184
315
|
}
|
|
185
316
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
317
|
+
/**
|
|
318
|
+
* @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
|
|
319
|
+
*/
|
|
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
|
+
);
|
|
203
336
|
}
|
|
204
337
|
|
|
205
338
|
/**
|
|
206
|
-
* @returns The
|
|
339
|
+
* @returns The initial margin requirement in USDC. : QUOTE_PRECISION
|
|
207
340
|
*/
|
|
208
|
-
public
|
|
209
|
-
return this.
|
|
210
|
-
.positions.reduce((marginRequirement, marketPosition) => {
|
|
211
|
-
const market = this.clearingHouse.getMarketAccount(
|
|
212
|
-
marketPosition.marketIndex
|
|
213
|
-
);
|
|
214
|
-
return marginRequirement.add(
|
|
215
|
-
calculateBaseAssetValue(
|
|
216
|
-
market,
|
|
217
|
-
marketPosition,
|
|
218
|
-
this.getOracleDataForMarket(market.marketIndex)
|
|
219
|
-
)
|
|
220
|
-
.mul(new BN(market.marginRatioPartial))
|
|
221
|
-
.div(MARGIN_PRECISION)
|
|
222
|
-
);
|
|
223
|
-
}, ZERO)
|
|
224
|
-
.add(this.getTotalLiability());
|
|
341
|
+
public getInitialMarginRequirement(): BN {
|
|
342
|
+
return this.getMarginRequirement('Initial');
|
|
225
343
|
}
|
|
226
344
|
|
|
227
345
|
/**
|
|
228
|
-
*
|
|
229
|
-
* @returns : Precision QUOTE_PRECISION
|
|
346
|
+
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
230
347
|
*/
|
|
231
|
-
public
|
|
232
|
-
return this.
|
|
233
|
-
.positions.filter((pos) =>
|
|
234
|
-
marketIndex ? pos.marketIndex === marketIndex : true
|
|
235
|
-
)
|
|
236
|
-
.reduce((pnl, marketPosition) => {
|
|
237
|
-
const market = this.clearingHouse.getMarketAccount(
|
|
238
|
-
marketPosition.marketIndex
|
|
239
|
-
);
|
|
240
|
-
return pnl.add(
|
|
241
|
-
calculatePositionPNL(
|
|
242
|
-
market,
|
|
243
|
-
marketPosition,
|
|
244
|
-
withFunding,
|
|
245
|
-
this.getOracleDataForMarket(market.marketIndex)
|
|
246
|
-
)
|
|
247
|
-
);
|
|
248
|
-
}, ZERO);
|
|
348
|
+
public getMaintenanceMarginRequirement(liquidationBuffer?: BN): BN {
|
|
349
|
+
return this.getMarginRequirement('Maintenance', liquidationBuffer);
|
|
249
350
|
}
|
|
250
351
|
|
|
251
352
|
/**
|
|
252
353
|
* calculates unrealized position price pnl
|
|
253
354
|
* @returns : Precision QUOTE_PRECISION
|
|
254
355
|
*/
|
|
255
|
-
public
|
|
356
|
+
public getUnrealizedPNL(
|
|
357
|
+
withFunding?: boolean,
|
|
358
|
+
marketIndex?: number,
|
|
359
|
+
withWeightMarginCategory?: MarginCategory
|
|
360
|
+
): BN {
|
|
361
|
+
const quoteSpotMarket = this.clearingHouse.getQuoteSpotMarketAccount();
|
|
256
362
|
return this.getUserAccount()
|
|
257
|
-
.
|
|
363
|
+
.perpPositions.filter((pos) =>
|
|
258
364
|
marketIndex ? pos.marketIndex === marketIndex : true
|
|
259
365
|
)
|
|
260
|
-
.reduce((
|
|
261
|
-
|
|
366
|
+
.reduce((unrealizedPnl, perpPosition) => {
|
|
367
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
368
|
+
perpPosition.marketIndex
|
|
369
|
+
);
|
|
370
|
+
const oraclePriceData = this.getOracleDataForMarket(market.marketIndex);
|
|
371
|
+
|
|
372
|
+
let positionUnrealizedPnl = calculatePositionPNL(
|
|
373
|
+
market,
|
|
374
|
+
perpPosition,
|
|
375
|
+
withFunding,
|
|
376
|
+
oraclePriceData
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
if (withWeightMarginCategory !== undefined) {
|
|
380
|
+
if (positionUnrealizedPnl.gt(ZERO)) {
|
|
381
|
+
positionUnrealizedPnl = positionUnrealizedPnl
|
|
382
|
+
.mul(
|
|
383
|
+
calculateUnrealizedAssetWeight(
|
|
384
|
+
market,
|
|
385
|
+
quoteSpotMarket,
|
|
386
|
+
positionUnrealizedPnl,
|
|
387
|
+
withWeightMarginCategory,
|
|
388
|
+
oraclePriceData
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
.div(new BN(SPOT_MARKET_WEIGHT_PRECISION));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return unrealizedPnl.add(positionUnrealizedPnl);
|
|
262
396
|
}, ZERO);
|
|
263
397
|
}
|
|
264
398
|
|
|
@@ -266,163 +400,426 @@ export class ClearingHouseUser {
|
|
|
266
400
|
* calculates unrealized funding payment pnl
|
|
267
401
|
* @returns : Precision QUOTE_PRECISION
|
|
268
402
|
*/
|
|
269
|
-
public getUnrealizedFundingPNL(marketIndex?:
|
|
403
|
+
public getUnrealizedFundingPNL(marketIndex?: number): BN {
|
|
270
404
|
return this.getUserAccount()
|
|
271
|
-
.
|
|
405
|
+
.perpPositions.filter((pos) =>
|
|
272
406
|
marketIndex ? pos.marketIndex === marketIndex : true
|
|
273
407
|
)
|
|
274
|
-
.reduce((pnl,
|
|
275
|
-
const market = this.clearingHouse.
|
|
276
|
-
|
|
408
|
+
.reduce((pnl, perpPosition) => {
|
|
409
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
410
|
+
perpPosition.marketIndex
|
|
277
411
|
);
|
|
278
|
-
return pnl.add(calculatePositionFundingPNL(market,
|
|
412
|
+
return pnl.add(calculatePositionFundingPNL(market, perpPosition));
|
|
279
413
|
}, ZERO);
|
|
280
414
|
}
|
|
281
415
|
|
|
282
|
-
public
|
|
283
|
-
|
|
284
|
-
|
|
416
|
+
public getSpotMarketLiabilityValue(
|
|
417
|
+
marketIndex?: number,
|
|
418
|
+
marginCategory?: MarginCategory,
|
|
419
|
+
liquidationBuffer?: BN,
|
|
420
|
+
includeOpenOrders?: boolean
|
|
421
|
+
): BN {
|
|
422
|
+
return this.getUserAccount().spotPositions.reduce(
|
|
423
|
+
(totalLiabilityValue, spotPosition) => {
|
|
285
424
|
if (
|
|
286
|
-
|
|
287
|
-
|
|
425
|
+
isSpotPositionAvailable(spotPosition) ||
|
|
426
|
+
(marketIndex !== undefined &&
|
|
427
|
+
spotPosition.marketIndex !== marketIndex)
|
|
288
428
|
) {
|
|
289
|
-
return
|
|
429
|
+
return totalLiabilityValue;
|
|
290
430
|
}
|
|
291
431
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
bankBalance.bankIndex
|
|
295
|
-
);
|
|
432
|
+
const spotMarketAccount: SpotMarketAccount =
|
|
433
|
+
this.clearingHouse.getSpotMarketAccount(spotPosition.marketIndex);
|
|
296
434
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
435
|
+
if (spotPosition.marketIndex === 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
|
|
308
463
|
);
|
|
464
|
+
|
|
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)
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
let newTotalLiabilityValue = totalLiabilityValue;
|
|
493
|
+
if (worstCaseTokenAmount.lt(ZERO)) {
|
|
494
|
+
const baseLiabilityValue = this.getSpotLiabilityValue(
|
|
495
|
+
worstCaseTokenAmount,
|
|
496
|
+
oraclePriceData,
|
|
497
|
+
spotMarketAccount,
|
|
498
|
+
marginCategory,
|
|
499
|
+
liquidationBuffer
|
|
500
|
+
);
|
|
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
|
|
516
|
+
.mul(weight)
|
|
517
|
+
.div(SPOT_MARKET_WEIGHT_PRECISION);
|
|
518
|
+
|
|
519
|
+
newTotalLiabilityValue =
|
|
520
|
+
newTotalLiabilityValue.add(weightedTokenValue);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return newTotalLiabilityValue;
|
|
309
524
|
},
|
|
310
525
|
ZERO
|
|
311
526
|
);
|
|
312
527
|
}
|
|
313
528
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
529
|
+
getSpotLiabilityValue(
|
|
530
|
+
tokenAmount: BN,
|
|
531
|
+
oraclePriceData: OraclePriceData,
|
|
532
|
+
spotMarketAccount: SpotMarketAccount,
|
|
533
|
+
marginCategory?: MarginCategory,
|
|
534
|
+
liquidationBuffer?: BN
|
|
535
|
+
): BN {
|
|
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?: number,
|
|
570
|
+
marginCategory?: MarginCategory,
|
|
571
|
+
includeOpenOrders?: boolean
|
|
572
|
+
): BN {
|
|
573
|
+
return this.getUserAccount().spotPositions.reduce(
|
|
574
|
+
(totalAssetValue, spotPosition) => {
|
|
317
575
|
if (
|
|
318
|
-
|
|
319
|
-
(
|
|
576
|
+
isSpotPositionAvailable(spotPosition) ||
|
|
577
|
+
(marketIndex !== undefined &&
|
|
578
|
+
spotPosition.marketIndex !== marketIndex)
|
|
320
579
|
) {
|
|
321
580
|
return totalAssetValue;
|
|
322
581
|
}
|
|
323
582
|
|
|
324
583
|
// Todo this needs to account for whether it's based on initial or maintenance requirements
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
584
|
+
const spotMarketAccount: SpotMarketAccount =
|
|
585
|
+
this.clearingHouse.getSpotMarketAccount(spotPosition.marketIndex);
|
|
586
|
+
|
|
587
|
+
if (spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX) {
|
|
588
|
+
if (isVariant(spotPosition.balanceType, 'deposit')) {
|
|
589
|
+
const tokenAmount = getTokenAmount(
|
|
590
|
+
spotPosition.balance,
|
|
591
|
+
spotMarketAccount,
|
|
592
|
+
spotPosition.balanceType
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
return totalAssetValue.add(tokenAmount);
|
|
596
|
+
} else {
|
|
597
|
+
return totalAssetValue;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
328
600
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
bankAccount,
|
|
332
|
-
bankBalance.balanceType
|
|
601
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(
|
|
602
|
+
spotPosition.marketIndex
|
|
333
603
|
);
|
|
334
604
|
|
|
335
|
-
if (
|
|
336
|
-
|
|
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
|
+
}
|
|
337
622
|
}
|
|
338
623
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
624
|
+
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
|
|
625
|
+
getWorstCaseTokenAmounts(
|
|
626
|
+
spotPosition,
|
|
627
|
+
spotMarketAccount,
|
|
628
|
+
this.getOracleDataForSpotMarket(spotPosition.marketIndex)
|
|
629
|
+
);
|
|
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
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return newTotalAssetValue;
|
|
344
650
|
},
|
|
345
651
|
ZERO
|
|
346
652
|
);
|
|
347
653
|
}
|
|
348
654
|
|
|
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)
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
349
689
|
/**
|
|
350
690
|
* calculates TotalCollateral: collateral + unrealized pnl
|
|
351
|
-
* TODO: rename to total equity (for perpetuals swaps)
|
|
352
691
|
* @returns : Precision QUOTE_PRECISION
|
|
353
692
|
*/
|
|
354
|
-
public getTotalCollateral(): BN {
|
|
355
|
-
return this.
|
|
356
|
-
.
|
|
357
|
-
|
|
358
|
-
bankBalance.balance.eq(ZERO) ||
|
|
359
|
-
isVariant(bankBalance.balanceType, 'borrow')
|
|
360
|
-
) {
|
|
361
|
-
return totalAssetValue;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Todo this needs to account for whether it's based on initial or maintenance requirements
|
|
365
|
-
const bankAccount: BankAccount = this.clearingHouse.getBankAccount(
|
|
366
|
-
bankBalance.bankIndex
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
const tokenAmount = getTokenAmount(
|
|
370
|
-
bankBalance.balance,
|
|
371
|
-
bankAccount,
|
|
372
|
-
bankBalance.balanceType
|
|
373
|
-
);
|
|
374
|
-
return totalAssetValue.add(
|
|
375
|
-
tokenAmount
|
|
376
|
-
.mul(this.getOracleDataForBank(bankAccount.bankIndex).price)
|
|
377
|
-
.mul(bankAccount.initialAssetWeight)
|
|
378
|
-
.div(BANK_WEIGHT_PRECISION)
|
|
379
|
-
.div(MARK_PRICE_PRECISION)
|
|
380
|
-
);
|
|
381
|
-
}, ZERO)
|
|
382
|
-
.add(this.getUnrealizedPNL(true))
|
|
383
|
-
.add(this.getUnsettledPNL());
|
|
693
|
+
public getTotalCollateral(marginCategory: MarginCategory = 'Initial'): BN {
|
|
694
|
+
return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(
|
|
695
|
+
this.getUnrealizedPNL(true, undefined, marginCategory)
|
|
696
|
+
);
|
|
384
697
|
}
|
|
385
698
|
|
|
386
699
|
/**
|
|
387
|
-
* calculates sum of position value across all positions
|
|
700
|
+
* calculates sum of position value across all positions in margin system
|
|
388
701
|
* @returns : Precision QUOTE_PRECISION
|
|
389
702
|
*/
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
marketPosition,
|
|
400
|
-
this.getOracleDataForMarket(market.marketIndex)
|
|
401
|
-
)
|
|
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
|
|
402
712
|
);
|
|
713
|
+
|
|
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(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);
|
|
403
796
|
},
|
|
404
797
|
ZERO
|
|
405
798
|
);
|
|
406
799
|
}
|
|
407
800
|
|
|
408
801
|
/**
|
|
409
|
-
* calculates position value
|
|
802
|
+
* calculates position value in margin system
|
|
410
803
|
* @returns : Precision QUOTE_PRECISION
|
|
411
804
|
*/
|
|
412
|
-
public
|
|
413
|
-
marketIndex:
|
|
805
|
+
public getPerpPositionValue(
|
|
806
|
+
marketIndex: number,
|
|
414
807
|
oraclePriceData: OraclePriceData
|
|
415
808
|
): BN {
|
|
416
809
|
const userPosition =
|
|
417
810
|
this.getUserPosition(marketIndex) || this.getEmptyPosition(marketIndex);
|
|
418
|
-
const market = this.clearingHouse.
|
|
811
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
419
812
|
userPosition.marketIndex
|
|
420
813
|
);
|
|
421
|
-
return
|
|
814
|
+
return calculateBaseAssetValueWithOracle(
|
|
815
|
+
market,
|
|
816
|
+
userPosition,
|
|
817
|
+
oraclePriceData
|
|
818
|
+
);
|
|
422
819
|
}
|
|
423
820
|
|
|
424
821
|
public getPositionSide(
|
|
425
|
-
currentPosition: Pick<
|
|
822
|
+
currentPosition: Pick<PerpPosition, 'baseAssetAmount'>
|
|
426
823
|
): PositionDirection | undefined {
|
|
427
824
|
if (currentPosition.baseAssetAmount.gt(ZERO)) {
|
|
428
825
|
return PositionDirection.LONG;
|
|
@@ -434,14 +831,17 @@ export class ClearingHouseUser {
|
|
|
434
831
|
}
|
|
435
832
|
|
|
436
833
|
/**
|
|
437
|
-
* calculates average exit price for closing 100% of position
|
|
438
|
-
* @returns : Precision
|
|
834
|
+
* calculates average exit price (optionally for closing up to 100% of position)
|
|
835
|
+
* @returns : Precision PRICE_PRECISION
|
|
439
836
|
*/
|
|
440
837
|
public getPositionEstimatedExitPriceAndPnl(
|
|
441
|
-
position:
|
|
442
|
-
amountToClose?: BN
|
|
838
|
+
position: PerpPosition,
|
|
839
|
+
amountToClose?: BN,
|
|
840
|
+
useAMMClose = false
|
|
443
841
|
): [BN, BN] {
|
|
444
|
-
const market = this.clearingHouse.
|
|
842
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
843
|
+
position.marketIndex
|
|
844
|
+
);
|
|
445
845
|
|
|
446
846
|
const entryPrice = calculateEntryPrice(position);
|
|
447
847
|
|
|
@@ -449,34 +849,44 @@ export class ClearingHouseUser {
|
|
|
449
849
|
|
|
450
850
|
if (amountToClose) {
|
|
451
851
|
if (amountToClose.eq(ZERO)) {
|
|
452
|
-
return [
|
|
852
|
+
return [calculateReservePrice(market, oraclePriceData), ZERO];
|
|
453
853
|
}
|
|
454
854
|
position = {
|
|
455
855
|
baseAssetAmount: amountToClose,
|
|
456
856
|
lastCumulativeFundingRate: position.lastCumulativeFundingRate,
|
|
457
857
|
marketIndex: position.marketIndex,
|
|
458
858
|
quoteAssetAmount: position.quoteAssetAmount,
|
|
459
|
-
} as
|
|
859
|
+
} as PerpPosition;
|
|
460
860
|
}
|
|
461
861
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
862
|
+
let baseAssetValue: BN;
|
|
863
|
+
|
|
864
|
+
if (useAMMClose) {
|
|
865
|
+
baseAssetValue = calculateBaseAssetValue(
|
|
866
|
+
market,
|
|
867
|
+
position,
|
|
868
|
+
oraclePriceData
|
|
869
|
+
);
|
|
870
|
+
} else {
|
|
871
|
+
baseAssetValue = calculateBaseAssetValueWithOracle(
|
|
872
|
+
market,
|
|
873
|
+
position,
|
|
874
|
+
oraclePriceData
|
|
875
|
+
);
|
|
876
|
+
}
|
|
467
877
|
if (position.baseAssetAmount.eq(ZERO)) {
|
|
468
878
|
return [ZERO, ZERO];
|
|
469
879
|
}
|
|
470
880
|
|
|
471
881
|
const exitPrice = baseAssetValue
|
|
472
882
|
.mul(AMM_TO_QUOTE_PRECISION_RATIO)
|
|
473
|
-
.mul(
|
|
883
|
+
.mul(PRICE_PRECISION)
|
|
474
884
|
.div(position.baseAssetAmount.abs());
|
|
475
885
|
|
|
476
886
|
const pnlPerBase = exitPrice.sub(entryPrice);
|
|
477
887
|
const pnl = pnlPerBase
|
|
478
888
|
.mul(position.baseAssetAmount)
|
|
479
|
-
.div(
|
|
889
|
+
.div(PRICE_PRECISION)
|
|
480
890
|
.div(AMM_TO_QUOTE_PRECISION_RATIO);
|
|
481
891
|
|
|
482
892
|
return [exitPrice, pnl];
|
|
@@ -486,13 +896,31 @@ export class ClearingHouseUser {
|
|
|
486
896
|
* calculates current user leverage across all positions
|
|
487
897
|
* @returns : Precision TEN_THOUSAND
|
|
488
898
|
*/
|
|
489
|
-
public getLeverage(): BN {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
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)) {
|
|
493
920
|
return ZERO;
|
|
494
921
|
}
|
|
495
|
-
|
|
922
|
+
|
|
923
|
+
return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
|
|
496
924
|
}
|
|
497
925
|
|
|
498
926
|
/**
|
|
@@ -501,26 +929,17 @@ export class ClearingHouseUser {
|
|
|
501
929
|
* @returns : Precision TEN_THOUSAND
|
|
502
930
|
*/
|
|
503
931
|
public getMaxLeverage(
|
|
504
|
-
marketIndex:
|
|
932
|
+
marketIndex: number,
|
|
505
933
|
category: MarginCategory = 'Initial'
|
|
506
934
|
): BN {
|
|
507
|
-
const market = this.clearingHouse.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
marginRatioCategory = market.marginRatioMaintenance;
|
|
516
|
-
break;
|
|
517
|
-
case 'Partial':
|
|
518
|
-
marginRatioCategory = market.marginRatioPartial;
|
|
519
|
-
break;
|
|
520
|
-
default:
|
|
521
|
-
marginRatioCategory = market.marginRatioInitial;
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
935
|
+
const market = this.clearingHouse.getPerpMarketAccount(marketIndex);
|
|
936
|
+
|
|
937
|
+
const marginRatioCategory = calculateMarketMarginRatio(
|
|
938
|
+
market,
|
|
939
|
+
// worstCaseBaseAssetAmount.abs(),
|
|
940
|
+
ZERO, // todo
|
|
941
|
+
category
|
|
942
|
+
);
|
|
524
943
|
const maxLeverage = TEN_THOUSAND.mul(TEN_THOUSAND).div(
|
|
525
944
|
new BN(marginRatioCategory)
|
|
526
945
|
);
|
|
@@ -531,22 +950,46 @@ export class ClearingHouseUser {
|
|
|
531
950
|
* calculates margin ratio: total collateral / |total position value|
|
|
532
951
|
* @returns : Precision TEN_THOUSAND
|
|
533
952
|
*/
|
|
534
|
-
public getMarginRatio(): BN {
|
|
535
|
-
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
|
+
);
|
|
536
966
|
|
|
537
|
-
if (
|
|
967
|
+
if (totalLiabilityValue.eq(ZERO)) {
|
|
538
968
|
return BN_MAX;
|
|
539
969
|
}
|
|
540
970
|
|
|
541
|
-
|
|
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);
|
|
542
978
|
}
|
|
543
979
|
|
|
544
|
-
public canBeLiquidated():
|
|
980
|
+
public canBeLiquidated(): boolean {
|
|
545
981
|
const totalCollateral = this.getTotalCollateral();
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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);
|
|
550
993
|
}
|
|
551
994
|
|
|
552
995
|
/**
|
|
@@ -554,12 +997,12 @@ export class ClearingHouseUser {
|
|
|
554
997
|
* @returns
|
|
555
998
|
*/
|
|
556
999
|
public needsToSettleFundingPayment(): boolean {
|
|
557
|
-
for (const userPosition of this.getUserAccount().
|
|
1000
|
+
for (const userPosition of this.getUserAccount().perpPositions) {
|
|
558
1001
|
if (userPosition.baseAssetAmount.eq(ZERO)) {
|
|
559
1002
|
continue;
|
|
560
1003
|
}
|
|
561
1004
|
|
|
562
|
-
const market = this.clearingHouse.
|
|
1005
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
563
1006
|
userPosition.marketIndex
|
|
564
1007
|
);
|
|
565
1008
|
if (
|
|
@@ -580,15 +1023,14 @@ export class ClearingHouseUser {
|
|
|
580
1023
|
|
|
581
1024
|
/**
|
|
582
1025
|
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
583
|
-
* @param
|
|
1026
|
+
* @param PerpPosition
|
|
584
1027
|
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
585
1028
|
* @param partial
|
|
586
|
-
* @returns Precision :
|
|
1029
|
+
* @returns Precision : PRICE_PRECISION
|
|
587
1030
|
*/
|
|
588
1031
|
public liquidationPrice(
|
|
589
|
-
|
|
590
|
-
positionBaseSizeChange: BN = ZERO
|
|
591
|
-
partial = false
|
|
1032
|
+
perpPosition: Pick<PerpPosition, 'marketIndex'>,
|
|
1033
|
+
positionBaseSizeChange: BN = ZERO
|
|
592
1034
|
): BN {
|
|
593
1035
|
// solves formula for example canBeLiquidated below
|
|
594
1036
|
|
|
@@ -606,65 +1048,73 @@ export class ClearingHouseUser {
|
|
|
606
1048
|
|
|
607
1049
|
// calculate the total position value ignoring any value from the target market of the trade
|
|
608
1050
|
const totalPositionValueExcludingTargetMarket =
|
|
609
|
-
this.
|
|
1051
|
+
this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
|
|
610
1052
|
|
|
611
|
-
const
|
|
612
|
-
this.getUserPosition(
|
|
613
|
-
this.getEmptyPosition(
|
|
1053
|
+
const currentPerpPosition =
|
|
1054
|
+
this.getUserPosition(perpPosition.marketIndex) ||
|
|
1055
|
+
this.getEmptyPosition(perpPosition.marketIndex);
|
|
614
1056
|
|
|
615
|
-
const
|
|
1057
|
+
const currentPerpPositionBaseSize = currentPerpPosition.baseAssetAmount;
|
|
616
1058
|
|
|
617
|
-
const proposedBaseAssetAmount =
|
|
1059
|
+
const proposedBaseAssetAmount = currentPerpPositionBaseSize.add(
|
|
618
1060
|
positionBaseSizeChange
|
|
619
1061
|
);
|
|
620
1062
|
|
|
621
1063
|
// calculate position for current market after trade
|
|
622
|
-
const
|
|
623
|
-
marketIndex:
|
|
1064
|
+
const proposedPerpPosition: PerpPosition = {
|
|
1065
|
+
marketIndex: perpPosition.marketIndex,
|
|
624
1066
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
625
|
-
|
|
626
|
-
currentMarketPosition.lastCumulativeFundingRate,
|
|
1067
|
+
remainderBaseAssetAmount: ZERO,
|
|
627
1068
|
quoteAssetAmount: new BN(0),
|
|
1069
|
+
lastCumulativeFundingRate: ZERO,
|
|
628
1070
|
quoteEntryAmount: new BN(0),
|
|
629
|
-
openOrders:
|
|
630
|
-
unsettledPnl: new BN(0),
|
|
1071
|
+
openOrders: 0,
|
|
631
1072
|
openBids: new BN(0),
|
|
632
1073
|
openAsks: new BN(0),
|
|
1074
|
+
settledPnl: ZERO,
|
|
1075
|
+
lpShares: ZERO,
|
|
1076
|
+
lastFeePerLp: ZERO,
|
|
1077
|
+
lastNetBaseAssetAmountPerLp: ZERO,
|
|
1078
|
+
lastNetQuoteAssetAmountPerLp: ZERO,
|
|
633
1079
|
};
|
|
634
1080
|
|
|
635
1081
|
if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
|
|
636
1082
|
|
|
637
|
-
const market = this.clearingHouse.
|
|
638
|
-
|
|
1083
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
1084
|
+
proposedPerpPosition.marketIndex
|
|
639
1085
|
);
|
|
640
1086
|
|
|
641
|
-
const
|
|
1087
|
+
const proposedPerpPositionValue = calculateBaseAssetValueWithOracle(
|
|
642
1088
|
market,
|
|
643
|
-
|
|
1089
|
+
proposedPerpPosition,
|
|
644
1090
|
this.getOracleDataForMarket(market.marketIndex)
|
|
645
1091
|
);
|
|
646
1092
|
|
|
647
1093
|
// total position value after trade
|
|
648
1094
|
const totalPositionValueAfterTrade =
|
|
649
|
-
totalPositionValueExcludingTargetMarket.add(
|
|
1095
|
+
totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
|
|
650
1096
|
|
|
651
1097
|
const marginRequirementExcludingTargetMarket =
|
|
652
|
-
this.getUserAccount().
|
|
1098
|
+
this.getUserAccount().perpPositions.reduce(
|
|
653
1099
|
(totalMarginRequirement, position) => {
|
|
654
|
-
if (
|
|
655
|
-
const market = this.clearingHouse.
|
|
1100
|
+
if (position.marketIndex !== perpPosition.marketIndex) {
|
|
1101
|
+
const market = this.clearingHouse.getPerpMarketAccount(
|
|
656
1102
|
position.marketIndex
|
|
657
1103
|
);
|
|
658
|
-
const positionValue =
|
|
1104
|
+
const positionValue = calculateBaseAssetValueWithOracle(
|
|
659
1105
|
market,
|
|
660
1106
|
position,
|
|
661
1107
|
this.getOracleDataForMarket(market.marketIndex)
|
|
662
1108
|
);
|
|
663
1109
|
const marketMarginRequirement = positionValue
|
|
664
1110
|
.mul(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1111
|
+
new BN(
|
|
1112
|
+
calculateMarketMarginRatio(
|
|
1113
|
+
market,
|
|
1114
|
+
position.baseAssetAmount.abs(),
|
|
1115
|
+
'Maintenance'
|
|
1116
|
+
)
|
|
1117
|
+
)
|
|
668
1118
|
)
|
|
669
1119
|
.div(MARGIN_PRECISION);
|
|
670
1120
|
totalMarginRequirement = totalMarginRequirement.add(
|
|
@@ -683,18 +1133,22 @@ export class ClearingHouseUser {
|
|
|
683
1133
|
// if the position value after the trade is less than free collateral, there is no liq price
|
|
684
1134
|
if (
|
|
685
1135
|
totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
686
|
-
|
|
1136
|
+
proposedPerpPosition.baseAssetAmount.abs().gt(ZERO)
|
|
687
1137
|
) {
|
|
688
1138
|
return new BN(-1);
|
|
689
1139
|
}
|
|
690
1140
|
|
|
691
1141
|
const marginRequirementAfterTrade =
|
|
692
1142
|
marginRequirementExcludingTargetMarket.add(
|
|
693
|
-
|
|
1143
|
+
proposedPerpPositionValue
|
|
694
1144
|
.mul(
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1145
|
+
new BN(
|
|
1146
|
+
calculateMarketMarginRatio(
|
|
1147
|
+
market,
|
|
1148
|
+
proposedPerpPosition.baseAssetAmount.abs(),
|
|
1149
|
+
'Maintenance'
|
|
1150
|
+
)
|
|
1151
|
+
)
|
|
698
1152
|
)
|
|
699
1153
|
.div(MARGIN_PRECISION)
|
|
700
1154
|
);
|
|
@@ -702,9 +1156,10 @@ export class ClearingHouseUser {
|
|
|
702
1156
|
marginRequirementAfterTrade
|
|
703
1157
|
);
|
|
704
1158
|
|
|
705
|
-
const marketMaxLeverage =
|
|
706
|
-
|
|
707
|
-
|
|
1159
|
+
const marketMaxLeverage = this.getMaxLeverage(
|
|
1160
|
+
proposedPerpPosition.marketIndex,
|
|
1161
|
+
'Maintenance'
|
|
1162
|
+
);
|
|
708
1163
|
|
|
709
1164
|
let priceDelta;
|
|
710
1165
|
if (proposedBaseAssetAmount.lt(ZERO)) {
|
|
@@ -725,9 +1180,9 @@ export class ClearingHouseUser {
|
|
|
725
1180
|
|
|
726
1181
|
let markPriceAfterTrade;
|
|
727
1182
|
if (positionBaseSizeChange.eq(ZERO)) {
|
|
728
|
-
markPriceAfterTrade =
|
|
729
|
-
this.clearingHouse.
|
|
730
|
-
this.getOracleDataForMarket(
|
|
1183
|
+
markPriceAfterTrade = calculateReservePrice(
|
|
1184
|
+
this.clearingHouse.getPerpMarketAccount(perpPosition.marketIndex),
|
|
1185
|
+
this.getOracleDataForMarket(perpPosition.marketIndex)
|
|
731
1186
|
);
|
|
732
1187
|
} else {
|
|
733
1188
|
const direction = positionBaseSizeChange.gt(ZERO)
|
|
@@ -736,9 +1191,9 @@ export class ClearingHouseUser {
|
|
|
736
1191
|
markPriceAfterTrade = calculateTradeSlippage(
|
|
737
1192
|
direction,
|
|
738
1193
|
positionBaseSizeChange.abs(),
|
|
739
|
-
this.clearingHouse.
|
|
1194
|
+
this.clearingHouse.getPerpMarketAccount(perpPosition.marketIndex),
|
|
740
1195
|
'base',
|
|
741
|
-
this.getOracleDataForMarket(
|
|
1196
|
+
this.getOracleDataForMarket(perpPosition.marketIndex)
|
|
742
1197
|
)[3]; // newPrice after swap
|
|
743
1198
|
}
|
|
744
1199
|
|
|
@@ -753,10 +1208,10 @@ export class ClearingHouseUser {
|
|
|
753
1208
|
* Calculates the estimated liquidation price for a position after closing a quote amount of the position.
|
|
754
1209
|
* @param positionMarketIndex
|
|
755
1210
|
* @param closeQuoteAmount
|
|
756
|
-
* @returns : Precision
|
|
1211
|
+
* @returns : Precision PRICE_PRECISION
|
|
757
1212
|
*/
|
|
758
1213
|
public liquidationPriceAfterClose(
|
|
759
|
-
positionMarketIndex:
|
|
1214
|
+
positionMarketIndex: number,
|
|
760
1215
|
closeQuoteAmount: BN
|
|
761
1216
|
): BN {
|
|
762
1217
|
const currentPosition =
|
|
@@ -765,11 +1220,11 @@ export class ClearingHouseUser {
|
|
|
765
1220
|
|
|
766
1221
|
const closeBaseAmount = currentPosition.baseAssetAmount
|
|
767
1222
|
.mul(closeQuoteAmount)
|
|
768
|
-
.div(currentPosition.quoteAssetAmount)
|
|
1223
|
+
.div(currentPosition.quoteAssetAmount.abs())
|
|
769
1224
|
.add(
|
|
770
1225
|
currentPosition.baseAssetAmount
|
|
771
1226
|
.mul(closeQuoteAmount)
|
|
772
|
-
.mod(currentPosition.quoteAssetAmount)
|
|
1227
|
+
.mod(currentPosition.quoteAssetAmount.abs())
|
|
773
1228
|
)
|
|
774
1229
|
.neg();
|
|
775
1230
|
|
|
@@ -777,8 +1232,7 @@ export class ClearingHouseUser {
|
|
|
777
1232
|
{
|
|
778
1233
|
marketIndex: positionMarketIndex,
|
|
779
1234
|
},
|
|
780
|
-
closeBaseAmount
|
|
781
|
-
true
|
|
1235
|
+
closeBaseAmount
|
|
782
1236
|
);
|
|
783
1237
|
}
|
|
784
1238
|
|
|
@@ -804,7 +1258,7 @@ export class ClearingHouseUser {
|
|
|
804
1258
|
* @returns tradeSizeAllowed : Precision QUOTE_PRECISION
|
|
805
1259
|
*/
|
|
806
1260
|
public getMaxTradeSizeUSDC(
|
|
807
|
-
targetMarketIndex:
|
|
1261
|
+
targetMarketIndex: number,
|
|
808
1262
|
tradeSide: PositionDirection
|
|
809
1263
|
): BN {
|
|
810
1264
|
const currentPosition =
|
|
@@ -826,7 +1280,7 @@ export class ClearingHouseUser {
|
|
|
826
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.
|
|
827
1281
|
const oppositeSizeValueUSDC = targetingSameSide
|
|
828
1282
|
? ZERO
|
|
829
|
-
: this.
|
|
1283
|
+
: this.getPerpPositionValue(targetMarketIndex, oracleData);
|
|
830
1284
|
|
|
831
1285
|
let maxPositionSize = this.getBuyingPower(targetMarketIndex);
|
|
832
1286
|
if (maxPositionSize.gte(ZERO)) {
|
|
@@ -843,21 +1297,22 @@ export class ClearingHouseUser {
|
|
|
843
1297
|
// current leverage is greater than max leverage - can only reduce position size
|
|
844
1298
|
|
|
845
1299
|
if (!targetingSameSide) {
|
|
846
|
-
const market =
|
|
847
|
-
|
|
1300
|
+
const market =
|
|
1301
|
+
this.clearingHouse.getPerpMarketAccount(targetMarketIndex);
|
|
1302
|
+
const perpPositionValue = this.getPerpPositionValue(
|
|
848
1303
|
targetMarketIndex,
|
|
849
1304
|
oracleData
|
|
850
1305
|
);
|
|
851
1306
|
const totalCollateral = this.getTotalCollateral();
|
|
852
1307
|
const marginRequirement = this.getInitialMarginRequirement();
|
|
853
|
-
const marginFreedByClosing =
|
|
1308
|
+
const marginFreedByClosing = perpPositionValue
|
|
854
1309
|
.mul(new BN(market.marginRatioInitial))
|
|
855
1310
|
.div(MARGIN_PRECISION);
|
|
856
1311
|
const marginRequirementAfterClosing =
|
|
857
1312
|
marginRequirement.sub(marginFreedByClosing);
|
|
858
1313
|
|
|
859
1314
|
if (marginRequirementAfterClosing.gt(totalCollateral)) {
|
|
860
|
-
maxPositionSize =
|
|
1315
|
+
maxPositionSize = perpPositionValue;
|
|
861
1316
|
} else {
|
|
862
1317
|
const freeCollateralAfterClose = totalCollateral.sub(
|
|
863
1318
|
marginRequirementAfterClosing
|
|
@@ -865,7 +1320,7 @@ export class ClearingHouseUser {
|
|
|
865
1320
|
const buyingPowerAfterClose = freeCollateralAfterClose
|
|
866
1321
|
.mul(this.getMaxLeverage(targetMarketIndex))
|
|
867
1322
|
.div(TEN_THOUSAND);
|
|
868
|
-
maxPositionSize =
|
|
1323
|
+
maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
|
|
869
1324
|
}
|
|
870
1325
|
} else {
|
|
871
1326
|
// do nothing if targetting same side
|
|
@@ -888,7 +1343,7 @@ export class ClearingHouseUser {
|
|
|
888
1343
|
* @returns leverageRatio : Precision TEN_THOUSAND
|
|
889
1344
|
*/
|
|
890
1345
|
public accountLeverageRatioAfterTrade(
|
|
891
|
-
targetMarketIndex:
|
|
1346
|
+
targetMarketIndex: number,
|
|
892
1347
|
tradeQuoteAmount: BN,
|
|
893
1348
|
tradeSide: PositionDirection
|
|
894
1349
|
): BN {
|
|
@@ -898,7 +1353,7 @@ export class ClearingHouseUser {
|
|
|
898
1353
|
|
|
899
1354
|
const oracleData = this.getOracleDataForMarket(targetMarketIndex);
|
|
900
1355
|
|
|
901
|
-
let currentPositionQuoteAmount = this.
|
|
1356
|
+
let currentPositionQuoteAmount = this.getPerpPositionValue(
|
|
902
1357
|
targetMarketIndex,
|
|
903
1358
|
oracleData
|
|
904
1359
|
);
|
|
@@ -914,16 +1369,16 @@ export class ClearingHouseUser {
|
|
|
914
1369
|
if (tradeSide === PositionDirection.SHORT)
|
|
915
1370
|
tradeQuoteAmount = tradeQuoteAmount.neg();
|
|
916
1371
|
|
|
917
|
-
const
|
|
1372
|
+
const currentPerpPositionAfterTrade = currentPositionQuoteAmount
|
|
918
1373
|
.add(tradeQuoteAmount)
|
|
919
1374
|
.abs();
|
|
920
1375
|
|
|
921
1376
|
const totalPositionAfterTradeExcludingTargetMarket =
|
|
922
|
-
this.
|
|
1377
|
+
this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex);
|
|
923
1378
|
|
|
924
1379
|
const totalCollateral = this.getTotalCollateral();
|
|
925
1380
|
if (totalCollateral.gt(ZERO)) {
|
|
926
|
-
const newLeverage =
|
|
1381
|
+
const newLeverage = currentPerpPositionAfterTrade
|
|
927
1382
|
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
928
1383
|
.abs()
|
|
929
1384
|
.mul(TEN_THOUSAND)
|
|
@@ -940,11 +1395,11 @@ export class ClearingHouseUser {
|
|
|
940
1395
|
* @returns feeForQuote : Precision QUOTE_PRECISION
|
|
941
1396
|
*/
|
|
942
1397
|
public calculateFeeForQuoteAmount(quoteAmount: BN): BN {
|
|
943
|
-
const
|
|
944
|
-
|
|
1398
|
+
const feeTier =
|
|
1399
|
+
this.clearingHouse.getStateAccount().perpFeeStructure.feeTiers[0];
|
|
945
1400
|
return quoteAmount
|
|
946
|
-
.mul(
|
|
947
|
-
.div(
|
|
1401
|
+
.mul(new BN(feeTier.feeNumerator))
|
|
1402
|
+
.div(new BN(feeTier.feeDenominator));
|
|
948
1403
|
}
|
|
949
1404
|
|
|
950
1405
|
/**
|
|
@@ -952,34 +1407,35 @@ export class ClearingHouseUser {
|
|
|
952
1407
|
* @param marketToIgnore
|
|
953
1408
|
* @returns positionValue : Precision QUOTE_PRECISION
|
|
954
1409
|
*/
|
|
955
|
-
private
|
|
956
|
-
const
|
|
1410
|
+
private getTotalPerpPositionValueExcludingMarket(marketToIgnore: number): BN {
|
|
1411
|
+
const currentPerpPosition =
|
|
957
1412
|
this.getUserPosition(marketToIgnore) ||
|
|
958
1413
|
this.getEmptyPosition(marketToIgnore);
|
|
959
1414
|
|
|
960
1415
|
const oracleData = this.getOracleDataForMarket(marketToIgnore);
|
|
961
1416
|
|
|
962
|
-
let
|
|
963
|
-
if (
|
|
964
|
-
|
|
1417
|
+
let currentPerpPositionValueUSDC = ZERO;
|
|
1418
|
+
if (currentPerpPosition) {
|
|
1419
|
+
currentPerpPositionValueUSDC = this.getPerpPositionValue(
|
|
965
1420
|
marketToIgnore,
|
|
966
1421
|
oracleData
|
|
967
1422
|
);
|
|
968
1423
|
}
|
|
969
1424
|
|
|
970
|
-
return this.
|
|
1425
|
+
return this.getTotalPerpPositionValue().sub(currentPerpPositionValueUSDC);
|
|
971
1426
|
}
|
|
972
1427
|
|
|
973
|
-
private getOracleDataForMarket(marketIndex:
|
|
1428
|
+
private getOracleDataForMarket(marketIndex: number): OraclePriceData {
|
|
974
1429
|
const oracleKey =
|
|
975
|
-
this.clearingHouse.
|
|
1430
|
+
this.clearingHouse.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
976
1431
|
const oracleData =
|
|
977
1432
|
this.clearingHouse.getOraclePriceDataAndSlot(oracleKey).data;
|
|
978
1433
|
|
|
979
1434
|
return oracleData;
|
|
980
1435
|
}
|
|
981
|
-
private
|
|
982
|
-
const oracleKey =
|
|
1436
|
+
private getOracleDataForSpotMarket(marketIndex: number): OraclePriceData {
|
|
1437
|
+
const oracleKey =
|
|
1438
|
+
this.clearingHouse.getSpotMarketAccount(marketIndex).oracle;
|
|
983
1439
|
|
|
984
1440
|
const oracleData =
|
|
985
1441
|
this.clearingHouse.getOraclePriceDataAndSlot(oracleKey).data;
|