@drift-labs/sdk 0.1.21-master.3 → 0.1.23-master.0
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/lib/accounts/bulkUserSubscription.js +39 -1
- package/lib/accounts/pollingUserAccountSubscriber.d.ts +2 -2
- package/lib/accounts/pollingUserAccountSubscriber.js +37 -16
- package/lib/accounts/types.d.ts +5 -0
- package/lib/clearingHouseUser.d.ts +16 -1
- package/lib/clearingHouseUser.js +77 -28
- package/lib/idl/clearing_house.json +1 -1
- package/lib/math/amm.d.ts +2 -0
- package/lib/math/amm.js +91 -1
- package/package.json +1 -1
- package/src/accounts/bulkUserSubscription.ts +51 -1
- package/src/accounts/pollingUserAccountSubscriber.ts +49 -30
- package/src/accounts/types.ts +6 -0
- package/src/clearingHouseUser.ts +92 -37
- package/src/idl/clearing_house.json +1 -1
- package/src/math/amm.ts +122 -0
|
@@ -16,8 +16,46 @@ exports.bulkPollingUserSubscribe = void 0;
|
|
|
16
16
|
*/
|
|
17
17
|
function bulkPollingUserSubscribe(users, accountLoader) {
|
|
18
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
if (users.length === 0) {
|
|
20
|
+
yield accountLoader.load();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Fetch all the accounts first
|
|
24
|
+
const program = users[0].clearingHouse.program;
|
|
25
|
+
let userProgramAccounts;
|
|
26
|
+
let orderProgramAccounts;
|
|
27
|
+
yield Promise.all([
|
|
28
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
userProgramAccounts = yield program.account.user.all();
|
|
30
|
+
}))(),
|
|
31
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
orderProgramAccounts = yield program.account.userOrders.all();
|
|
33
|
+
}))(),
|
|
34
|
+
]);
|
|
35
|
+
// Create a map of the authority to keys
|
|
36
|
+
const authorityToKeys = new Map();
|
|
37
|
+
const userToAuthority = new Map();
|
|
38
|
+
for (const userProgramAccount of userProgramAccounts) {
|
|
39
|
+
const userAccountPublicKey = userProgramAccount.publicKey;
|
|
40
|
+
const userAccount = userProgramAccount.account;
|
|
41
|
+
authorityToKeys.set(userAccount.authority.toString(), {
|
|
42
|
+
user: userAccountPublicKey,
|
|
43
|
+
userPositions: userAccount.positions,
|
|
44
|
+
userOrders: undefined,
|
|
45
|
+
});
|
|
46
|
+
userToAuthority.set(userAccountPublicKey.toString(), userAccount.authority.toString());
|
|
47
|
+
}
|
|
48
|
+
for (const orderProgramAccount of orderProgramAccounts) {
|
|
49
|
+
const userOrderAccountPublicKey = orderProgramAccount.publicKey;
|
|
50
|
+
const userOrderAccount = orderProgramAccount.account;
|
|
51
|
+
const authority = userToAuthority.get(userOrderAccount.user.toString());
|
|
52
|
+
const userPublicKeys = authorityToKeys.get(authority);
|
|
53
|
+
userPublicKeys.userOrders = userOrderAccountPublicKey;
|
|
54
|
+
}
|
|
19
55
|
yield Promise.all(users.map((user) => {
|
|
20
|
-
|
|
56
|
+
// Pull the keys from the authority map so we can skip fetching them in addToAccountLoader
|
|
57
|
+
const userPublicKeys = authorityToKeys.get(user.authority.toString());
|
|
58
|
+
return user.accountSubscriber.addToAccountLoader(userPublicKeys);
|
|
21
59
|
}));
|
|
22
60
|
yield accountLoader.load();
|
|
23
61
|
yield Promise.all(users.map((user) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { AccountToPoll, UserAccountEvents, UserAccountSubscriber } from './types';
|
|
2
|
+
import { AccountToPoll, UserAccountEvents, UserAccountSubscriber, UserPublicKeys } from './types';
|
|
3
3
|
import { Program } from '@project-serum/anchor';
|
|
4
4
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
5
5
|
import { EventEmitter } from 'events';
|
|
@@ -21,7 +21,7 @@ export declare class PollingUserAccountSubscriber implements UserAccountSubscrib
|
|
|
21
21
|
type: ClearingHouseConfigType;
|
|
22
22
|
constructor(program: Program, authority: PublicKey, accountLoader: BulkAccountLoader);
|
|
23
23
|
subscribe(): Promise<boolean>;
|
|
24
|
-
addToAccountLoader(): Promise<void>;
|
|
24
|
+
addToAccountLoader(userPublicKeys?: UserPublicKeys): Promise<void>;
|
|
25
25
|
fetchIfUnloaded(): Promise<void>;
|
|
26
26
|
fetch(): Promise<void>;
|
|
27
27
|
unsubscribe(): Promise<void>;
|
|
@@ -36,34 +36,55 @@ class PollingUserAccountSubscriber {
|
|
|
36
36
|
return true;
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
addToAccountLoader() {
|
|
39
|
+
addToAccountLoader(userPublicKeys) {
|
|
40
40
|
return __awaiter(this, void 0, void 0, function* () {
|
|
41
41
|
if (this.accountsToPoll.size > 0) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (userOrdersExist) {
|
|
44
|
+
if (!userPublicKeys) {
|
|
45
|
+
const userPublicKey = yield addresses_1.getUserAccountPublicKey(this.program.programId, this.authority);
|
|
46
|
+
const userAccount = (yield this.program.account.user.fetch(userPublicKey));
|
|
47
|
+
this.accountsToPoll.set(userPublicKey.toString(), {
|
|
48
|
+
key: 'user',
|
|
49
|
+
publicKey: userPublicKey,
|
|
50
|
+
eventType: 'userAccountData',
|
|
51
|
+
});
|
|
52
|
+
this.accountsToPoll.set(userAccount.positions.toString(), {
|
|
53
|
+
key: 'userPositions',
|
|
54
|
+
publicKey: userAccount.positions,
|
|
55
|
+
eventType: 'userPositionsData',
|
|
56
|
+
});
|
|
57
|
+
const userOrdersPublicKey = yield addresses_1.getUserOrdersAccountPublicKey(this.program.programId, userPublicKey);
|
|
59
58
|
this.accountsToPoll.set(userOrdersPublicKey.toString(), {
|
|
60
59
|
key: 'userOrders',
|
|
61
60
|
publicKey: userOrdersPublicKey,
|
|
62
61
|
eventType: 'userOrdersData',
|
|
63
62
|
});
|
|
64
63
|
}
|
|
64
|
+
else {
|
|
65
|
+
this.accountsToPoll.set(userPublicKeys.user.toString(), {
|
|
66
|
+
key: 'user',
|
|
67
|
+
publicKey: userPublicKeys.user,
|
|
68
|
+
eventType: 'userAccountData',
|
|
69
|
+
});
|
|
70
|
+
this.accountsToPoll.set(userPublicKeys.userPositions.toString(), {
|
|
71
|
+
key: 'userPositions',
|
|
72
|
+
publicKey: userPublicKeys.userPositions,
|
|
73
|
+
eventType: 'userPositionsData',
|
|
74
|
+
});
|
|
75
|
+
if (userPublicKeys.userOrders) {
|
|
76
|
+
this.accountsToPoll.set(userPublicKeys.userOrders.toString(), {
|
|
77
|
+
key: 'userOrders',
|
|
78
|
+
publicKey: userPublicKeys.userOrders,
|
|
79
|
+
eventType: 'userOrdersData',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
65
83
|
for (const [_, accountToPoll] of this.accountsToPoll) {
|
|
66
84
|
accountToPoll.callbackId = this.accountLoader.addAccount(accountToPoll.publicKey, (buffer) => {
|
|
85
|
+
if (!buffer) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
67
88
|
const account = this.program.account[accountToPoll.key].coder.accounts.decode(utils_1.capitalize(accountToPoll.key), buffer);
|
|
68
89
|
this[accountToPoll.key] = account;
|
|
69
90
|
// @ts-ignore
|
package/lib/accounts/types.d.ts
CHANGED
|
@@ -48,6 +48,11 @@ export interface ClearingHouseAccountSubscriber {
|
|
|
48
48
|
getOrderHistoryAccount(): OrderHistoryAccount;
|
|
49
49
|
type: ClearingHouseConfigType;
|
|
50
50
|
}
|
|
51
|
+
export declare type UserPublicKeys = {
|
|
52
|
+
user: PublicKey;
|
|
53
|
+
userPositions: PublicKey;
|
|
54
|
+
userOrders: PublicKey | undefined;
|
|
55
|
+
};
|
|
51
56
|
export interface UserAccountEvents {
|
|
52
57
|
userAccountData: (payload: UserAccount) => void;
|
|
53
58
|
userPositionsData: (payload: UserPositionsAccount) => void;
|
|
@@ -146,7 +146,22 @@ export declare class ClearingHouseUser {
|
|
|
146
146
|
liquidationPriceAfterClose(positionMarketIndex: BN, closeQuoteAmount: BN): BN;
|
|
147
147
|
/**
|
|
148
148
|
* Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
|
|
149
|
-
*
|
|
149
|
+
*
|
|
150
|
+
* To Calculate Max Quote Available:
|
|
151
|
+
*
|
|
152
|
+
* Case 1: SameSide
|
|
153
|
+
* => Remaining quote to get to maxLeverage
|
|
154
|
+
*
|
|
155
|
+
* Case 2: NOT SameSide && currentLeverage <= maxLeverage
|
|
156
|
+
* => Current opposite position x2 + remaining to get to maxLeverage
|
|
157
|
+
*
|
|
158
|
+
* Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
|
|
159
|
+
* => strictly reduce current position size
|
|
160
|
+
*
|
|
161
|
+
* Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
|
|
162
|
+
* => current position + remaining to get to maxLeverage
|
|
163
|
+
*
|
|
164
|
+
* @param targetMarketIndex
|
|
150
165
|
* @param tradeSide
|
|
151
166
|
* @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
|
|
152
167
|
* @returns tradeSizeAllowed : Precision QUOTE_PRECISION
|
package/lib/clearingHouseUser.js
CHANGED
|
@@ -513,32 +513,42 @@ class ClearingHouseUser {
|
|
|
513
513
|
}
|
|
514
514
|
/**
|
|
515
515
|
* Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
|
|
516
|
-
*
|
|
516
|
+
*
|
|
517
|
+
* To Calculate Max Quote Available:
|
|
518
|
+
*
|
|
519
|
+
* Case 1: SameSide
|
|
520
|
+
* => Remaining quote to get to maxLeverage
|
|
521
|
+
*
|
|
522
|
+
* Case 2: NOT SameSide && currentLeverage <= maxLeverage
|
|
523
|
+
* => Current opposite position x2 + remaining to get to maxLeverage
|
|
524
|
+
*
|
|
525
|
+
* Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
|
|
526
|
+
* => strictly reduce current position size
|
|
527
|
+
*
|
|
528
|
+
* Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
|
|
529
|
+
* => current position + remaining to get to maxLeverage
|
|
530
|
+
*
|
|
531
|
+
* @param targetMarketIndex
|
|
517
532
|
* @param tradeSide
|
|
518
533
|
* @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
|
|
519
534
|
* @returns tradeSizeAllowed : Precision QUOTE_PRECISION
|
|
520
535
|
*/
|
|
521
536
|
getMaxTradeSizeUSDC(targetMarketIndex, tradeSide, userMaxLeverageSetting) {
|
|
522
|
-
// inline function which get's the current position size on the opposite side of the target trade
|
|
523
|
-
const getOppositePositionValueUSDC = () => {
|
|
524
|
-
if (!currentPosition)
|
|
525
|
-
return numericConstants_1.ZERO;
|
|
526
|
-
const side = tradeSide === _1.PositionDirection.SHORT ? 'short' : 'long';
|
|
527
|
-
if (side === 'long' && (currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.baseAssetAmount.isNeg())) {
|
|
528
|
-
return this.getPositionValue(targetMarketIndex);
|
|
529
|
-
}
|
|
530
|
-
else if (side === 'short' &&
|
|
531
|
-
!(currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.baseAssetAmount.isNeg())) {
|
|
532
|
-
return this.getPositionValue(targetMarketIndex);
|
|
533
|
-
}
|
|
534
|
-
return numericConstants_1.ZERO;
|
|
535
|
-
};
|
|
536
537
|
const currentPosition = this.getUserPosition(targetMarketIndex) ||
|
|
537
538
|
this.getEmptyPosition(targetMarketIndex);
|
|
539
|
+
const targetSide = tradeSide === _1.PositionDirection.SHORT ? 'short' : 'long';
|
|
540
|
+
const currentPositionSide = (currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.baseAssetAmount.isNeg())
|
|
541
|
+
? 'short'
|
|
542
|
+
: 'long';
|
|
543
|
+
const targettingSameSide = !currentPosition
|
|
544
|
+
? true
|
|
545
|
+
: targetSide === currentPositionSide;
|
|
546
|
+
// 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.
|
|
547
|
+
const oppositeSizeValueUSDC = targettingSameSide
|
|
548
|
+
? numericConstants_1.ZERO
|
|
549
|
+
: this.getPositionValue(targetMarketIndex);
|
|
538
550
|
// get current leverage
|
|
539
551
|
const currentLeverage = this.getLeverage();
|
|
540
|
-
// remaining leverage
|
|
541
|
-
// let remainingLeverage = userMaxLeverageSetting;
|
|
542
552
|
const remainingLeverage = _1.BN.max(userMaxLeverageSetting.sub(currentLeverage), numericConstants_1.ZERO);
|
|
543
553
|
// get total collateral
|
|
544
554
|
const totalCollateral = this.getTotalCollateral();
|
|
@@ -546,9 +556,43 @@ class ClearingHouseUser {
|
|
|
546
556
|
let maxPositionSize = remainingLeverage
|
|
547
557
|
.mul(totalCollateral)
|
|
548
558
|
.div(numericConstants_1.TEN_THOUSAND);
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
559
|
+
if (userMaxLeverageSetting.sub(currentLeverage).gte(numericConstants_1.ZERO)) {
|
|
560
|
+
if (oppositeSizeValueUSDC.eq(numericConstants_1.ZERO)) {
|
|
561
|
+
// case 1 : Regular trade where current total position less than max, and no opposite position to account for
|
|
562
|
+
// do nothing
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
// case 2 : trade where current total position less than max, but need to account for flipping the current position over to the other side
|
|
566
|
+
maxPositionSize = maxPositionSize.add(oppositeSizeValueUSDC.mul(new _1.BN(2)));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
// current leverage is greater than max leverage - can only reduce position size
|
|
571
|
+
if (!targettingSameSide) {
|
|
572
|
+
const currentPositionQuoteSize = this.getPositionValue(targetMarketIndex);
|
|
573
|
+
const currentTotalQuoteSize = currentLeverage
|
|
574
|
+
.mul(totalCollateral)
|
|
575
|
+
.div(numericConstants_1.TEN_THOUSAND);
|
|
576
|
+
const otherPositionsTotalQuoteSize = currentTotalQuoteSize.sub(currentPositionQuoteSize);
|
|
577
|
+
const quoteValueOfMaxLeverage = userMaxLeverageSetting
|
|
578
|
+
.mul(totalCollateral)
|
|
579
|
+
.div(numericConstants_1.TEN_THOUSAND);
|
|
580
|
+
if (otherPositionsTotalQuoteSize
|
|
581
|
+
.sub(currentPositionQuoteSize)
|
|
582
|
+
.gte(quoteValueOfMaxLeverage)) {
|
|
583
|
+
// case 3: Can only reduce the current position because it will still be greater than max leverage
|
|
584
|
+
maxPositionSize = currentPositionQuoteSize;
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
// case 4: Can reduce the position, and then take extra remaining quote to get to max leverage
|
|
588
|
+
const allowedQuoteSizeAfterClosingCurrentPosition = quoteValueOfMaxLeverage.sub(otherPositionsTotalQuoteSize);
|
|
589
|
+
maxPositionSize = currentPositionQuoteSize.add(allowedQuoteSizeAfterClosingCurrentPosition);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
// do nothing if targetting same side
|
|
594
|
+
}
|
|
595
|
+
}
|
|
552
596
|
// subtract oneMillionth of maxPositionSize
|
|
553
597
|
// => to avoid rounding errors when taking max leverage
|
|
554
598
|
const oneMilli = maxPositionSize.div(numericConstants_1.QUOTE_PRECISION);
|
|
@@ -577,12 +621,18 @@ class ClearingHouseUser {
|
|
|
577
621
|
.add(tradeQuoteAmount)
|
|
578
622
|
.abs();
|
|
579
623
|
const totalPositionAfterTradeExcludingTargetMarket = this.getTotalPositionValueExcludingMarket(targetMarketIndex);
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
624
|
+
const totalCollateral = this.getTotalCollateral();
|
|
625
|
+
if (totalCollateral.gt(numericConstants_1.ZERO)) {
|
|
626
|
+
const newLeverage = currentMarketPositionAfterTrade
|
|
627
|
+
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
628
|
+
.abs()
|
|
629
|
+
.mul(numericConstants_1.TEN_THOUSAND)
|
|
630
|
+
.div(totalCollateral);
|
|
631
|
+
return newLeverage;
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
return new _1.BN(0);
|
|
635
|
+
}
|
|
586
636
|
}
|
|
587
637
|
/**
|
|
588
638
|
* Calculates how much fee will be taken for a given sized trade
|
|
@@ -605,8 +655,7 @@ class ClearingHouseUser {
|
|
|
605
655
|
this.getEmptyPosition(marketToIgnore);
|
|
606
656
|
let currentMarketPositionValueUSDC = numericConstants_1.ZERO;
|
|
607
657
|
if (currentMarketPosition) {
|
|
608
|
-
|
|
609
|
-
currentMarketPositionValueUSDC = _1.calculateBaseAssetValue(market, currentMarketPosition);
|
|
658
|
+
currentMarketPositionValueUSDC = this.getPositionValue(marketToIgnore);
|
|
610
659
|
}
|
|
611
660
|
return this.getTotalPositionValue().sub(currentMarketPositionValueUSDC);
|
|
612
661
|
}
|
package/lib/math/amm.d.ts
CHANGED
|
@@ -64,3 +64,5 @@ export declare function calculateRepegCost(market: Market, marketIndex: BN, newP
|
|
|
64
64
|
*/
|
|
65
65
|
export declare function calculateTerminalPrice(market: Market): BN;
|
|
66
66
|
export declare function calculateMaxBaseAssetAmountToTrade(amm: AMM, limit_price: BN): [BN, PositionDirection];
|
|
67
|
+
export declare function calculateBudgetedK(market: Market, cost: BN): [BN, BN];
|
|
68
|
+
export declare function calculateBudgetedPeg(market: Market, cost: BN): BN;
|
package/lib/math/amm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.calculateRepegCost = exports.calculateAdjustKCost = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = void 0;
|
|
3
|
+
exports.calculateBudgetedPeg = exports.calculateBudgetedK = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.calculateRepegCost = exports.calculateAdjustKCost = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = void 0;
|
|
4
4
|
const anchor_1 = require("@project-serum/anchor");
|
|
5
5
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
6
6
|
const position_1 = require("./position");
|
|
@@ -116,6 +116,26 @@ function calculateAdjustKCost(market, marketIndex, numerator, denomenator) {
|
|
|
116
116
|
marketNewK.amm.sqrtK = market.amm.sqrtK.mul(numerator).div(denomenator);
|
|
117
117
|
netUserPosition.quoteAssetAmount = currentValue;
|
|
118
118
|
const cost = __1.calculatePositionPNL(marketNewK, netUserPosition);
|
|
119
|
+
const p = numericConstants_1.PEG_PRECISION.mul(numerator).div(denomenator);
|
|
120
|
+
const x = market.amm.baseAssetReserve;
|
|
121
|
+
const y = market.amm.quoteAssetReserve;
|
|
122
|
+
const delta = market.baseAssetAmount;
|
|
123
|
+
const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
124
|
+
const numer1 = numericConstants_1.PEG_PRECISION.sub(p).mul(y).div(numericConstants_1.PEG_PRECISION);
|
|
125
|
+
const numer20 = k
|
|
126
|
+
.mul(p)
|
|
127
|
+
.mul(p)
|
|
128
|
+
.div(numericConstants_1.PEG_PRECISION)
|
|
129
|
+
.div(numericConstants_1.PEG_PRECISION)
|
|
130
|
+
.div(x.mul(p).div(numericConstants_1.PEG_PRECISION).add(delta));
|
|
131
|
+
const numer21 = k.div(x.add(delta));
|
|
132
|
+
const formulaCost = numer21
|
|
133
|
+
.sub(numer20)
|
|
134
|
+
.sub(numer1)
|
|
135
|
+
.mul(market.amm.pegMultiplier)
|
|
136
|
+
.div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
|
|
137
|
+
console.log(__1.convertToNumber(formulaCost, numericConstants_1.QUOTE_PRECISION));
|
|
138
|
+
// p.div(p.mul(x).add(delta)).sub()
|
|
119
139
|
return cost;
|
|
120
140
|
}
|
|
121
141
|
exports.calculateAdjustKCost = calculateAdjustKCost;
|
|
@@ -144,6 +164,13 @@ function calculateRepegCost(market, marketIndex, newPeg) {
|
|
|
144
164
|
marketNewPeg.amm.pegMultiplier = newPeg;
|
|
145
165
|
console.log('Price moves from', __1.convertToNumber(prevMarketPrice), 'to', __1.convertToNumber(__1.calculateMarkPrice(marketNewPeg)));
|
|
146
166
|
const cost = __1.calculatePositionPNL(marketNewPeg, netUserPosition);
|
|
167
|
+
const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
168
|
+
const newQuoteAssetReserve = k.div(market.amm.baseAssetReserve.add(netUserPosition.baseAssetAmount));
|
|
169
|
+
const deltaQuoteAssetReserves = newQuoteAssetReserve.sub(market.amm.quoteAssetReserve);
|
|
170
|
+
const cost2 = deltaQuoteAssetReserves
|
|
171
|
+
.mul(market.amm.pegMultiplier.sub(newPeg))
|
|
172
|
+
.div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
|
|
173
|
+
console.log(__1.convertToNumber(cost2, numericConstants_1.QUOTE_PRECISION));
|
|
147
174
|
return cost;
|
|
148
175
|
}
|
|
149
176
|
exports.calculateRepegCost = calculateRepegCost;
|
|
@@ -192,3 +219,66 @@ function calculateMaxBaseAssetAmountToTrade(amm, limit_price) {
|
|
|
192
219
|
}
|
|
193
220
|
}
|
|
194
221
|
exports.calculateMaxBaseAssetAmountToTrade = calculateMaxBaseAssetAmountToTrade;
|
|
222
|
+
function calculateBudgetedK(market, cost) {
|
|
223
|
+
// wolframalpha.com
|
|
224
|
+
// (1/(x+d) - p/(x*p+d))*y*d*Q = C solve for p
|
|
225
|
+
// p = (d(y*d*Q - C(x+d))) / (C*x(x+d) + y*y*d*Q)
|
|
226
|
+
// todo: assumes k = x * y
|
|
227
|
+
// otherwise use: (y(1-p) + (kp^2/(x*p+d)) - k/(x+d)) * Q = C solve for p
|
|
228
|
+
// const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
229
|
+
const x = market.amm.baseAssetReserve;
|
|
230
|
+
const y = market.amm.quoteAssetReserve;
|
|
231
|
+
const d = market.baseAssetAmount;
|
|
232
|
+
const Q = market.amm.pegMultiplier;
|
|
233
|
+
const C = cost.mul(new anchor_1.BN(-1));
|
|
234
|
+
const numer1 = y.mul(d).mul(Q).div(numericConstants_1.AMM_RESERVE_PRECISION).div(numericConstants_1.PEG_PRECISION);
|
|
235
|
+
const numer2 = C.mul(x.add(d)).div(numericConstants_1.QUOTE_PRECISION);
|
|
236
|
+
const denom1 = C.mul(x)
|
|
237
|
+
.mul(x.add(d))
|
|
238
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
239
|
+
.div(numericConstants_1.QUOTE_PRECISION);
|
|
240
|
+
const denom2 = y
|
|
241
|
+
.mul(d)
|
|
242
|
+
.mul(d)
|
|
243
|
+
.mul(Q)
|
|
244
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
245
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
246
|
+
.div(numericConstants_1.PEG_PRECISION);
|
|
247
|
+
const numerator = d
|
|
248
|
+
.mul(numer1.add(numer2))
|
|
249
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
250
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
251
|
+
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
|
|
252
|
+
const denominator = denom1
|
|
253
|
+
.add(denom2)
|
|
254
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
255
|
+
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
|
|
256
|
+
console.log(numerator, denominator);
|
|
257
|
+
// const p = (numerator).div(denominator);
|
|
258
|
+
// const formulaCost = (numer21.sub(numer20).sub(numer1)).mul(market.amm.pegMultiplier).div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
|
|
259
|
+
// console.log(convertToNumber(formulaCost, QUOTE_PRECISION))
|
|
260
|
+
return [numerator, denominator];
|
|
261
|
+
}
|
|
262
|
+
exports.calculateBudgetedK = calculateBudgetedK;
|
|
263
|
+
function calculateBudgetedPeg(market, cost) {
|
|
264
|
+
// wolframalpha.com
|
|
265
|
+
// (1/(x+d) - p/(x*p+d))*y*d*Q = C solve for p
|
|
266
|
+
// p = (d(y*d*Q - C(x+d))) / (C*x(x+d) + y*y*d*Q)
|
|
267
|
+
// todo: assumes k = x * y
|
|
268
|
+
// otherwise use: (y(1-p) + (kp^2/(x*p+d)) - k/(x+d)) * Q = C solve for p
|
|
269
|
+
const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
270
|
+
const x = market.amm.baseAssetReserve;
|
|
271
|
+
const y = market.amm.quoteAssetReserve;
|
|
272
|
+
const d = market.baseAssetAmount;
|
|
273
|
+
const Q = market.amm.pegMultiplier;
|
|
274
|
+
const C = cost.mul(new anchor_1.BN(-1));
|
|
275
|
+
const deltaQuoteAssetReserves = y.sub(k.div(x.add(d)));
|
|
276
|
+
const deltaPegMultiplier = C.mul(numericConstants_1.MARK_PRICE_PRECISION)
|
|
277
|
+
.div(deltaQuoteAssetReserves.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO))
|
|
278
|
+
.mul(numericConstants_1.PEG_PRECISION)
|
|
279
|
+
.div(numericConstants_1.QUOTE_PRECISION);
|
|
280
|
+
console.log(Q.toNumber(), 'change by', deltaPegMultiplier.toNumber() / numericConstants_1.MARK_PRICE_PRECISION.toNumber());
|
|
281
|
+
const newPeg = Q.sub(deltaPegMultiplier.mul(numericConstants_1.PEG_PRECISION).div(numericConstants_1.MARK_PRICE_PRECISION));
|
|
282
|
+
return newPeg;
|
|
283
|
+
}
|
|
284
|
+
exports.calculateBudgetedPeg = calculateBudgetedPeg;
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ClearingHouseUser } from '../clearingHouseUser';
|
|
2
2
|
import { BulkAccountLoader } from './bulkAccountLoader';
|
|
3
3
|
import { PollingUserAccountSubscriber } from './pollingUserAccountSubscriber';
|
|
4
|
+
import { UserAccount, UserOrdersAccount } from '../types';
|
|
5
|
+
import { UserPublicKeys } from './types';
|
|
6
|
+
import { ProgramAccount } from '@project-serum/anchor';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* @param users
|
|
@@ -10,11 +13,58 @@ export async function bulkPollingUserSubscribe(
|
|
|
10
13
|
users: ClearingHouseUser[],
|
|
11
14
|
accountLoader: BulkAccountLoader
|
|
12
15
|
): Promise<void> {
|
|
16
|
+
if (users.length === 0) {
|
|
17
|
+
await accountLoader.load();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fetch all the accounts first
|
|
22
|
+
const program = users[0].clearingHouse.program;
|
|
23
|
+
let userProgramAccounts: ProgramAccount[];
|
|
24
|
+
let orderProgramAccounts: ProgramAccount[];
|
|
25
|
+
await Promise.all([
|
|
26
|
+
(async () => {
|
|
27
|
+
userProgramAccounts = await program.account.user.all();
|
|
28
|
+
})(),
|
|
29
|
+
(async () => {
|
|
30
|
+
orderProgramAccounts = await program.account.userOrders.all();
|
|
31
|
+
})(),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// Create a map of the authority to keys
|
|
35
|
+
const authorityToKeys = new Map<string, UserPublicKeys>();
|
|
36
|
+
const userToAuthority = new Map<string, string>();
|
|
37
|
+
for (const userProgramAccount of userProgramAccounts) {
|
|
38
|
+
const userAccountPublicKey = userProgramAccount.publicKey;
|
|
39
|
+
const userAccount = userProgramAccount.account as UserAccount;
|
|
40
|
+
|
|
41
|
+
authorityToKeys.set(userAccount.authority.toString(), {
|
|
42
|
+
user: userAccountPublicKey,
|
|
43
|
+
userPositions: userAccount.positions,
|
|
44
|
+
userOrders: undefined,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
userToAuthority.set(
|
|
48
|
+
userAccountPublicKey.toString(),
|
|
49
|
+
userAccount.authority.toString()
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
for (const orderProgramAccount of orderProgramAccounts) {
|
|
53
|
+
const userOrderAccountPublicKey = orderProgramAccount.publicKey;
|
|
54
|
+
const userOrderAccount = orderProgramAccount.account as UserOrdersAccount;
|
|
55
|
+
|
|
56
|
+
const authority = userToAuthority.get(userOrderAccount.user.toString());
|
|
57
|
+
const userPublicKeys = authorityToKeys.get(authority);
|
|
58
|
+
userPublicKeys.userOrders = userOrderAccountPublicKey;
|
|
59
|
+
}
|
|
60
|
+
|
|
13
61
|
await Promise.all(
|
|
14
62
|
users.map((user) => {
|
|
63
|
+
// Pull the keys from the authority map so we can skip fetching them in addToAccountLoader
|
|
64
|
+
const userPublicKeys = authorityToKeys.get(user.authority.toString());
|
|
15
65
|
return (
|
|
16
66
|
user.accountSubscriber as PollingUserAccountSubscriber
|
|
17
|
-
).addToAccountLoader();
|
|
67
|
+
).addToAccountLoader(userPublicKeys);
|
|
18
68
|
})
|
|
19
69
|
);
|
|
20
70
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
NotSubscribedError,
|
|
4
4
|
UserAccountEvents,
|
|
5
5
|
UserAccountSubscriber,
|
|
6
|
+
UserPublicKeys,
|
|
6
7
|
} from './types';
|
|
7
8
|
import { Program } from '@project-serum/anchor';
|
|
8
9
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
@@ -58,55 +59,73 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
|
|
|
58
59
|
return true;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
async addToAccountLoader(): Promise<void> {
|
|
62
|
+
async addToAccountLoader(userPublicKeys?: UserPublicKeys): Promise<void> {
|
|
62
63
|
if (this.accountsToPoll.size > 0) {
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if (!userPublicKeys) {
|
|
68
|
+
const userPublicKey = await getUserAccountPublicKey(
|
|
69
|
+
this.program.programId,
|
|
70
|
+
this.authority
|
|
71
|
+
);
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
const userAccount = (await this.program.account.user.fetch(
|
|
74
|
+
userPublicKey
|
|
75
|
+
)) as UserAccount;
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
this.accountsToPoll.set(userPublicKey.toString(), {
|
|
78
|
+
key: 'user',
|
|
79
|
+
publicKey: userPublicKey,
|
|
80
|
+
eventType: 'userAccountData',
|
|
81
|
+
});
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
this.accountsToPoll.set(userAccount.positions.toString(), {
|
|
84
|
+
key: 'userPositions',
|
|
85
|
+
publicKey: userAccount.positions,
|
|
86
|
+
eventType: 'userPositionsData',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const userOrdersPublicKey = await getUserOrdersAccountPublicKey(
|
|
90
|
+
this.program.programId,
|
|
91
|
+
userPublicKey
|
|
92
|
+
);
|
|
86
93
|
|
|
87
|
-
const userOrdersPublicKey = await getUserOrdersAccountPublicKey(
|
|
88
|
-
this.program.programId,
|
|
89
|
-
userPublicKey
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const userOrdersExist =
|
|
93
|
-
(
|
|
94
|
-
await this.program.provider.connection.getParsedAccountInfo(
|
|
95
|
-
userOrdersPublicKey
|
|
96
|
-
)
|
|
97
|
-
).value !== null;
|
|
98
|
-
if (userOrdersExist) {
|
|
99
94
|
this.accountsToPoll.set(userOrdersPublicKey.toString(), {
|
|
100
95
|
key: 'userOrders',
|
|
101
96
|
publicKey: userOrdersPublicKey,
|
|
102
97
|
eventType: 'userOrdersData',
|
|
103
98
|
});
|
|
99
|
+
} else {
|
|
100
|
+
this.accountsToPoll.set(userPublicKeys.user.toString(), {
|
|
101
|
+
key: 'user',
|
|
102
|
+
publicKey: userPublicKeys.user,
|
|
103
|
+
eventType: 'userAccountData',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.accountsToPoll.set(userPublicKeys.userPositions.toString(), {
|
|
107
|
+
key: 'userPositions',
|
|
108
|
+
publicKey: userPublicKeys.userPositions,
|
|
109
|
+
eventType: 'userPositionsData',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (userPublicKeys.userOrders) {
|
|
113
|
+
this.accountsToPoll.set(userPublicKeys.userOrders.toString(), {
|
|
114
|
+
key: 'userOrders',
|
|
115
|
+
publicKey: userPublicKeys.userOrders,
|
|
116
|
+
eventType: 'userOrdersData',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
for (const [_, accountToPoll] of this.accountsToPoll) {
|
|
107
122
|
accountToPoll.callbackId = this.accountLoader.addAccount(
|
|
108
123
|
accountToPoll.publicKey,
|
|
109
124
|
(buffer) => {
|
|
125
|
+
if (!buffer) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
110
129
|
const account = this.program.account[
|
|
111
130
|
accountToPoll.key
|
|
112
131
|
].coder.accounts.decode(capitalize(accountToPoll.key), buffer);
|
package/src/accounts/types.ts
CHANGED
|
@@ -82,6 +82,12 @@ export interface ClearingHouseAccountSubscriber {
|
|
|
82
82
|
type: ClearingHouseConfigType;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export type UserPublicKeys = {
|
|
86
|
+
user: PublicKey;
|
|
87
|
+
userPositions: PublicKey;
|
|
88
|
+
userOrders: PublicKey | undefined;
|
|
89
|
+
};
|
|
90
|
+
|
|
85
91
|
export interface UserAccountEvents {
|
|
86
92
|
userAccountData: (payload: UserAccount) => void;
|
|
87
93
|
userPositionsData: (payload: UserPositionsAccount) => void;
|
package/src/clearingHouseUser.ts
CHANGED
|
@@ -733,7 +733,22 @@ export class ClearingHouseUser {
|
|
|
733
733
|
|
|
734
734
|
/**
|
|
735
735
|
* Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
|
|
736
|
-
*
|
|
736
|
+
*
|
|
737
|
+
* To Calculate Max Quote Available:
|
|
738
|
+
*
|
|
739
|
+
* Case 1: SameSide
|
|
740
|
+
* => Remaining quote to get to maxLeverage
|
|
741
|
+
*
|
|
742
|
+
* Case 2: NOT SameSide && currentLeverage <= maxLeverage
|
|
743
|
+
* => Current opposite position x2 + remaining to get to maxLeverage
|
|
744
|
+
*
|
|
745
|
+
* Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
|
|
746
|
+
* => strictly reduce current position size
|
|
747
|
+
*
|
|
748
|
+
* Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
|
|
749
|
+
* => current position + remaining to get to maxLeverage
|
|
750
|
+
*
|
|
751
|
+
* @param targetMarketIndex
|
|
737
752
|
* @param tradeSide
|
|
738
753
|
* @param userMaxLeverageSetting - leverage : Precision TEN_THOUSAND
|
|
739
754
|
* @returns tradeSizeAllowed : Precision QUOTE_PRECISION
|
|
@@ -743,34 +758,28 @@ export class ClearingHouseUser {
|
|
|
743
758
|
tradeSide: PositionDirection,
|
|
744
759
|
userMaxLeverageSetting: BN
|
|
745
760
|
): BN {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
761
|
+
const currentPosition =
|
|
762
|
+
this.getUserPosition(targetMarketIndex) ||
|
|
763
|
+
this.getEmptyPosition(targetMarketIndex);
|
|
749
764
|
|
|
750
|
-
|
|
765
|
+
const targetSide = tradeSide === PositionDirection.SHORT ? 'short' : 'long';
|
|
751
766
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
side === 'short' &&
|
|
756
|
-
!currentPosition?.baseAssetAmount.isNeg()
|
|
757
|
-
) {
|
|
758
|
-
return this.getPositionValue(targetMarketIndex);
|
|
759
|
-
}
|
|
767
|
+
const currentPositionSide = currentPosition?.baseAssetAmount.isNeg()
|
|
768
|
+
? 'short'
|
|
769
|
+
: 'long';
|
|
760
770
|
|
|
761
|
-
|
|
762
|
-
|
|
771
|
+
const targettingSameSide = !currentPosition
|
|
772
|
+
? true
|
|
773
|
+
: targetSide === currentPositionSide;
|
|
763
774
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
775
|
+
// 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.
|
|
776
|
+
const oppositeSizeValueUSDC = targettingSameSide
|
|
777
|
+
? ZERO
|
|
778
|
+
: this.getPositionValue(targetMarketIndex);
|
|
767
779
|
|
|
768
780
|
// get current leverage
|
|
769
781
|
const currentLeverage = this.getLeverage();
|
|
770
782
|
|
|
771
|
-
// remaining leverage
|
|
772
|
-
// let remainingLeverage = userMaxLeverageSetting;
|
|
773
|
-
|
|
774
783
|
const remainingLeverage = BN.max(
|
|
775
784
|
userMaxLeverageSetting.sub(currentLeverage),
|
|
776
785
|
ZERO
|
|
@@ -784,10 +793,57 @@ export class ClearingHouseUser {
|
|
|
784
793
|
.mul(totalCollateral)
|
|
785
794
|
.div(TEN_THOUSAND);
|
|
786
795
|
|
|
787
|
-
|
|
788
|
-
|
|
796
|
+
if (userMaxLeverageSetting.sub(currentLeverage).gte(ZERO)) {
|
|
797
|
+
if (oppositeSizeValueUSDC.eq(ZERO)) {
|
|
798
|
+
// case 1 : Regular trade where current total position less than max, and no opposite position to account for
|
|
799
|
+
// do nothing
|
|
800
|
+
} else {
|
|
801
|
+
// case 2 : trade where current total position less than max, but need to account for flipping the current position over to the other side
|
|
802
|
+
maxPositionSize = maxPositionSize.add(
|
|
803
|
+
oppositeSizeValueUSDC.mul(new BN(2))
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
// current leverage is greater than max leverage - can only reduce position size
|
|
789
808
|
|
|
790
|
-
|
|
809
|
+
if (!targettingSameSide) {
|
|
810
|
+
const currentPositionQuoteSize =
|
|
811
|
+
this.getPositionValue(targetMarketIndex);
|
|
812
|
+
|
|
813
|
+
const currentTotalQuoteSize = currentLeverage
|
|
814
|
+
.mul(totalCollateral)
|
|
815
|
+
.div(TEN_THOUSAND);
|
|
816
|
+
|
|
817
|
+
const otherPositionsTotalQuoteSize = currentTotalQuoteSize.sub(
|
|
818
|
+
currentPositionQuoteSize
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
const quoteValueOfMaxLeverage = userMaxLeverageSetting
|
|
822
|
+
.mul(totalCollateral)
|
|
823
|
+
.div(TEN_THOUSAND);
|
|
824
|
+
|
|
825
|
+
if (
|
|
826
|
+
otherPositionsTotalQuoteSize
|
|
827
|
+
.sub(currentPositionQuoteSize)
|
|
828
|
+
.gte(quoteValueOfMaxLeverage)
|
|
829
|
+
) {
|
|
830
|
+
// case 3: Can only reduce the current position because it will still be greater than max leverage
|
|
831
|
+
|
|
832
|
+
maxPositionSize = currentPositionQuoteSize;
|
|
833
|
+
} else {
|
|
834
|
+
// case 4: Can reduce the position, and then take extra remaining quote to get to max leverage
|
|
835
|
+
|
|
836
|
+
const allowedQuoteSizeAfterClosingCurrentPosition =
|
|
837
|
+
quoteValueOfMaxLeverage.sub(otherPositionsTotalQuoteSize);
|
|
838
|
+
|
|
839
|
+
maxPositionSize = currentPositionQuoteSize.add(
|
|
840
|
+
allowedQuoteSizeAfterClosingCurrentPosition
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
// do nothing if targetting same side
|
|
845
|
+
}
|
|
846
|
+
}
|
|
791
847
|
|
|
792
848
|
// subtract oneMillionth of maxPositionSize
|
|
793
849
|
// => to avoid rounding errors when taking max leverage
|
|
@@ -832,13 +888,18 @@ export class ClearingHouseUser {
|
|
|
832
888
|
|
|
833
889
|
const totalPositionAfterTradeExcludingTargetMarket =
|
|
834
890
|
this.getTotalPositionValueExcludingMarket(targetMarketIndex);
|
|
835
|
-
const newLeverage = currentMarketPositionAfterTrade
|
|
836
|
-
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
837
|
-
.abs()
|
|
838
|
-
.mul(TEN_THOUSAND)
|
|
839
|
-
.div(this.getTotalCollateral());
|
|
840
891
|
|
|
841
|
-
|
|
892
|
+
const totalCollateral = this.getTotalCollateral();
|
|
893
|
+
if (totalCollateral.gt(ZERO)) {
|
|
894
|
+
const newLeverage = currentMarketPositionAfterTrade
|
|
895
|
+
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
896
|
+
.abs()
|
|
897
|
+
.mul(TEN_THOUSAND)
|
|
898
|
+
.div(totalCollateral);
|
|
899
|
+
return newLeverage;
|
|
900
|
+
} else {
|
|
901
|
+
return new BN(0);
|
|
902
|
+
}
|
|
842
903
|
}
|
|
843
904
|
|
|
844
905
|
/**
|
|
@@ -866,13 +927,7 @@ export class ClearingHouseUser {
|
|
|
866
927
|
|
|
867
928
|
let currentMarketPositionValueUSDC = ZERO;
|
|
868
929
|
if (currentMarketPosition) {
|
|
869
|
-
|
|
870
|
-
currentMarketPosition.marketIndex
|
|
871
|
-
);
|
|
872
|
-
currentMarketPositionValueUSDC = calculateBaseAssetValue(
|
|
873
|
-
market,
|
|
874
|
-
currentMarketPosition
|
|
875
|
-
);
|
|
930
|
+
currentMarketPositionValueUSDC = this.getPositionValue(marketToIgnore);
|
|
876
931
|
}
|
|
877
932
|
|
|
878
933
|
return this.getTotalPositionValue().sub(currentMarketPositionValueUSDC);
|
package/src/math/amm.ts
CHANGED
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
MARK_PRICE_PRECISION,
|
|
5
5
|
PEG_PRECISION,
|
|
6
6
|
ZERO,
|
|
7
|
+
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
8
|
+
QUOTE_PRECISION,
|
|
9
|
+
AMM_RESERVE_PRECISION,
|
|
7
10
|
} from '../constants/numericConstants';
|
|
8
11
|
import { calculateBaseAssetValue } from './position';
|
|
9
12
|
import {
|
|
@@ -175,6 +178,30 @@ export function calculateAdjustKCost(
|
|
|
175
178
|
|
|
176
179
|
const cost = calculatePositionPNL(marketNewK, netUserPosition);
|
|
177
180
|
|
|
181
|
+
const p = PEG_PRECISION.mul(numerator).div(denomenator);
|
|
182
|
+
const x = market.amm.baseAssetReserve;
|
|
183
|
+
const y = market.amm.quoteAssetReserve;
|
|
184
|
+
const delta = market.baseAssetAmount;
|
|
185
|
+
const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
186
|
+
|
|
187
|
+
const numer1 = PEG_PRECISION.sub(p).mul(y).div(PEG_PRECISION);
|
|
188
|
+
const numer20 = k
|
|
189
|
+
.mul(p)
|
|
190
|
+
.mul(p)
|
|
191
|
+
.div(PEG_PRECISION)
|
|
192
|
+
.div(PEG_PRECISION)
|
|
193
|
+
.div(x.mul(p).div(PEG_PRECISION).add(delta));
|
|
194
|
+
const numer21 = k.div(x.add(delta));
|
|
195
|
+
|
|
196
|
+
const formulaCost = numer21
|
|
197
|
+
.sub(numer20)
|
|
198
|
+
.sub(numer1)
|
|
199
|
+
.mul(market.amm.pegMultiplier)
|
|
200
|
+
.div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
|
|
201
|
+
console.log(convertToNumber(formulaCost, QUOTE_PRECISION));
|
|
202
|
+
|
|
203
|
+
// p.div(p.mul(x).add(delta)).sub()
|
|
204
|
+
|
|
178
205
|
return cost;
|
|
179
206
|
}
|
|
180
207
|
|
|
@@ -217,6 +244,17 @@ export function calculateRepegCost(
|
|
|
217
244
|
|
|
218
245
|
const cost = calculatePositionPNL(marketNewPeg, netUserPosition);
|
|
219
246
|
|
|
247
|
+
const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
248
|
+
const newQuoteAssetReserve = k.div(
|
|
249
|
+
market.amm.baseAssetReserve.add(netUserPosition.baseAssetAmount)
|
|
250
|
+
);
|
|
251
|
+
const deltaQuoteAssetReserves = newQuoteAssetReserve.sub(
|
|
252
|
+
market.amm.quoteAssetReserve
|
|
253
|
+
);
|
|
254
|
+
const cost2 = deltaQuoteAssetReserves
|
|
255
|
+
.mul(market.amm.pegMultiplier.sub(newPeg))
|
|
256
|
+
.div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
|
|
257
|
+
console.log(convertToNumber(cost2, QUOTE_PRECISION));
|
|
220
258
|
return cost;
|
|
221
259
|
}
|
|
222
260
|
|
|
@@ -238,6 +276,7 @@ export function calculateTerminalPrice(market: Market) {
|
|
|
238
276
|
market.baseAssetAmount.abs(),
|
|
239
277
|
getSwapDirection('base', directionToClose)
|
|
240
278
|
);
|
|
279
|
+
|
|
241
280
|
const terminalPrice = newQuoteAssetReserve
|
|
242
281
|
.mul(MARK_PRICE_PRECISION)
|
|
243
282
|
.mul(market.amm.pegMultiplier)
|
|
@@ -276,3 +315,86 @@ export function calculateMaxBaseAssetAmountToTrade(
|
|
|
276
315
|
return [new BN(0), PositionDirection.LONG];
|
|
277
316
|
}
|
|
278
317
|
}
|
|
318
|
+
|
|
319
|
+
export function calculateBudgetedK(market: Market, cost: BN): [BN, BN] {
|
|
320
|
+
// wolframalpha.com
|
|
321
|
+
// (1/(x+d) - p/(x*p+d))*y*d*Q = C solve for p
|
|
322
|
+
// p = (d(y*d*Q - C(x+d))) / (C*x(x+d) + y*y*d*Q)
|
|
323
|
+
|
|
324
|
+
// todo: assumes k = x * y
|
|
325
|
+
// otherwise use: (y(1-p) + (kp^2/(x*p+d)) - k/(x+d)) * Q = C solve for p
|
|
326
|
+
|
|
327
|
+
// const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
328
|
+
const x = market.amm.baseAssetReserve;
|
|
329
|
+
const y = market.amm.quoteAssetReserve;
|
|
330
|
+
|
|
331
|
+
const d = market.baseAssetAmount;
|
|
332
|
+
const Q = market.amm.pegMultiplier;
|
|
333
|
+
|
|
334
|
+
const C = cost.mul(new BN(-1));
|
|
335
|
+
|
|
336
|
+
const numer1 = y.mul(d).mul(Q).div(AMM_RESERVE_PRECISION).div(PEG_PRECISION);
|
|
337
|
+
const numer2 = C.mul(x.add(d)).div(QUOTE_PRECISION);
|
|
338
|
+
const denom1 = C.mul(x)
|
|
339
|
+
.mul(x.add(d))
|
|
340
|
+
.div(AMM_RESERVE_PRECISION)
|
|
341
|
+
.div(QUOTE_PRECISION);
|
|
342
|
+
const denom2 = y
|
|
343
|
+
.mul(d)
|
|
344
|
+
.mul(d)
|
|
345
|
+
.mul(Q)
|
|
346
|
+
.div(AMM_RESERVE_PRECISION)
|
|
347
|
+
.div(AMM_RESERVE_PRECISION)
|
|
348
|
+
.div(PEG_PRECISION);
|
|
349
|
+
|
|
350
|
+
const numerator = d
|
|
351
|
+
.mul(numer1.add(numer2))
|
|
352
|
+
.div(AMM_RESERVE_PRECISION)
|
|
353
|
+
.div(AMM_RESERVE_PRECISION)
|
|
354
|
+
.div(AMM_TO_QUOTE_PRECISION_RATIO);
|
|
355
|
+
const denominator = denom1
|
|
356
|
+
.add(denom2)
|
|
357
|
+
.div(AMM_RESERVE_PRECISION)
|
|
358
|
+
.div(AMM_TO_QUOTE_PRECISION_RATIO);
|
|
359
|
+
console.log(numerator, denominator);
|
|
360
|
+
// const p = (numerator).div(denominator);
|
|
361
|
+
|
|
362
|
+
// const formulaCost = (numer21.sub(numer20).sub(numer1)).mul(market.amm.pegMultiplier).div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
|
|
363
|
+
// console.log(convertToNumber(formulaCost, QUOTE_PRECISION))
|
|
364
|
+
|
|
365
|
+
return [numerator, denominator];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function calculateBudgetedPeg(market: Market, cost: BN): BN {
|
|
369
|
+
// wolframalpha.com
|
|
370
|
+
// (1/(x+d) - p/(x*p+d))*y*d*Q = C solve for p
|
|
371
|
+
// p = (d(y*d*Q - C(x+d))) / (C*x(x+d) + y*y*d*Q)
|
|
372
|
+
|
|
373
|
+
// todo: assumes k = x * y
|
|
374
|
+
// otherwise use: (y(1-p) + (kp^2/(x*p+d)) - k/(x+d)) * Q = C solve for p
|
|
375
|
+
|
|
376
|
+
const k = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
377
|
+
const x = market.amm.baseAssetReserve;
|
|
378
|
+
const y = market.amm.quoteAssetReserve;
|
|
379
|
+
|
|
380
|
+
const d = market.baseAssetAmount;
|
|
381
|
+
const Q = market.amm.pegMultiplier;
|
|
382
|
+
|
|
383
|
+
const C = cost.mul(new BN(-1));
|
|
384
|
+
|
|
385
|
+
const deltaQuoteAssetReserves = y.sub(k.div(x.add(d)));
|
|
386
|
+
const deltaPegMultiplier = C.mul(MARK_PRICE_PRECISION)
|
|
387
|
+
.div(deltaQuoteAssetReserves.div(AMM_TO_QUOTE_PRECISION_RATIO))
|
|
388
|
+
.mul(PEG_PRECISION)
|
|
389
|
+
.div(QUOTE_PRECISION);
|
|
390
|
+
console.log(
|
|
391
|
+
Q.toNumber(),
|
|
392
|
+
'change by',
|
|
393
|
+
deltaPegMultiplier.toNumber() / MARK_PRICE_PRECISION.toNumber()
|
|
394
|
+
);
|
|
395
|
+
const newPeg = Q.sub(
|
|
396
|
+
deltaPegMultiplier.mul(PEG_PRECISION).div(MARK_PRICE_PRECISION)
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
return newPeg;
|
|
400
|
+
}
|