@drift-labs/sdk 2.52.0-beta.0 → 2.52.0-beta.2
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/basicUserAccountSubscriber.d.ts +4 -0
- package/lib/accounts/basicUserAccountSubscriber.js +4 -0
- package/lib/accounts/bulkAccountLoader.js +7 -1
- package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
- package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
- package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
- 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/adminClient.d.ts +1 -0
- package/lib/adminClient.js +9 -0
- package/lib/constants/numericConstants.d.ts +3 -0
- package/lib/constants/numericConstants.js +4 -1
- package/lib/dlob/orderBookLevels.d.ts +22 -0
- package/lib/dlob/orderBookLevels.js +115 -1
- package/lib/driftClient.d.ts +1 -0
- package/lib/driftClient.js +14 -5
- 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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- 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/userMap/userMap.d.ts +3 -0
- package/lib/userMap/userMap.js +10 -1
- package/lib/userStats.d.ts +1 -0
- package/lib/userStats.js +3 -0
- package/package.json +1 -1
- package/src/accounts/basicUserAccountSubscriber.ts +4 -0
- package/src/accounts/bulkAccountLoader.ts +10 -3
- package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
- package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
- package/src/accounts/webSocketAccountSubscriber.ts +7 -4
- package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
- package/src/adminClient.ts +13 -0
- package/src/constants/numericConstants.ts +4 -0
- package/src/dlob/orderBookLevels.ts +136 -0
- package/src/driftClient.ts +21 -10
- package/src/events/webSocketLogProvider.ts +3 -3
- package/src/factory/bigNum.ts +11 -2
- package/src/idl/drift.json +13 -1
- package/src/index.ts +1 -0
- 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/userMap/userMap.ts +17 -3
- package/src/userStats.ts +8 -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
|
@@ -407,3 +407,139 @@ function groupL2Levels(
|
|
|
407
407
|
}
|
|
408
408
|
return groupedLevels;
|
|
409
409
|
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
|
|
413
|
+
* This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
|
|
414
|
+
* crossing book)
|
|
415
|
+
*
|
|
416
|
+
* Things to note about how it works:
|
|
417
|
+
* - it will not uncross the user's liquidity
|
|
418
|
+
* - it does the uncrossing by "shifting" the crossing liquidity to the nearest uncrossed levels. Thus the output liquidity maintains the same total size.
|
|
419
|
+
*
|
|
420
|
+
* @param bids
|
|
421
|
+
* @param asks
|
|
422
|
+
* @param oraclePrice
|
|
423
|
+
* @param oracleTwap5Min
|
|
424
|
+
* @param markTwap5Min
|
|
425
|
+
* @param grouping
|
|
426
|
+
* @param userBids
|
|
427
|
+
* @param userAsks
|
|
428
|
+
*/
|
|
429
|
+
export function uncrossL2(
|
|
430
|
+
bids: L2Level[],
|
|
431
|
+
asks: L2Level[],
|
|
432
|
+
oraclePrice: BN,
|
|
433
|
+
oracleTwap5Min: BN,
|
|
434
|
+
markTwap5Min: BN,
|
|
435
|
+
grouping: BN,
|
|
436
|
+
userBids: Set<string>,
|
|
437
|
+
userAsks: Set<string>
|
|
438
|
+
): { bids: L2Level[]; asks: L2Level[] } {
|
|
439
|
+
// If there are no bids or asks, there is nothing to center
|
|
440
|
+
if (bids.length === 0 || asks.length === 0) {
|
|
441
|
+
return { bids, asks };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// If the top of the book is already centered, there is nothing to do
|
|
445
|
+
if (bids[0].price.lt(asks[0].price)) {
|
|
446
|
+
return { bids, asks };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const newBids = [];
|
|
450
|
+
const newAsks = [];
|
|
451
|
+
|
|
452
|
+
const updateLevels = (newPrice: BN, oldLevel: L2Level, levels: L2Level[]) => {
|
|
453
|
+
if (levels.length > 0 && levels[levels.length - 1].price.eq(newPrice)) {
|
|
454
|
+
levels[levels.length - 1].size = levels[levels.length - 1].size.add(
|
|
455
|
+
oldLevel.size
|
|
456
|
+
);
|
|
457
|
+
for (const [source, size] of Object.entries(oldLevel.sources)) {
|
|
458
|
+
if (levels[levels.length - 1].sources[source]) {
|
|
459
|
+
levels[levels.length - 1].sources = {
|
|
460
|
+
...levels[levels.length - 1].sources,
|
|
461
|
+
[source]: levels[levels.length - 1].sources[source].add(size),
|
|
462
|
+
};
|
|
463
|
+
} else {
|
|
464
|
+
levels[levels.length - 1].sources[source] = size;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
levels.push({
|
|
469
|
+
price: newPrice,
|
|
470
|
+
size: oldLevel.size,
|
|
471
|
+
sources: oldLevel.sources,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// This is the best estimate of the premium in the market vs oracle to filter crossing around
|
|
477
|
+
const referencePrice = oraclePrice.add(markTwap5Min.sub(oracleTwap5Min));
|
|
478
|
+
|
|
479
|
+
let bidIndex = 0;
|
|
480
|
+
let askIndex = 0;
|
|
481
|
+
while (bidIndex < bids.length || askIndex < asks.length) {
|
|
482
|
+
const nextBid = bids[bidIndex];
|
|
483
|
+
const nextAsk = asks[askIndex];
|
|
484
|
+
|
|
485
|
+
if (!nextBid) {
|
|
486
|
+
newAsks.push(nextAsk);
|
|
487
|
+
askIndex++;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (!nextAsk) {
|
|
492
|
+
newBids.push(nextBid);
|
|
493
|
+
bidIndex++;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (nextBid.price.gt(nextAsk.price)) {
|
|
498
|
+
if (userBids.has(nextBid.price.toString())) {
|
|
499
|
+
newBids.push(nextBid);
|
|
500
|
+
bidIndex++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (userAsks.has(nextAsk.price.toString())) {
|
|
505
|
+
newAsks.push(nextAsk);
|
|
506
|
+
askIndex++;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (
|
|
511
|
+
nextBid.price.gt(referencePrice) &&
|
|
512
|
+
nextAsk.price.gt(referencePrice)
|
|
513
|
+
) {
|
|
514
|
+
const newBidPrice = nextAsk.price.sub(grouping);
|
|
515
|
+
updateLevels(newBidPrice, nextBid, newBids);
|
|
516
|
+
bidIndex++;
|
|
517
|
+
} else if (
|
|
518
|
+
nextAsk.price.lt(referencePrice) &&
|
|
519
|
+
nextBid.price.lt(referencePrice)
|
|
520
|
+
) {
|
|
521
|
+
const newAskPrice = nextBid.price.add(grouping);
|
|
522
|
+
updateLevels(newAskPrice, nextAsk, newAsks);
|
|
523
|
+
askIndex++;
|
|
524
|
+
} else {
|
|
525
|
+
const newBidPrice = referencePrice.sub(grouping);
|
|
526
|
+
const newAskPrice = referencePrice.add(grouping);
|
|
527
|
+
updateLevels(newBidPrice, nextBid, newBids);
|
|
528
|
+
updateLevels(newAskPrice, nextAsk, newAsks);
|
|
529
|
+
bidIndex++;
|
|
530
|
+
askIndex++;
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
newAsks.push(nextAsk);
|
|
534
|
+
askIndex++;
|
|
535
|
+
|
|
536
|
+
newBids.push(nextBid);
|
|
537
|
+
bidIndex++;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
bids: newBids,
|
|
543
|
+
asks: newAsks,
|
|
544
|
+
};
|
|
545
|
+
}
|
package/src/driftClient.ts
CHANGED
|
@@ -1112,6 +1112,19 @@ export class DriftClient {
|
|
|
1112
1112
|
);
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
+
public async getUserDeletionIx(userAccountPublicKey: PublicKey) {
|
|
1116
|
+
const ix = await this.program.instruction.deleteUser({
|
|
1117
|
+
accounts: {
|
|
1118
|
+
user: userAccountPublicKey,
|
|
1119
|
+
userStats: this.getUserStatsAccountPublicKey(),
|
|
1120
|
+
authority: this.wallet.publicKey,
|
|
1121
|
+
state: await this.getStatePublicKey(),
|
|
1122
|
+
},
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
return ix;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1115
1128
|
public async deleteUser(
|
|
1116
1129
|
subAccountId = 0,
|
|
1117
1130
|
txParams?: TxParams
|
|
@@ -1122,14 +1135,7 @@ export class DriftClient {
|
|
|
1122
1135
|
subAccountId
|
|
1123
1136
|
);
|
|
1124
1137
|
|
|
1125
|
-
const ix = await this.
|
|
1126
|
-
accounts: {
|
|
1127
|
-
user: userAccountPublicKey,
|
|
1128
|
-
userStats: this.getUserStatsAccountPublicKey(),
|
|
1129
|
-
authority: this.wallet.publicKey,
|
|
1130
|
-
state: await this.getStatePublicKey(),
|
|
1131
|
-
},
|
|
1132
|
-
});
|
|
1138
|
+
const ix = await this.getUserDeletionIx(userAccountPublicKey);
|
|
1133
1139
|
|
|
1134
1140
|
const { txSig } = await this.sendTransaction(
|
|
1135
1141
|
await this.buildTransaction(ix, txParams),
|
|
@@ -3953,9 +3959,14 @@ export class DriftClient {
|
|
|
3953
3959
|
userAccountPublicKey || (await this.getUserAccountPublicKey());
|
|
3954
3960
|
|
|
3955
3961
|
const userAccounts = [];
|
|
3956
|
-
|
|
3957
|
-
|
|
3962
|
+
try {
|
|
3963
|
+
if (this.hasUser() && this.getUser().getUserAccountAndSlot()) {
|
|
3964
|
+
userAccounts.push(this.getUser().getUserAccountAndSlot()!.data);
|
|
3965
|
+
}
|
|
3966
|
+
} catch (err) {
|
|
3967
|
+
// ignore
|
|
3958
3968
|
}
|
|
3969
|
+
|
|
3959
3970
|
const remainingAccounts = this.getRemainingAccounts({
|
|
3960
3971
|
userAccounts,
|
|
3961
3972
|
writableSpotMarketIndexes: [outMarketIndex, inMarketIndex],
|
|
@@ -23,7 +23,7 @@ export class WebSocketLogProvider implements LogProvider {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
public async subscribe(callback: logProviderCallback): Promise<boolean> {
|
|
26
|
-
if (this.subscriptionId) {
|
|
26
|
+
if (this.subscriptionId != null) {
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -62,7 +62,7 @@ export class WebSocketLogProvider implements LogProvider {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
public isSubscribed(): boolean {
|
|
65
|
-
return this.subscriptionId
|
|
65
|
+
return this.subscriptionId != null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
public async unsubscribe(external = false): Promise<boolean> {
|
|
@@ -71,7 +71,7 @@ export class WebSocketLogProvider implements LogProvider {
|
|
|
71
71
|
clearTimeout(this.timeoutId);
|
|
72
72
|
this.timeoutId = undefined;
|
|
73
73
|
|
|
74
|
-
if (this.subscriptionId
|
|
74
|
+
if (this.subscriptionId != null) {
|
|
75
75
|
try {
|
|
76
76
|
await this.connection.removeOnLogsListener(this.subscriptionId);
|
|
77
77
|
this.subscriptionId = undefined;
|
package/src/factory/bigNum.ts
CHANGED
|
@@ -497,7 +497,11 @@ export class BigNum {
|
|
|
497
497
|
return `${prefix}${val.replace('-', '')}`;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
public toMillified(
|
|
500
|
+
public toMillified(
|
|
501
|
+
precision = 3,
|
|
502
|
+
rounded = false,
|
|
503
|
+
type: 'financial' | 'scientific' = 'financial'
|
|
504
|
+
): string {
|
|
501
505
|
if (rounded) {
|
|
502
506
|
return this.toRounded(precision).toMillified(precision);
|
|
503
507
|
}
|
|
@@ -520,7 +524,12 @@ export class BigNum {
|
|
|
520
524
|
return this.shift(new BN(precision)).toPrecision(precision, true);
|
|
521
525
|
}
|
|
522
526
|
|
|
523
|
-
const unitTicks =
|
|
527
|
+
const unitTicks =
|
|
528
|
+
type === 'financial'
|
|
529
|
+
? ['', 'K', 'M', 'B', 'T', 'Q']
|
|
530
|
+
: ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
531
|
+
// TODO -- handle nubers which are larger than the max unit tick
|
|
532
|
+
|
|
524
533
|
const unitNumber = Math.floor((leftSide.length - 1) / 3);
|
|
525
534
|
const unit = unitTicks[unitNumber];
|
|
526
535
|
|
package/src/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/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from './accounts/pollingUserAccountSubscriber';
|
|
|
21
21
|
export * from './accounts/pollingUserStatsAccountSubscriber';
|
|
22
22
|
export * from './accounts/pollingInsuranceFundStakeAccountSubscriber';
|
|
23
23
|
export * from './accounts/basicUserAccountSubscriber';
|
|
24
|
+
export * from './accounts/oneShotUserAccountSubscriber';
|
|
24
25
|
export * from './accounts/types';
|
|
25
26
|
export * from './addresses/pda';
|
|
26
27
|
export * from './adminClient';
|
package/src/math/amm.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
PRICE_DIV_PEG,
|
|
13
13
|
PERCENTAGE_PRECISION,
|
|
14
14
|
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
|
|
15
|
+
FUNDING_RATE_BUFFER_PRECISION,
|
|
15
16
|
TWO,
|
|
16
17
|
} from '../constants/numericConstants';
|
|
17
18
|
import {
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
isVariant,
|
|
23
24
|
} from '../types';
|
|
24
25
|
import { assert } from '../assert/assert';
|
|
25
|
-
import { squareRootBN, clampBN, standardizeBaseAssetAmount } from '..';
|
|
26
|
+
import { squareRootBN, sigNum, clampBN, standardizeBaseAssetAmount } from '..';
|
|
26
27
|
|
|
27
28
|
import { OraclePriceData } from '../oracles/types';
|
|
28
29
|
import {
|
|
@@ -352,21 +353,12 @@ export function calculateMarketOpenBidAsk(
|
|
|
352
353
|
return [openBids, openAsks];
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
export function
|
|
356
|
+
export function calculateInventoryLiquidityRatio(
|
|
356
357
|
baseAssetAmountWithAmm: BN,
|
|
357
358
|
baseAssetReserve: BN,
|
|
358
359
|
minBaseAssetReserve: BN,
|
|
359
|
-
maxBaseAssetReserve: BN
|
|
360
|
-
|
|
361
|
-
maxSpread: number
|
|
362
|
-
): number {
|
|
363
|
-
if (baseAssetAmountWithAmm.eq(ZERO)) {
|
|
364
|
-
return 1;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
|
|
368
|
-
new BN(10)
|
|
369
|
-
);
|
|
360
|
+
maxBaseAssetReserve: BN
|
|
361
|
+
): BN {
|
|
370
362
|
// inventory skew
|
|
371
363
|
const [openBids, openAsks] = calculateMarketOpenBidAsk(
|
|
372
364
|
baseAssetReserve,
|
|
@@ -383,6 +375,31 @@ export function calculateInventoryScale(
|
|
|
383
375
|
.abs(),
|
|
384
376
|
PERCENTAGE_PRECISION
|
|
385
377
|
);
|
|
378
|
+
return inventoryScaleBN;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function calculateInventoryScale(
|
|
382
|
+
baseAssetAmountWithAmm: BN,
|
|
383
|
+
baseAssetReserve: BN,
|
|
384
|
+
minBaseAssetReserve: BN,
|
|
385
|
+
maxBaseAssetReserve: BN,
|
|
386
|
+
directionalSpread: number,
|
|
387
|
+
maxSpread: number
|
|
388
|
+
): number {
|
|
389
|
+
if (baseAssetAmountWithAmm.eq(ZERO)) {
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
|
|
394
|
+
new BN(10)
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const inventoryScaleBN = calculateInventoryLiquidityRatio(
|
|
398
|
+
baseAssetAmountWithAmm,
|
|
399
|
+
baseAssetReserve,
|
|
400
|
+
minBaseAssetReserve,
|
|
401
|
+
maxBaseAssetReserve
|
|
402
|
+
);
|
|
386
403
|
|
|
387
404
|
const inventoryScaleMaxBN = BN.max(
|
|
388
405
|
MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
|
|
@@ -402,6 +419,76 @@ export function calculateInventoryScale(
|
|
|
402
419
|
return inventoryScaleCapped;
|
|
403
420
|
}
|
|
404
421
|
|
|
422
|
+
export function calculateReferencePriceOffset(
|
|
423
|
+
reservePrice: BN,
|
|
424
|
+
last24hAvgFundingRate: BN,
|
|
425
|
+
liquidityFraction: BN,
|
|
426
|
+
oracleTwapFast: BN,
|
|
427
|
+
markTwapFast: BN,
|
|
428
|
+
oracleTwapSlow: BN,
|
|
429
|
+
markTwapSlow: BN,
|
|
430
|
+
maxOffsetPct: number
|
|
431
|
+
): BN {
|
|
432
|
+
if (last24hAvgFundingRate.eq(ZERO)) {
|
|
433
|
+
return ZERO;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const maxOffsetInPrice = new BN(maxOffsetPct)
|
|
437
|
+
.mul(reservePrice)
|
|
438
|
+
.div(PERCENTAGE_PRECISION);
|
|
439
|
+
|
|
440
|
+
// Calculate quote denominated market premium
|
|
441
|
+
const markPremiumMinute = clampBN(
|
|
442
|
+
markTwapFast.sub(oracleTwapFast),
|
|
443
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
444
|
+
maxOffsetInPrice
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const markPremiumHour = clampBN(
|
|
448
|
+
markTwapSlow.sub(oracleTwapSlow),
|
|
449
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
450
|
+
maxOffsetInPrice
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Convert last24hAvgFundingRate to quote denominated premium
|
|
454
|
+
const markPremiumDay = clampBN(
|
|
455
|
+
last24hAvgFundingRate.div(FUNDING_RATE_BUFFER_PRECISION).mul(new BN(24)),
|
|
456
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
457
|
+
maxOffsetInPrice
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
// Take average clamped premium as the price-based offset
|
|
461
|
+
const markPremiumAvg = markPremiumMinute
|
|
462
|
+
.add(markPremiumHour)
|
|
463
|
+
.add(markPremiumDay)
|
|
464
|
+
.div(new BN(3));
|
|
465
|
+
|
|
466
|
+
const markPremiumAvgPct = markPremiumAvg
|
|
467
|
+
.mul(PRICE_PRECISION)
|
|
468
|
+
.div(reservePrice);
|
|
469
|
+
|
|
470
|
+
const inventoryPct = clampBN(
|
|
471
|
+
liquidityFraction.mul(new BN(maxOffsetPct)).div(PERCENTAGE_PRECISION),
|
|
472
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
473
|
+
maxOffsetInPrice
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Only apply when inventory is consistent with recent and 24h market premium
|
|
477
|
+
let offsetPct = markPremiumAvgPct.add(inventoryPct);
|
|
478
|
+
|
|
479
|
+
if (!sigNum(inventoryPct).eq(sigNum(markPremiumAvgPct))) {
|
|
480
|
+
offsetPct = ZERO;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const clampedOffsetPct = clampBN(
|
|
484
|
+
offsetPct,
|
|
485
|
+
new BN(-maxOffsetPct),
|
|
486
|
+
new BN(maxOffsetPct)
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
return clampedOffsetPct;
|
|
490
|
+
}
|
|
491
|
+
|
|
405
492
|
export function calculateEffectiveLeverage(
|
|
406
493
|
baseSpread: number,
|
|
407
494
|
quoteAssetReserve: BN,
|
|
@@ -532,6 +619,8 @@ export function calculateSpreadBN(
|
|
|
532
619
|
halfRevenueRetreatAmount: 0,
|
|
533
620
|
longSpreadwRevRetreat: 0,
|
|
534
621
|
shortSpreadwRevRetreat: 0,
|
|
622
|
+
longSpreadwOffsetShrink: 0,
|
|
623
|
+
shortSpreadwOffsetShrink: 0,
|
|
535
624
|
totalSpread: 0,
|
|
536
625
|
longSpread: 0,
|
|
537
626
|
shortSpread: 0,
|
|
@@ -678,7 +767,6 @@ export function calculateSpreadBN(
|
|
|
678
767
|
spreadTerms.totalSpread = totalSpread;
|
|
679
768
|
spreadTerms.longSpread = longSpread;
|
|
680
769
|
spreadTerms.shortSpread = shortSpread;
|
|
681
|
-
|
|
682
770
|
if (returnTerms) {
|
|
683
771
|
return spreadTerms;
|
|
684
772
|
}
|
|
@@ -688,17 +776,20 @@ export function calculateSpreadBN(
|
|
|
688
776
|
export function calculateSpread(
|
|
689
777
|
amm: AMM,
|
|
690
778
|
oraclePriceData: OraclePriceData,
|
|
691
|
-
now?: BN
|
|
779
|
+
now?: BN,
|
|
780
|
+
reservePrice?: BN
|
|
692
781
|
): [number, number] {
|
|
693
782
|
if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
|
|
694
783
|
return [amm.baseSpread / 2, amm.baseSpread / 2];
|
|
695
784
|
}
|
|
696
785
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
786
|
+
if (!reservePrice) {
|
|
787
|
+
reservePrice = calculatePrice(
|
|
788
|
+
amm.baseAssetReserve,
|
|
789
|
+
amm.quoteAssetReserve,
|
|
790
|
+
amm.pegMultiplier
|
|
791
|
+
);
|
|
792
|
+
}
|
|
702
793
|
|
|
703
794
|
const targetPrice = oraclePriceData?.price || reservePrice;
|
|
704
795
|
const confInterval = oraclePriceData.confidence || ZERO;
|
|
@@ -760,13 +851,22 @@ export function calculateSpreadReserves(
|
|
|
760
851
|
quoteAssetReserve: amm.quoteAssetReserve,
|
|
761
852
|
};
|
|
762
853
|
}
|
|
763
|
-
|
|
854
|
+
let spreadFraction = new BN(spread / 2);
|
|
855
|
+
|
|
856
|
+
// make non-zero
|
|
857
|
+
if (spreadFraction.eq(ZERO)) {
|
|
858
|
+
spreadFraction = spread >= 0 ? new BN(1) : new BN(-1);
|
|
859
|
+
}
|
|
860
|
+
|
|
764
861
|
const quoteAssetReserveDelta = amm.quoteAssetReserve.div(
|
|
765
862
|
BID_ASK_SPREAD_PRECISION.div(spreadFraction)
|
|
766
863
|
);
|
|
767
864
|
|
|
768
865
|
let quoteAssetReserve;
|
|
769
|
-
if (
|
|
866
|
+
if (
|
|
867
|
+
(spread >= 0 && isVariant(direction, 'long')) ||
|
|
868
|
+
(spread <= 0 && isVariant(direction, 'short'))
|
|
869
|
+
) {
|
|
770
870
|
quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
|
|
771
871
|
} else {
|
|
772
872
|
quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
|
|
@@ -779,14 +879,48 @@ export function calculateSpreadReserves(
|
|
|
779
879
|
};
|
|
780
880
|
}
|
|
781
881
|
|
|
782
|
-
const
|
|
882
|
+
const reservePrice = calculatePrice(
|
|
883
|
+
amm.baseAssetReserve,
|
|
884
|
+
amm.quoteAssetReserve,
|
|
885
|
+
amm.pegMultiplier
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
// always allow 10 bps of price offset, up to a fifth of the market's max_spread
|
|
889
|
+
const maxOffset = Math.max(
|
|
890
|
+
amm.maxSpread / 5,
|
|
891
|
+
PERCENTAGE_PRECISION.toNumber() / 1000
|
|
892
|
+
);
|
|
893
|
+
const liquidityFraction = calculateInventoryLiquidityRatio(
|
|
894
|
+
amm.baseAssetAmountWithAmm,
|
|
895
|
+
amm.baseAssetReserve,
|
|
896
|
+
amm.minBaseAssetReserve,
|
|
897
|
+
amm.maxBaseAssetReserve
|
|
898
|
+
);
|
|
899
|
+
const referencePriceOffset = calculateReferencePriceOffset(
|
|
900
|
+
reservePrice,
|
|
901
|
+
amm.last24HAvgFundingRate,
|
|
902
|
+
liquidityFraction,
|
|
903
|
+
amm.historicalOracleData.lastOraclePriceTwap5Min,
|
|
904
|
+
amm.lastMarkPriceTwap5Min,
|
|
905
|
+
amm.historicalOracleData.lastOraclePriceTwap,
|
|
906
|
+
amm.lastMarkPriceTwap,
|
|
907
|
+
maxOffset
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
const [longSpread, shortSpread] = calculateSpread(
|
|
911
|
+
amm,
|
|
912
|
+
oraclePriceData,
|
|
913
|
+
now,
|
|
914
|
+
reservePrice
|
|
915
|
+
);
|
|
916
|
+
|
|
783
917
|
const askReserves = calculateSpreadReserve(
|
|
784
|
-
longSpread,
|
|
918
|
+
longSpread + referencePriceOffset.toNumber(),
|
|
785
919
|
PositionDirection.LONG,
|
|
786
920
|
amm
|
|
787
921
|
);
|
|
788
922
|
const bidReserves = calculateSpreadReserve(
|
|
789
|
-
shortSpread,
|
|
923
|
+
shortSpread + referencePriceOffset.toNumber(),
|
|
790
924
|
PositionDirection.SHORT,
|
|
791
925
|
amm
|
|
792
926
|
);
|
|
@@ -178,9 +178,9 @@ export class PhoenixSubscriber implements L2OrderBookGenerator {
|
|
|
178
178
|
|
|
179
179
|
for (let i = 0; i < ladder[side].length; i++) {
|
|
180
180
|
const { price, quantity } = ladder[side][i];
|
|
181
|
-
const size = new BN(
|
|
181
|
+
const size = new BN(quantity).mul(new BN(basePrecision));
|
|
182
182
|
yield {
|
|
183
|
-
price: new BN(
|
|
183
|
+
price: new BN(price).mul(new BN(pricePrecision)),
|
|
184
184
|
size,
|
|
185
185
|
sources: {
|
|
186
186
|
phoenix: size,
|
|
@@ -3,7 +3,9 @@ import { EventEmitter } from 'events';
|
|
|
3
3
|
import StrictEventEmitter from 'strict-event-emitter-types/types/src';
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
6
|
-
type SlotSubscriberConfig = {
|
|
6
|
+
type SlotSubscriberConfig = {
|
|
7
|
+
resubTimeoutMs?: number;
|
|
8
|
+
}; // for future customization
|
|
7
9
|
|
|
8
10
|
export interface SlotSubscriberEvents {
|
|
9
11
|
newSlot: (newSlot: number) => void;
|
|
@@ -14,15 +16,22 @@ export class SlotSubscriber {
|
|
|
14
16
|
subscriptionId: number;
|
|
15
17
|
eventEmitter: StrictEventEmitter<EventEmitter, SlotSubscriberEvents>;
|
|
16
18
|
|
|
19
|
+
// Reconnection
|
|
20
|
+
timeoutId?: NodeJS.Timeout;
|
|
21
|
+
resubTimeoutMs?: number;
|
|
22
|
+
isUnsubscribing = false;
|
|
23
|
+
receivingData = false;
|
|
24
|
+
|
|
17
25
|
public constructor(
|
|
18
26
|
private connection: Connection,
|
|
19
|
-
|
|
27
|
+
config?: SlotSubscriberConfig
|
|
20
28
|
) {
|
|
21
29
|
this.eventEmitter = new EventEmitter();
|
|
30
|
+
this.resubTimeoutMs = config?.resubTimeoutMs;
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
public async subscribe(): Promise<void> {
|
|
25
|
-
if (this.subscriptionId) {
|
|
34
|
+
if (this.subscriptionId != null) {
|
|
26
35
|
return;
|
|
27
36
|
}
|
|
28
37
|
|
|
@@ -30,19 +39,57 @@ export class SlotSubscriber {
|
|
|
30
39
|
|
|
31
40
|
this.subscriptionId = this.connection.onSlotChange((slotInfo) => {
|
|
32
41
|
if (!this.currentSlot || this.currentSlot < slotInfo.slot) {
|
|
42
|
+
if (this.resubTimeoutMs && !this.isUnsubscribing) {
|
|
43
|
+
this.receivingData = true;
|
|
44
|
+
clearTimeout(this.timeoutId);
|
|
45
|
+
this.setTimeout();
|
|
46
|
+
}
|
|
33
47
|
this.currentSlot = slotInfo.slot;
|
|
34
48
|
this.eventEmitter.emit('newSlot', slotInfo.slot);
|
|
35
49
|
}
|
|
36
50
|
});
|
|
51
|
+
|
|
52
|
+
if (this.resubTimeoutMs) {
|
|
53
|
+
this.setTimeout();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private setTimeout(): void {
|
|
58
|
+
this.timeoutId = setTimeout(async () => {
|
|
59
|
+
if (this.isUnsubscribing) {
|
|
60
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.receivingData) {
|
|
65
|
+
console.log(
|
|
66
|
+
`No new slot in ${this.resubTimeoutMs}ms, slot subscriber resubscribing`
|
|
67
|
+
);
|
|
68
|
+
await this.unsubscribe(true);
|
|
69
|
+
this.receivingData = false;
|
|
70
|
+
await this.subscribe();
|
|
71
|
+
}
|
|
72
|
+
}, this.resubTimeoutMs);
|
|
37
73
|
}
|
|
38
74
|
|
|
39
75
|
public getSlot(): number {
|
|
40
76
|
return this.currentSlot;
|
|
41
77
|
}
|
|
42
78
|
|
|
43
|
-
public async unsubscribe(): Promise<void> {
|
|
44
|
-
if (
|
|
79
|
+
public async unsubscribe(onResub = false): Promise<void> {
|
|
80
|
+
if (!onResub) {
|
|
81
|
+
this.resubTimeoutMs = undefined;
|
|
82
|
+
}
|
|
83
|
+
this.isUnsubscribing = true;
|
|
84
|
+
clearTimeout(this.timeoutId);
|
|
85
|
+
this.timeoutId = undefined;
|
|
86
|
+
|
|
87
|
+
if (this.subscriptionId != null) {
|
|
45
88
|
await this.connection.removeSlotChangeListener(this.subscriptionId);
|
|
89
|
+
this.subscriptionId = undefined;
|
|
90
|
+
this.isUnsubscribing = false;
|
|
91
|
+
} else {
|
|
92
|
+
this.isUnsubscribing = false;
|
|
46
93
|
}
|
|
47
94
|
}
|
|
48
95
|
}
|
package/src/types.ts
CHANGED