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