@drift-labs/sdk 2.52.0-beta.1 → 2.52.0-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/accounts/webSocketAccountSubscriber.d.ts +1 -1
- package/lib/accounts/webSocketAccountSubscriber.js +7 -4
- package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
- package/lib/accounts/webSocketProgramAccountSubscriber.js +7 -4
- package/lib/dlob/orderBookLevels.d.ts +22 -0
- package/lib/dlob/orderBookLevels.js +115 -1
- package/lib/driftClient.js +7 -2
- package/lib/events/webSocketLogProvider.js +3 -3
- package/lib/factory/bigNum.d.ts +1 -1
- package/lib/factory/bigNum.js +5 -2
- package/lib/idl/drift.json +13 -1
- package/lib/math/amm.d.ts +5 -1
- package/lib/math/amm.js +62 -13
- package/lib/phoenix/phoenixSubscriber.js +2 -2
- package/lib/slot/SlotSubscriber.d.ts +11 -3
- package/lib/slot/SlotSubscriber.js +40 -4
- package/lib/types.d.ts +1 -1
- package/lib/user.d.ts +1 -1
- package/lib/user.js +14 -6
- package/lib/userMap/userMap.d.ts +3 -0
- package/lib/userMap/userMap.js +9 -0
- package/package.json +1 -1
- package/src/accounts/webSocketAccountSubscriber.ts +7 -4
- package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
- package/src/dlob/orderBookLevels.ts +136 -0
- package/src/driftClient.ts +7 -2
- package/src/events/webSocketLogProvider.ts +3 -3
- package/src/factory/bigNum.ts +11 -2
- package/src/idl/drift.json +13 -1
- package/src/math/amm.ts +159 -25
- package/src/phoenix/phoenixSubscriber.ts +2 -2
- package/src/slot/SlotSubscriber.ts +52 -5
- package/src/types.ts +1 -1
- package/src/user.ts +19 -6
- package/src/userMap/userMap.ts +12 -0
- package/tests/amm/test.ts +219 -11
- package/tests/bn/test.ts +27 -0
- package/tests/dlob/helpers.ts +1 -1
- package/tests/dlob/test.ts +372 -2
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.52.0-beta.
|
|
1
|
+
2.52.0-beta.3
|
|
@@ -24,5 +24,5 @@ export declare class WebSocketAccountSubscriber<T> implements AccountSubscriber<
|
|
|
24
24
|
fetch(): Promise<void>;
|
|
25
25
|
handleRpcResponse(context: Context, accountInfo?: AccountInfo<Buffer>): void;
|
|
26
26
|
decodeBuffer(buffer: Buffer): T;
|
|
27
|
-
unsubscribe(): Promise<void>;
|
|
27
|
+
unsubscribe(onResub?: boolean): Promise<void>;
|
|
28
28
|
}
|
|
@@ -15,7 +15,7 @@ class WebSocketAccountSubscriber {
|
|
|
15
15
|
commitment !== null && commitment !== void 0 ? commitment : this.program.provider.opts.commitment;
|
|
16
16
|
}
|
|
17
17
|
async subscribe(onChange) {
|
|
18
|
-
if (this.listenerId || this.isUnsubscribing) {
|
|
18
|
+
if (this.listenerId != null || this.isUnsubscribing) {
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
this.onChange = onChange;
|
|
@@ -58,7 +58,7 @@ class WebSocketAccountSubscriber {
|
|
|
58
58
|
}
|
|
59
59
|
if (this.receivingData) {
|
|
60
60
|
console.log(`No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`);
|
|
61
|
-
await this.unsubscribe();
|
|
61
|
+
await this.unsubscribe(true);
|
|
62
62
|
this.receivingData = false;
|
|
63
63
|
await this.subscribe(this.onChange);
|
|
64
64
|
}
|
|
@@ -114,11 +114,14 @@ class WebSocketAccountSubscriber {
|
|
|
114
114
|
return this.program.account[this.accountName].coder.accounts.decode((0, utils_1.capitalize)(this.accountName), buffer);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
unsubscribe() {
|
|
117
|
+
unsubscribe(onResub = false) {
|
|
118
|
+
if (!onResub) {
|
|
119
|
+
this.resubTimeoutMs = undefined;
|
|
120
|
+
}
|
|
118
121
|
this.isUnsubscribing = true;
|
|
119
122
|
clearTimeout(this.timeoutId);
|
|
120
123
|
this.timeoutId = undefined;
|
|
121
|
-
if (this.listenerId) {
|
|
124
|
+
if (this.listenerId != null) {
|
|
122
125
|
const promise = this.program.provider.connection
|
|
123
126
|
.removeAccountChangeListener(this.listenerId)
|
|
124
127
|
.then(() => {
|
|
@@ -29,5 +29,5 @@ export declare class WebSocketProgramAccountSubscriber<T> implements ProgramAcco
|
|
|
29
29
|
subscribe(onChange: (accountId: PublicKey, data: T, context: Context) => void): Promise<void>;
|
|
30
30
|
private setTimeout;
|
|
31
31
|
handleRpcResponse(context: Context, keyedAccountInfo: KeyedAccountInfo): void;
|
|
32
|
-
unsubscribe(): Promise<void>;
|
|
32
|
+
unsubscribe(onResub?: boolean): Promise<void>;
|
|
33
33
|
}
|
|
@@ -17,7 +17,7 @@ class WebSocketProgramAccountSubscriber {
|
|
|
17
17
|
}
|
|
18
18
|
async subscribe(onChange) {
|
|
19
19
|
var _a;
|
|
20
|
-
if (this.listenerId || this.isUnsubscribing) {
|
|
20
|
+
if (this.listenerId != null || this.isUnsubscribing) {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
this.onChange = onChange;
|
|
@@ -47,7 +47,7 @@ class WebSocketProgramAccountSubscriber {
|
|
|
47
47
|
}
|
|
48
48
|
if (this.receivingData) {
|
|
49
49
|
console.log(`No ws data from ${this.subscriptionName} in ${this.resubTimeoutMs}ms, resubscribing`);
|
|
50
|
-
await this.unsubscribe();
|
|
50
|
+
await this.unsubscribe(true);
|
|
51
51
|
this.receivingData = false;
|
|
52
52
|
await this.subscribe(this.onChange);
|
|
53
53
|
}
|
|
@@ -93,11 +93,14 @@ class WebSocketProgramAccountSubscriber {
|
|
|
93
93
|
this.onChange(keyedAccountInfo.accountId, account, context);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
unsubscribe() {
|
|
96
|
+
unsubscribe(onResub = false) {
|
|
97
|
+
if (!onResub) {
|
|
98
|
+
this.resubTimeoutMs = undefined;
|
|
99
|
+
}
|
|
97
100
|
this.isUnsubscribing = true;
|
|
98
101
|
clearTimeout(this.timeoutId);
|
|
99
102
|
this.timeoutId = undefined;
|
|
100
|
-
if (this.listenerId) {
|
|
103
|
+
if (this.listenerId != null) {
|
|
101
104
|
const promise = this.program.provider.connection
|
|
102
105
|
.removeAccountChangeListener(this.listenerId)
|
|
103
106
|
.then(() => {
|
|
@@ -47,4 +47,26 @@ export declare function getVammL2Generator({ marketAccount, oraclePriceData, num
|
|
|
47
47
|
topOfBookQuoteAmounts?: BN[];
|
|
48
48
|
}): L2OrderBookGenerator;
|
|
49
49
|
export declare function groupL2(l2: L2OrderBook, grouping: BN, depth: number): L2OrderBook;
|
|
50
|
+
/**
|
|
51
|
+
* The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
|
|
52
|
+
* This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
|
|
53
|
+
* crossing book)
|
|
54
|
+
*
|
|
55
|
+
* Things to note about how it works:
|
|
56
|
+
* - it will not uncross the user's liquidity
|
|
57
|
+
* - it does the uncrossing by "shifting" the crossing liquidity to the nearest uncrossed levels. Thus the output liquidity maintains the same total size.
|
|
58
|
+
*
|
|
59
|
+
* @param bids
|
|
60
|
+
* @param asks
|
|
61
|
+
* @param oraclePrice
|
|
62
|
+
* @param oracleTwap5Min
|
|
63
|
+
* @param markTwap5Min
|
|
64
|
+
* @param grouping
|
|
65
|
+
* @param userBids
|
|
66
|
+
* @param userAsks
|
|
67
|
+
*/
|
|
68
|
+
export declare function uncrossL2(bids: L2Level[], asks: L2Level[], oraclePrice: BN, oracleTwap5Min: BN, markTwap5Min: BN, grouping: BN, userBids: Set<string>, userAsks: Set<string>): {
|
|
69
|
+
bids: L2Level[];
|
|
70
|
+
asks: L2Level[];
|
|
71
|
+
};
|
|
50
72
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.groupL2 = exports.getVammL2Generator = exports.createL2Levels = exports.mergeL2LevelGenerators = exports.getL2GeneratorFromDLOBNodes = exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = void 0;
|
|
3
|
+
exports.uncrossL2 = exports.groupL2 = exports.getVammL2Generator = exports.createL2Levels = exports.mergeL2LevelGenerators = exports.getL2GeneratorFromDLOBNodes = exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = void 0;
|
|
4
4
|
const __1 = require("..");
|
|
5
5
|
const assert_1 = require("../assert/assert");
|
|
6
6
|
exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = [
|
|
@@ -245,3 +245,117 @@ function groupL2Levels(levels, grouping, direction, depth) {
|
|
|
245
245
|
}
|
|
246
246
|
return groupedLevels;
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
|
|
250
|
+
* This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
|
|
251
|
+
* crossing book)
|
|
252
|
+
*
|
|
253
|
+
* Things to note about how it works:
|
|
254
|
+
* - it will not uncross the user's liquidity
|
|
255
|
+
* - it does the uncrossing by "shifting" the crossing liquidity to the nearest uncrossed levels. Thus the output liquidity maintains the same total size.
|
|
256
|
+
*
|
|
257
|
+
* @param bids
|
|
258
|
+
* @param asks
|
|
259
|
+
* @param oraclePrice
|
|
260
|
+
* @param oracleTwap5Min
|
|
261
|
+
* @param markTwap5Min
|
|
262
|
+
* @param grouping
|
|
263
|
+
* @param userBids
|
|
264
|
+
* @param userAsks
|
|
265
|
+
*/
|
|
266
|
+
function uncrossL2(bids, asks, oraclePrice, oracleTwap5Min, markTwap5Min, grouping, userBids, userAsks) {
|
|
267
|
+
// If there are no bids or asks, there is nothing to center
|
|
268
|
+
if (bids.length === 0 || asks.length === 0) {
|
|
269
|
+
return { bids, asks };
|
|
270
|
+
}
|
|
271
|
+
// If the top of the book is already centered, there is nothing to do
|
|
272
|
+
if (bids[0].price.lt(asks[0].price)) {
|
|
273
|
+
return { bids, asks };
|
|
274
|
+
}
|
|
275
|
+
const newBids = [];
|
|
276
|
+
const newAsks = [];
|
|
277
|
+
const updateLevels = (newPrice, oldLevel, levels) => {
|
|
278
|
+
if (levels.length > 0 && levels[levels.length - 1].price.eq(newPrice)) {
|
|
279
|
+
levels[levels.length - 1].size = levels[levels.length - 1].size.add(oldLevel.size);
|
|
280
|
+
for (const [source, size] of Object.entries(oldLevel.sources)) {
|
|
281
|
+
if (levels[levels.length - 1].sources[source]) {
|
|
282
|
+
levels[levels.length - 1].sources = {
|
|
283
|
+
...levels[levels.length - 1].sources,
|
|
284
|
+
[source]: levels[levels.length - 1].sources[source].add(size),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
levels[levels.length - 1].sources[source] = size;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
levels.push({
|
|
294
|
+
price: newPrice,
|
|
295
|
+
size: oldLevel.size,
|
|
296
|
+
sources: oldLevel.sources,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
// This is the best estimate of the premium in the market vs oracle to filter crossing around
|
|
301
|
+
const referencePrice = oraclePrice.add(markTwap5Min.sub(oracleTwap5Min));
|
|
302
|
+
let bidIndex = 0;
|
|
303
|
+
let askIndex = 0;
|
|
304
|
+
while (bidIndex < bids.length || askIndex < asks.length) {
|
|
305
|
+
const nextBid = bids[bidIndex];
|
|
306
|
+
const nextAsk = asks[askIndex];
|
|
307
|
+
if (!nextBid) {
|
|
308
|
+
newAsks.push(nextAsk);
|
|
309
|
+
askIndex++;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (!nextAsk) {
|
|
313
|
+
newBids.push(nextBid);
|
|
314
|
+
bidIndex++;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (nextBid.price.gt(nextAsk.price)) {
|
|
318
|
+
if (userBids.has(nextBid.price.toString())) {
|
|
319
|
+
newBids.push(nextBid);
|
|
320
|
+
bidIndex++;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (userAsks.has(nextAsk.price.toString())) {
|
|
324
|
+
newAsks.push(nextAsk);
|
|
325
|
+
askIndex++;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (nextBid.price.gt(referencePrice) &&
|
|
329
|
+
nextAsk.price.gt(referencePrice)) {
|
|
330
|
+
const newBidPrice = nextAsk.price.sub(grouping);
|
|
331
|
+
updateLevels(newBidPrice, nextBid, newBids);
|
|
332
|
+
bidIndex++;
|
|
333
|
+
}
|
|
334
|
+
else if (nextAsk.price.lt(referencePrice) &&
|
|
335
|
+
nextBid.price.lt(referencePrice)) {
|
|
336
|
+
const newAskPrice = nextBid.price.add(grouping);
|
|
337
|
+
updateLevels(newAskPrice, nextAsk, newAsks);
|
|
338
|
+
askIndex++;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
const newBidPrice = referencePrice.sub(grouping);
|
|
342
|
+
const newAskPrice = referencePrice.add(grouping);
|
|
343
|
+
updateLevels(newBidPrice, nextBid, newBids);
|
|
344
|
+
updateLevels(newAskPrice, nextAsk, newAsks);
|
|
345
|
+
bidIndex++;
|
|
346
|
+
askIndex++;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
newAsks.push(nextAsk);
|
|
351
|
+
askIndex++;
|
|
352
|
+
newBids.push(nextBid);
|
|
353
|
+
bidIndex++;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
bids: newBids,
|
|
358
|
+
asks: newAsks,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
exports.uncrossL2 = uncrossL2;
|
package/lib/driftClient.js
CHANGED
|
@@ -2216,8 +2216,13 @@ class DriftClient {
|
|
|
2216
2216
|
async getSwapIx({ outMarketIndex, inMarketIndex, amountIn, inTokenAccount, outTokenAccount, limitPrice, reduceOnly, userAccountPublicKey, }) {
|
|
2217
2217
|
const userAccountPublicKeyToUse = userAccountPublicKey || (await this.getUserAccountPublicKey());
|
|
2218
2218
|
const userAccounts = [];
|
|
2219
|
-
|
|
2220
|
-
|
|
2219
|
+
try {
|
|
2220
|
+
if (this.hasUser() && this.getUser().getUserAccountAndSlot()) {
|
|
2221
|
+
userAccounts.push(this.getUser().getUserAccountAndSlot().data);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
catch (err) {
|
|
2225
|
+
// ignore
|
|
2221
2226
|
}
|
|
2222
2227
|
const remainingAccounts = this.getRemainingAccounts({
|
|
2223
2228
|
userAccounts,
|
|
@@ -17,7 +17,7 @@ class WebSocketLogProvider {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
async subscribe(callback) {
|
|
20
|
-
if (this.subscriptionId) {
|
|
20
|
+
if (this.subscriptionId != null) {
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
23
23
|
this.callback = callback;
|
|
@@ -48,14 +48,14 @@ class WebSocketLogProvider {
|
|
|
48
48
|
}, this.commitment);
|
|
49
49
|
}
|
|
50
50
|
isSubscribed() {
|
|
51
|
-
return this.subscriptionId
|
|
51
|
+
return this.subscriptionId != null;
|
|
52
52
|
}
|
|
53
53
|
async unsubscribe(external = false) {
|
|
54
54
|
this.isUnsubscribing = true;
|
|
55
55
|
this.externalUnsubscribe = external;
|
|
56
56
|
clearTimeout(this.timeoutId);
|
|
57
57
|
this.timeoutId = undefined;
|
|
58
|
-
if (this.subscriptionId
|
|
58
|
+
if (this.subscriptionId != null) {
|
|
59
59
|
try {
|
|
60
60
|
await this.connection.removeOnLogsListener(this.subscriptionId);
|
|
61
61
|
this.subscriptionId = undefined;
|
package/lib/factory/bigNum.d.ts
CHANGED
|
@@ -86,7 +86,7 @@ export declare class BigNum {
|
|
|
86
86
|
* @returns
|
|
87
87
|
*/
|
|
88
88
|
toNotional(useTradePrecision?: boolean, precisionOverride?: number): string;
|
|
89
|
-
toMillified(precision?: number, rounded?: boolean): string;
|
|
89
|
+
toMillified(precision?: number, rounded?: boolean, type?: 'financial' | 'scientific'): string;
|
|
90
90
|
toJSON(): {
|
|
91
91
|
val: string;
|
|
92
92
|
precision: string;
|
package/lib/factory/bigNum.js
CHANGED
|
@@ -330,7 +330,7 @@ class BigNum {
|
|
|
330
330
|
}
|
|
331
331
|
return `${prefix}${val.replace('-', '')}`;
|
|
332
332
|
}
|
|
333
|
-
toMillified(precision = 3, rounded = false) {
|
|
333
|
+
toMillified(precision = 3, rounded = false, type = 'financial') {
|
|
334
334
|
if (rounded) {
|
|
335
335
|
return this.toRounded(precision).toMillified(precision);
|
|
336
336
|
}
|
|
@@ -346,7 +346,10 @@ class BigNum {
|
|
|
346
346
|
if (leftSide.length <= 3) {
|
|
347
347
|
return this.shift(new anchor_1.BN(precision)).toPrecision(precision, true);
|
|
348
348
|
}
|
|
349
|
-
const unitTicks =
|
|
349
|
+
const unitTicks = type === 'financial'
|
|
350
|
+
? ['', 'K', 'M', 'B', 'T', 'Q']
|
|
351
|
+
: ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
352
|
+
// TODO -- handle nubers which are larger than the max unit tick
|
|
350
353
|
const unitNumber = Math.floor((leftSide.length - 1) / 3);
|
|
351
354
|
const unit = unitTicks[unitNumber];
|
|
352
355
|
let leadDigits = leftSide.slice(0, precision);
|
package/lib/idl/drift.json
CHANGED
|
@@ -7343,12 +7343,24 @@
|
|
|
7343
7343
|
"name": "totalFeeEarnedPerLp",
|
|
7344
7344
|
"type": "u64"
|
|
7345
7345
|
},
|
|
7346
|
+
{
|
|
7347
|
+
"name": "netUnsettledFundingPnl",
|
|
7348
|
+
"type": "i64"
|
|
7349
|
+
},
|
|
7350
|
+
{
|
|
7351
|
+
"name": "quoteAssetAmountWithUnsettledLp",
|
|
7352
|
+
"type": "i64"
|
|
7353
|
+
},
|
|
7354
|
+
{
|
|
7355
|
+
"name": "referencePriceOffset",
|
|
7356
|
+
"type": "i32"
|
|
7357
|
+
},
|
|
7346
7358
|
{
|
|
7347
7359
|
"name": "padding",
|
|
7348
7360
|
"type": {
|
|
7349
7361
|
"array": [
|
|
7350
7362
|
"u8",
|
|
7351
|
-
|
|
7363
|
+
12
|
|
7352
7364
|
]
|
|
7353
7365
|
}
|
|
7354
7366
|
}
|
package/lib/math/amm.d.ts
CHANGED
|
@@ -34,7 +34,9 @@ export type AssetType = 'quote' | 'base';
|
|
|
34
34
|
*/
|
|
35
35
|
export declare function calculateAmmReservesAfterSwap(amm: Pick<AMM, 'pegMultiplier' | 'quoteAssetReserve' | 'sqrtK' | 'baseAssetReserve'>, inputAssetType: AssetType, swapAmount: BN, swapDirection: SwapDirection): [BN, BN];
|
|
36
36
|
export declare function calculateMarketOpenBidAsk(baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN, stepSize?: BN): [BN, BN];
|
|
37
|
+
export declare function calculateInventoryLiquidityRatio(baseAssetAmountWithAmm: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN): BN;
|
|
37
38
|
export declare function calculateInventoryScale(baseAssetAmountWithAmm: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN, directionalSpread: number, maxSpread: number): number;
|
|
39
|
+
export declare function calculateReferencePriceOffset(reservePrice: BN, last24hAvgFundingRate: BN, liquidityFraction: BN, oracleTwapFast: BN, markTwapFast: BN, oracleTwapSlow: BN, markTwapSlow: BN, maxOffsetPct: number): BN;
|
|
38
40
|
export declare function calculateEffectiveLeverage(baseSpread: number, quoteAssetReserve: BN, terminalQuoteAssetReserve: BN, pegMultiplier: BN, netBaseAssetAmount: BN, reservePrice: BN, totalFeeMinusDistributions: BN): number;
|
|
39
41
|
export declare function calculateMaxSpread(marginRatioInitial: number): number;
|
|
40
42
|
export declare function calculateVolSpreadBN(lastOracleConfPct: BN, reservePrice: BN, markStd: BN, oracleStd: BN, longIntensity: BN, shortIntensity: BN, volume24H: BN): [BN, BN];
|
|
@@ -55,11 +57,13 @@ export declare function calculateSpreadBN(baseSpread: number, lastOracleReserveP
|
|
|
55
57
|
halfRevenueRetreatAmount: number;
|
|
56
58
|
longSpreadwRevRetreat: number;
|
|
57
59
|
shortSpreadwRevRetreat: number;
|
|
60
|
+
longSpreadwOffsetShrink: number;
|
|
61
|
+
shortSpreadwOffsetShrink: number;
|
|
58
62
|
totalSpread: number;
|
|
59
63
|
longSpread: number;
|
|
60
64
|
shortSpread: number;
|
|
61
65
|
};
|
|
62
|
-
export declare function calculateSpread(amm: AMM, oraclePriceData: OraclePriceData, now?: BN): [number, number];
|
|
66
|
+
export declare function calculateSpread(amm: AMM, oraclePriceData: OraclePriceData, now?: BN, reservePrice?: BN): [number, number];
|
|
63
67
|
export declare function calculateSpreadReserves(amm: AMM, oraclePriceData: OraclePriceData, now?: BN): {
|
|
64
68
|
baseAssetReserve: any;
|
|
65
69
|
quoteAssetReserve: any;
|
package/lib/math/amm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateInventoryScale = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0;
|
|
3
|
+
exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateReferencePriceOffset = exports.calculateInventoryScale = exports.calculateInventoryLiquidityRatio = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0;
|
|
4
4
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
5
5
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
6
6
|
const types_1 = require("../types");
|
|
@@ -201,11 +201,7 @@ function calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBas
|
|
|
201
201
|
return [openBids, openAsks];
|
|
202
202
|
}
|
|
203
203
|
exports.calculateMarketOpenBidAsk = calculateMarketOpenBidAsk;
|
|
204
|
-
function
|
|
205
|
-
if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
|
|
206
|
-
return 1;
|
|
207
|
-
}
|
|
208
|
-
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
|
|
204
|
+
function calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve) {
|
|
209
205
|
// inventory skew
|
|
210
206
|
const [openBids, openAsks] = calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
|
|
211
207
|
const minSideLiquidity = anchor_1.BN.min(openBids.abs(), openAsks.abs());
|
|
@@ -213,6 +209,15 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
|
|
|
213
209
|
.mul(numericConstants_1.PERCENTAGE_PRECISION)
|
|
214
210
|
.div(anchor_1.BN.max(minSideLiquidity, numericConstants_1.ONE))
|
|
215
211
|
.abs(), numericConstants_1.PERCENTAGE_PRECISION);
|
|
212
|
+
return inventoryScaleBN;
|
|
213
|
+
}
|
|
214
|
+
exports.calculateInventoryLiquidityRatio = calculateInventoryLiquidityRatio;
|
|
215
|
+
function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, directionalSpread, maxSpread) {
|
|
216
|
+
if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
|
|
220
|
+
const inventoryScaleBN = calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
|
|
216
221
|
const inventoryScaleMaxBN = anchor_1.BN.max(MAX_BID_ASK_INVENTORY_SKEW_FACTOR, new anchor_1.BN(maxSpread)
|
|
217
222
|
.mul(numericConstants_1.BID_ASK_SPREAD_PRECISION)
|
|
218
223
|
.div(new anchor_1.BN(Math.max(directionalSpread, 1))));
|
|
@@ -220,6 +225,36 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
|
|
|
220
225
|
return inventoryScaleCapped;
|
|
221
226
|
}
|
|
222
227
|
exports.calculateInventoryScale = calculateInventoryScale;
|
|
228
|
+
function calculateReferencePriceOffset(reservePrice, last24hAvgFundingRate, liquidityFraction, oracleTwapFast, markTwapFast, oracleTwapSlow, markTwapSlow, maxOffsetPct) {
|
|
229
|
+
if (last24hAvgFundingRate.eq(numericConstants_1.ZERO)) {
|
|
230
|
+
return numericConstants_1.ZERO;
|
|
231
|
+
}
|
|
232
|
+
const maxOffsetInPrice = new anchor_1.BN(maxOffsetPct)
|
|
233
|
+
.mul(reservePrice)
|
|
234
|
+
.div(numericConstants_1.PERCENTAGE_PRECISION);
|
|
235
|
+
// Calculate quote denominated market premium
|
|
236
|
+
const markPremiumMinute = (0, __1.clampBN)(markTwapFast.sub(oracleTwapFast), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
237
|
+
const markPremiumHour = (0, __1.clampBN)(markTwapSlow.sub(oracleTwapSlow), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
238
|
+
// Convert last24hAvgFundingRate to quote denominated premium
|
|
239
|
+
const markPremiumDay = (0, __1.clampBN)(last24hAvgFundingRate.div(numericConstants_1.FUNDING_RATE_BUFFER_PRECISION).mul(new anchor_1.BN(24)), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
240
|
+
// Take average clamped premium as the price-based offset
|
|
241
|
+
const markPremiumAvg = markPremiumMinute
|
|
242
|
+
.add(markPremiumHour)
|
|
243
|
+
.add(markPremiumDay)
|
|
244
|
+
.div(new anchor_1.BN(3));
|
|
245
|
+
const markPremiumAvgPct = markPremiumAvg
|
|
246
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
247
|
+
.div(reservePrice);
|
|
248
|
+
const inventoryPct = (0, __1.clampBN)(liquidityFraction.mul(new anchor_1.BN(maxOffsetPct)).div(numericConstants_1.PERCENTAGE_PRECISION), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
249
|
+
// Only apply when inventory is consistent with recent and 24h market premium
|
|
250
|
+
let offsetPct = markPremiumAvgPct.add(inventoryPct);
|
|
251
|
+
if (!(0, __1.sigNum)(inventoryPct).eq((0, __1.sigNum)(markPremiumAvgPct))) {
|
|
252
|
+
offsetPct = numericConstants_1.ZERO;
|
|
253
|
+
}
|
|
254
|
+
const clampedOffsetPct = (0, __1.clampBN)(offsetPct, new anchor_1.BN(-maxOffsetPct), new anchor_1.BN(maxOffsetPct));
|
|
255
|
+
return clampedOffsetPct;
|
|
256
|
+
}
|
|
257
|
+
exports.calculateReferencePriceOffset = calculateReferencePriceOffset;
|
|
223
258
|
function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, netBaseAssetAmount, reservePrice, totalFeeMinusDistributions) {
|
|
224
259
|
// vAMM skew
|
|
225
260
|
const netBaseAssetValue = quoteAssetReserve
|
|
@@ -283,6 +318,8 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
|
|
|
283
318
|
halfRevenueRetreatAmount: 0,
|
|
284
319
|
longSpreadwRevRetreat: 0,
|
|
285
320
|
shortSpreadwRevRetreat: 0,
|
|
321
|
+
longSpreadwOffsetShrink: 0,
|
|
322
|
+
shortSpreadwOffsetShrink: 0,
|
|
286
323
|
totalSpread: 0,
|
|
287
324
|
longSpread: 0,
|
|
288
325
|
shortSpread: 0,
|
|
@@ -380,11 +417,13 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
|
|
|
380
417
|
return [longSpread, shortSpread];
|
|
381
418
|
}
|
|
382
419
|
exports.calculateSpreadBN = calculateSpreadBN;
|
|
383
|
-
function calculateSpread(amm, oraclePriceData, now) {
|
|
420
|
+
function calculateSpread(amm, oraclePriceData, now, reservePrice) {
|
|
384
421
|
if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
|
|
385
422
|
return [amm.baseSpread / 2, amm.baseSpread / 2];
|
|
386
423
|
}
|
|
387
|
-
|
|
424
|
+
if (!reservePrice) {
|
|
425
|
+
reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
|
|
426
|
+
}
|
|
388
427
|
const targetPrice = (oraclePriceData === null || oraclePriceData === void 0 ? void 0 : oraclePriceData.price) || reservePrice;
|
|
389
428
|
const confInterval = oraclePriceData.confidence || numericConstants_1.ZERO;
|
|
390
429
|
const targetMarkSpreadPct = reservePrice
|
|
@@ -410,10 +449,15 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
|
|
|
410
449
|
quoteAssetReserve: amm.quoteAssetReserve,
|
|
411
450
|
};
|
|
412
451
|
}
|
|
413
|
-
|
|
452
|
+
let spreadFraction = new anchor_1.BN(spread / 2);
|
|
453
|
+
// make non-zero
|
|
454
|
+
if (spreadFraction.eq(numericConstants_1.ZERO)) {
|
|
455
|
+
spreadFraction = spread >= 0 ? new anchor_1.BN(1) : new anchor_1.BN(-1);
|
|
456
|
+
}
|
|
414
457
|
const quoteAssetReserveDelta = amm.quoteAssetReserve.div(numericConstants_1.BID_ASK_SPREAD_PRECISION.div(spreadFraction));
|
|
415
458
|
let quoteAssetReserve;
|
|
416
|
-
if ((0, types_1.isVariant)(direction, 'long'))
|
|
459
|
+
if ((spread >= 0 && (0, types_1.isVariant)(direction, 'long')) ||
|
|
460
|
+
(spread <= 0 && (0, types_1.isVariant)(direction, 'short'))) {
|
|
417
461
|
quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
|
|
418
462
|
}
|
|
419
463
|
else {
|
|
@@ -425,9 +469,14 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
|
|
|
425
469
|
quoteAssetReserve,
|
|
426
470
|
};
|
|
427
471
|
}
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
const
|
|
472
|
+
const reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
|
|
473
|
+
// always allow 10 bps of price offset, up to a fifth of the market's max_spread
|
|
474
|
+
const maxOffset = Math.max(amm.maxSpread / 5, numericConstants_1.PERCENTAGE_PRECISION.toNumber() / 1000);
|
|
475
|
+
const liquidityFraction = calculateInventoryLiquidityRatio(amm.baseAssetAmountWithAmm, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve);
|
|
476
|
+
const referencePriceOffset = calculateReferencePriceOffset(reservePrice, amm.last24HAvgFundingRate, liquidityFraction, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, amm.lastMarkPriceTwap, maxOffset);
|
|
477
|
+
const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now, reservePrice);
|
|
478
|
+
const askReserves = calculateSpreadReserve(longSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.LONG, amm);
|
|
479
|
+
const bidReserves = calculateSpreadReserve(shortSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.SHORT, amm);
|
|
431
480
|
return [bidReserves, askReserves];
|
|
432
481
|
}
|
|
433
482
|
exports.calculateSpreadReserves = calculateSpreadReserves;
|
|
@@ -104,9 +104,9 @@ class PhoenixSubscriber {
|
|
|
104
104
|
const ladder = (0, phoenix_sdk_1.getMarketUiLadder)(this.market, this.lastSlot, this.lastUnixTimestamp, 20);
|
|
105
105
|
for (let i = 0; i < ladder[side].length; i++) {
|
|
106
106
|
const { price, quantity } = ladder[side][i];
|
|
107
|
-
const size = new anchor_1.BN(
|
|
107
|
+
const size = new anchor_1.BN(quantity).mul(new anchor_1.BN(basePrecision));
|
|
108
108
|
yield {
|
|
109
|
-
price: new anchor_1.BN(
|
|
109
|
+
price: new anchor_1.BN(price).mul(new anchor_1.BN(pricePrecision)),
|
|
110
110
|
size,
|
|
111
111
|
sources: {
|
|
112
112
|
phoenix: size,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
2
3
|
import { Connection } from '@solana/web3.js';
|
|
3
4
|
import { EventEmitter } from 'events';
|
|
4
5
|
import StrictEventEmitter from 'strict-event-emitter-types/types/src';
|
|
5
|
-
type SlotSubscriberConfig = {
|
|
6
|
+
type SlotSubscriberConfig = {
|
|
7
|
+
resubTimeoutMs?: number;
|
|
8
|
+
};
|
|
6
9
|
export interface SlotSubscriberEvents {
|
|
7
10
|
newSlot: (newSlot: number) => void;
|
|
8
11
|
}
|
|
@@ -11,9 +14,14 @@ export declare class SlotSubscriber {
|
|
|
11
14
|
currentSlot: number;
|
|
12
15
|
subscriptionId: number;
|
|
13
16
|
eventEmitter: StrictEventEmitter<EventEmitter, SlotSubscriberEvents>;
|
|
14
|
-
|
|
17
|
+
timeoutId?: NodeJS.Timeout;
|
|
18
|
+
resubTimeoutMs?: number;
|
|
19
|
+
isUnsubscribing: boolean;
|
|
20
|
+
receivingData: boolean;
|
|
21
|
+
constructor(connection: Connection, config?: SlotSubscriberConfig);
|
|
15
22
|
subscribe(): Promise<void>;
|
|
23
|
+
private setTimeout;
|
|
16
24
|
getSlot(): number;
|
|
17
|
-
unsubscribe(): Promise<void>;
|
|
25
|
+
unsubscribe(onResub?: boolean): Promise<void>;
|
|
18
26
|
}
|
|
19
27
|
export {};
|
|
@@ -3,28 +3,64 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SlotSubscriber = void 0;
|
|
4
4
|
const events_1 = require("events");
|
|
5
5
|
class SlotSubscriber {
|
|
6
|
-
constructor(connection,
|
|
6
|
+
constructor(connection, config) {
|
|
7
7
|
this.connection = connection;
|
|
8
|
+
this.isUnsubscribing = false;
|
|
9
|
+
this.receivingData = false;
|
|
8
10
|
this.eventEmitter = new events_1.EventEmitter();
|
|
11
|
+
this.resubTimeoutMs = config === null || config === void 0 ? void 0 : config.resubTimeoutMs;
|
|
9
12
|
}
|
|
10
13
|
async subscribe() {
|
|
11
|
-
if (this.subscriptionId) {
|
|
14
|
+
if (this.subscriptionId != null) {
|
|
12
15
|
return;
|
|
13
16
|
}
|
|
14
17
|
this.currentSlot = await this.connection.getSlot('confirmed');
|
|
15
18
|
this.subscriptionId = this.connection.onSlotChange((slotInfo) => {
|
|
16
19
|
if (!this.currentSlot || this.currentSlot < slotInfo.slot) {
|
|
20
|
+
if (this.resubTimeoutMs && !this.isUnsubscribing) {
|
|
21
|
+
this.receivingData = true;
|
|
22
|
+
clearTimeout(this.timeoutId);
|
|
23
|
+
this.setTimeout();
|
|
24
|
+
}
|
|
17
25
|
this.currentSlot = slotInfo.slot;
|
|
18
26
|
this.eventEmitter.emit('newSlot', slotInfo.slot);
|
|
19
27
|
}
|
|
20
28
|
});
|
|
29
|
+
if (this.resubTimeoutMs) {
|
|
30
|
+
this.setTimeout();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
setTimeout() {
|
|
34
|
+
this.timeoutId = setTimeout(async () => {
|
|
35
|
+
if (this.isUnsubscribing) {
|
|
36
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (this.receivingData) {
|
|
40
|
+
console.log(`No new slot in ${this.resubTimeoutMs}ms, slot subscriber resubscribing`);
|
|
41
|
+
await this.unsubscribe(true);
|
|
42
|
+
this.receivingData = false;
|
|
43
|
+
await this.subscribe();
|
|
44
|
+
}
|
|
45
|
+
}, this.resubTimeoutMs);
|
|
21
46
|
}
|
|
22
47
|
getSlot() {
|
|
23
48
|
return this.currentSlot;
|
|
24
49
|
}
|
|
25
|
-
async unsubscribe() {
|
|
26
|
-
if (
|
|
50
|
+
async unsubscribe(onResub = false) {
|
|
51
|
+
if (!onResub) {
|
|
52
|
+
this.resubTimeoutMs = undefined;
|
|
53
|
+
}
|
|
54
|
+
this.isUnsubscribing = true;
|
|
55
|
+
clearTimeout(this.timeoutId);
|
|
56
|
+
this.timeoutId = undefined;
|
|
57
|
+
if (this.subscriptionId != null) {
|
|
27
58
|
await this.connection.removeSlotChangeListener(this.subscriptionId);
|
|
59
|
+
this.subscriptionId = undefined;
|
|
60
|
+
this.isUnsubscribing = false;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.isUnsubscribing = false;
|
|
28
64
|
}
|
|
29
65
|
}
|
|
30
66
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -767,7 +767,7 @@ export type AMM = {
|
|
|
767
767
|
pegMultiplier: BN;
|
|
768
768
|
cumulativeFundingRateLong: BN;
|
|
769
769
|
cumulativeFundingRateShort: BN;
|
|
770
|
-
|
|
770
|
+
last24HAvgFundingRate: BN;
|
|
771
771
|
lastFundingRateShort: BN;
|
|
772
772
|
lastFundingRateLong: BN;
|
|
773
773
|
totalLiquidationFee: BN;
|
package/lib/user.d.ts
CHANGED
|
@@ -353,7 +353,7 @@ export declare class User {
|
|
|
353
353
|
* @param quoteAmount
|
|
354
354
|
* @returns feeForQuote : Precision QUOTE_PRECISION
|
|
355
355
|
*/
|
|
356
|
-
calculateFeeForQuoteAmount(quoteAmount: BN): BN;
|
|
356
|
+
calculateFeeForQuoteAmount(quoteAmount: BN, marketIndex?: number): BN;
|
|
357
357
|
/**
|
|
358
358
|
* Calculates a user's max withdrawal amounts for a spot market. If reduceOnly is true,
|
|
359
359
|
* it will return the max withdrawal amount without opening a liability for the user
|