@drift-labs/sdk 0.2.0-master.4 → 0.2.0-master.41
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 +27 -27
- package/lib/accounts/bulkAccountLoader.d.ts +2 -0
- package/lib/accounts/bulkAccountLoader.js +36 -29
- package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
- package/lib/accounts/bulkUserStatsSubscription.js +21 -0
- package/lib/accounts/bulkUserSubscription.d.ts +2 -2
- 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 → pollingDriftClientAccountSubscriber.d.ts} +20 -25
- package/lib/accounts/{pollingClearingHouseAccountSubscriber.js → pollingDriftClientAccountSubscriber.js} +45 -49
- package/lib/accounts/{pollingOracleSubscriber.d.ts → pollingOracleAccountSubscriber.d.ts} +2 -2
- package/lib/accounts/{pollingOracleSubscriber.js → pollingOracleAccountSubscriber.js} +3 -3
- package/lib/accounts/pollingUserAccountSubscriber.d.ts +1 -1
- package/lib/accounts/pollingUserAccountSubscriber.js +5 -11
- package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
- package/lib/accounts/pollingUserStatsAccountSubscriber.js +107 -0
- package/lib/accounts/types.d.ts +26 -15
- package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +49 -0
- package/lib/accounts/{webSocketClearingHouseAccountSubscriber.js → webSocketDriftClientAccountSubscriber.js} +47 -45
- 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/marketAddresses.js +1 -1
- package/lib/addresses/pda.d.ts +15 -9
- package/lib/addresses/pda.js +73 -35
- package/lib/adminClient.d.ts +65 -0
- package/lib/adminClient.js +637 -0
- package/lib/config.d.ts +9 -9
- package/lib/config.js +25 -21
- package/lib/constants/numericConstants.d.ts +30 -12
- package/lib/constants/numericConstants.js +41 -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 +82 -0
- package/lib/dlob/DLOB.js +694 -0
- package/lib/dlob/DLOBNode.d.ts +54 -0
- package/lib/dlob/DLOBNode.js +77 -0
- package/lib/dlob/NodeList.d.ts +27 -0
- package/lib/dlob/NodeList.js +144 -0
- package/lib/driftClient.d.ts +234 -0
- package/lib/driftClient.js +2108 -0
- package/lib/{clearingHouseConfig.d.ts → driftClientConfig.d.ts} +9 -9
- package/lib/{clearingHouseConfig.js → driftClientConfig.js} +0 -0
- package/lib/events/eventList.js +3 -0
- package/lib/events/eventSubscriber.d.ts +5 -2
- package/lib/events/eventSubscriber.js +25 -11
- package/lib/events/fetchLogs.d.ts +13 -2
- package/lib/events/fetchLogs.js +40 -12
- package/lib/events/pollingLogProvider.d.ts +2 -1
- package/lib/events/pollingLogProvider.js +7 -3
- package/lib/events/sort.js +8 -11
- package/lib/events/types.d.ts +11 -3
- package/lib/events/types.js +8 -0
- package/lib/events/webSocketLogProvider.js +1 -1
- package/lib/examples/makeTradeExample.js +30 -18
- package/lib/factory/bigNum.d.ts +8 -4
- package/lib/factory/bigNum.js +109 -19
- package/lib/idl/drift.json +8386 -0
- package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
- package/lib/index.d.ts +30 -13
- package/lib/index.js +30 -13
- package/lib/math/amm.d.ts +9 -6
- package/lib/math/amm.js +91 -38
- package/lib/math/conversion.js +1 -1
- package/lib/math/exchangeStatus.d.ts +4 -0
- package/lib/math/exchangeStatus.js +18 -0
- package/lib/math/funding.d.ts +6 -6
- package/lib/math/funding.js +23 -21
- 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 +82 -0
- package/lib/math/market.d.ts +14 -9
- package/lib/math/market.js +70 -10
- package/lib/math/oracles.d.ts +4 -0
- package/lib/math/oracles.js +36 -8
- package/lib/math/orders.d.ts +16 -6
- package/lib/math/orders.js +97 -17
- package/lib/math/position.d.ts +27 -13
- package/lib/math/position.js +92 -36
- package/lib/math/repeg.js +17 -8
- 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 +27 -31
- 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 +4 -4
- package/lib/orderParams.js +12 -4
- package/lib/serum/serumFulfillmentConfigMap.d.ts +10 -0
- package/lib/serum/serumFulfillmentConfigMap.js +17 -0
- package/lib/serum/serumSubscriber.d.ts +27 -0
- package/lib/serum/serumSubscriber.js +56 -0
- package/lib/serum/types.d.ts +11 -0
- package/lib/{clearingHouseUserConfig.js → serum/types.js} +0 -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 +688 -216
- package/lib/types.js +153 -24
- package/lib/user.d.ts +228 -0
- package/lib/user.js +959 -0
- package/lib/userConfig.d.ts +14 -0
- package/lib/userConfig.js +2 -0
- package/lib/userMap/userMap.d.ts +41 -0
- package/lib/userMap/userMap.js +85 -0
- package/lib/userMap/userStatsMap.d.ts +19 -0
- package/lib/userMap/userStatsMap.js +68 -0
- package/lib/userName.d.ts +1 -0
- package/lib/userName.js +3 -2
- package/lib/userStats.d.ts +18 -0
- package/lib/userStats.js +49 -0
- package/lib/userStatsConfig.d.ts +14 -0
- package/lib/userStatsConfig.js +2 -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 +11 -4
- package/src/accounts/bulkAccountLoader.ts +44 -34
- package/src/accounts/bulkUserStatsSubscription.ts +33 -0
- package/src/accounts/bulkUserSubscription.ts +2 -3
- package/src/accounts/fetch.ts +27 -2
- package/src/accounts/{pollingClearingHouseAccountSubscriber.ts → pollingDriftClientAccountSubscriber.ts} +65 -75
- package/src/accounts/{pollingOracleSubscriber.ts → pollingOracleAccountSubscriber.ts} +2 -2
- package/src/accounts/pollingUserAccountSubscriber.ts +5 -12
- package/src/accounts/pollingUserStatsAccountSubscriber.ts +166 -0
- package/src/accounts/types.ts +35 -15
- package/src/accounts/{webSocketClearingHouseAccountSubscriber.ts → webSocketDriftClientAccountSubscriber.ts} +78 -73
- package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
- package/src/addresses/marketAddresses.ts +3 -4
- package/src/addresses/pda.ts +105 -33
- package/src/adminClient.ts +1207 -0
- package/src/assert/assert.js +9 -0
- package/src/config.ts +37 -31
- package/src/constants/numericConstants.ts +58 -24
- package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
- package/src/constants/spotMarkets.ts +73 -0
- package/src/dlob/DLOB.ts +1120 -0
- package/src/dlob/DLOBNode.ts +155 -0
- package/src/dlob/NodeList.ts +195 -0
- package/src/driftClient.ts +3594 -0
- package/src/{clearingHouseConfig.ts → driftClientConfig.ts} +9 -8
- package/src/events/eventList.js +77 -0
- package/src/events/eventList.ts +3 -0
- package/src/events/eventSubscriber.ts +36 -14
- package/src/events/fetchLogs.ts +55 -13
- package/src/events/pollingLogProvider.ts +11 -3
- package/src/events/sort.ts +11 -15
- package/src/events/types.ts +27 -2
- package/src/events/webSocketLogProvider.ts +1 -1
- package/src/examples/makeTradeExample.js +157 -0
- package/src/examples/makeTradeExample.ts +44 -28
- package/src/factory/bigNum.ts +150 -22
- package/src/idl/drift.json +8386 -0
- package/src/idl/pyth.json +98 -2
- package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
- package/src/index.ts +30 -13
- package/src/math/amm.ts +161 -48
- package/src/math/conversion.ts +2 -2
- package/src/math/exchangeStatus.ts +31 -0
- package/src/math/funding.ts +41 -31
- package/src/math/insurance.ts +35 -0
- package/src/math/margin.ts +133 -0
- package/src/math/market.ts +143 -14
- package/src/math/oracles.ts +63 -9
- package/src/math/orders.ts +168 -26
- package/src/math/position.ts +136 -58
- package/src/math/repeg.ts +19 -9
- 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 +33 -37
- 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 +16 -8
- package/src/serum/serumFulfillmentConfigMap.ts +26 -0
- package/src/serum/serumSubscriber.ts +99 -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 +650 -189
- package/src/user.ts +1599 -0
- package/src/{clearingHouseUserConfig.ts → userConfig.ts} +5 -5
- package/src/userMap/userMap.ts +124 -0
- package/src/userMap/userStatsMap.ts +108 -0
- package/src/userName.ts +2 -1
- package/src/userStats.ts +75 -0
- package/src/userStatsConfig.ts +18 -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 +46 -11
- package/tests/dlob/helpers.ts +620 -0
- package/tests/dlob/test.ts +4586 -0
- package/yarn-error.log +3160 -0
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +0 -49
- package/lib/admin.d.ts +0 -44
- package/lib/admin.js +0 -433
- package/lib/clearingHouse.d.ts +0 -133
- package/lib/clearingHouse.js +0 -931
- package/lib/clearingHouseUser.d.ts +0 -187
- package/lib/clearingHouseUser.js +0 -643
- package/lib/clearingHouseUserConfig.d.ts +0 -14
- 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/idl/clearing_house.json +0 -3998
- 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/admin.ts +0 -722
- package/src/clearingHouse.ts +0 -1451
- package/src/clearingHouseUser.ts +0 -989
- package/src/constants/banks.ts +0 -43
- package/src/idl/clearing_house.json +0 -3998
- 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/lib/user.js
ADDED
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.User = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
const position_1 = require("./math/position");
|
|
6
|
+
const numericConstants_1 = require("./constants/numericConstants");
|
|
7
|
+
const _1 = require(".");
|
|
8
|
+
const spotBalance_1 = require("./math/spotBalance");
|
|
9
|
+
const amm_1 = require("./math/amm");
|
|
10
|
+
const margin_1 = require("./math/margin");
|
|
11
|
+
const pollingUserAccountSubscriber_1 = require("./accounts/pollingUserAccountSubscriber");
|
|
12
|
+
const webSocketUserAccountSubscriber_1 = require("./accounts/webSocketUserAccountSubscriber");
|
|
13
|
+
const spotPosition_1 = require("./math/spotPosition");
|
|
14
|
+
class User {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
var _a;
|
|
17
|
+
this._isSubscribed = false;
|
|
18
|
+
this.driftClient = config.driftClient;
|
|
19
|
+
this.userAccountPublicKey = config.userAccountPublicKey;
|
|
20
|
+
if (((_a = config.accountSubscription) === null || _a === void 0 ? void 0 : _a.type) === 'polling') {
|
|
21
|
+
this.accountSubscriber = new pollingUserAccountSubscriber_1.PollingUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, config.accountSubscription.accountLoader);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.accountSubscriber = new webSocketUserAccountSubscriber_1.WebSocketUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey);
|
|
25
|
+
}
|
|
26
|
+
this.eventEmitter = this.accountSubscriber.eventEmitter;
|
|
27
|
+
}
|
|
28
|
+
get isSubscribed() {
|
|
29
|
+
return this._isSubscribed && this.accountSubscriber.isSubscribed;
|
|
30
|
+
}
|
|
31
|
+
set isSubscribed(val) {
|
|
32
|
+
this._isSubscribed = val;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to User state accounts
|
|
36
|
+
* @returns SusbcriptionSuccess result
|
|
37
|
+
*/
|
|
38
|
+
async subscribe() {
|
|
39
|
+
this.isSubscribed = await this.accountSubscriber.subscribe();
|
|
40
|
+
return this.isSubscribed;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Forces the accountSubscriber to fetch account updates from rpc
|
|
44
|
+
*/
|
|
45
|
+
async fetchAccounts() {
|
|
46
|
+
await this.accountSubscriber.fetch();
|
|
47
|
+
}
|
|
48
|
+
async unsubscribe() {
|
|
49
|
+
await this.accountSubscriber.unsubscribe();
|
|
50
|
+
this.isSubscribed = false;
|
|
51
|
+
}
|
|
52
|
+
getUserAccount() {
|
|
53
|
+
return this.accountSubscriber.getUserAccountAndSlot().data;
|
|
54
|
+
}
|
|
55
|
+
getUserAccountAndSlot() {
|
|
56
|
+
return this.accountSubscriber.getUserAccountAndSlot();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Gets the user's current position for a given perp market. If the user has no position returns undefined
|
|
60
|
+
* @param marketIndex
|
|
61
|
+
* @returns userPerpPosition
|
|
62
|
+
*/
|
|
63
|
+
getPerpPosition(marketIndex) {
|
|
64
|
+
return this.getUserAccount().perpPositions.find((position) => position.marketIndex === marketIndex);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Gets the user's current position for a given spot market. If the user has no position returns undefined
|
|
68
|
+
* @param marketIndex
|
|
69
|
+
* @returns userSpotPosition
|
|
70
|
+
*/
|
|
71
|
+
getSpotPosition(marketIndex) {
|
|
72
|
+
return this.getUserAccount().spotPositions.find((position) => position.marketIndex === marketIndex);
|
|
73
|
+
}
|
|
74
|
+
getEmptyPosition(marketIndex) {
|
|
75
|
+
return {
|
|
76
|
+
baseAssetAmount: numericConstants_1.ZERO,
|
|
77
|
+
remainderBaseAssetAmount: 0,
|
|
78
|
+
lastCumulativeFundingRate: numericConstants_1.ZERO,
|
|
79
|
+
marketIndex,
|
|
80
|
+
quoteAssetAmount: numericConstants_1.ZERO,
|
|
81
|
+
quoteEntryAmount: numericConstants_1.ZERO,
|
|
82
|
+
quoteBreakEvenAmount: numericConstants_1.ZERO,
|
|
83
|
+
openOrders: 0,
|
|
84
|
+
openBids: numericConstants_1.ZERO,
|
|
85
|
+
openAsks: numericConstants_1.ZERO,
|
|
86
|
+
settledPnl: numericConstants_1.ZERO,
|
|
87
|
+
lpShares: numericConstants_1.ZERO,
|
|
88
|
+
lastNetBaseAssetAmountPerLp: numericConstants_1.ZERO,
|
|
89
|
+
lastNetQuoteAssetAmountPerLp: numericConstants_1.ZERO,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
getClonedPosition(position) {
|
|
93
|
+
const clonedPosition = Object.assign({}, position);
|
|
94
|
+
return clonedPosition;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @param orderId
|
|
98
|
+
* @returns Order
|
|
99
|
+
*/
|
|
100
|
+
getOrder(orderId) {
|
|
101
|
+
return this.getUserAccount().orders.find((order) => order.orderId === orderId);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param userOrderId
|
|
105
|
+
* @returns Order
|
|
106
|
+
*/
|
|
107
|
+
getOrderByUserOrderId(userOrderId) {
|
|
108
|
+
return this.getUserAccount().orders.find((order) => order.userOrderId === userOrderId);
|
|
109
|
+
}
|
|
110
|
+
getUserAccountPublicKey() {
|
|
111
|
+
return this.userAccountPublicKey;
|
|
112
|
+
}
|
|
113
|
+
async exists() {
|
|
114
|
+
const userAccountRPCResponse = await this.driftClient.connection.getParsedAccountInfo(this.userAccountPublicKey);
|
|
115
|
+
return userAccountRPCResponse.value !== null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* calculates the total open bids/asks in a perp market (including lps)
|
|
119
|
+
* @returns : open bids
|
|
120
|
+
* @returns : open asks
|
|
121
|
+
*/
|
|
122
|
+
getPerpBidAsks(marketIndex) {
|
|
123
|
+
const position = this.getPerpPosition(marketIndex);
|
|
124
|
+
const [lpOpenBids, lpOpenAsks] = this.getLPBidAsks(marketIndex);
|
|
125
|
+
const totalOpenBids = lpOpenBids.add(position.openBids);
|
|
126
|
+
const totalOpenAsks = lpOpenAsks.add(position.openAsks);
|
|
127
|
+
return [totalOpenBids, totalOpenAsks];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* calculates the open bids and asks for an lp
|
|
131
|
+
* @returns : lp open bids
|
|
132
|
+
* @returns : lp open asks
|
|
133
|
+
*/
|
|
134
|
+
getLPBidAsks(marketIndex) {
|
|
135
|
+
const position = this.getPerpPosition(marketIndex);
|
|
136
|
+
if (position === undefined || position.lpShares.eq(numericConstants_1.ZERO)) {
|
|
137
|
+
return [numericConstants_1.ZERO, numericConstants_1.ZERO];
|
|
138
|
+
}
|
|
139
|
+
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
140
|
+
const [marketOpenBids, marketOpenAsks] = (0, amm_1.calculateMarketOpenBidAsk)(market.amm.baseAssetReserve, market.amm.minBaseAssetReserve, market.amm.maxBaseAssetReserve);
|
|
141
|
+
const lpOpenBids = marketOpenBids
|
|
142
|
+
.mul(position.lpShares)
|
|
143
|
+
.div(market.amm.sqrtK);
|
|
144
|
+
const lpOpenAsks = marketOpenAsks
|
|
145
|
+
.mul(position.lpShares)
|
|
146
|
+
.div(market.amm.sqrtK);
|
|
147
|
+
return [lpOpenBids, lpOpenAsks];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* calculates the market position if the lp position was settled
|
|
151
|
+
* @returns : the settled userPosition
|
|
152
|
+
* @returns : the dust base asset amount (ie, < stepsize)
|
|
153
|
+
* @returns : pnl from settle
|
|
154
|
+
*/
|
|
155
|
+
getSettledLPPosition(marketIndex) {
|
|
156
|
+
const _position = this.getPerpPosition(marketIndex);
|
|
157
|
+
const position = this.getClonedPosition(_position);
|
|
158
|
+
if (position.lpShares.eq(numericConstants_1.ZERO)) {
|
|
159
|
+
return [position, numericConstants_1.ZERO, numericConstants_1.ZERO];
|
|
160
|
+
}
|
|
161
|
+
const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
|
|
162
|
+
const nShares = position.lpShares;
|
|
163
|
+
const deltaBaa = market.amm.baseAssetAmountPerLp
|
|
164
|
+
.sub(position.lastNetBaseAssetAmountPerLp)
|
|
165
|
+
.mul(nShares)
|
|
166
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION);
|
|
167
|
+
const deltaQaa = market.amm.quoteAssetAmountPerLp
|
|
168
|
+
.sub(position.lastNetQuoteAssetAmountPerLp)
|
|
169
|
+
.mul(nShares)
|
|
170
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION);
|
|
171
|
+
function sign(v) {
|
|
172
|
+
const sign = { true: new _1.BN(1), false: new _1.BN(-1) }[v.gte(numericConstants_1.ZERO).toString()];
|
|
173
|
+
return sign;
|
|
174
|
+
}
|
|
175
|
+
function standardize(amount, stepsize) {
|
|
176
|
+
const remainder = amount.abs().mod(stepsize).mul(sign(amount));
|
|
177
|
+
const standardizedAmount = amount.sub(remainder);
|
|
178
|
+
return [standardizedAmount, remainder];
|
|
179
|
+
}
|
|
180
|
+
const [standardizedBaa, remainderBaa] = standardize(deltaBaa, market.amm.orderStepSize);
|
|
181
|
+
position.remainderBaseAssetAmount += remainderBaa.toNumber();
|
|
182
|
+
if (Math.abs(position.remainderBaseAssetAmount) >
|
|
183
|
+
market.amm.orderStepSize.toNumber()) {
|
|
184
|
+
const [newStandardizedBaa, newRemainderBaa] = standardize(new _1.BN(position.remainderBaseAssetAmount), market.amm.orderStepSize);
|
|
185
|
+
position.baseAssetAmount =
|
|
186
|
+
position.baseAssetAmount.add(newStandardizedBaa);
|
|
187
|
+
position.remainderBaseAssetAmount = newRemainderBaa.toNumber();
|
|
188
|
+
}
|
|
189
|
+
let updateType;
|
|
190
|
+
if (position.baseAssetAmount.eq(numericConstants_1.ZERO)) {
|
|
191
|
+
updateType = 'open';
|
|
192
|
+
}
|
|
193
|
+
else if (sign(position.baseAssetAmount).eq(sign(deltaBaa))) {
|
|
194
|
+
updateType = 'increase';
|
|
195
|
+
}
|
|
196
|
+
else if (position.baseAssetAmount.abs().gt(deltaBaa.abs())) {
|
|
197
|
+
updateType = 'reduce';
|
|
198
|
+
}
|
|
199
|
+
else if (position.baseAssetAmount.abs().eq(deltaBaa.abs())) {
|
|
200
|
+
updateType = 'close';
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
updateType = 'flip';
|
|
204
|
+
}
|
|
205
|
+
let newQuoteEntry;
|
|
206
|
+
let pnl;
|
|
207
|
+
if (updateType == 'open' || updateType == 'increase') {
|
|
208
|
+
newQuoteEntry = position.quoteEntryAmount.add(deltaQaa);
|
|
209
|
+
pnl = 0;
|
|
210
|
+
}
|
|
211
|
+
else if (updateType == 'reduce' || updateType == 'close') {
|
|
212
|
+
newQuoteEntry = position.quoteEntryAmount.sub(position.quoteEntryAmount
|
|
213
|
+
.mul(deltaBaa.abs())
|
|
214
|
+
.div(position.baseAssetAmount.abs()));
|
|
215
|
+
pnl = position.quoteEntryAmount.sub(newQuoteEntry).add(deltaQaa);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
newQuoteEntry = deltaQaa.sub(deltaQaa.mul(position.baseAssetAmount.abs()).div(deltaBaa.abs()));
|
|
219
|
+
pnl = position.quoteEntryAmount.add(deltaQaa.sub(newQuoteEntry));
|
|
220
|
+
}
|
|
221
|
+
position.quoteEntryAmount = newQuoteEntry;
|
|
222
|
+
position.baseAssetAmount = position.baseAssetAmount.add(standardizedBaa);
|
|
223
|
+
position.quoteAssetAmount = position.quoteAssetAmount.add(deltaQaa);
|
|
224
|
+
if (position.baseAssetAmount.gt(numericConstants_1.ZERO)) {
|
|
225
|
+
position.lastCumulativeFundingRate = market.amm.cumulativeFundingRateLong;
|
|
226
|
+
}
|
|
227
|
+
else if (position.baseAssetAmount.lt(numericConstants_1.ZERO)) {
|
|
228
|
+
position.lastCumulativeFundingRate =
|
|
229
|
+
market.amm.cumulativeFundingRateShort;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
position.lastCumulativeFundingRate = numericConstants_1.ZERO;
|
|
233
|
+
}
|
|
234
|
+
return [position, remainderBaa, pnl];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* calculates Buying Power = FC * MAX_LEVERAGE
|
|
238
|
+
* @returns : Precision QUOTE_PRECISION
|
|
239
|
+
*/
|
|
240
|
+
getBuyingPower(marketIndex) {
|
|
241
|
+
return this.getFreeCollateral()
|
|
242
|
+
.mul(this.getMaxLeverage(marketIndex, 'Initial'))
|
|
243
|
+
.div(numericConstants_1.TEN_THOUSAND);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* calculates Free Collateral = Total collateral - initial margin requirement
|
|
247
|
+
* @returns : Precision QUOTE_PRECISION
|
|
248
|
+
*/
|
|
249
|
+
getFreeCollateral() {
|
|
250
|
+
const totalCollateral = this.getTotalCollateral();
|
|
251
|
+
const initialMarginRequirement = this.getInitialMarginRequirement();
|
|
252
|
+
const freeCollateral = totalCollateral.sub(initialMarginRequirement);
|
|
253
|
+
return freeCollateral.gte(numericConstants_1.ZERO) ? freeCollateral : numericConstants_1.ZERO;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
|
|
257
|
+
*/
|
|
258
|
+
getMarginRequirement(marginCategory, liquidationBuffer) {
|
|
259
|
+
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* @returns The initial margin requirement in USDC. : QUOTE_PRECISION
|
|
263
|
+
*/
|
|
264
|
+
getInitialMarginRequirement() {
|
|
265
|
+
return this.getMarginRequirement('Initial');
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
269
|
+
*/
|
|
270
|
+
getMaintenanceMarginRequirement(liquidationBuffer) {
|
|
271
|
+
return this.getMarginRequirement('Maintenance', liquidationBuffer);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* calculates unrealized position price pnl
|
|
275
|
+
* @returns : Precision QUOTE_PRECISION
|
|
276
|
+
*/
|
|
277
|
+
getUnrealizedPNL(withFunding, marketIndex, withWeightMarginCategory) {
|
|
278
|
+
const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount();
|
|
279
|
+
return this.getUserAccount()
|
|
280
|
+
.perpPositions.filter((pos) => marketIndex ? pos.marketIndex === marketIndex : true)
|
|
281
|
+
.reduce((unrealizedPnl, perpPosition) => {
|
|
282
|
+
const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
283
|
+
const oraclePriceData = this.getOracleDataForPerpMarket(market.marketIndex);
|
|
284
|
+
let positionUnrealizedPnl = (0, _1.calculatePositionPNL)(market, perpPosition, withFunding, oraclePriceData);
|
|
285
|
+
if (withWeightMarginCategory !== undefined) {
|
|
286
|
+
if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
|
|
287
|
+
positionUnrealizedPnl = positionUnrealizedPnl
|
|
288
|
+
.mul((0, _1.calculateUnrealizedAssetWeight)(market, quoteSpotMarket, positionUnrealizedPnl, withWeightMarginCategory, oraclePriceData))
|
|
289
|
+
.div(new _1.BN(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return unrealizedPnl.add(positionUnrealizedPnl);
|
|
293
|
+
}, numericConstants_1.ZERO);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* calculates unrealized funding payment pnl
|
|
297
|
+
* @returns : Precision QUOTE_PRECISION
|
|
298
|
+
*/
|
|
299
|
+
getUnrealizedFundingPNL(marketIndex) {
|
|
300
|
+
return this.getUserAccount()
|
|
301
|
+
.perpPositions.filter((pos) => marketIndex ? pos.marketIndex === marketIndex : true)
|
|
302
|
+
.reduce((pnl, perpPosition) => {
|
|
303
|
+
const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
304
|
+
return pnl.add((0, _1.calculatePositionFundingPNL)(market, perpPosition));
|
|
305
|
+
}, numericConstants_1.ZERO);
|
|
306
|
+
}
|
|
307
|
+
getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders) {
|
|
308
|
+
return this.getUserAccount().spotPositions.reduce((totalLiabilityValue, spotPosition) => {
|
|
309
|
+
if ((0, spotPosition_1.isSpotPositionAvailable)(spotPosition) ||
|
|
310
|
+
(marketIndex !== undefined &&
|
|
311
|
+
spotPosition.marketIndex !== marketIndex)) {
|
|
312
|
+
return totalLiabilityValue;
|
|
313
|
+
}
|
|
314
|
+
const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
|
|
315
|
+
if (spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
316
|
+
if ((0, types_1.isVariant)(spotPosition.balanceType, 'borrow')) {
|
|
317
|
+
const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
318
|
+
let weight = numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION;
|
|
319
|
+
if (marginCategory === 'Initial') {
|
|
320
|
+
weight = _1.BN.max(weight, new _1.BN(this.getUserAccount().maxMarginRatio));
|
|
321
|
+
}
|
|
322
|
+
const weightedTokenValue = tokenAmount
|
|
323
|
+
.mul(weight)
|
|
324
|
+
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
325
|
+
return totalLiabilityValue.add(weightedTokenValue);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
return totalLiabilityValue;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
|
|
332
|
+
if (!includeOpenOrders) {
|
|
333
|
+
if ((0, types_1.isVariant)(spotPosition.balanceType, 'borrow')) {
|
|
334
|
+
const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
335
|
+
const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
336
|
+
return totalLiabilityValue.add(liabilityValue);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
return totalLiabilityValue;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
343
|
+
let newTotalLiabilityValue = totalLiabilityValue;
|
|
344
|
+
if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
|
|
345
|
+
const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
|
|
346
|
+
newTotalLiabilityValue =
|
|
347
|
+
newTotalLiabilityValue.add(baseLiabilityValue);
|
|
348
|
+
}
|
|
349
|
+
if (worstCaseQuoteTokenAmount.lt(numericConstants_1.ZERO)) {
|
|
350
|
+
let weight = numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION;
|
|
351
|
+
if (marginCategory === 'Initial') {
|
|
352
|
+
weight = _1.BN.max(weight, new _1.BN(this.getUserAccount().maxMarginRatio));
|
|
353
|
+
}
|
|
354
|
+
const weightedTokenValue = worstCaseQuoteTokenAmount
|
|
355
|
+
.abs()
|
|
356
|
+
.mul(weight)
|
|
357
|
+
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
358
|
+
newTotalLiabilityValue =
|
|
359
|
+
newTotalLiabilityValue.add(weightedTokenValue);
|
|
360
|
+
}
|
|
361
|
+
return newTotalLiabilityValue;
|
|
362
|
+
}, numericConstants_1.ZERO);
|
|
363
|
+
}
|
|
364
|
+
getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer) {
|
|
365
|
+
let liabilityValue = (0, _1.getTokenValue)(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
|
|
366
|
+
if (marginCategory !== undefined) {
|
|
367
|
+
let weight = (0, spotBalance_1.calculateLiabilityWeight)(tokenAmount, spotMarketAccount, marginCategory);
|
|
368
|
+
if (marginCategory === 'Initial') {
|
|
369
|
+
weight = _1.BN.max(weight, new _1.BN(this.getUserAccount().maxMarginRatio));
|
|
370
|
+
}
|
|
371
|
+
if (liquidationBuffer !== undefined) {
|
|
372
|
+
weight = weight.add(liquidationBuffer);
|
|
373
|
+
}
|
|
374
|
+
liabilityValue = liabilityValue
|
|
375
|
+
.mul(weight)
|
|
376
|
+
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
377
|
+
}
|
|
378
|
+
return liabilityValue;
|
|
379
|
+
}
|
|
380
|
+
getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders) {
|
|
381
|
+
return this.getUserAccount().spotPositions.reduce((totalAssetValue, spotPosition) => {
|
|
382
|
+
if ((0, spotPosition_1.isSpotPositionAvailable)(spotPosition) ||
|
|
383
|
+
(marketIndex !== undefined &&
|
|
384
|
+
spotPosition.marketIndex !== marketIndex)) {
|
|
385
|
+
return totalAssetValue;
|
|
386
|
+
}
|
|
387
|
+
// Todo this needs to account for whether it's based on initial or maintenance requirements
|
|
388
|
+
const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
|
|
389
|
+
if (spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
390
|
+
if ((0, types_1.isVariant)(spotPosition.balanceType, 'deposit')) {
|
|
391
|
+
const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
392
|
+
return totalAssetValue.add(tokenAmount);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
return totalAssetValue;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
|
|
399
|
+
if (!includeOpenOrders) {
|
|
400
|
+
if ((0, types_1.isVariant)(spotPosition.balanceType, 'deposit')) {
|
|
401
|
+
const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
|
|
402
|
+
const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
|
|
403
|
+
return totalAssetValue.add(assetValue);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
return totalAssetValue;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
|
|
410
|
+
let newTotalAssetValue = totalAssetValue;
|
|
411
|
+
if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
|
|
412
|
+
const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
|
|
413
|
+
newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
|
|
414
|
+
}
|
|
415
|
+
if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO)) {
|
|
416
|
+
newTotalAssetValue = newTotalAssetValue.add(worstCaseQuoteTokenAmount);
|
|
417
|
+
}
|
|
418
|
+
return newTotalAssetValue;
|
|
419
|
+
}, numericConstants_1.ZERO);
|
|
420
|
+
}
|
|
421
|
+
getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory) {
|
|
422
|
+
let assetValue = (0, _1.getTokenValue)(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
|
|
423
|
+
if (marginCategory !== undefined) {
|
|
424
|
+
const weight = (0, spotBalance_1.calculateAssetWeight)(tokenAmount, spotMarketAccount, marginCategory);
|
|
425
|
+
assetValue = assetValue.mul(weight).div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
426
|
+
}
|
|
427
|
+
return assetValue;
|
|
428
|
+
}
|
|
429
|
+
getNetSpotMarketValue(withWeightMarginCategory) {
|
|
430
|
+
return this.getSpotMarketAssetValue(undefined, withWeightMarginCategory).sub(this.getSpotMarketLiabilityValue(undefined, withWeightMarginCategory));
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* calculates TotalCollateral: collateral + unrealized pnl
|
|
434
|
+
* @returns : Precision QUOTE_PRECISION
|
|
435
|
+
*/
|
|
436
|
+
getTotalCollateral(marginCategory = 'Initial') {
|
|
437
|
+
return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(this.getUnrealizedPNL(true, undefined, marginCategory));
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* calculates sum of position value across all positions in margin system
|
|
441
|
+
* @returns : Precision QUOTE_PRECISION
|
|
442
|
+
*/
|
|
443
|
+
getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders) {
|
|
444
|
+
return this.getUserAccount().perpPositions.reduce((totalPerpValue, perpPosition) => {
|
|
445
|
+
const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
446
|
+
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
447
|
+
// is an lp
|
|
448
|
+
// clone so we dont mutate the position
|
|
449
|
+
perpPosition = this.getClonedPosition(perpPosition);
|
|
450
|
+
// settle position
|
|
451
|
+
const [settledPosition, dustBaa, _] = this.getSettledLPPosition(market.marketIndex);
|
|
452
|
+
perpPosition.baseAssetAmount =
|
|
453
|
+
settledPosition.baseAssetAmount.add(dustBaa);
|
|
454
|
+
perpPosition.quoteAssetAmount = settledPosition.quoteAssetAmount;
|
|
455
|
+
const [totalOpenBids, totalOpenAsks] = this.getPerpBidAsks(market.marketIndex);
|
|
456
|
+
perpPosition.openAsks = totalOpenAsks;
|
|
457
|
+
perpPosition.openBids = totalOpenBids;
|
|
458
|
+
}
|
|
459
|
+
let valuationPrice = this.getOracleDataForPerpMarket(market.marketIndex).price;
|
|
460
|
+
if ((0, types_1.isVariant)(market.status, 'settlement')) {
|
|
461
|
+
valuationPrice = market.expiryPrice;
|
|
462
|
+
}
|
|
463
|
+
const baseAssetAmount = includeOpenOrders
|
|
464
|
+
? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
|
|
465
|
+
: perpPosition.baseAssetAmount;
|
|
466
|
+
let baseAssetValue = baseAssetAmount
|
|
467
|
+
.abs()
|
|
468
|
+
.mul(valuationPrice)
|
|
469
|
+
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO.mul(numericConstants_1.PRICE_PRECISION));
|
|
470
|
+
if (marginCategory) {
|
|
471
|
+
let marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(market, baseAssetAmount.abs(), marginCategory));
|
|
472
|
+
if (marginCategory === 'Initial') {
|
|
473
|
+
marginRatio = _1.BN.max(marginRatio, new _1.BN(this.getUserAccount().maxMarginRatio));
|
|
474
|
+
}
|
|
475
|
+
if (liquidationBuffer !== undefined) {
|
|
476
|
+
marginRatio = marginRatio.add(liquidationBuffer);
|
|
477
|
+
}
|
|
478
|
+
if ((0, types_1.isVariant)(market.status, 'settlement')) {
|
|
479
|
+
marginRatio = numericConstants_1.ZERO;
|
|
480
|
+
}
|
|
481
|
+
baseAssetValue = baseAssetValue
|
|
482
|
+
.mul(marginRatio)
|
|
483
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
484
|
+
}
|
|
485
|
+
return totalPerpValue.add(baseAssetValue);
|
|
486
|
+
}, numericConstants_1.ZERO);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* calculates position value in margin system
|
|
490
|
+
* @returns : Precision QUOTE_PRECISION
|
|
491
|
+
*/
|
|
492
|
+
getPerpPositionValue(marketIndex, oraclePriceData) {
|
|
493
|
+
const userPosition = this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
|
|
494
|
+
const market = this.driftClient.getPerpMarketAccount(userPosition.marketIndex);
|
|
495
|
+
return (0, margin_1.calculateBaseAssetValueWithOracle)(market, userPosition, oraclePriceData);
|
|
496
|
+
}
|
|
497
|
+
getPositionSide(currentPosition) {
|
|
498
|
+
if (currentPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
|
|
499
|
+
return _1.PositionDirection.LONG;
|
|
500
|
+
}
|
|
501
|
+
else if (currentPosition.baseAssetAmount.lt(numericConstants_1.ZERO)) {
|
|
502
|
+
return _1.PositionDirection.SHORT;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* calculates average exit price (optionally for closing up to 100% of position)
|
|
510
|
+
* @returns : Precision PRICE_PRECISION
|
|
511
|
+
*/
|
|
512
|
+
getPositionEstimatedExitPriceAndPnl(position, amountToClose, useAMMClose = false) {
|
|
513
|
+
const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
|
|
514
|
+
const entryPrice = (0, position_1.calculateEntryPrice)(position);
|
|
515
|
+
const oraclePriceData = this.getOracleDataForPerpMarket(position.marketIndex);
|
|
516
|
+
if (amountToClose) {
|
|
517
|
+
if (amountToClose.eq(numericConstants_1.ZERO)) {
|
|
518
|
+
return [(0, _1.calculateReservePrice)(market, oraclePriceData), numericConstants_1.ZERO];
|
|
519
|
+
}
|
|
520
|
+
position = {
|
|
521
|
+
baseAssetAmount: amountToClose,
|
|
522
|
+
lastCumulativeFundingRate: position.lastCumulativeFundingRate,
|
|
523
|
+
marketIndex: position.marketIndex,
|
|
524
|
+
quoteAssetAmount: position.quoteAssetAmount,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
let baseAssetValue;
|
|
528
|
+
if (useAMMClose) {
|
|
529
|
+
baseAssetValue = (0, _1.calculateBaseAssetValue)(market, position, oraclePriceData);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
baseAssetValue = (0, margin_1.calculateBaseAssetValueWithOracle)(market, position, oraclePriceData);
|
|
533
|
+
}
|
|
534
|
+
if (position.baseAssetAmount.eq(numericConstants_1.ZERO)) {
|
|
535
|
+
return [numericConstants_1.ZERO, numericConstants_1.ZERO];
|
|
536
|
+
}
|
|
537
|
+
const exitPrice = baseAssetValue
|
|
538
|
+
.mul(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO)
|
|
539
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
540
|
+
.div(position.baseAssetAmount.abs());
|
|
541
|
+
const pnlPerBase = exitPrice.sub(entryPrice);
|
|
542
|
+
const pnl = pnlPerBase
|
|
543
|
+
.mul(position.baseAssetAmount)
|
|
544
|
+
.div(numericConstants_1.PRICE_PRECISION)
|
|
545
|
+
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
|
|
546
|
+
return [exitPrice, pnl];
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* calculates current user leverage across all positions
|
|
550
|
+
* @returns : Precision TEN_THOUSAND
|
|
551
|
+
*/
|
|
552
|
+
getLeverage() {
|
|
553
|
+
const totalLiabilityValue = this.getTotalLiabilityValue();
|
|
554
|
+
const totalAssetValue = this.getTotalAssetValue();
|
|
555
|
+
if (totalAssetValue.eq(numericConstants_1.ZERO) && totalLiabilityValue.eq(numericConstants_1.ZERO)) {
|
|
556
|
+
return numericConstants_1.ZERO;
|
|
557
|
+
}
|
|
558
|
+
return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(totalAssetValue);
|
|
559
|
+
}
|
|
560
|
+
getTotalLiabilityValue() {
|
|
561
|
+
return this.getTotalPerpPositionValue(undefined, undefined, true).add(this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true));
|
|
562
|
+
}
|
|
563
|
+
getTotalAssetValue() {
|
|
564
|
+
return this.getSpotMarketAssetValue(undefined, undefined, true).add(this.getUnrealizedPNL(true, undefined, undefined));
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* calculates max allowable leverage exceeding hitting requirement category
|
|
568
|
+
* @params category {Initial, Maintenance}
|
|
569
|
+
* @returns : Precision TEN_THOUSAND
|
|
570
|
+
*/
|
|
571
|
+
getMaxLeverage(marketIndex, category = 'Initial') {
|
|
572
|
+
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
573
|
+
const totalAssetValue = this.getTotalAssetValue();
|
|
574
|
+
if (totalAssetValue.eq(numericConstants_1.ZERO)) {
|
|
575
|
+
return numericConstants_1.ZERO;
|
|
576
|
+
}
|
|
577
|
+
const totalLiabilityValue = this.getTotalLiabilityValue();
|
|
578
|
+
const marginRatio = (0, _1.calculateMarketMarginRatio)(market,
|
|
579
|
+
// worstCaseBaseAssetAmount.abs(),
|
|
580
|
+
numericConstants_1.ZERO, // todo
|
|
581
|
+
category);
|
|
582
|
+
const freeCollateral = this.getFreeCollateral();
|
|
583
|
+
// how much more liabilities can be opened w remaining free collateral
|
|
584
|
+
const additionalLiabilities = freeCollateral
|
|
585
|
+
.mul(numericConstants_1.MARGIN_PRECISION)
|
|
586
|
+
.div(new _1.BN(marginRatio));
|
|
587
|
+
return totalLiabilityValue
|
|
588
|
+
.add(additionalLiabilities)
|
|
589
|
+
.mul(numericConstants_1.TEN_THOUSAND)
|
|
590
|
+
.div(totalAssetValue);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* calculates margin ratio: total collateral / |total position value|
|
|
594
|
+
* @returns : Precision TEN_THOUSAND
|
|
595
|
+
*/
|
|
596
|
+
getMarginRatio() {
|
|
597
|
+
const totalLiabilityValue = this.getTotalLiabilityValue();
|
|
598
|
+
if (totalLiabilityValue.eq(numericConstants_1.ZERO)) {
|
|
599
|
+
return numericConstants_1.BN_MAX;
|
|
600
|
+
}
|
|
601
|
+
const totalAssetValue = this.getTotalAssetValue();
|
|
602
|
+
return totalAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
|
|
603
|
+
}
|
|
604
|
+
canBeLiquidated() {
|
|
605
|
+
const totalCollateral = this.getTotalCollateral();
|
|
606
|
+
// if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
|
|
607
|
+
let liquidationBuffer = undefined;
|
|
608
|
+
const isBeingLiquidated = (0, types_1.isVariant)(this.getUserAccount().status, 'beingLiquidated');
|
|
609
|
+
if (isBeingLiquidated) {
|
|
610
|
+
liquidationBuffer = new _1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio);
|
|
611
|
+
}
|
|
612
|
+
const maintenanceRequirement = this.getMaintenanceMarginRequirement(liquidationBuffer);
|
|
613
|
+
return totalCollateral.lt(maintenanceRequirement);
|
|
614
|
+
}
|
|
615
|
+
isBeingLiquidated() {
|
|
616
|
+
return (0, types_1.isOneOfVariant)(this.getUserAccount().status, [
|
|
617
|
+
'beingLiquidated',
|
|
618
|
+
'bankrupt',
|
|
619
|
+
]);
|
|
620
|
+
}
|
|
621
|
+
isBankrupt() {
|
|
622
|
+
return (0, types_1.isVariant)(this.getUserAccount().status, 'bankrupt');
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Checks if any user position cumulative funding differs from respective market cumulative funding
|
|
626
|
+
* @returns
|
|
627
|
+
*/
|
|
628
|
+
needsToSettleFundingPayment() {
|
|
629
|
+
for (const userPosition of this.getUserAccount().perpPositions) {
|
|
630
|
+
if (userPosition.baseAssetAmount.eq(numericConstants_1.ZERO)) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
const market = this.driftClient.getPerpMarketAccount(userPosition.marketIndex);
|
|
634
|
+
if (market.amm.cumulativeFundingRateLong.eq(userPosition.lastCumulativeFundingRate) ||
|
|
635
|
+
market.amm.cumulativeFundingRateShort.eq(userPosition.lastCumulativeFundingRate)) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
644
|
+
* @param PerpPosition
|
|
645
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
646
|
+
* @param partial
|
|
647
|
+
* @returns Precision : PRICE_PRECISION
|
|
648
|
+
*/
|
|
649
|
+
liquidationPrice(perpPosition, positionBaseSizeChange = numericConstants_1.ZERO) {
|
|
650
|
+
// solves formula for example canBeLiquidated below
|
|
651
|
+
/* example: assume BTC price is $40k (examine 10% up/down)
|
|
652
|
+
|
|
653
|
+
if 10k deposit and levered 10x short BTC => BTC up $400 means:
|
|
654
|
+
1. higher base_asset_value (+$4k)
|
|
655
|
+
2. lower collateral (-$4k)
|
|
656
|
+
3. (10k - 4k)/(100k + 4k) => 6k/104k => .0576
|
|
657
|
+
|
|
658
|
+
for 10x long, BTC down $400:
|
|
659
|
+
3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
|
|
660
|
+
const totalCollateral = this.getTotalCollateral();
|
|
661
|
+
// calculate the total position value ignoring any value from the target market of the trade
|
|
662
|
+
const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
|
|
663
|
+
const currentPerpPosition = this.getPerpPosition(perpPosition.marketIndex) ||
|
|
664
|
+
this.getEmptyPosition(perpPosition.marketIndex);
|
|
665
|
+
const currentPerpPositionBaseSize = currentPerpPosition.baseAssetAmount;
|
|
666
|
+
const proposedBaseAssetAmount = currentPerpPositionBaseSize.add(positionBaseSizeChange);
|
|
667
|
+
// calculate position for current market after trade
|
|
668
|
+
const proposedPerpPosition = {
|
|
669
|
+
marketIndex: perpPosition.marketIndex,
|
|
670
|
+
baseAssetAmount: proposedBaseAssetAmount,
|
|
671
|
+
remainderBaseAssetAmount: 0,
|
|
672
|
+
quoteAssetAmount: new _1.BN(0),
|
|
673
|
+
lastCumulativeFundingRate: numericConstants_1.ZERO,
|
|
674
|
+
quoteBreakEvenAmount: new _1.BN(0),
|
|
675
|
+
quoteEntryAmount: new _1.BN(0),
|
|
676
|
+
openOrders: 0,
|
|
677
|
+
openBids: new _1.BN(0),
|
|
678
|
+
openAsks: new _1.BN(0),
|
|
679
|
+
settledPnl: numericConstants_1.ZERO,
|
|
680
|
+
lpShares: numericConstants_1.ZERO,
|
|
681
|
+
lastNetBaseAssetAmountPerLp: numericConstants_1.ZERO,
|
|
682
|
+
lastNetQuoteAssetAmountPerLp: numericConstants_1.ZERO,
|
|
683
|
+
};
|
|
684
|
+
if (proposedBaseAssetAmount.eq(numericConstants_1.ZERO))
|
|
685
|
+
return new _1.BN(-1);
|
|
686
|
+
const market = this.driftClient.getPerpMarketAccount(proposedPerpPosition.marketIndex);
|
|
687
|
+
const proposedPerpPositionValue = (0, margin_1.calculateBaseAssetValueWithOracle)(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
|
|
688
|
+
// total position value after trade
|
|
689
|
+
const totalPositionValueAfterTrade = totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
|
|
690
|
+
const marginRequirementExcludingTargetMarket = this.getUserAccount().perpPositions.reduce((totalMarginRequirement, position) => {
|
|
691
|
+
if (position.marketIndex !== perpPosition.marketIndex) {
|
|
692
|
+
const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
|
|
693
|
+
const positionValue = (0, margin_1.calculateBaseAssetValueWithOracle)(market, position, this.getOracleDataForPerpMarket(market.marketIndex));
|
|
694
|
+
const marketMarginRequirement = positionValue
|
|
695
|
+
.mul(new _1.BN((0, _1.calculateMarketMarginRatio)(market, position.baseAssetAmount.abs(), 'Maintenance')))
|
|
696
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
697
|
+
totalMarginRequirement = totalMarginRequirement.add(marketMarginRequirement);
|
|
698
|
+
}
|
|
699
|
+
return totalMarginRequirement;
|
|
700
|
+
}, numericConstants_1.ZERO);
|
|
701
|
+
const freeCollateralExcludingTargetMarket = totalCollateral.sub(marginRequirementExcludingTargetMarket);
|
|
702
|
+
// if the position value after the trade is less than free collateral, there is no liq price
|
|
703
|
+
if (totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
704
|
+
proposedPerpPosition.baseAssetAmount.abs().gt(numericConstants_1.ZERO)) {
|
|
705
|
+
return new _1.BN(-1);
|
|
706
|
+
}
|
|
707
|
+
const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(proposedPerpPositionValue
|
|
708
|
+
.mul(new _1.BN((0, _1.calculateMarketMarginRatio)(market, proposedPerpPosition.baseAssetAmount.abs(), 'Maintenance')))
|
|
709
|
+
.div(numericConstants_1.MARGIN_PRECISION));
|
|
710
|
+
const freeCollateralAfterTrade = totalCollateral.sub(marginRequirementAfterTrade);
|
|
711
|
+
const marketMaxLeverage = this.getMaxLeverage(proposedPerpPosition.marketIndex, 'Maintenance');
|
|
712
|
+
let priceDelta;
|
|
713
|
+
if (proposedBaseAssetAmount.lt(numericConstants_1.ZERO)) {
|
|
714
|
+
priceDelta = freeCollateralAfterTrade
|
|
715
|
+
.mul(marketMaxLeverage) // precision is TEN_THOUSAND
|
|
716
|
+
.div(marketMaxLeverage.add(numericConstants_1.TEN_THOUSAND))
|
|
717
|
+
.mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
|
|
718
|
+
.mul(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
719
|
+
.div(proposedBaseAssetAmount);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
priceDelta = freeCollateralAfterTrade
|
|
723
|
+
.mul(marketMaxLeverage) // precision is TEN_THOUSAND
|
|
724
|
+
.div(marketMaxLeverage.sub(numericConstants_1.TEN_THOUSAND))
|
|
725
|
+
.mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
|
|
726
|
+
.mul(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
727
|
+
.div(proposedBaseAssetAmount);
|
|
728
|
+
}
|
|
729
|
+
let markPriceAfterTrade;
|
|
730
|
+
if (positionBaseSizeChange.eq(numericConstants_1.ZERO)) {
|
|
731
|
+
markPriceAfterTrade = (0, _1.calculateReservePrice)(this.driftClient.getPerpMarketAccount(perpPosition.marketIndex), this.getOracleDataForPerpMarket(perpPosition.marketIndex));
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const direction = positionBaseSizeChange.gt(numericConstants_1.ZERO)
|
|
735
|
+
? _1.PositionDirection.LONG
|
|
736
|
+
: _1.PositionDirection.SHORT;
|
|
737
|
+
markPriceAfterTrade = (0, _1.calculateTradeSlippage)(direction, positionBaseSizeChange.abs(), this.driftClient.getPerpMarketAccount(perpPosition.marketIndex), 'base', this.getOracleDataForPerpMarket(perpPosition.marketIndex))[3]; // newPrice after swap
|
|
738
|
+
}
|
|
739
|
+
if (priceDelta.gt(markPriceAfterTrade)) {
|
|
740
|
+
return new _1.BN(-1);
|
|
741
|
+
}
|
|
742
|
+
return markPriceAfterTrade.sub(priceDelta);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Calculates the estimated liquidation price for a position after closing a quote amount of the position.
|
|
746
|
+
* @param positionMarketIndex
|
|
747
|
+
* @param closeQuoteAmount
|
|
748
|
+
* @returns : Precision PRICE_PRECISION
|
|
749
|
+
*/
|
|
750
|
+
liquidationPriceAfterClose(positionMarketIndex, closeQuoteAmount) {
|
|
751
|
+
const currentPosition = this.getPerpPosition(positionMarketIndex) ||
|
|
752
|
+
this.getEmptyPosition(positionMarketIndex);
|
|
753
|
+
const closeBaseAmount = currentPosition.baseAssetAmount
|
|
754
|
+
.mul(closeQuoteAmount)
|
|
755
|
+
.div(currentPosition.quoteAssetAmount.abs())
|
|
756
|
+
.add(currentPosition.baseAssetAmount
|
|
757
|
+
.mul(closeQuoteAmount)
|
|
758
|
+
.mod(currentPosition.quoteAssetAmount.abs()))
|
|
759
|
+
.neg();
|
|
760
|
+
return this.liquidationPrice({
|
|
761
|
+
marketIndex: positionMarketIndex,
|
|
762
|
+
}, closeBaseAmount);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
|
|
766
|
+
*
|
|
767
|
+
* To Calculate Max Quote Available:
|
|
768
|
+
*
|
|
769
|
+
* Case 1: SameSide
|
|
770
|
+
* => Remaining quote to get to maxLeverage
|
|
771
|
+
*
|
|
772
|
+
* Case 2: NOT SameSide && currentLeverage <= maxLeverage
|
|
773
|
+
* => Current opposite position x2 + remaining to get to maxLeverage
|
|
774
|
+
*
|
|
775
|
+
* Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
|
|
776
|
+
* => strictly reduce current position size
|
|
777
|
+
*
|
|
778
|
+
* Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
|
|
779
|
+
* => current position + remaining to get to maxLeverage
|
|
780
|
+
*
|
|
781
|
+
* @param targetMarketIndex
|
|
782
|
+
* @param tradeSide
|
|
783
|
+
* @returns tradeSizeAllowed : Precision QUOTE_PRECISION
|
|
784
|
+
*/
|
|
785
|
+
getMaxTradeSizeUSDC(targetMarketIndex, tradeSide) {
|
|
786
|
+
const currentPosition = this.getPerpPosition(targetMarketIndex) ||
|
|
787
|
+
this.getEmptyPosition(targetMarketIndex);
|
|
788
|
+
const targetSide = (0, types_1.isVariant)(tradeSide, 'short') ? 'short' : 'long';
|
|
789
|
+
const currentPositionSide = (currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.baseAssetAmount.isNeg())
|
|
790
|
+
? 'short'
|
|
791
|
+
: 'long';
|
|
792
|
+
const targetingSameSide = !currentPosition
|
|
793
|
+
? true
|
|
794
|
+
: targetSide === currentPositionSide;
|
|
795
|
+
const oracleData = this.getOracleDataForPerpMarket(targetMarketIndex);
|
|
796
|
+
// 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.
|
|
797
|
+
const oppositeSizeValueUSDC = targetingSameSide
|
|
798
|
+
? numericConstants_1.ZERO
|
|
799
|
+
: this.getPerpPositionValue(targetMarketIndex, oracleData);
|
|
800
|
+
let maxPositionSize = this.getBuyingPower(targetMarketIndex);
|
|
801
|
+
if (maxPositionSize.gte(numericConstants_1.ZERO)) {
|
|
802
|
+
if (oppositeSizeValueUSDC.eq(numericConstants_1.ZERO)) {
|
|
803
|
+
// case 1 : Regular trade where current total position less than max, and no opposite position to account for
|
|
804
|
+
// do nothing
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
// case 2 : trade where current total position less than max, but need to account for flipping the current position over to the other side
|
|
808
|
+
maxPositionSize = maxPositionSize.add(oppositeSizeValueUSDC.mul(new _1.BN(2)));
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
// current leverage is greater than max leverage - can only reduce position size
|
|
813
|
+
if (!targetingSameSide) {
|
|
814
|
+
const market = this.driftClient.getPerpMarketAccount(targetMarketIndex);
|
|
815
|
+
const perpPositionValue = this.getPerpPositionValue(targetMarketIndex, oracleData);
|
|
816
|
+
const totalCollateral = this.getTotalCollateral();
|
|
817
|
+
const marginRequirement = this.getInitialMarginRequirement();
|
|
818
|
+
const marginFreedByClosing = perpPositionValue
|
|
819
|
+
.mul(new _1.BN(market.marginRatioInitial))
|
|
820
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
821
|
+
const marginRequirementAfterClosing = marginRequirement.sub(marginFreedByClosing);
|
|
822
|
+
if (marginRequirementAfterClosing.gt(totalCollateral)) {
|
|
823
|
+
maxPositionSize = perpPositionValue;
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
const freeCollateralAfterClose = totalCollateral.sub(marginRequirementAfterClosing);
|
|
827
|
+
const buyingPowerAfterClose = freeCollateralAfterClose
|
|
828
|
+
.mul(this.getMaxLeverage(targetMarketIndex))
|
|
829
|
+
.div(numericConstants_1.TEN_THOUSAND);
|
|
830
|
+
maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// do nothing if targetting same side
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// subtract oneMillionth of maxPositionSize
|
|
838
|
+
// => to avoid rounding errors when taking max leverage
|
|
839
|
+
const oneMilli = maxPositionSize.div(numericConstants_1.QUOTE_PRECISION);
|
|
840
|
+
return maxPositionSize.sub(oneMilli);
|
|
841
|
+
}
|
|
842
|
+
// TODO - should this take the price impact of the trade into account for strict accuracy?
|
|
843
|
+
/**
|
|
844
|
+
* Returns the leverage ratio for the account after adding (or subtracting) the given quote size to the given position
|
|
845
|
+
* @param targetMarketIndex
|
|
846
|
+
* @param positionMarketIndex
|
|
847
|
+
* @param tradeQuoteAmount
|
|
848
|
+
* @returns leverageRatio : Precision TEN_THOUSAND
|
|
849
|
+
*/
|
|
850
|
+
accountLeverageRatioAfterTrade(targetMarketIndex, tradeQuoteAmount, tradeSide, includeOpenOrders = true) {
|
|
851
|
+
const currentPosition = this.getPerpPosition(targetMarketIndex) ||
|
|
852
|
+
this.getEmptyPosition(targetMarketIndex);
|
|
853
|
+
const oracleData = this.getOracleDataForPerpMarket(targetMarketIndex);
|
|
854
|
+
let currentPositionQuoteAmount = this.getPerpPositionValue(targetMarketIndex, oracleData);
|
|
855
|
+
const currentSide = currentPosition && currentPosition.baseAssetAmount.isNeg()
|
|
856
|
+
? _1.PositionDirection.SHORT
|
|
857
|
+
: _1.PositionDirection.LONG;
|
|
858
|
+
if (currentSide === _1.PositionDirection.SHORT)
|
|
859
|
+
currentPositionQuoteAmount = currentPositionQuoteAmount.neg();
|
|
860
|
+
if (tradeSide === _1.PositionDirection.SHORT)
|
|
861
|
+
tradeQuoteAmount = tradeQuoteAmount.neg();
|
|
862
|
+
const currentPerpPositionAfterTrade = currentPositionQuoteAmount
|
|
863
|
+
.add(tradeQuoteAmount)
|
|
864
|
+
.abs();
|
|
865
|
+
const totalPositionAfterTradeExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex, undefined, undefined, includeOpenOrders);
|
|
866
|
+
const totalAssetValue = this.getTotalAssetValue();
|
|
867
|
+
const totalPerpPositionValue = currentPerpPositionAfterTrade
|
|
868
|
+
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
869
|
+
.abs();
|
|
870
|
+
const totalLiabilitiesAfterTrade = totalPerpPositionValue.add(this.getSpotMarketLiabilityValue(undefined, undefined, undefined, false));
|
|
871
|
+
if (totalAssetValue.eq(numericConstants_1.ZERO) && totalLiabilitiesAfterTrade.eq(numericConstants_1.ZERO)) {
|
|
872
|
+
return numericConstants_1.ZERO;
|
|
873
|
+
}
|
|
874
|
+
const newLeverage = totalLiabilitiesAfterTrade
|
|
875
|
+
.mul(numericConstants_1.TEN_THOUSAND)
|
|
876
|
+
.div(totalAssetValue);
|
|
877
|
+
return newLeverage;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Calculates how much fee will be taken for a given sized trade
|
|
881
|
+
* @param quoteAmount
|
|
882
|
+
* @returns feeForQuote : Precision QUOTE_PRECISION
|
|
883
|
+
*/
|
|
884
|
+
calculateFeeForQuoteAmount(quoteAmount) {
|
|
885
|
+
const feeTier = this.driftClient.getStateAccount().perpFeeStructure.feeTiers[0];
|
|
886
|
+
return quoteAmount
|
|
887
|
+
.mul(new _1.BN(feeTier.feeNumerator))
|
|
888
|
+
.div(new _1.BN(feeTier.feeDenominator));
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Calculates a user's max withdrawal amounts for a spot market. If reduceOnly is true,
|
|
892
|
+
* it will return the max withdrawal amount without opening a liability for the user
|
|
893
|
+
* @param marketIndex
|
|
894
|
+
* @returns withdrawalLimit : Precision is the token precision for the chosen SpotMarket
|
|
895
|
+
*/
|
|
896
|
+
getWithdrawalLimit(marketIndex, reduceOnly) {
|
|
897
|
+
const nowTs = new _1.BN(Math.floor(Date.now() / 1000));
|
|
898
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
899
|
+
const { borrowLimit, withdrawLimit } = (0, spotBalance_1.calculateWithdrawLimit)(spotMarket, nowTs);
|
|
900
|
+
const freeCollateral = this.getFreeCollateral();
|
|
901
|
+
const oracleData = this.getOracleDataForSpotMarket(marketIndex);
|
|
902
|
+
const precisionIncrease = numericConstants_1.TEN.pow(new _1.BN(spotMarket.decimals - 6));
|
|
903
|
+
const amountWithdrawable = freeCollateral
|
|
904
|
+
.mul(numericConstants_1.MARGIN_PRECISION)
|
|
905
|
+
.div(new _1.BN(spotMarket.initialAssetWeight))
|
|
906
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
907
|
+
.div(oracleData.price)
|
|
908
|
+
.mul(precisionIncrease);
|
|
909
|
+
const userSpotPosition = this.getUserAccount().spotPositions.find((spotPosition) => (0, types_1.isVariant)(spotPosition.balanceType, 'deposit') &&
|
|
910
|
+
spotPosition.marketIndex == marketIndex);
|
|
911
|
+
const userSpotBalance = userSpotPosition
|
|
912
|
+
? (0, spotBalance_1.getTokenAmount)(userSpotPosition.scaledBalance, this.driftClient.getSpotMarketAccount(marketIndex), _1.SpotBalanceType.DEPOSIT)
|
|
913
|
+
: numericConstants_1.ZERO;
|
|
914
|
+
const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userSpotBalance), withdrawLimit.abs());
|
|
915
|
+
if (reduceOnly) {
|
|
916
|
+
return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
const weightedAssetValue = this.getSpotMarketAssetValue(marketIndex, 'Initial', false);
|
|
920
|
+
const freeCollatAfterWithdraw = userSpotBalance.gt(numericConstants_1.ZERO)
|
|
921
|
+
? freeCollateral.sub(weightedAssetValue)
|
|
922
|
+
: freeCollateral;
|
|
923
|
+
const maxLiabilityAllowed = freeCollatAfterWithdraw
|
|
924
|
+
.mul(numericConstants_1.MARGIN_PRECISION)
|
|
925
|
+
.div(new _1.BN(spotMarket.initialLiabilityWeight))
|
|
926
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
927
|
+
.div(oracleData.price)
|
|
928
|
+
.mul(precisionIncrease);
|
|
929
|
+
const maxBorrowValue = _1.BN.min(maxWithdrawValue.add(maxLiabilityAllowed), borrowLimit.abs());
|
|
930
|
+
return _1.BN.max(maxBorrowValue, numericConstants_1.ZERO);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Get the total position value, excluding any position coming from the given target market
|
|
935
|
+
* @param marketToIgnore
|
|
936
|
+
* @returns positionValue : Precision QUOTE_PRECISION
|
|
937
|
+
*/
|
|
938
|
+
getTotalPerpPositionValueExcludingMarket(marketToIgnore, marginCategory, liquidationBuffer, includeOpenOrders) {
|
|
939
|
+
const currentPerpPosition = this.getPerpPosition(marketToIgnore) ||
|
|
940
|
+
this.getEmptyPosition(marketToIgnore);
|
|
941
|
+
const oracleData = this.getOracleDataForPerpMarket(marketToIgnore);
|
|
942
|
+
let currentPerpPositionValueUSDC = numericConstants_1.ZERO;
|
|
943
|
+
if (currentPerpPosition) {
|
|
944
|
+
currentPerpPositionValueUSDC = this.getPerpPositionValue(marketToIgnore, oracleData);
|
|
945
|
+
}
|
|
946
|
+
return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders).sub(currentPerpPositionValueUSDC);
|
|
947
|
+
}
|
|
948
|
+
getOracleDataForPerpMarket(marketIndex) {
|
|
949
|
+
const oracleKey = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
950
|
+
const oracleData = this.driftClient.getOraclePriceDataAndSlot(oracleKey).data;
|
|
951
|
+
return oracleData;
|
|
952
|
+
}
|
|
953
|
+
getOracleDataForSpotMarket(marketIndex) {
|
|
954
|
+
const oracleKey = this.driftClient.getSpotMarketAccount(marketIndex).oracle;
|
|
955
|
+
const oracleData = this.driftClient.getOraclePriceDataAndSlot(oracleKey).data;
|
|
956
|
+
return oracleData;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
exports.User = User;
|