@drift-labs/sdk 2.34.1-beta.2 → 2.34.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/lib/math/funding.d.ts +3 -12
- package/lib/math/funding.js +71 -113
- package/lib/user.d.ts +2 -1
- package/lib/user.js +36 -3
- package/package.json +2 -1
- package/src/math/funding.ts +142 -142
- package/src/user.ts +57 -6
- package/tests/amm/test.ts +122 -0
- package/tests/dlob/helpers.ts +6 -5
- package/tests/spot/test.ts +43 -2
- package/tests/user/helpers.ts +89 -0
- package/tests/user/test.ts +286 -0
package/src/math/funding.ts
CHANGED
|
@@ -4,10 +4,107 @@ import {
|
|
|
4
4
|
PRICE_PRECISION,
|
|
5
5
|
QUOTE_PRECISION,
|
|
6
6
|
ZERO,
|
|
7
|
+
ONE,
|
|
7
8
|
} from '../constants/numericConstants';
|
|
8
9
|
import { PerpMarketAccount, isVariant } from '../types';
|
|
9
10
|
import { OraclePriceData } from '../oracles/types';
|
|
10
11
|
import { calculateBidAskPrice } from './amm';
|
|
12
|
+
import { calculateLiveOracleTwap } from './oracles';
|
|
13
|
+
|
|
14
|
+
function calculateLiveMarkTwap(
|
|
15
|
+
market: PerpMarketAccount,
|
|
16
|
+
oraclePriceData?: OraclePriceData,
|
|
17
|
+
markPrice?: BN,
|
|
18
|
+
now?: BN,
|
|
19
|
+
period = new BN(3600)
|
|
20
|
+
): BN {
|
|
21
|
+
now = now || new BN((Date.now() / 1000).toFixed(0));
|
|
22
|
+
|
|
23
|
+
const lastMarkTwapWithMantissa = market.amm.lastMarkPriceTwap;
|
|
24
|
+
const lastMarkPriceTwapTs = market.amm.lastMarkPriceTwapTs;
|
|
25
|
+
|
|
26
|
+
const timeSinceLastMarkChange = now.sub(lastMarkPriceTwapTs);
|
|
27
|
+
const markTwapTimeSinceLastUpdate = BN.max(
|
|
28
|
+
period,
|
|
29
|
+
BN.max(ZERO, period.sub(timeSinceLastMarkChange))
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (!markPrice) {
|
|
33
|
+
const [bid, ask] = calculateBidAskPrice(market.amm, oraclePriceData);
|
|
34
|
+
markPrice = bid.add(ask).div(new BN(2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const markTwapWithMantissa = markTwapTimeSinceLastUpdate
|
|
38
|
+
.mul(lastMarkTwapWithMantissa)
|
|
39
|
+
.add(timeSinceLastMarkChange.mul(markPrice))
|
|
40
|
+
.div(timeSinceLastMarkChange.add(markTwapTimeSinceLastUpdate));
|
|
41
|
+
|
|
42
|
+
return markTwapWithMantissa;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function shrinkStaleTwaps(
|
|
46
|
+
market: PerpMarketAccount,
|
|
47
|
+
markTwapWithMantissa: BN,
|
|
48
|
+
oracleTwapWithMantissa: BN,
|
|
49
|
+
now?: BN
|
|
50
|
+
) {
|
|
51
|
+
now = now || new BN((Date.now() / 1000).toFixed(0));
|
|
52
|
+
let newMarkTwap = markTwapWithMantissa;
|
|
53
|
+
let newOracleTwap = oracleTwapWithMantissa;
|
|
54
|
+
if (
|
|
55
|
+
market.amm.lastMarkPriceTwapTs.gt(
|
|
56
|
+
market.amm.historicalOracleData.lastOraclePriceTwapTs
|
|
57
|
+
)
|
|
58
|
+
) {
|
|
59
|
+
// shrink oracle based on invalid intervals
|
|
60
|
+
const oracleInvalidDuration = BN.max(
|
|
61
|
+
ZERO,
|
|
62
|
+
market.amm.lastMarkPriceTwapTs.sub(
|
|
63
|
+
market.amm.historicalOracleData.lastOraclePriceTwapTs
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
const timeSinceLastOracleTwapUpdate = now.sub(
|
|
67
|
+
market.amm.historicalOracleData.lastOraclePriceTwapTs
|
|
68
|
+
);
|
|
69
|
+
const oracleTwapTimeSinceLastUpdate = BN.max(
|
|
70
|
+
ONE,
|
|
71
|
+
BN.min(
|
|
72
|
+
market.amm.fundingPeriod,
|
|
73
|
+
BN.max(ONE, market.amm.fundingPeriod.sub(timeSinceLastOracleTwapUpdate))
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
newOracleTwap = oracleTwapTimeSinceLastUpdate
|
|
77
|
+
.mul(oracleTwapWithMantissa)
|
|
78
|
+
.add(oracleInvalidDuration.mul(markTwapWithMantissa))
|
|
79
|
+
.div(oracleTwapTimeSinceLastUpdate.add(oracleInvalidDuration));
|
|
80
|
+
} else if (
|
|
81
|
+
market.amm.lastMarkPriceTwapTs.lt(
|
|
82
|
+
market.amm.historicalOracleData.lastOraclePriceTwapTs
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
// shrink mark to oracle twap over tradless intervals
|
|
86
|
+
const tradelessDuration = BN.max(
|
|
87
|
+
ZERO,
|
|
88
|
+
market.amm.historicalOracleData.lastOraclePriceTwapTs.sub(
|
|
89
|
+
market.amm.lastMarkPriceTwapTs
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
const timeSinceLastMarkTwapUpdate = now.sub(market.amm.lastMarkPriceTwapTs);
|
|
93
|
+
const markTwapTimeSinceLastUpdate = BN.max(
|
|
94
|
+
ONE,
|
|
95
|
+
BN.min(
|
|
96
|
+
market.amm.fundingPeriod,
|
|
97
|
+
BN.max(ONE, market.amm.fundingPeriod.sub(timeSinceLastMarkTwapUpdate))
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
newMarkTwap = markTwapTimeSinceLastUpdate
|
|
101
|
+
.mul(markTwapWithMantissa)
|
|
102
|
+
.add(tradelessDuration.mul(oracleTwapWithMantissa))
|
|
103
|
+
.div(markTwapTimeSinceLastUpdate.add(tradelessDuration));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [newMarkTwap, newOracleTwap];
|
|
107
|
+
}
|
|
11
108
|
|
|
12
109
|
/**
|
|
13
110
|
*
|
|
@@ -19,110 +116,65 @@ import { calculateBidAskPrice } from './amm';
|
|
|
19
116
|
export async function calculateAllEstimatedFundingRate(
|
|
20
117
|
market: PerpMarketAccount,
|
|
21
118
|
oraclePriceData?: OraclePriceData,
|
|
22
|
-
|
|
119
|
+
markPrice?: BN,
|
|
23
120
|
now?: BN
|
|
24
121
|
): Promise<[BN, BN, BN, BN, BN]> {
|
|
25
|
-
// periodAdjustment
|
|
26
|
-
// 1: hourly
|
|
27
|
-
// 24: daily
|
|
28
|
-
// 24 * 365.25: annualized
|
|
29
|
-
const secondsInHour = new BN(3600);
|
|
30
|
-
const hoursInDay = new BN(24);
|
|
31
|
-
const ONE = new BN(1);
|
|
32
|
-
|
|
33
122
|
if (isVariant(market.status, 'uninitialized')) {
|
|
34
123
|
return [ZERO, ZERO, ZERO, ZERO, ZERO];
|
|
35
124
|
}
|
|
36
125
|
|
|
37
|
-
const payFreq = new BN(market.amm.fundingPeriod);
|
|
38
|
-
|
|
39
126
|
// todo: sufficiently differs from blockchain timestamp?
|
|
40
127
|
now = now || new BN((Date.now() / 1000).toFixed(0));
|
|
41
|
-
const timeSinceLastUpdate = now.sub(market.amm.lastFundingRateTs);
|
|
42
128
|
|
|
43
|
-
// calculate real-time mark twap
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
BN.max(ZERO, secondsInHour.sub(timeSinceLastMarkChange))
|
|
129
|
+
// calculate real-time mark and oracle twap
|
|
130
|
+
const liveMarkTwap = calculateLiveMarkTwap(
|
|
131
|
+
market,
|
|
132
|
+
oraclePriceData,
|
|
133
|
+
markPrice,
|
|
134
|
+
now,
|
|
135
|
+
market.amm.fundingPeriod
|
|
51
136
|
);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
.add(timeSinceLastMarkChange.mul(baseAssetPriceWithMantissa))
|
|
58
|
-
.div(timeSinceLastMarkChange.add(markTwapTimeSinceLastUpdate));
|
|
59
|
-
|
|
60
|
-
// calculate real-time (predicted) oracle twap
|
|
61
|
-
// note: oracle twap depends on `when the chord is struck` (market is trade)
|
|
62
|
-
const lastOracleTwapWithMantissa =
|
|
63
|
-
market.amm.historicalOracleData.lastOraclePriceTwap;
|
|
64
|
-
const lastOraclePriceTwapTs =
|
|
65
|
-
market.amm.historicalOracleData.lastOraclePriceTwapTs;
|
|
66
|
-
|
|
67
|
-
const oracleInvalidDuration = BN.max(
|
|
68
|
-
ZERO,
|
|
69
|
-
lastMarkPriceTwapTs.sub(lastOraclePriceTwapTs)
|
|
137
|
+
const liveOracleTwap = calculateLiveOracleTwap(
|
|
138
|
+
market.amm.historicalOracleData,
|
|
139
|
+
oraclePriceData,
|
|
140
|
+
now,
|
|
141
|
+
market.amm.fundingPeriod
|
|
70
142
|
);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
secondsInHour,
|
|
77
|
-
BN.max(ONE, secondsInHour.sub(timeSinceLastOracleTwapUpdate))
|
|
78
|
-
)
|
|
143
|
+
const [markTwap, oracleTwap] = shrinkStaleTwaps(
|
|
144
|
+
market,
|
|
145
|
+
liveMarkTwap,
|
|
146
|
+
liveOracleTwap,
|
|
147
|
+
now
|
|
79
148
|
);
|
|
80
|
-
let oracleTwapWithMantissa = lastOracleTwapWithMantissa;
|
|
81
|
-
|
|
82
|
-
// if passing live oracle data, improve predicted calc estimate
|
|
83
|
-
if (oraclePriceData) {
|
|
84
|
-
const oraclePrice = oraclePriceData.price;
|
|
85
149
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.mul(PRICE_PRECISION)
|
|
90
|
-
.mul(new BN(100))
|
|
91
|
-
.div(lastOracleTwapWithMantissa);
|
|
150
|
+
// if(!markTwap.eq(liveMarkTwap)){
|
|
151
|
+
// console.log('shrink mark:', liveMarkTwap.toString(), '->', markTwap.toString());
|
|
152
|
+
// }
|
|
92
153
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.mul(lastOracleTwapWithMantissa)
|
|
97
|
-
.add(timeSinceLastMarkChange.mul(oraclePrice))
|
|
98
|
-
.div(timeSinceLastMarkChange.add(oracleTwapTimeSinceLastUpdate));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const shrunkLastOracleTwapwithMantissa = oracleTwapTimeSinceLastUpdate
|
|
103
|
-
.mul(oracleTwapWithMantissa)
|
|
104
|
-
.add(oracleInvalidDuration.mul(lastMarkTwapWithMantissa))
|
|
105
|
-
.div(oracleTwapTimeSinceLastUpdate.add(oracleInvalidDuration));
|
|
106
|
-
|
|
107
|
-
const twapSpread = markTwapWithMantissa.sub(shrunkLastOracleTwapwithMantissa);
|
|
154
|
+
// if(!oracleTwap.eq(liveOracleTwap)){
|
|
155
|
+
// console.log('shrink orac:', liveOracleTwap.toString(), '->', oracleTwap.toString());
|
|
156
|
+
// }
|
|
108
157
|
|
|
158
|
+
const twapSpread = markTwap.sub(oracleTwap);
|
|
109
159
|
const twapSpreadPct = twapSpread
|
|
110
160
|
.mul(PRICE_PRECISION)
|
|
111
161
|
.mul(new BN(100))
|
|
112
|
-
.div(
|
|
162
|
+
.div(oracleTwap);
|
|
163
|
+
|
|
164
|
+
const secondsInHour = new BN(3600);
|
|
165
|
+
const hoursInDay = new BN(24);
|
|
166
|
+
const timeSinceLastUpdate = now.sub(market.amm.lastFundingRateTs);
|
|
113
167
|
|
|
114
168
|
const lowerboundEst = twapSpreadPct
|
|
115
|
-
.mul(
|
|
169
|
+
.mul(market.amm.fundingPeriod)
|
|
116
170
|
.mul(BN.min(secondsInHour, timeSinceLastUpdate))
|
|
117
|
-
.mul(periodAdjustment)
|
|
118
171
|
.div(secondsInHour)
|
|
119
172
|
.div(secondsInHour)
|
|
120
173
|
.div(hoursInDay);
|
|
121
174
|
|
|
122
|
-
const interpEst = twapSpreadPct.
|
|
175
|
+
const interpEst = twapSpreadPct.div(hoursInDay);
|
|
123
176
|
|
|
124
177
|
const interpRateQuote = twapSpreadPct
|
|
125
|
-
.mul(periodAdjustment)
|
|
126
178
|
.div(hoursInDay)
|
|
127
179
|
.div(PRICE_PRECISION.div(QUOTE_PRECISION));
|
|
128
180
|
|
|
@@ -140,13 +192,7 @@ export async function calculateAllEstimatedFundingRate(
|
|
|
140
192
|
largerSide = market.amm.baseAssetAmountLong.abs();
|
|
141
193
|
smallerSide = market.amm.baseAssetAmountShort.abs();
|
|
142
194
|
if (twapSpread.gt(new BN(0))) {
|
|
143
|
-
return [
|
|
144
|
-
markTwapWithMantissa,
|
|
145
|
-
oracleTwapWithMantissa,
|
|
146
|
-
lowerboundEst,
|
|
147
|
-
interpEst,
|
|
148
|
-
interpEst,
|
|
149
|
-
];
|
|
195
|
+
return [markTwap, oracleTwap, lowerboundEst, interpEst, interpEst];
|
|
150
196
|
}
|
|
151
197
|
} else if (
|
|
152
198
|
market.amm.baseAssetAmountLong.lt(market.amm.baseAssetAmountShort.abs())
|
|
@@ -154,22 +200,10 @@ export async function calculateAllEstimatedFundingRate(
|
|
|
154
200
|
largerSide = market.amm.baseAssetAmountShort.abs();
|
|
155
201
|
smallerSide = market.amm.baseAssetAmountLong.abs();
|
|
156
202
|
if (twapSpread.lt(new BN(0))) {
|
|
157
|
-
return [
|
|
158
|
-
markTwapWithMantissa,
|
|
159
|
-
oracleTwapWithMantissa,
|
|
160
|
-
lowerboundEst,
|
|
161
|
-
interpEst,
|
|
162
|
-
interpEst,
|
|
163
|
-
];
|
|
203
|
+
return [markTwap, oracleTwap, lowerboundEst, interpEst, interpEst];
|
|
164
204
|
}
|
|
165
205
|
} else {
|
|
166
|
-
return [
|
|
167
|
-
markTwapWithMantissa,
|
|
168
|
-
oracleTwapWithMantissa,
|
|
169
|
-
lowerboundEst,
|
|
170
|
-
interpEst,
|
|
171
|
-
interpEst,
|
|
172
|
-
];
|
|
206
|
+
return [markTwap, oracleTwap, lowerboundEst, interpEst, interpEst];
|
|
173
207
|
}
|
|
174
208
|
|
|
175
209
|
if (largerSide.gt(ZERO)) {
|
|
@@ -183,8 +217,7 @@ export async function calculateAllEstimatedFundingRate(
|
|
|
183
217
|
cappedAltEst = cappedAltEst
|
|
184
218
|
.mul(PRICE_PRECISION)
|
|
185
219
|
.mul(new BN(100))
|
|
186
|
-
.div(
|
|
187
|
-
.mul(periodAdjustment);
|
|
220
|
+
.div(oracleTwap);
|
|
188
221
|
|
|
189
222
|
if (cappedAltEst.abs().gte(interpEst.abs())) {
|
|
190
223
|
cappedAltEst = interpEst;
|
|
@@ -193,44 +226,7 @@ export async function calculateAllEstimatedFundingRate(
|
|
|
193
226
|
cappedAltEst = interpEst;
|
|
194
227
|
}
|
|
195
228
|
|
|
196
|
-
return [
|
|
197
|
-
markTwapWithMantissa,
|
|
198
|
-
oracleTwapWithMantissa,
|
|
199
|
-
lowerboundEst,
|
|
200
|
-
cappedAltEst,
|
|
201
|
-
interpEst,
|
|
202
|
-
];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
*
|
|
207
|
-
* @param market
|
|
208
|
-
* @param oraclePriceData
|
|
209
|
-
* @param periodAdjustment
|
|
210
|
-
* @param estimationMethod
|
|
211
|
-
* @returns Estimated funding rate. : Precision //TODO-PRECISION
|
|
212
|
-
*/
|
|
213
|
-
export async function calculateEstimatedFundingRate(
|
|
214
|
-
market: PerpMarketAccount,
|
|
215
|
-
oraclePriceData?: OraclePriceData,
|
|
216
|
-
periodAdjustment: BN = new BN(1),
|
|
217
|
-
estimationMethod?: 'interpolated' | 'lowerbound' | 'capped'
|
|
218
|
-
): Promise<BN> {
|
|
219
|
-
const [_1, _2, lowerboundEst, cappedAltEst, interpEst] =
|
|
220
|
-
await calculateAllEstimatedFundingRate(
|
|
221
|
-
market,
|
|
222
|
-
oraclePriceData,
|
|
223
|
-
periodAdjustment
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
if (estimationMethod == 'lowerbound') {
|
|
227
|
-
//assuming remaining funding period has no gap
|
|
228
|
-
return lowerboundEst;
|
|
229
|
-
} else if (estimationMethod == 'capped') {
|
|
230
|
-
return cappedAltEst;
|
|
231
|
-
} else {
|
|
232
|
-
return interpEst;
|
|
233
|
-
}
|
|
229
|
+
return [markTwap, oracleTwap, lowerboundEst, cappedAltEst, interpEst];
|
|
234
230
|
}
|
|
235
231
|
|
|
236
232
|
/**
|
|
@@ -243,13 +239,15 @@ export async function calculateEstimatedFundingRate(
|
|
|
243
239
|
export async function calculateLongShortFundingRate(
|
|
244
240
|
market: PerpMarketAccount,
|
|
245
241
|
oraclePriceData?: OraclePriceData,
|
|
246
|
-
|
|
242
|
+
markPrice?: BN,
|
|
243
|
+
now?: BN
|
|
247
244
|
): Promise<[BN, BN]> {
|
|
248
245
|
const [_1, _2, _, cappedAltEst, interpEst] =
|
|
249
246
|
await calculateAllEstimatedFundingRate(
|
|
250
247
|
market,
|
|
251
248
|
oraclePriceData,
|
|
252
|
-
|
|
249
|
+
markPrice,
|
|
250
|
+
now
|
|
253
251
|
);
|
|
254
252
|
|
|
255
253
|
if (market.amm.baseAssetAmountLong.gt(market.amm.baseAssetAmountShort)) {
|
|
@@ -273,13 +271,15 @@ export async function calculateLongShortFundingRate(
|
|
|
273
271
|
export async function calculateLongShortFundingRateAndLiveTwaps(
|
|
274
272
|
market: PerpMarketAccount,
|
|
275
273
|
oraclePriceData?: OraclePriceData,
|
|
276
|
-
|
|
274
|
+
markPrice?: BN,
|
|
275
|
+
now?: BN
|
|
277
276
|
): Promise<[BN, BN, BN, BN]> {
|
|
278
277
|
const [markTwapLive, oracleTwapLive, _2, cappedAltEst, interpEst] =
|
|
279
278
|
await calculateAllEstimatedFundingRate(
|
|
280
279
|
market,
|
|
281
280
|
oraclePriceData,
|
|
282
|
-
|
|
281
|
+
markPrice,
|
|
282
|
+
now
|
|
283
283
|
);
|
|
284
284
|
|
|
285
285
|
if (
|
package/src/user.ts
CHANGED
|
@@ -1442,14 +1442,17 @@ export class User {
|
|
|
1442
1442
|
|
|
1443
1443
|
/**
|
|
1444
1444
|
* calculates max allowable leverage exceeding hitting requirement category
|
|
1445
|
+
* for large sizes where imf factor activates, result is a lower bound
|
|
1445
1446
|
* @params category {Initial, Maintenance}
|
|
1446
1447
|
* @returns : Precision TEN_THOUSAND
|
|
1447
1448
|
*/
|
|
1448
1449
|
public getMaxLeverageForPerp(
|
|
1449
1450
|
perpMarketIndex: number,
|
|
1450
|
-
|
|
1451
|
+
marginCategory: MarginCategory = 'Initial'
|
|
1451
1452
|
): BN {
|
|
1452
1453
|
const market = this.driftClient.getPerpMarketAccount(perpMarketIndex);
|
|
1454
|
+
const marketPrice =
|
|
1455
|
+
this.driftClient.getOracleDataForPerpMarket(perpMarketIndex).price;
|
|
1453
1456
|
|
|
1454
1457
|
const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
|
|
1455
1458
|
this.getLeverageComponents();
|
|
@@ -1464,13 +1467,61 @@ export class User {
|
|
|
1464
1467
|
|
|
1465
1468
|
const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
|
|
1466
1469
|
|
|
1467
|
-
const marginRatio = calculateMarketMarginRatio(
|
|
1468
|
-
market,
|
|
1469
|
-
ZERO, // todo
|
|
1470
|
-
category
|
|
1471
|
-
);
|
|
1472
1470
|
const freeCollateral = this.getFreeCollateral();
|
|
1473
1471
|
|
|
1472
|
+
let rawMarginRatio;
|
|
1473
|
+
|
|
1474
|
+
switch (marginCategory) {
|
|
1475
|
+
case 'Initial':
|
|
1476
|
+
rawMarginRatio = market.marginRatioInitial;
|
|
1477
|
+
break;
|
|
1478
|
+
case 'Maintenance':
|
|
1479
|
+
rawMarginRatio = market.marginRatioMaintenance;
|
|
1480
|
+
break;
|
|
1481
|
+
default:
|
|
1482
|
+
rawMarginRatio = market.marginRatioInitial;
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// absolute max fesible size (upper bound)
|
|
1487
|
+
const maxSize = BN.max(
|
|
1488
|
+
ZERO,
|
|
1489
|
+
freeCollateral
|
|
1490
|
+
.mul(MARGIN_PRECISION)
|
|
1491
|
+
.div(new BN(rawMarginRatio))
|
|
1492
|
+
.mul(PRICE_PRECISION)
|
|
1493
|
+
.div(marketPrice)
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
// margin ratio incorporting upper bound on size
|
|
1497
|
+
let marginRatio = calculateMarketMarginRatio(
|
|
1498
|
+
market,
|
|
1499
|
+
maxSize,
|
|
1500
|
+
marginCategory
|
|
1501
|
+
);
|
|
1502
|
+
|
|
1503
|
+
// use more fesible size since imf factor activated
|
|
1504
|
+
let attempts = 0;
|
|
1505
|
+
while (marginRatio > rawMarginRatio + 1e-4 && attempts < 10) {
|
|
1506
|
+
// more fesible size (upper bound)
|
|
1507
|
+
const targetSize = BN.max(
|
|
1508
|
+
ZERO,
|
|
1509
|
+
freeCollateral
|
|
1510
|
+
.mul(MARGIN_PRECISION)
|
|
1511
|
+
.div(new BN(marginRatio))
|
|
1512
|
+
.mul(PRICE_PRECISION)
|
|
1513
|
+
.div(marketPrice)
|
|
1514
|
+
);
|
|
1515
|
+
|
|
1516
|
+
// margin ratio incorporting more fesible target size
|
|
1517
|
+
marginRatio = calculateMarketMarginRatio(
|
|
1518
|
+
market,
|
|
1519
|
+
targetSize,
|
|
1520
|
+
marginCategory
|
|
1521
|
+
);
|
|
1522
|
+
attempts += 1;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1474
1525
|
// how much more liabilities can be opened w remaining free collateral
|
|
1475
1526
|
const additionalLiabilities = freeCollateral
|
|
1476
1527
|
.mul(MARGIN_PRECISION)
|
package/tests/amm/test.ts
CHANGED
|
@@ -10,10 +10,14 @@ import {
|
|
|
10
10
|
calculateLiveOracleStd,
|
|
11
11
|
calculateLiveOracleTwap,
|
|
12
12
|
calculateInventoryScale,
|
|
13
|
+
calculateAllEstimatedFundingRate,
|
|
14
|
+
calculateLongShortFundingRateAndLiveTwaps,
|
|
15
|
+
OraclePriceData
|
|
13
16
|
} from '../../src';
|
|
14
17
|
import { mockPerpMarkets } from '../dlob/helpers';
|
|
15
18
|
|
|
16
19
|
import { assert } from '../../src/assert/assert';
|
|
20
|
+
import * as _ from 'lodash';
|
|
17
21
|
|
|
18
22
|
class AMMSpreadTerms {
|
|
19
23
|
longVolSpread: number;
|
|
@@ -386,4 +390,122 @@ describe('AMM Tests', () => {
|
|
|
386
390
|
console.log('liveOracleStd:', liveOracleStd.toNumber());
|
|
387
391
|
assert(liveOracleStd.eq(new BN(192962)));
|
|
388
392
|
});
|
|
393
|
+
|
|
394
|
+
it('predicted funding rate mock1', async () => {
|
|
395
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
396
|
+
const mockMarket1 = myMockPerpMarkets[0];
|
|
397
|
+
|
|
398
|
+
// make it like RNDR
|
|
399
|
+
const now = new BN(1688878353);
|
|
400
|
+
|
|
401
|
+
mockMarket1.amm.fundingPeriod = new BN(3600);
|
|
402
|
+
mockMarket1.amm.lastFundingRateTs = new BN(1688860817);
|
|
403
|
+
|
|
404
|
+
const currentMarkPrice = new BN(1.9843 * PRICE_PRECISION.toNumber()); // trading at a premium
|
|
405
|
+
const oraclePriceData: OraclePriceData = {
|
|
406
|
+
price: new BN(1.9535 * PRICE_PRECISION.toNumber()),
|
|
407
|
+
slot: new BN(0),
|
|
408
|
+
confidence: new BN(1),
|
|
409
|
+
hasSufficientNumberOfDataPoints: true,
|
|
410
|
+
};
|
|
411
|
+
mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(1.9535 * PRICE_PRECISION.toNumber());
|
|
412
|
+
|
|
413
|
+
// mockMarket1.amm.pegMultiplier = new BN(1.897573 * 1e3);
|
|
414
|
+
|
|
415
|
+
mockMarket1.amm.lastMarkPriceTwap = new BN(1.945594 * PRICE_PRECISION.toNumber());
|
|
416
|
+
mockMarket1.amm.lastBidPriceTwap = new BN(1.941629 * PRICE_PRECISION.toNumber());
|
|
417
|
+
mockMarket1.amm.lastAskPriceTwap = new BN(1.94956 * PRICE_PRECISION.toNumber());
|
|
418
|
+
mockMarket1.amm.lastMarkPriceTwapTs = new BN(1688877729);
|
|
419
|
+
|
|
420
|
+
mockMarket1.amm.historicalOracleData.lastOraclePriceTwap = new BN(1.942449 * PRICE_PRECISION.toNumber());
|
|
421
|
+
mockMarket1.amm.historicalOracleData.lastOraclePriceTwapTs = new BN(1688878333);
|
|
422
|
+
|
|
423
|
+
const [_markTwapLive, _oracleTwapLive, _lowerboundEst, _cappedAltEst, _interpEst] =
|
|
424
|
+
await calculateAllEstimatedFundingRate(
|
|
425
|
+
mockMarket1,
|
|
426
|
+
oraclePriceData,
|
|
427
|
+
currentMarkPrice,
|
|
428
|
+
now
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const [markTwapLive, oracleTwapLive, est1, est2] = await calculateLongShortFundingRateAndLiveTwaps(
|
|
432
|
+
mockMarket1,
|
|
433
|
+
oraclePriceData,
|
|
434
|
+
currentMarkPrice,
|
|
435
|
+
now
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// console.log(markTwapLive.toString());
|
|
439
|
+
// console.log(oracleTwapLive.toString());
|
|
440
|
+
// console.log(est1.toString());
|
|
441
|
+
// console.log(est2.toString());
|
|
442
|
+
|
|
443
|
+
assert(markTwapLive.eq(new BN('1949826')));
|
|
444
|
+
assert(oracleTwapLive.eq(new BN('1942510')));
|
|
445
|
+
assert(est1.eq(new BN('15692')));
|
|
446
|
+
assert(est2.eq(new BN('15692')));
|
|
447
|
+
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('predicted funding rate mock2', async () => {
|
|
451
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
452
|
+
const mockMarket1 = myMockPerpMarkets[0];
|
|
453
|
+
|
|
454
|
+
// make it like OP
|
|
455
|
+
const now = new BN(1688881915);
|
|
456
|
+
|
|
457
|
+
mockMarket1.amm.fundingPeriod = new BN(3600);
|
|
458
|
+
mockMarket1.amm.lastFundingRateTs = new BN(1688864415);
|
|
459
|
+
|
|
460
|
+
const currentMarkPrice = new BN(1.2242 * PRICE_PRECISION.toNumber()); // trading at a premium
|
|
461
|
+
const oraclePriceData: OraclePriceData = {
|
|
462
|
+
price: new BN(1.224 * PRICE_PRECISION.toNumber()),
|
|
463
|
+
slot: new BN(0),
|
|
464
|
+
confidence: new BN(1),
|
|
465
|
+
hasSufficientNumberOfDataPoints: true,
|
|
466
|
+
};
|
|
467
|
+
mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(1.9535 * PRICE_PRECISION.toNumber());
|
|
468
|
+
|
|
469
|
+
// mockMarket1.amm.pegMultiplier = new BN(1.897573 * 1e3);
|
|
470
|
+
|
|
471
|
+
mockMarket1.amm.lastMarkPriceTwap = new BN(1.218363 * PRICE_PRECISION.toNumber());
|
|
472
|
+
mockMarket1.amm.lastBidPriceTwap = new BN(1.218363 * PRICE_PRECISION.toNumber());
|
|
473
|
+
mockMarket1.amm.lastAskPriceTwap = new BN(1.218364 * PRICE_PRECISION.toNumber());
|
|
474
|
+
mockMarket1.amm.lastMarkPriceTwapTs = new BN(1688878815);
|
|
475
|
+
|
|
476
|
+
mockMarket1.amm.historicalOracleData.lastOraclePriceTwap = new BN(1.220964 * PRICE_PRECISION.toNumber());
|
|
477
|
+
mockMarket1.amm.historicalOracleData.lastOraclePriceTwapTs = new BN(1688879991);
|
|
478
|
+
|
|
479
|
+
const [_markTwapLive, _oracleTwapLive, _lowerboundEst, _cappedAltEst, _interpEst] =
|
|
480
|
+
await calculateAllEstimatedFundingRate(
|
|
481
|
+
mockMarket1,
|
|
482
|
+
oraclePriceData,
|
|
483
|
+
currentMarkPrice,
|
|
484
|
+
now
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// console.log(_markTwapLive.toString());
|
|
488
|
+
// console.log(_oracleTwapLive.toString());
|
|
489
|
+
// console.log(_lowerboundEst.toString());
|
|
490
|
+
// console.log(_cappedAltEst.toString());
|
|
491
|
+
// console.log(_interpEst.toString());
|
|
492
|
+
// console.log('-----');
|
|
493
|
+
|
|
494
|
+
const [markTwapLive, oracleTwapLive, est1, est2] = await calculateLongShortFundingRateAndLiveTwaps(
|
|
495
|
+
mockMarket1,
|
|
496
|
+
oraclePriceData,
|
|
497
|
+
currentMarkPrice,
|
|
498
|
+
now
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
console.log('markTwapLive:', mockMarket1.amm.lastMarkPriceTwap.toString(), '->', markTwapLive.toString());
|
|
502
|
+
console.log('oracTwapLive:', mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(), '->', oracleTwapLive.toString());
|
|
503
|
+
console.log('pred funding:', est1.toString(), est2.toString());
|
|
504
|
+
|
|
505
|
+
assert(markTwapLive.eq(new BN('1222131')));
|
|
506
|
+
assert(oracleTwapLive.eq(new BN('1222586')));
|
|
507
|
+
assert(est1.eq(est2));
|
|
508
|
+
assert(est2.eq(new BN('-1550')));
|
|
509
|
+
|
|
510
|
+
});
|
|
389
511
|
});
|
package/tests/dlob/helpers.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
OrderRecord,
|
|
22
22
|
ZERO,
|
|
23
23
|
ContractTier,
|
|
24
|
+
SPOT_MARKET_BALANCE_PRECISION,
|
|
24
25
|
} from '../../src';
|
|
25
26
|
|
|
26
27
|
export const mockPerpPosition: PerpPosition = {
|
|
@@ -151,8 +152,8 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
|
|
|
151
152
|
amm: mockAMM,
|
|
152
153
|
numberOfUsersWithBase: 0,
|
|
153
154
|
numberOfUsers: 0,
|
|
154
|
-
marginRatioInitial:
|
|
155
|
-
marginRatioMaintenance:
|
|
155
|
+
marginRatioInitial: 2000,
|
|
156
|
+
marginRatioMaintenance: 1000,
|
|
156
157
|
nextFillRecordId: new BN(0),
|
|
157
158
|
pnlPool: {
|
|
158
159
|
scaledBalance: new BN(0),
|
|
@@ -257,7 +258,7 @@ export const mockSpotMarkets: Array<SpotMarketAccount> = [
|
|
|
257
258
|
status: MarketStatus.ACTIVE,
|
|
258
259
|
assetTier: AssetTier.COLLATERAL,
|
|
259
260
|
name: [],
|
|
260
|
-
maxTokenDeposits: new BN(
|
|
261
|
+
maxTokenDeposits: new BN(1000000),
|
|
261
262
|
marketIndex: 0,
|
|
262
263
|
pubkey: PublicKey.default,
|
|
263
264
|
mint: DevnetSpotMarkets[0].mint,
|
|
@@ -285,8 +286,8 @@ export const mockSpotMarkets: Array<SpotMarketAccount> = [
|
|
|
285
286
|
optimalUtilization: 0,
|
|
286
287
|
optimalBorrowRate: 0,
|
|
287
288
|
maxBorrowRate: 0,
|
|
288
|
-
cumulativeDepositInterest:
|
|
289
|
-
cumulativeBorrowInterest:
|
|
289
|
+
cumulativeDepositInterest: SPOT_MARKET_BALANCE_PRECISION,
|
|
290
|
+
cumulativeBorrowInterest: SPOT_MARKET_BALANCE_PRECISION,
|
|
290
291
|
totalSocialLoss: new BN(0),
|
|
291
292
|
totalQuoteSocialLoss: new BN(0),
|
|
292
293
|
depositBalance: new BN(0),
|