@drift-labs/sdk 0.1.36-master.5 → 0.1.36-master.6
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/bulkAccountLoader.d.ts +1 -0
- package/lib/accounts/bulkAccountLoader.js +4 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/math/oracles.d.ts +3 -0
- package/lib/math/oracles.js +26 -0
- package/lib/math/trade.d.ts +10 -6
- package/lib/math/trade.js +68 -13
- package/lib/oracles/pythClient.js +1 -0
- package/lib/oracles/switchboardClient.js +3 -0
- package/lib/oracles/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/accounts/bulkAccountLoader.ts +5 -0
- package/src/index.ts +1 -0
- package/src/math/oracles.ts +36 -0
- package/src/math/trade.ts +84 -16
- package/src/oracles/pythClient.ts +1 -0
- package/src/oracles/switchboardClient.ts +5 -0
- package/src/oracles/types.ts +1 -0
|
@@ -16,6 +16,7 @@ export declare class BulkAccountLoader {
|
|
|
16
16
|
loadPromise?: Promise<void>;
|
|
17
17
|
loadPromiseResolver: () => void;
|
|
18
18
|
lastTimeLoadingPromiseCleared: number;
|
|
19
|
+
mostRecentSlot: number;
|
|
19
20
|
constructor(connection: Connection, commitment: Commitment, pollingFrequency: number);
|
|
20
21
|
addAccount(publicKey: PublicKey, callback: (buffer: Buffer) => void): string;
|
|
21
22
|
removeAccount(publicKey: PublicKey, callbackId: string): void;
|
|
@@ -20,6 +20,7 @@ class BulkAccountLoader {
|
|
|
20
20
|
this.accountData = new Map();
|
|
21
21
|
this.errorCallbacks = new Map();
|
|
22
22
|
this.lastTimeLoadingPromiseCleared = Date.now();
|
|
23
|
+
this.mostRecentSlot = 0;
|
|
23
24
|
this.connection = connection;
|
|
24
25
|
this.commitment = commitment;
|
|
25
26
|
this.pollingFrequency = pollingFrequency;
|
|
@@ -127,6 +128,9 @@ class BulkAccountLoader {
|
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
const newSlot = rpcResponse.result.context.slot;
|
|
131
|
+
if (newSlot > this.mostRecentSlot) {
|
|
132
|
+
this.mostRecentSlot = newSlot;
|
|
133
|
+
}
|
|
130
134
|
for (const i in accountsToLoad) {
|
|
131
135
|
const accountToLoad = accountsToLoad[i];
|
|
132
136
|
const key = accountToLoad.publicKey.toString();
|
package/lib/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from './math/funding';
|
|
|
25
25
|
export * from './math/insuranceFund';
|
|
26
26
|
export * from './math/market';
|
|
27
27
|
export * from './math/position';
|
|
28
|
+
export * from './math/oracles';
|
|
28
29
|
export * from './math/amm';
|
|
29
30
|
export * from './math/trade';
|
|
30
31
|
export * from './math/orders';
|
package/lib/index.js
CHANGED
|
@@ -44,6 +44,7 @@ __exportStar(require("./math/funding"), exports);
|
|
|
44
44
|
__exportStar(require("./math/insuranceFund"), exports);
|
|
45
45
|
__exportStar(require("./math/market"), exports);
|
|
46
46
|
__exportStar(require("./math/position"), exports);
|
|
47
|
+
__exportStar(require("./math/oracles"), exports);
|
|
47
48
|
__exportStar(require("./math/amm"), exports);
|
|
48
49
|
__exportStar(require("./math/trade"), exports);
|
|
49
50
|
__exportStar(require("./math/orders"), exports);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isOracleValid = void 0;
|
|
4
|
+
const numericConstants_1 = require("../constants/numericConstants");
|
|
5
|
+
const index_1 = require("../index");
|
|
6
|
+
function isOracleValid(amm, oraclePriceData, oracleGuardRails, slot) {
|
|
7
|
+
const isOraclePriceNonPositive = oraclePriceData.price.lt(numericConstants_1.ZERO);
|
|
8
|
+
const isOraclePriceTooVolatile = oraclePriceData.price
|
|
9
|
+
.div(index_1.BN.max(numericConstants_1.ONE, amm.lastOraclePriceTwap))
|
|
10
|
+
.gt(oracleGuardRails.validity.tooVolatileRatio) ||
|
|
11
|
+
amm.lastOraclePriceTwap
|
|
12
|
+
.div(index_1.BN.max(numericConstants_1.ONE, oraclePriceData.price))
|
|
13
|
+
.gt(oracleGuardRails.validity.tooVolatileRatio);
|
|
14
|
+
const isConfidenceTooLarge = oraclePriceData.price
|
|
15
|
+
.div(index_1.BN.max(numericConstants_1.ONE, oraclePriceData.confidence))
|
|
16
|
+
.lt(oracleGuardRails.validity.confidenceIntervalMaxSize);
|
|
17
|
+
const oracleIsStale = oraclePriceData.slot
|
|
18
|
+
.sub(new index_1.BN(slot))
|
|
19
|
+
.gt(oracleGuardRails.validity.slotsBeforeStale);
|
|
20
|
+
return !(!oraclePriceData.hasSufficientNumberOfDataPoints ||
|
|
21
|
+
oracleIsStale ||
|
|
22
|
+
isOraclePriceNonPositive ||
|
|
23
|
+
isOraclePriceTooVolatile ||
|
|
24
|
+
isConfidenceTooLarge);
|
|
25
|
+
}
|
|
26
|
+
exports.isOracleValid = isOracleValid;
|
package/lib/math/trade.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare type PriceImpactUnit = 'entryPrice' | 'maxPrice' | 'priceDelta' |
|
|
|
8
8
|
* @param direction
|
|
9
9
|
* @param amount
|
|
10
10
|
* @param market
|
|
11
|
+
* @param inputAssetType which asset is being traded
|
|
12
|
+
* @param useSpread whether to consider spread with calculating slippage
|
|
11
13
|
* @return [pctAvgSlippage, pctMaxSlippage, entryPrice, newPrice]
|
|
12
14
|
*
|
|
13
15
|
* 'pctAvgSlippage' => the percentage change to entryPrice (average est slippage in execution) : Precision MARK_PRICE_PRECISION
|
|
@@ -18,7 +20,7 @@ export declare type PriceImpactUnit = 'entryPrice' | 'maxPrice' | 'priceDelta' |
|
|
|
18
20
|
*
|
|
19
21
|
* 'newPrice' => the price of the asset after the trade : Precision MARK_PRICE_PRECISION
|
|
20
22
|
*/
|
|
21
|
-
export declare function calculateTradeSlippage(direction: PositionDirection, amount: BN, market: Market, inputAssetType?: AssetType): [BN, BN, BN, BN];
|
|
23
|
+
export declare function calculateTradeSlippage(direction: PositionDirection, amount: BN, market: Market, inputAssetType?: AssetType, useSpread?: boolean): [BN, BN, BN, BN];
|
|
22
24
|
/**
|
|
23
25
|
* Calculates acquired amounts for trade executed
|
|
24
26
|
* @param direction
|
|
@@ -27,7 +29,7 @@ export declare function calculateTradeSlippage(direction: PositionDirection, amo
|
|
|
27
29
|
* @param inputAssetType
|
|
28
30
|
* @param useSpread
|
|
29
31
|
* @return
|
|
30
|
-
* | 'acquiredBase' => positive/negative change in user's base : BN
|
|
32
|
+
* | 'acquiredBase' => positive/negative change in user's base : BN AMM_RESERVE_PRECISION
|
|
31
33
|
* | 'acquiredQuote' => positive/negative change in user's quote : BN TODO-PRECISION
|
|
32
34
|
*/
|
|
33
35
|
export declare function calculateTradeAcquiredAmounts(direction: PositionDirection, amount: BN, market: Market, inputAssetType?: AssetType, useSpread?: boolean): [BN, BN];
|
|
@@ -37,13 +39,15 @@ export declare function calculateTradeAcquiredAmounts(direction: PositionDirecti
|
|
|
37
39
|
* @param market
|
|
38
40
|
* @param targetPrice
|
|
39
41
|
* @param pct optional default is 100% gap filling, can set smaller.
|
|
42
|
+
* @param outputAssetType which asset to trade.
|
|
43
|
+
* @param useSpread whether or not to consider the spread when calculating the trade size
|
|
40
44
|
* @returns trade direction/size in order to push price to a targetPrice,
|
|
41
45
|
*
|
|
42
46
|
* [
|
|
43
|
-
* direction => direction of trade required,
|
|
47
|
+
* direction => direction of trade required, PositionDirection
|
|
44
48
|
* tradeSize => size of trade required, TODO-PRECISION
|
|
45
|
-
* entryPrice => the entry price for the trade,
|
|
46
|
-
* targetPrice => the target price
|
|
49
|
+
* entryPrice => the entry price for the trade, MARK_PRICE_PRECISION
|
|
50
|
+
* targetPrice => the target price MARK_PRICE_PRECISION
|
|
47
51
|
* ]
|
|
48
52
|
*/
|
|
49
|
-
export declare function calculateTargetPriceTrade(market: Market, targetPrice: BN, pct?: BN, outputAssetType?: AssetType): [PositionDirection, BN, BN, BN];
|
|
53
|
+
export declare function calculateTargetPriceTrade(market: Market, targetPrice: BN, pct?: BN, outputAssetType?: AssetType, useSpread?: boolean): [PositionDirection, BN, BN, BN];
|
package/lib/math/trade.js
CHANGED
|
@@ -8,12 +8,15 @@ const numericConstants_1 = require("../constants/numericConstants");
|
|
|
8
8
|
const market_1 = require("./market");
|
|
9
9
|
const amm_1 = require("./amm");
|
|
10
10
|
const utils_1 = require("./utils");
|
|
11
|
+
const types_2 = require("../types");
|
|
11
12
|
const MAXPCT = new anchor_1.BN(1000); //percentage units are [0,1000] => [0,1]
|
|
12
13
|
/**
|
|
13
14
|
* Calculates avg/max slippage (price impact) for candidate trade
|
|
14
15
|
* @param direction
|
|
15
16
|
* @param amount
|
|
16
17
|
* @param market
|
|
18
|
+
* @param inputAssetType which asset is being traded
|
|
19
|
+
* @param useSpread whether to consider spread with calculating slippage
|
|
17
20
|
* @return [pctAvgSlippage, pctMaxSlippage, entryPrice, newPrice]
|
|
18
21
|
*
|
|
19
22
|
* 'pctAvgSlippage' => the percentage change to entryPrice (average est slippage in execution) : Precision MARK_PRICE_PRECISION
|
|
@@ -24,14 +27,38 @@ const MAXPCT = new anchor_1.BN(1000); //percentage units are [0,1000] => [0,1]
|
|
|
24
27
|
*
|
|
25
28
|
* 'newPrice' => the price of the asset after the trade : Precision MARK_PRICE_PRECISION
|
|
26
29
|
*/
|
|
27
|
-
function calculateTradeSlippage(direction, amount, market, inputAssetType = 'quote') {
|
|
28
|
-
|
|
30
|
+
function calculateTradeSlippage(direction, amount, market, inputAssetType = 'quote', useSpread = true) {
|
|
31
|
+
let oldPrice;
|
|
32
|
+
if (useSpread && market.amm.baseSpread > 0) {
|
|
33
|
+
if ((0, types_2.isVariant)(direction, 'long')) {
|
|
34
|
+
oldPrice = (0, market_1.calculateAskPrice)(market);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
oldPrice = (0, market_1.calculateBidPrice)(market);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
oldPrice = (0, market_1.calculateMarkPrice)(market);
|
|
42
|
+
}
|
|
29
43
|
if (amount.eq(numericConstants_1.ZERO)) {
|
|
30
44
|
return [numericConstants_1.ZERO, numericConstants_1.ZERO, oldPrice, oldPrice];
|
|
31
45
|
}
|
|
32
|
-
const [acquiredBase, acquiredQuote] = calculateTradeAcquiredAmounts(direction, amount, market, inputAssetType);
|
|
46
|
+
const [acquiredBase, acquiredQuote] = calculateTradeAcquiredAmounts(direction, amount, market, inputAssetType, useSpread);
|
|
33
47
|
const entryPrice = (0, amm_1.calculatePrice)(acquiredBase, acquiredQuote, market.amm.pegMultiplier).mul(new anchor_1.BN(-1));
|
|
34
|
-
|
|
48
|
+
let amm;
|
|
49
|
+
if (useSpread && market.amm.baseSpread > 0) {
|
|
50
|
+
const { baseAssetReserve, quoteAssetReserve } = (0, amm_1.calculateSpreadReserves)(market.amm, direction);
|
|
51
|
+
amm = {
|
|
52
|
+
baseAssetReserve,
|
|
53
|
+
quoteAssetReserve,
|
|
54
|
+
sqrtK: market.amm.sqrtK,
|
|
55
|
+
pegMultiplier: market.amm.pegMultiplier,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
amm = market.amm;
|
|
60
|
+
}
|
|
61
|
+
const newPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve.sub(acquiredBase), amm.quoteAssetReserve.sub(acquiredQuote), amm.pegMultiplier);
|
|
35
62
|
if (direction == types_1.PositionDirection.SHORT) {
|
|
36
63
|
(0, assert_1.assert)(newPrice.lt(oldPrice));
|
|
37
64
|
}
|
|
@@ -59,7 +86,7 @@ exports.calculateTradeSlippage = calculateTradeSlippage;
|
|
|
59
86
|
* @param inputAssetType
|
|
60
87
|
* @param useSpread
|
|
61
88
|
* @return
|
|
62
|
-
* | 'acquiredBase' => positive/negative change in user's base : BN
|
|
89
|
+
* | 'acquiredBase' => positive/negative change in user's base : BN AMM_RESERVE_PRECISION
|
|
63
90
|
* | 'acquiredQuote' => positive/negative change in user's quote : BN TODO-PRECISION
|
|
64
91
|
*/
|
|
65
92
|
function calculateTradeAcquiredAmounts(direction, amount, market, inputAssetType = 'quote', useSpread = true) {
|
|
@@ -92,35 +119,50 @@ exports.calculateTradeAcquiredAmounts = calculateTradeAcquiredAmounts;
|
|
|
92
119
|
* @param market
|
|
93
120
|
* @param targetPrice
|
|
94
121
|
* @param pct optional default is 100% gap filling, can set smaller.
|
|
122
|
+
* @param outputAssetType which asset to trade.
|
|
123
|
+
* @param useSpread whether or not to consider the spread when calculating the trade size
|
|
95
124
|
* @returns trade direction/size in order to push price to a targetPrice,
|
|
96
125
|
*
|
|
97
126
|
* [
|
|
98
|
-
* direction => direction of trade required,
|
|
127
|
+
* direction => direction of trade required, PositionDirection
|
|
99
128
|
* tradeSize => size of trade required, TODO-PRECISION
|
|
100
|
-
* entryPrice => the entry price for the trade,
|
|
101
|
-
* targetPrice => the target price
|
|
129
|
+
* entryPrice => the entry price for the trade, MARK_PRICE_PRECISION
|
|
130
|
+
* targetPrice => the target price MARK_PRICE_PRECISION
|
|
102
131
|
* ]
|
|
103
132
|
*/
|
|
104
|
-
function calculateTargetPriceTrade(market, targetPrice, pct = MAXPCT, outputAssetType = 'quote') {
|
|
133
|
+
function calculateTargetPriceTrade(market, targetPrice, pct = MAXPCT, outputAssetType = 'quote', useSpread = true) {
|
|
105
134
|
(0, assert_1.assert)(market.amm.baseAssetReserve.gt(numericConstants_1.ZERO));
|
|
106
135
|
(0, assert_1.assert)(targetPrice.gt(numericConstants_1.ZERO));
|
|
107
136
|
(0, assert_1.assert)(pct.lte(MAXPCT) && pct.gt(numericConstants_1.ZERO));
|
|
108
137
|
const markPriceBefore = (0, market_1.calculateMarkPrice)(market);
|
|
138
|
+
const bidPriceBefore = (0, market_1.calculateBidPrice)(market);
|
|
139
|
+
const askPriceBefore = (0, market_1.calculateAskPrice)(market);
|
|
140
|
+
let direction;
|
|
109
141
|
if (targetPrice.gt(markPriceBefore)) {
|
|
110
142
|
const priceGap = targetPrice.sub(markPriceBefore);
|
|
111
143
|
const priceGapScaled = priceGap.mul(pct).div(MAXPCT);
|
|
112
144
|
targetPrice = markPriceBefore.add(priceGapScaled);
|
|
145
|
+
direction = types_1.PositionDirection.LONG;
|
|
113
146
|
}
|
|
114
147
|
else {
|
|
115
148
|
const priceGap = markPriceBefore.sub(targetPrice);
|
|
116
149
|
const priceGapScaled = priceGap.mul(pct).div(MAXPCT);
|
|
117
150
|
targetPrice = markPriceBefore.sub(priceGapScaled);
|
|
151
|
+
direction = types_1.PositionDirection.SHORT;
|
|
118
152
|
}
|
|
119
|
-
let direction;
|
|
120
153
|
let tradeSize;
|
|
121
154
|
let baseSize;
|
|
122
|
-
|
|
123
|
-
|
|
155
|
+
let baseAssetReserveBefore;
|
|
156
|
+
let quoteAssetReserveBefore;
|
|
157
|
+
if (useSpread && market.amm.baseSpread > 0) {
|
|
158
|
+
const { baseAssetReserve, quoteAssetReserve } = (0, amm_1.calculateSpreadReserves)(market.amm, direction);
|
|
159
|
+
baseAssetReserveBefore = baseAssetReserve;
|
|
160
|
+
quoteAssetReserveBefore = quoteAssetReserve;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
baseAssetReserveBefore = market.amm.baseAssetReserve;
|
|
164
|
+
quoteAssetReserveBefore = market.amm.quoteAssetReserve;
|
|
165
|
+
}
|
|
124
166
|
const peg = market.amm.pegMultiplier;
|
|
125
167
|
const invariant = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
126
168
|
const k = invariant.mul(numericConstants_1.MARK_PRICE_PRECISION);
|
|
@@ -128,7 +170,20 @@ function calculateTargetPriceTrade(market, targetPrice, pct = MAXPCT, outputAsse
|
|
|
128
170
|
let quoteAssetReserveAfter;
|
|
129
171
|
const biasModifier = new anchor_1.BN(1);
|
|
130
172
|
let markPriceAfter;
|
|
131
|
-
if (
|
|
173
|
+
if (useSpread &&
|
|
174
|
+
targetPrice.lt(askPriceBefore) &&
|
|
175
|
+
targetPrice.gt(bidPriceBefore)) {
|
|
176
|
+
// no trade, market is at target
|
|
177
|
+
if (markPriceBefore.gt(targetPrice)) {
|
|
178
|
+
direction = types_1.PositionDirection.SHORT;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
direction = types_1.PositionDirection.LONG;
|
|
182
|
+
}
|
|
183
|
+
tradeSize = numericConstants_1.ZERO;
|
|
184
|
+
return [direction, tradeSize, targetPrice, targetPrice];
|
|
185
|
+
}
|
|
186
|
+
else if (markPriceBefore.gt(targetPrice)) {
|
|
132
187
|
// overestimate y2
|
|
133
188
|
baseAssetReserveAfter = (0, utils_1.squareRootBN)(k.div(targetPrice).mul(peg).div(numericConstants_1.PEG_PRECISION).sub(biasModifier)).sub(new anchor_1.BN(1));
|
|
134
189
|
quoteAssetReserveAfter = k
|
|
@@ -38,6 +38,7 @@ class PythClient {
|
|
|
38
38
|
confidence: convertPythPrice(priceData.confidence, priceData.exponent),
|
|
39
39
|
twap: convertPythPrice(priceData.twap.value, priceData.exponent),
|
|
40
40
|
twapConfidence: convertPythPrice(priceData.twac.value, priceData.exponent),
|
|
41
|
+
hasSufficientNumberOfDataPoints: true,
|
|
41
42
|
};
|
|
42
43
|
});
|
|
43
44
|
}
|
|
@@ -39,11 +39,14 @@ class SwitchboardClient {
|
|
|
39
39
|
const price = convertSwitchboardDecimal(aggregatorAccountData.latestConfirmedRound.result);
|
|
40
40
|
const confidence = convertSwitchboardDecimal(aggregatorAccountData.latestConfirmedRound
|
|
41
41
|
.stdDeviation);
|
|
42
|
+
const hasSufficientNumberOfDataPoints = aggregatorAccountData.latestConfirmedRound.numSuccess >=
|
|
43
|
+
aggregatorAccountData.minOracleResults;
|
|
42
44
|
const slot = aggregatorAccountData.latestConfirmedRound.roundOpenSlot;
|
|
43
45
|
return {
|
|
44
46
|
price,
|
|
45
47
|
slot,
|
|
46
48
|
confidence,
|
|
49
|
+
hasSufficientNumberOfDataPoints,
|
|
47
50
|
};
|
|
48
51
|
});
|
|
49
52
|
}
|
package/lib/oracles/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ export class BulkAccountLoader {
|
|
|
24
24
|
loadPromise?: Promise<void>;
|
|
25
25
|
loadPromiseResolver: () => void;
|
|
26
26
|
lastTimeLoadingPromiseCleared = Date.now();
|
|
27
|
+
mostRecentSlot = 0;
|
|
27
28
|
|
|
28
29
|
public constructor(
|
|
29
30
|
connection: Connection,
|
|
@@ -159,6 +160,10 @@ export class BulkAccountLoader {
|
|
|
159
160
|
|
|
160
161
|
const newSlot = rpcResponse.result.context.slot;
|
|
161
162
|
|
|
163
|
+
if (newSlot > this.mostRecentSlot) {
|
|
164
|
+
this.mostRecentSlot = newSlot;
|
|
165
|
+
}
|
|
166
|
+
|
|
162
167
|
for (const i in accountsToLoad) {
|
|
163
168
|
const accountToLoad = accountsToLoad[i];
|
|
164
169
|
const key = accountToLoad.publicKey.toString();
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export * from './math/funding';
|
|
|
26
26
|
export * from './math/insuranceFund';
|
|
27
27
|
export * from './math/market';
|
|
28
28
|
export * from './math/position';
|
|
29
|
+
export * from './math/oracles';
|
|
29
30
|
export * from './math/amm';
|
|
30
31
|
export * from './math/trade';
|
|
31
32
|
export * from './math/orders';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AMM, OracleGuardRails } from '../types';
|
|
2
|
+
import { OraclePriceData } from '../oracles/types';
|
|
3
|
+
import { ONE, ZERO } from '../constants/numericConstants';
|
|
4
|
+
import { BN } from '../index';
|
|
5
|
+
|
|
6
|
+
export function isOracleValid(
|
|
7
|
+
amm: AMM,
|
|
8
|
+
oraclePriceData: OraclePriceData,
|
|
9
|
+
oracleGuardRails: OracleGuardRails,
|
|
10
|
+
slot: number
|
|
11
|
+
): boolean {
|
|
12
|
+
const isOraclePriceNonPositive = oraclePriceData.price.lt(ZERO);
|
|
13
|
+
const isOraclePriceTooVolatile =
|
|
14
|
+
oraclePriceData.price
|
|
15
|
+
.div(BN.max(ONE, amm.lastOraclePriceTwap))
|
|
16
|
+
.gt(oracleGuardRails.validity.tooVolatileRatio) ||
|
|
17
|
+
amm.lastOraclePriceTwap
|
|
18
|
+
.div(BN.max(ONE, oraclePriceData.price))
|
|
19
|
+
.gt(oracleGuardRails.validity.tooVolatileRatio);
|
|
20
|
+
|
|
21
|
+
const isConfidenceTooLarge = oraclePriceData.price
|
|
22
|
+
.div(BN.max(ONE, oraclePriceData.confidence))
|
|
23
|
+
.lt(oracleGuardRails.validity.confidenceIntervalMaxSize);
|
|
24
|
+
|
|
25
|
+
const oracleIsStale = oraclePriceData.slot
|
|
26
|
+
.sub(new BN(slot))
|
|
27
|
+
.gt(oracleGuardRails.validity.slotsBeforeStale);
|
|
28
|
+
|
|
29
|
+
return !(
|
|
30
|
+
!oraclePriceData.hasSufficientNumberOfDataPoints ||
|
|
31
|
+
oracleIsStale ||
|
|
32
|
+
isOraclePriceNonPositive ||
|
|
33
|
+
isOraclePriceTooVolatile ||
|
|
34
|
+
isConfidenceTooLarge
|
|
35
|
+
);
|
|
36
|
+
}
|
package/src/math/trade.ts
CHANGED
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
8
8
|
ZERO,
|
|
9
9
|
} from '../constants/numericConstants';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
calculateBidPrice,
|
|
12
|
+
calculateAskPrice,
|
|
13
|
+
calculateMarkPrice,
|
|
14
|
+
} from './market';
|
|
11
15
|
import {
|
|
12
16
|
calculateAmmReservesAfterSwap,
|
|
13
17
|
calculatePrice,
|
|
@@ -16,6 +20,7 @@ import {
|
|
|
16
20
|
calculateSpreadReserves,
|
|
17
21
|
} from './amm';
|
|
18
22
|
import { squareRootBN } from './utils';
|
|
23
|
+
import { isVariant } from '../types';
|
|
19
24
|
|
|
20
25
|
const MAXPCT = new BN(1000); //percentage units are [0,1000] => [0,1]
|
|
21
26
|
|
|
@@ -37,6 +42,8 @@ export type PriceImpactUnit =
|
|
|
37
42
|
* @param direction
|
|
38
43
|
* @param amount
|
|
39
44
|
* @param market
|
|
45
|
+
* @param inputAssetType which asset is being traded
|
|
46
|
+
* @param useSpread whether to consider spread with calculating slippage
|
|
40
47
|
* @return [pctAvgSlippage, pctMaxSlippage, entryPrice, newPrice]
|
|
41
48
|
*
|
|
42
49
|
* 'pctAvgSlippage' => the percentage change to entryPrice (average est slippage in execution) : Precision MARK_PRICE_PRECISION
|
|
@@ -51,9 +58,20 @@ export function calculateTradeSlippage(
|
|
|
51
58
|
direction: PositionDirection,
|
|
52
59
|
amount: BN,
|
|
53
60
|
market: Market,
|
|
54
|
-
inputAssetType: AssetType = 'quote'
|
|
61
|
+
inputAssetType: AssetType = 'quote',
|
|
62
|
+
useSpread = true
|
|
55
63
|
): [BN, BN, BN, BN] {
|
|
56
|
-
|
|
64
|
+
let oldPrice: BN;
|
|
65
|
+
|
|
66
|
+
if (useSpread && market.amm.baseSpread > 0) {
|
|
67
|
+
if (isVariant(direction, 'long')) {
|
|
68
|
+
oldPrice = calculateAskPrice(market);
|
|
69
|
+
} else {
|
|
70
|
+
oldPrice = calculateBidPrice(market);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
oldPrice = calculateMarkPrice(market);
|
|
74
|
+
}
|
|
57
75
|
if (amount.eq(ZERO)) {
|
|
58
76
|
return [ZERO, ZERO, oldPrice, oldPrice];
|
|
59
77
|
}
|
|
@@ -61,7 +79,8 @@ export function calculateTradeSlippage(
|
|
|
61
79
|
direction,
|
|
62
80
|
amount,
|
|
63
81
|
market,
|
|
64
|
-
inputAssetType
|
|
82
|
+
inputAssetType,
|
|
83
|
+
useSpread
|
|
65
84
|
);
|
|
66
85
|
|
|
67
86
|
const entryPrice = calculatePrice(
|
|
@@ -70,10 +89,26 @@ export function calculateTradeSlippage(
|
|
|
70
89
|
market.amm.pegMultiplier
|
|
71
90
|
).mul(new BN(-1));
|
|
72
91
|
|
|
92
|
+
let amm: Parameters<typeof calculateAmmReservesAfterSwap>[0];
|
|
93
|
+
if (useSpread && market.amm.baseSpread > 0) {
|
|
94
|
+
const { baseAssetReserve, quoteAssetReserve } = calculateSpreadReserves(
|
|
95
|
+
market.amm,
|
|
96
|
+
direction
|
|
97
|
+
);
|
|
98
|
+
amm = {
|
|
99
|
+
baseAssetReserve,
|
|
100
|
+
quoteAssetReserve,
|
|
101
|
+
sqrtK: market.amm.sqrtK,
|
|
102
|
+
pegMultiplier: market.amm.pegMultiplier,
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
amm = market.amm;
|
|
106
|
+
}
|
|
107
|
+
|
|
73
108
|
const newPrice = calculatePrice(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
109
|
+
amm.baseAssetReserve.sub(acquiredBase),
|
|
110
|
+
amm.quoteAssetReserve.sub(acquiredQuote),
|
|
111
|
+
amm.pegMultiplier
|
|
77
112
|
);
|
|
78
113
|
|
|
79
114
|
if (direction == PositionDirection.SHORT) {
|
|
@@ -104,7 +139,7 @@ export function calculateTradeSlippage(
|
|
|
104
139
|
* @param inputAssetType
|
|
105
140
|
* @param useSpread
|
|
106
141
|
* @return
|
|
107
|
-
* | 'acquiredBase' => positive/negative change in user's base : BN
|
|
142
|
+
* | 'acquiredBase' => positive/negative change in user's base : BN AMM_RESERVE_PRECISION
|
|
108
143
|
* | 'acquiredQuote' => positive/negative change in user's quote : BN TODO-PRECISION
|
|
109
144
|
*/
|
|
110
145
|
export function calculateTradeAcquiredAmounts(
|
|
@@ -150,43 +185,63 @@ export function calculateTradeAcquiredAmounts(
|
|
|
150
185
|
* @param market
|
|
151
186
|
* @param targetPrice
|
|
152
187
|
* @param pct optional default is 100% gap filling, can set smaller.
|
|
188
|
+
* @param outputAssetType which asset to trade.
|
|
189
|
+
* @param useSpread whether or not to consider the spread when calculating the trade size
|
|
153
190
|
* @returns trade direction/size in order to push price to a targetPrice,
|
|
154
191
|
*
|
|
155
192
|
* [
|
|
156
|
-
* direction => direction of trade required,
|
|
193
|
+
* direction => direction of trade required, PositionDirection
|
|
157
194
|
* tradeSize => size of trade required, TODO-PRECISION
|
|
158
|
-
* entryPrice => the entry price for the trade,
|
|
159
|
-
* targetPrice => the target price
|
|
195
|
+
* entryPrice => the entry price for the trade, MARK_PRICE_PRECISION
|
|
196
|
+
* targetPrice => the target price MARK_PRICE_PRECISION
|
|
160
197
|
* ]
|
|
161
198
|
*/
|
|
162
199
|
export function calculateTargetPriceTrade(
|
|
163
200
|
market: Market,
|
|
164
201
|
targetPrice: BN,
|
|
165
202
|
pct: BN = MAXPCT,
|
|
166
|
-
outputAssetType: AssetType = 'quote'
|
|
203
|
+
outputAssetType: AssetType = 'quote',
|
|
204
|
+
useSpread = true
|
|
167
205
|
): [PositionDirection, BN, BN, BN] {
|
|
168
206
|
assert(market.amm.baseAssetReserve.gt(ZERO));
|
|
169
207
|
assert(targetPrice.gt(ZERO));
|
|
170
208
|
assert(pct.lte(MAXPCT) && pct.gt(ZERO));
|
|
171
209
|
|
|
172
210
|
const markPriceBefore = calculateMarkPrice(market);
|
|
211
|
+
const bidPriceBefore = calculateBidPrice(market);
|
|
212
|
+
const askPriceBefore = calculateAskPrice(market);
|
|
173
213
|
|
|
214
|
+
let direction;
|
|
174
215
|
if (targetPrice.gt(markPriceBefore)) {
|
|
175
216
|
const priceGap = targetPrice.sub(markPriceBefore);
|
|
176
217
|
const priceGapScaled = priceGap.mul(pct).div(MAXPCT);
|
|
177
218
|
targetPrice = markPriceBefore.add(priceGapScaled);
|
|
219
|
+
direction = PositionDirection.LONG;
|
|
178
220
|
} else {
|
|
179
221
|
const priceGap = markPriceBefore.sub(targetPrice);
|
|
180
222
|
const priceGapScaled = priceGap.mul(pct).div(MAXPCT);
|
|
181
223
|
targetPrice = markPriceBefore.sub(priceGapScaled);
|
|
224
|
+
direction = PositionDirection.SHORT;
|
|
182
225
|
}
|
|
183
226
|
|
|
184
|
-
let direction;
|
|
185
227
|
let tradeSize;
|
|
186
228
|
let baseSize;
|
|
187
229
|
|
|
188
|
-
|
|
189
|
-
|
|
230
|
+
let baseAssetReserveBefore: BN;
|
|
231
|
+
let quoteAssetReserveBefore: BN;
|
|
232
|
+
|
|
233
|
+
if (useSpread && market.amm.baseSpread > 0) {
|
|
234
|
+
const { baseAssetReserve, quoteAssetReserve } = calculateSpreadReserves(
|
|
235
|
+
market.amm,
|
|
236
|
+
direction
|
|
237
|
+
);
|
|
238
|
+
baseAssetReserveBefore = baseAssetReserve;
|
|
239
|
+
quoteAssetReserveBefore = quoteAssetReserve;
|
|
240
|
+
} else {
|
|
241
|
+
baseAssetReserveBefore = market.amm.baseAssetReserve;
|
|
242
|
+
quoteAssetReserveBefore = market.amm.quoteAssetReserve;
|
|
243
|
+
}
|
|
244
|
+
|
|
190
245
|
const peg = market.amm.pegMultiplier;
|
|
191
246
|
const invariant = market.amm.sqrtK.mul(market.amm.sqrtK);
|
|
192
247
|
const k = invariant.mul(MARK_PRICE_PRECISION);
|
|
@@ -196,7 +251,20 @@ export function calculateTargetPriceTrade(
|
|
|
196
251
|
const biasModifier = new BN(1);
|
|
197
252
|
let markPriceAfter;
|
|
198
253
|
|
|
199
|
-
if (
|
|
254
|
+
if (
|
|
255
|
+
useSpread &&
|
|
256
|
+
targetPrice.lt(askPriceBefore) &&
|
|
257
|
+
targetPrice.gt(bidPriceBefore)
|
|
258
|
+
) {
|
|
259
|
+
// no trade, market is at target
|
|
260
|
+
if (markPriceBefore.gt(targetPrice)) {
|
|
261
|
+
direction = PositionDirection.SHORT;
|
|
262
|
+
} else {
|
|
263
|
+
direction = PositionDirection.LONG;
|
|
264
|
+
}
|
|
265
|
+
tradeSize = ZERO;
|
|
266
|
+
return [direction, tradeSize, targetPrice, targetPrice];
|
|
267
|
+
} else if (markPriceBefore.gt(targetPrice)) {
|
|
200
268
|
// overestimate y2
|
|
201
269
|
baseAssetReserveAfter = squareRootBN(
|
|
202
270
|
k.div(targetPrice).mul(peg).div(PEG_PRECISION).sub(biasModifier)
|
|
@@ -48,11 +48,16 @@ export class SwitchboardClient implements OracleClient {
|
|
|
48
48
|
.stdDeviation as SwitchboardDecimal
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
+
const hasSufficientNumberOfDataPoints =
|
|
52
|
+
aggregatorAccountData.latestConfirmedRound.numSuccess >=
|
|
53
|
+
aggregatorAccountData.minOracleResults;
|
|
54
|
+
|
|
51
55
|
const slot: BN = aggregatorAccountData.latestConfirmedRound.roundOpenSlot;
|
|
52
56
|
return {
|
|
53
57
|
price,
|
|
54
58
|
slot,
|
|
55
59
|
confidence,
|
|
60
|
+
hasSufficientNumberOfDataPoints,
|
|
56
61
|
};
|
|
57
62
|
}
|
|
58
63
|
|