@drift-labs/sdk 2.18.0-beta.0 → 2.18.0-beta.1
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/constants/perpMarkets.js +10 -0
- package/lib/dlob/DLOB.d.ts +1 -1
- package/lib/dlob/DLOB.js +18 -19
- package/lib/factory/oracleClient.js +4 -0
- package/lib/idl/drift.json +4 -1
- package/lib/math/trade.d.ts +6 -0
- package/lib/math/trade.js +6 -0
- package/lib/oracles/pythClient.d.ts +4 -3
- package/lib/oracles/pythClient.js +8 -7
- package/lib/types.d.ts +3 -0
- package/lib/types.js +1 -0
- package/lib/user.js +7 -5
- package/package.json +1 -1
- package/src/constants/perpMarkets.ts +10 -0
- package/src/dlob/DLOB.ts +26 -23
- package/src/factory/oracleClient.ts +5 -0
- package/src/idl/drift.json +4 -1
- package/src/math/trade.ts +6 -0
- package/src/oracles/pythClient.ts +27 -8
- package/src/types.ts +1 -0
- package/src/user.ts +6 -4
- package/tests/dlob/test.ts +187 -5
|
@@ -44,6 +44,16 @@ exports.DevnetPerpMarkets = [
|
|
|
44
44
|
launchTs: 1675610186000,
|
|
45
45
|
oracleSource: __1.OracleSource.PYTH,
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
fullName: 'Bonk',
|
|
49
|
+
category: ['Meme'],
|
|
50
|
+
symbol: 'BONK-PERP',
|
|
51
|
+
baseAssetSymbol: 'BONK',
|
|
52
|
+
marketIndex: 4,
|
|
53
|
+
oracle: new web3_js_1.PublicKey('6bquU99ktV1VRiHDr8gMhDFt3kMfhCQo5nfNrg2Urvsn'),
|
|
54
|
+
launchTs: 1677068931000,
|
|
55
|
+
oracleSource: __1.OracleSource.PYTH_1000,
|
|
56
|
+
},
|
|
47
57
|
];
|
|
48
58
|
exports.MainnetPerpMarkets = [
|
|
49
59
|
{
|
package/lib/dlob/DLOB.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ export declare class DLOB {
|
|
|
92
92
|
determineMakerAndTaker(askNode: DLOBNode, bidNode: DLOBNode): {
|
|
93
93
|
takerNode: DLOBNode;
|
|
94
94
|
makerNode: DLOBNode;
|
|
95
|
-
};
|
|
95
|
+
} | undefined;
|
|
96
96
|
getBestAsk(marketIndex: number, fallbackAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
|
|
97
97
|
getBestBid(marketIndex: number, fallbackBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
|
|
98
98
|
findNodesToTrigger(marketIndex: number, slot: number, oraclePrice: BN, marketType: MarketType, stateAccount: StateAccount): NodeToTrigger[];
|
package/lib/dlob/DLOB.js
CHANGED
|
@@ -714,21 +714,27 @@ class DLOB {
|
|
|
714
714
|
findCrossingRestingLimitOrders(marketIndex, slot, marketType, oraclePriceData, minAuctionDuration, fallbackAsk, fallbackBid) {
|
|
715
715
|
const nodesToFill = new Array();
|
|
716
716
|
for (const askNode of this.getRestingLimitAsks(marketIndex, slot, marketType, oraclePriceData)) {
|
|
717
|
-
|
|
717
|
+
const bidGenerator = this.getRestingLimitBids(marketIndex, slot, marketType, oraclePriceData);
|
|
718
|
+
for (const bidNode of bidGenerator) {
|
|
718
719
|
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
719
720
|
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
720
|
-
// orders don't cross
|
|
721
|
+
// orders don't cross
|
|
721
722
|
if (bidPrice.lt(askPrice)) {
|
|
722
|
-
|
|
723
|
+
break;
|
|
723
724
|
}
|
|
724
725
|
const bidOrder = bidNode.order;
|
|
725
726
|
const askOrder = askNode.order;
|
|
726
727
|
// Can't match orders from the same user
|
|
727
728
|
const sameUser = bidNode.userAccount.equals(askNode.userAccount);
|
|
728
|
-
if (sameUser
|
|
729
|
+
if (sameUser) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const makerAndTaker = this.determineMakerAndTaker(askNode, bidNode);
|
|
733
|
+
// unable to match maker and taker due to post only or slot
|
|
734
|
+
if (!makerAndTaker) {
|
|
729
735
|
continue;
|
|
730
736
|
}
|
|
731
|
-
const { takerNode, makerNode } =
|
|
737
|
+
const { takerNode, makerNode } = makerAndTaker;
|
|
732
738
|
// extra guard against bad fills for limit orders where auction is incomplete
|
|
733
739
|
if (!__1.isFallbackAvailableLiquiditySource(takerNode.order, minAuctionDuration, slot)) {
|
|
734
740
|
let bidPrice;
|
|
@@ -769,30 +775,23 @@ class DLOB {
|
|
|
769
775
|
return nodesToFill;
|
|
770
776
|
}
|
|
771
777
|
determineMakerAndTaker(askNode, bidNode) {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
makerNode: bidNode,
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
else if (askNode.order.postOnly) {
|
|
778
|
+
const askSlot = askNode.order.slot.add(new __1.BN(askNode.order.auctionDuration));
|
|
779
|
+
const bidSlot = bidNode.order.slot.add(new __1.BN(bidNode.order.auctionDuration));
|
|
780
|
+
if (askSlot.lte(bidSlot) && !bidNode.order.postOnly) {
|
|
779
781
|
return {
|
|
780
782
|
takerNode: bidNode,
|
|
781
783
|
makerNode: askNode,
|
|
782
784
|
};
|
|
783
785
|
}
|
|
784
|
-
else if (
|
|
785
|
-
return {
|
|
786
|
-
takerNode: bidNode,
|
|
787
|
-
makerNode: askNode,
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
786
|
+
else if (bidSlot.lte(askSlot) && !askNode.order.postOnly) {
|
|
791
787
|
return {
|
|
792
788
|
takerNode: askNode,
|
|
793
789
|
makerNode: bidNode,
|
|
794
790
|
};
|
|
795
791
|
}
|
|
792
|
+
else {
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
796
795
|
}
|
|
797
796
|
getBestAsk(marketIndex, fallbackAsk, slot, marketType, oraclePriceData) {
|
|
798
797
|
return this.getAsks(marketIndex, fallbackAsk, slot, marketType, oraclePriceData)
|
|
@@ -5,10 +5,14 @@ const types_1 = require("../types");
|
|
|
5
5
|
const pythClient_1 = require("../oracles/pythClient");
|
|
6
6
|
// import { SwitchboardClient } from '../oracles/switchboardClient';
|
|
7
7
|
const quoteAssetOracleClient_1 = require("../oracles/quoteAssetOracleClient");
|
|
8
|
+
const anchor_1 = require("@project-serum/anchor");
|
|
8
9
|
function getOracleClient(oracleSource, connection) {
|
|
9
10
|
if (types_1.isVariant(oracleSource, 'pyth')) {
|
|
10
11
|
return new pythClient_1.PythClient(connection);
|
|
11
12
|
}
|
|
13
|
+
if (types_1.isVariant(oracleSource, 'pyth1000')) {
|
|
14
|
+
return new pythClient_1.PythClient(connection, new anchor_1.BN(1000));
|
|
15
|
+
}
|
|
12
16
|
// if (isVariant(oracleSource, 'switchboard')) {
|
|
13
17
|
// return new SwitchboardClient(connection);
|
|
14
18
|
// }
|
package/lib/idl/drift.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.18.0-beta.
|
|
2
|
+
"version": "2.18.0-beta.1",
|
|
3
3
|
"name": "drift",
|
|
4
4
|
"instructions": [
|
|
5
5
|
{
|
|
@@ -6237,6 +6237,9 @@
|
|
|
6237
6237
|
{
|
|
6238
6238
|
"name": "Pyth"
|
|
6239
6239
|
},
|
|
6240
|
+
{
|
|
6241
|
+
"name": "Pyth1000"
|
|
6242
|
+
},
|
|
6240
6243
|
{
|
|
6241
6244
|
"name": "Switchboard"
|
|
6242
6245
|
},
|
package/lib/math/trade.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { Orderbook } from '@project-serum/serum';
|
|
|
9
9
|
export declare type PriceImpactUnit = 'entryPrice' | 'maxPrice' | 'priceDelta' | 'priceDeltaAsNumber' | 'pctAvg' | 'pctMax' | 'quoteAssetAmount' | 'quoteAssetAmountPeg' | 'acquiredBaseAssetAmount' | 'acquiredQuoteAssetAmount' | 'all';
|
|
10
10
|
/**
|
|
11
11
|
* Calculates avg/max slippage (price impact) for candidate trade
|
|
12
|
+
*
|
|
13
|
+
* @deprecated use calculateEstimatedPerpEntryPrice instead
|
|
14
|
+
*
|
|
12
15
|
* @param direction
|
|
13
16
|
* @param amount
|
|
14
17
|
* @param market
|
|
@@ -40,6 +43,9 @@ export declare function calculateTradeAcquiredAmounts(direction: PositionDirecti
|
|
|
40
43
|
/**
|
|
41
44
|
* calculateTargetPriceTrade
|
|
42
45
|
* simple function for finding arbitraging trades
|
|
46
|
+
*
|
|
47
|
+
* @deprecated
|
|
48
|
+
*
|
|
43
49
|
* @param market
|
|
44
50
|
* @param targetPrice
|
|
45
51
|
* @param pct optional default is 100% gap filling, can set smaller.
|
package/lib/math/trade.js
CHANGED
|
@@ -12,6 +12,9 @@ const types_2 = require("../types");
|
|
|
12
12
|
const MAXPCT = new anchor_1.BN(1000); //percentage units are [0,1000] => [0,1]
|
|
13
13
|
/**
|
|
14
14
|
* Calculates avg/max slippage (price impact) for candidate trade
|
|
15
|
+
*
|
|
16
|
+
* @deprecated use calculateEstimatedPerpEntryPrice instead
|
|
17
|
+
*
|
|
15
18
|
* @param direction
|
|
16
19
|
* @param amount
|
|
17
20
|
* @param market
|
|
@@ -120,6 +123,9 @@ exports.calculateTradeAcquiredAmounts = calculateTradeAcquiredAmounts;
|
|
|
120
123
|
/**
|
|
121
124
|
* calculateTargetPriceTrade
|
|
122
125
|
* simple function for finding arbitraging trades
|
|
126
|
+
*
|
|
127
|
+
* @deprecated
|
|
128
|
+
*
|
|
123
129
|
* @param market
|
|
124
130
|
* @param targetPrice
|
|
125
131
|
* @param pct optional default is 100% gap filling, can set smaller.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
/// <reference types="bn.js" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
3
|
import { Connection, PublicKey } from '@solana/web3.js';
|
|
4
4
|
import { OracleClient, OraclePriceData } from './types';
|
|
5
5
|
import { BN } from '@project-serum/anchor';
|
|
6
6
|
export declare class PythClient implements OracleClient {
|
|
7
7
|
private connection;
|
|
8
|
-
|
|
8
|
+
private multiple;
|
|
9
|
+
constructor(connection: Connection, multiple?: BN);
|
|
9
10
|
getOraclePriceData(pricePublicKey: PublicKey): Promise<OraclePriceData>;
|
|
10
11
|
getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData;
|
|
11
12
|
}
|
|
12
|
-
export declare function convertPythPrice(price: number, exponent: number): BN;
|
|
13
|
+
export declare function convertPythPrice(price: number, exponent: number, multiple: BN): BN;
|
|
@@ -5,8 +5,9 @@ const client_1 = require("@pythnetwork/client");
|
|
|
5
5
|
const anchor_1 = require("@project-serum/anchor");
|
|
6
6
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
7
7
|
class PythClient {
|
|
8
|
-
constructor(connection) {
|
|
8
|
+
constructor(connection, multiple = numericConstants_1.ONE) {
|
|
9
9
|
this.connection = connection;
|
|
10
|
+
this.multiple = multiple;
|
|
10
11
|
}
|
|
11
12
|
async getOraclePriceData(pricePublicKey) {
|
|
12
13
|
const accountInfo = await this.connection.getAccountInfo(pricePublicKey);
|
|
@@ -15,19 +16,19 @@ class PythClient {
|
|
|
15
16
|
getOraclePriceDataFromBuffer(buffer) {
|
|
16
17
|
const priceData = client_1.parsePriceData(buffer);
|
|
17
18
|
return {
|
|
18
|
-
price: convertPythPrice(priceData.aggregate.price, priceData.exponent),
|
|
19
|
+
price: convertPythPrice(priceData.aggregate.price, priceData.exponent, this.multiple),
|
|
19
20
|
slot: new anchor_1.BN(priceData.lastSlot.toString()),
|
|
20
|
-
confidence: convertPythPrice(priceData.confidence, priceData.exponent),
|
|
21
|
-
twap: convertPythPrice(priceData.twap.value, priceData.exponent),
|
|
22
|
-
twapConfidence: convertPythPrice(priceData.twac.value, priceData.exponent),
|
|
21
|
+
confidence: convertPythPrice(priceData.confidence, priceData.exponent, this.multiple),
|
|
22
|
+
twap: convertPythPrice(priceData.twap.value, priceData.exponent, this.multiple),
|
|
23
|
+
twapConfidence: convertPythPrice(priceData.twac.value, priceData.exponent, this.multiple),
|
|
23
24
|
hasSufficientNumberOfDataPoints: true,
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
exports.PythClient = PythClient;
|
|
28
|
-
function convertPythPrice(price, exponent) {
|
|
29
|
+
function convertPythPrice(price, exponent, multiple) {
|
|
29
30
|
exponent = Math.abs(exponent);
|
|
30
|
-
const pythPrecision = numericConstants_1.TEN.pow(new anchor_1.BN(exponent).abs());
|
|
31
|
+
const pythPrecision = numericConstants_1.TEN.pow(new anchor_1.BN(exponent).abs()).div(multiple);
|
|
31
32
|
return new anchor_1.BN(price * Math.pow(10, exponent))
|
|
32
33
|
.mul(numericConstants_1.PRICE_PRECISION)
|
|
33
34
|
.div(pythPrecision);
|
package/lib/types.d.ts
CHANGED
package/lib/types.js
CHANGED
|
@@ -78,6 +78,7 @@ class OracleSource {
|
|
|
78
78
|
}
|
|
79
79
|
exports.OracleSource = OracleSource;
|
|
80
80
|
OracleSource.PYTH = { pyth: {} };
|
|
81
|
+
OracleSource.PYTH_1000 = { pyth1000: {} };
|
|
81
82
|
// static readonly SWITCHBOARD = { switchboard: {} };
|
|
82
83
|
OracleSource.QUOTE_ASSET = { quoteAsset: {} };
|
|
83
84
|
class OrderType {
|
package/lib/user.js
CHANGED
|
@@ -400,11 +400,13 @@ class User {
|
|
|
400
400
|
}
|
|
401
401
|
totalLiabilityValue = totalLiabilityValue.add(new _1.BN(spotPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
|
|
402
402
|
}
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
403
|
+
if (marketIndex === undefined || marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
404
|
+
if (netQuoteValue.gt(numericConstants_1.ZERO)) {
|
|
405
|
+
totalAssetValue = totalAssetValue.add(netQuoteValue);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
totalLiabilityValue = totalLiabilityValue.add(netQuoteValue.abs());
|
|
409
|
+
}
|
|
408
410
|
}
|
|
409
411
|
return { totalAssetValue, totalLiabilityValue };
|
|
410
412
|
}
|
package/package.json
CHANGED
|
@@ -54,6 +54,16 @@ export const DevnetPerpMarkets: PerpMarketConfig[] = [
|
|
|
54
54
|
launchTs: 1675610186000,
|
|
55
55
|
oracleSource: OracleSource.PYTH,
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
fullName: 'Bonk',
|
|
59
|
+
category: ['Meme'],
|
|
60
|
+
symbol: 'BONK-PERP',
|
|
61
|
+
baseAssetSymbol: 'BONK',
|
|
62
|
+
marketIndex: 4,
|
|
63
|
+
oracle: new PublicKey('6bquU99ktV1VRiHDr8gMhDFt3kMfhCQo5nfNrg2Urvsn'),
|
|
64
|
+
launchTs: 1677068931000,
|
|
65
|
+
oracleSource: OracleSource.PYTH_1000,
|
|
66
|
+
},
|
|
57
67
|
];
|
|
58
68
|
|
|
59
69
|
export const MainnetPerpMarkets: PerpMarketConfig[] = [
|
package/src/dlob/DLOB.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
calculateBidPrice,
|
|
7
7
|
DriftClient,
|
|
8
8
|
convertToNumber,
|
|
9
|
-
isAuctionComplete,
|
|
10
9
|
isOrderExpired,
|
|
11
10
|
isOneOfVariant,
|
|
12
11
|
isVariant,
|
|
@@ -1308,18 +1307,20 @@ export class DLOB {
|
|
|
1308
1307
|
marketType,
|
|
1309
1308
|
oraclePriceData
|
|
1310
1309
|
)) {
|
|
1311
|
-
|
|
1310
|
+
const bidGenerator = this.getRestingLimitBids(
|
|
1312
1311
|
marketIndex,
|
|
1313
1312
|
slot,
|
|
1314
1313
|
marketType,
|
|
1315
1314
|
oraclePriceData
|
|
1316
|
-
)
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
for (const bidNode of bidGenerator) {
|
|
1317
1318
|
const bidPrice = bidNode.getPrice(oraclePriceData, slot);
|
|
1318
1319
|
const askPrice = askNode.getPrice(oraclePriceData, slot);
|
|
1319
1320
|
|
|
1320
|
-
// orders don't cross
|
|
1321
|
+
// orders don't cross
|
|
1321
1322
|
if (bidPrice.lt(askPrice)) {
|
|
1322
|
-
|
|
1323
|
+
break;
|
|
1323
1324
|
}
|
|
1324
1325
|
|
|
1325
1326
|
const bidOrder = bidNode.order;
|
|
@@ -1327,14 +1328,18 @@ export class DLOB {
|
|
|
1327
1328
|
|
|
1328
1329
|
// Can't match orders from the same user
|
|
1329
1330
|
const sameUser = bidNode.userAccount.equals(askNode.userAccount);
|
|
1330
|
-
if (sameUser
|
|
1331
|
+
if (sameUser) {
|
|
1331
1332
|
continue;
|
|
1332
1333
|
}
|
|
1333
1334
|
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
)
|
|
1335
|
+
const makerAndTaker = this.determineMakerAndTaker(askNode, bidNode);
|
|
1336
|
+
|
|
1337
|
+
// unable to match maker and taker due to post only or slot
|
|
1338
|
+
if (!makerAndTaker) {
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
const { takerNode, makerNode } = makerAndTaker;
|
|
1338
1343
|
|
|
1339
1344
|
// extra guard against bad fills for limit orders where auction is incomplete
|
|
1340
1345
|
if (
|
|
@@ -1408,27 +1413,25 @@ export class DLOB {
|
|
|
1408
1413
|
determineMakerAndTaker(
|
|
1409
1414
|
askNode: DLOBNode,
|
|
1410
1415
|
bidNode: DLOBNode
|
|
1411
|
-
): { takerNode: DLOBNode; makerNode: DLOBNode } {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
takerNode: bidNode,
|
|
1420
|
-
makerNode: askNode,
|
|
1421
|
-
};
|
|
1422
|
-
} else if (askNode.order.slot.lt(bidNode.order.slot)) {
|
|
1416
|
+
): { takerNode: DLOBNode; makerNode: DLOBNode } | undefined {
|
|
1417
|
+
const askSlot = askNode.order.slot.add(
|
|
1418
|
+
new BN(askNode.order.auctionDuration)
|
|
1419
|
+
);
|
|
1420
|
+
const bidSlot = bidNode.order.slot.add(
|
|
1421
|
+
new BN(bidNode.order.auctionDuration)
|
|
1422
|
+
);
|
|
1423
|
+
if (askSlot.lte(bidSlot) && !bidNode.order.postOnly) {
|
|
1423
1424
|
return {
|
|
1424
1425
|
takerNode: bidNode,
|
|
1425
1426
|
makerNode: askNode,
|
|
1426
1427
|
};
|
|
1427
|
-
} else {
|
|
1428
|
+
} else if (bidSlot.lte(askSlot) && !askNode.order.postOnly) {
|
|
1428
1429
|
return {
|
|
1429
1430
|
takerNode: askNode,
|
|
1430
1431
|
makerNode: bidNode,
|
|
1431
1432
|
};
|
|
1433
|
+
} else {
|
|
1434
|
+
return undefined;
|
|
1432
1435
|
}
|
|
1433
1436
|
}
|
|
1434
1437
|
|
|
@@ -4,6 +4,7 @@ import { OracleClient } from '../oracles/types';
|
|
|
4
4
|
import { PythClient } from '../oracles/pythClient';
|
|
5
5
|
// import { SwitchboardClient } from '../oracles/switchboardClient';
|
|
6
6
|
import { QuoteAssetOracleClient } from '../oracles/quoteAssetOracleClient';
|
|
7
|
+
import { BN } from '@project-serum/anchor';
|
|
7
8
|
|
|
8
9
|
export function getOracleClient(
|
|
9
10
|
oracleSource: OracleSource,
|
|
@@ -13,6 +14,10 @@ export function getOracleClient(
|
|
|
13
14
|
return new PythClient(connection);
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
if (isVariant(oracleSource, 'pyth1000')) {
|
|
18
|
+
return new PythClient(connection, new BN(1000));
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
// if (isVariant(oracleSource, 'switchboard')) {
|
|
17
22
|
// return new SwitchboardClient(connection);
|
|
18
23
|
// }
|
package/src/idl/drift.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.18.0-beta.
|
|
2
|
+
"version": "2.18.0-beta.1",
|
|
3
3
|
"name": "drift",
|
|
4
4
|
"instructions": [
|
|
5
5
|
{
|
|
@@ -6237,6 +6237,9 @@
|
|
|
6237
6237
|
{
|
|
6238
6238
|
"name": "Pyth"
|
|
6239
6239
|
},
|
|
6240
|
+
{
|
|
6241
|
+
"name": "Pyth1000"
|
|
6242
|
+
},
|
|
6240
6243
|
{
|
|
6241
6244
|
"name": "Switchboard"
|
|
6242
6245
|
},
|
package/src/math/trade.ts
CHANGED
|
@@ -52,6 +52,9 @@ export type PriceImpactUnit =
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Calculates avg/max slippage (price impact) for candidate trade
|
|
55
|
+
*
|
|
56
|
+
* @deprecated use calculateEstimatedPerpEntryPrice instead
|
|
57
|
+
*
|
|
55
58
|
* @param direction
|
|
56
59
|
* @param amount
|
|
57
60
|
* @param market
|
|
@@ -200,6 +203,9 @@ export function calculateTradeAcquiredAmounts(
|
|
|
200
203
|
/**
|
|
201
204
|
* calculateTargetPriceTrade
|
|
202
205
|
* simple function for finding arbitraging trades
|
|
206
|
+
*
|
|
207
|
+
* @deprecated
|
|
208
|
+
*
|
|
203
209
|
* @param market
|
|
204
210
|
* @param targetPrice
|
|
205
211
|
* @param pct optional default is 100% gap filling, can set smaller.
|
|
@@ -2,13 +2,15 @@ import { parsePriceData } from '@pythnetwork/client';
|
|
|
2
2
|
import { Connection, PublicKey } from '@solana/web3.js';
|
|
3
3
|
import { OracleClient, OraclePriceData } from './types';
|
|
4
4
|
import { BN } from '@project-serum/anchor';
|
|
5
|
-
import { PRICE_PRECISION, TEN } from '../constants/numericConstants';
|
|
5
|
+
import { ONE, PRICE_PRECISION, TEN } from '../constants/numericConstants';
|
|
6
6
|
|
|
7
7
|
export class PythClient implements OracleClient {
|
|
8
8
|
private connection: Connection;
|
|
9
|
+
private multiple: BN;
|
|
9
10
|
|
|
10
|
-
public constructor(connection: Connection) {
|
|
11
|
+
public constructor(connection: Connection, multiple = ONE) {
|
|
11
12
|
this.connection = connection;
|
|
13
|
+
this.multiple = multiple;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
public async getOraclePriceData(
|
|
@@ -21,22 +23,39 @@ export class PythClient implements OracleClient {
|
|
|
21
23
|
public getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData {
|
|
22
24
|
const priceData = parsePriceData(buffer);
|
|
23
25
|
return {
|
|
24
|
-
price: convertPythPrice(
|
|
26
|
+
price: convertPythPrice(
|
|
27
|
+
priceData.aggregate.price,
|
|
28
|
+
priceData.exponent,
|
|
29
|
+
this.multiple
|
|
30
|
+
),
|
|
25
31
|
slot: new BN(priceData.lastSlot.toString()),
|
|
26
|
-
confidence: convertPythPrice(
|
|
27
|
-
|
|
32
|
+
confidence: convertPythPrice(
|
|
33
|
+
priceData.confidence,
|
|
34
|
+
priceData.exponent,
|
|
35
|
+
this.multiple
|
|
36
|
+
),
|
|
37
|
+
twap: convertPythPrice(
|
|
38
|
+
priceData.twap.value,
|
|
39
|
+
priceData.exponent,
|
|
40
|
+
this.multiple
|
|
41
|
+
),
|
|
28
42
|
twapConfidence: convertPythPrice(
|
|
29
43
|
priceData.twac.value,
|
|
30
|
-
priceData.exponent
|
|
44
|
+
priceData.exponent,
|
|
45
|
+
this.multiple
|
|
31
46
|
),
|
|
32
47
|
hasSufficientNumberOfDataPoints: true,
|
|
33
48
|
};
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
export function convertPythPrice(
|
|
52
|
+
export function convertPythPrice(
|
|
53
|
+
price: number,
|
|
54
|
+
exponent: number,
|
|
55
|
+
multiple: BN
|
|
56
|
+
): BN {
|
|
38
57
|
exponent = Math.abs(exponent);
|
|
39
|
-
const pythPrecision = TEN.pow(new BN(exponent).abs());
|
|
58
|
+
const pythPrecision = TEN.pow(new BN(exponent).abs()).div(multiple);
|
|
40
59
|
return new BN(price * Math.pow(10, exponent))
|
|
41
60
|
.mul(PRICE_PRECISION)
|
|
42
61
|
.div(pythPrecision);
|
package/src/types.ts
CHANGED
|
@@ -76,6 +76,7 @@ export class DepositDirection {
|
|
|
76
76
|
|
|
77
77
|
export class OracleSource {
|
|
78
78
|
static readonly PYTH = { pyth: {} };
|
|
79
|
+
static readonly PYTH_1000 = { pyth1000: {} };
|
|
79
80
|
// static readonly SWITCHBOARD = { switchboard: {} };
|
|
80
81
|
static readonly QUOTE_ASSET = { quoteAsset: {} };
|
|
81
82
|
}
|
package/src/user.ts
CHANGED
|
@@ -697,10 +697,12 @@ export class User {
|
|
|
697
697
|
);
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
700
|
+
if (marketIndex === undefined || marketIndex === QUOTE_SPOT_MARKET_INDEX) {
|
|
701
|
+
if (netQuoteValue.gt(ZERO)) {
|
|
702
|
+
totalAssetValue = totalAssetValue.add(netQuoteValue);
|
|
703
|
+
} else {
|
|
704
|
+
totalLiabilityValue = totalLiabilityValue.add(netQuoteValue.abs());
|
|
705
|
+
}
|
|
704
706
|
}
|
|
705
707
|
|
|
706
708
|
return { totalAssetValue, totalLiabilityValue };
|
package/tests/dlob/test.ts
CHANGED
|
@@ -3435,7 +3435,7 @@ describe('DLOB Perp Tests', () => {
|
|
|
3435
3435
|
PositionDirection.LONG,
|
|
3436
3436
|
vBid,
|
|
3437
3437
|
vAsk,
|
|
3438
|
-
new BN(slot
|
|
3438
|
+
new BN(slot + 1), // later order becomes taker
|
|
3439
3439
|
new BN(200),
|
|
3440
3440
|
undefined,
|
|
3441
3441
|
undefined,
|
|
@@ -5109,7 +5109,8 @@ describe('DLOB Spot Tests', () => {
|
|
|
5109
5109
|
BASE_PRECISION, // quantity
|
|
5110
5110
|
PositionDirection.SHORT,
|
|
5111
5111
|
vBid,
|
|
5112
|
-
vAsk
|
|
5112
|
+
vAsk,
|
|
5113
|
+
new BN(1)
|
|
5113
5114
|
);
|
|
5114
5115
|
insertOrderToDLOB(
|
|
5115
5116
|
dlob,
|
|
@@ -5122,7 +5123,8 @@ describe('DLOB Spot Tests', () => {
|
|
|
5122
5123
|
BASE_PRECISION, // quantity
|
|
5123
5124
|
PositionDirection.SHORT,
|
|
5124
5125
|
vBid,
|
|
5125
|
-
vAsk
|
|
5126
|
+
vAsk,
|
|
5127
|
+
new BN(1)
|
|
5126
5128
|
);
|
|
5127
5129
|
|
|
5128
5130
|
// should have no crossing orders
|
|
@@ -5152,7 +5154,7 @@ describe('DLOB Spot Tests', () => {
|
|
|
5152
5154
|
PositionDirection.LONG,
|
|
5153
5155
|
vBid,
|
|
5154
5156
|
vAsk,
|
|
5155
|
-
|
|
5157
|
+
new BN(0),
|
|
5156
5158
|
undefined,
|
|
5157
5159
|
undefined,
|
|
5158
5160
|
true
|
|
@@ -5169,7 +5171,7 @@ describe('DLOB Spot Tests', () => {
|
|
|
5169
5171
|
PositionDirection.LONG,
|
|
5170
5172
|
vBid,
|
|
5171
5173
|
vAsk,
|
|
5172
|
-
|
|
5174
|
+
new BN(0),
|
|
5173
5175
|
undefined,
|
|
5174
5176
|
undefined,
|
|
5175
5177
|
true
|
|
@@ -5205,6 +5207,186 @@ describe('DLOB Spot Tests', () => {
|
|
|
5205
5207
|
expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(3);
|
|
5206
5208
|
});
|
|
5207
5209
|
|
|
5210
|
+
it('Test limit orders skipping more recent post onlys', () => {
|
|
5211
|
+
const vAsk = new BN(15);
|
|
5212
|
+
const vBid = new BN(8);
|
|
5213
|
+
|
|
5214
|
+
const user0 = Keypair.generate();
|
|
5215
|
+
const user1 = Keypair.generate();
|
|
5216
|
+
|
|
5217
|
+
const dlob = new DLOB();
|
|
5218
|
+
const marketIndex = 0;
|
|
5219
|
+
|
|
5220
|
+
const slot = 12;
|
|
5221
|
+
const oracle = {
|
|
5222
|
+
price: vBid.add(vAsk).div(new BN(2)),
|
|
5223
|
+
slot: new BN(slot),
|
|
5224
|
+
confidence: new BN(1),
|
|
5225
|
+
hasSufficientNumberOfDataPoints: true,
|
|
5226
|
+
};
|
|
5227
|
+
|
|
5228
|
+
// insert some limit sells below vAMM ask, above bid
|
|
5229
|
+
insertOrderToDLOB(
|
|
5230
|
+
dlob,
|
|
5231
|
+
user0.publicKey,
|
|
5232
|
+
OrderType.LIMIT,
|
|
5233
|
+
MarketType.PERP,
|
|
5234
|
+
1, // orderId
|
|
5235
|
+
marketIndex,
|
|
5236
|
+
new BN(14), // price
|
|
5237
|
+
BASE_PRECISION, // quantity
|
|
5238
|
+
PositionDirection.SHORT,
|
|
5239
|
+
vBid,
|
|
5240
|
+
vAsk,
|
|
5241
|
+
new BN(1)
|
|
5242
|
+
);
|
|
5243
|
+
insertOrderToDLOB(
|
|
5244
|
+
dlob,
|
|
5245
|
+
user1.publicKey,
|
|
5246
|
+
OrderType.LIMIT,
|
|
5247
|
+
MarketType.PERP,
|
|
5248
|
+
2, // orderId
|
|
5249
|
+
marketIndex,
|
|
5250
|
+
new BN(13), // price
|
|
5251
|
+
BASE_PRECISION, // quantity
|
|
5252
|
+
PositionDirection.SHORT,
|
|
5253
|
+
vBid,
|
|
5254
|
+
vAsk,
|
|
5255
|
+
new BN(1)
|
|
5256
|
+
);
|
|
5257
|
+
|
|
5258
|
+
// should have no crossing orders
|
|
5259
|
+
const nodesToFillBefore = dlob.findNodesToFill(
|
|
5260
|
+
marketIndex,
|
|
5261
|
+
undefined,
|
|
5262
|
+
undefined,
|
|
5263
|
+
12, // auction over
|
|
5264
|
+
Date.now(),
|
|
5265
|
+
MarketType.PERP,
|
|
5266
|
+
oracle,
|
|
5267
|
+
mockStateAccount,
|
|
5268
|
+
mockPerpMarkets[marketIndex]
|
|
5269
|
+
);
|
|
5270
|
+
expect(nodesToFillBefore.length).to.equal(0);
|
|
5271
|
+
|
|
5272
|
+
// add post only orders that are newer than resting limit orders and thus cant match
|
|
5273
|
+
insertOrderToDLOB(
|
|
5274
|
+
dlob,
|
|
5275
|
+
user1.publicKey,
|
|
5276
|
+
OrderType.LIMIT,
|
|
5277
|
+
MarketType.PERP,
|
|
5278
|
+
3, // orderId
|
|
5279
|
+
marketIndex,
|
|
5280
|
+
new BN(15), // price
|
|
5281
|
+
BASE_PRECISION, // quantity
|
|
5282
|
+
PositionDirection.LONG,
|
|
5283
|
+
vBid,
|
|
5284
|
+
vAsk,
|
|
5285
|
+
new BN(2),
|
|
5286
|
+
undefined,
|
|
5287
|
+
undefined,
|
|
5288
|
+
true
|
|
5289
|
+
);
|
|
5290
|
+
insertOrderToDLOB(
|
|
5291
|
+
dlob,
|
|
5292
|
+
user0.publicKey,
|
|
5293
|
+
OrderType.LIMIT,
|
|
5294
|
+
MarketType.PERP,
|
|
5295
|
+
4, // orderId
|
|
5296
|
+
marketIndex,
|
|
5297
|
+
new BN(14), // price
|
|
5298
|
+
BASE_PRECISION, // quantity
|
|
5299
|
+
PositionDirection.LONG,
|
|
5300
|
+
vBid,
|
|
5301
|
+
vAsk,
|
|
5302
|
+
new BN(2),
|
|
5303
|
+
undefined,
|
|
5304
|
+
undefined,
|
|
5305
|
+
true
|
|
5306
|
+
);
|
|
5307
|
+
|
|
5308
|
+
let nodesToFillAfter = dlob.findNodesToFill(
|
|
5309
|
+
marketIndex,
|
|
5310
|
+
undefined,
|
|
5311
|
+
undefined,
|
|
5312
|
+
slot, // auction over
|
|
5313
|
+
Date.now(),
|
|
5314
|
+
MarketType.PERP,
|
|
5315
|
+
oracle,
|
|
5316
|
+
mockStateAccount,
|
|
5317
|
+
mockPerpMarkets[marketIndex]
|
|
5318
|
+
);
|
|
5319
|
+
|
|
5320
|
+
expect(nodesToFillAfter.length).to.equal(0);
|
|
5321
|
+
|
|
5322
|
+
// add post only orders that are older than resting limit orders
|
|
5323
|
+
insertOrderToDLOB(
|
|
5324
|
+
dlob,
|
|
5325
|
+
user1.publicKey,
|
|
5326
|
+
OrderType.LIMIT,
|
|
5327
|
+
MarketType.PERP,
|
|
5328
|
+
5, // orderId
|
|
5329
|
+
marketIndex,
|
|
5330
|
+
new BN(15), // price
|
|
5331
|
+
BASE_PRECISION, // quantity
|
|
5332
|
+
PositionDirection.LONG,
|
|
5333
|
+
vBid,
|
|
5334
|
+
vAsk,
|
|
5335
|
+
new BN(0),
|
|
5336
|
+
undefined,
|
|
5337
|
+
undefined,
|
|
5338
|
+
true
|
|
5339
|
+
);
|
|
5340
|
+
insertOrderToDLOB(
|
|
5341
|
+
dlob,
|
|
5342
|
+
user0.publicKey,
|
|
5343
|
+
OrderType.LIMIT,
|
|
5344
|
+
MarketType.PERP,
|
|
5345
|
+
6, // orderId
|
|
5346
|
+
marketIndex,
|
|
5347
|
+
new BN(14), // price
|
|
5348
|
+
BASE_PRECISION, // quantity
|
|
5349
|
+
PositionDirection.LONG,
|
|
5350
|
+
vBid,
|
|
5351
|
+
vAsk,
|
|
5352
|
+
new BN(0),
|
|
5353
|
+
undefined,
|
|
5354
|
+
undefined,
|
|
5355
|
+
true
|
|
5356
|
+
);
|
|
5357
|
+
|
|
5358
|
+
nodesToFillAfter = dlob.findNodesToFill(
|
|
5359
|
+
marketIndex,
|
|
5360
|
+
undefined,
|
|
5361
|
+
undefined,
|
|
5362
|
+
slot, // auction over
|
|
5363
|
+
Date.now(),
|
|
5364
|
+
MarketType.PERP,
|
|
5365
|
+
oracle,
|
|
5366
|
+
mockStateAccount,
|
|
5367
|
+
mockPerpMarkets[marketIndex]
|
|
5368
|
+
);
|
|
5369
|
+
|
|
5370
|
+
expect(nodesToFillAfter.length).to.equal(2);
|
|
5371
|
+
|
|
5372
|
+
printBookState(dlob, marketIndex, vBid, vAsk, slot, oracle);
|
|
5373
|
+
|
|
5374
|
+
for (const n of nodesToFillAfter) {
|
|
5375
|
+
console.log(
|
|
5376
|
+
`cross found: taker orderId: ${n.node.order?.orderId.toString()}: BAA: ${n.node.order?.baseAssetAmountFilled.toString()}/${n.node.order?.baseAssetAmount.toString()}, maker orderId: ${n.makerNode?.order?.orderId.toString()}: BAA: ${n.makerNode?.order?.baseAssetAmountFilled.toString()}/${n.makerNode?.order?.baseAssetAmount.toString()}`
|
|
5377
|
+
);
|
|
5378
|
+
}
|
|
5379
|
+
expect(nodesToFillAfter.length).to.equal(2);
|
|
5380
|
+
|
|
5381
|
+
// taker should fill completely with best maker
|
|
5382
|
+
expect(nodesToFillAfter[0].node.order?.orderId).to.equal(2);
|
|
5383
|
+
expect(nodesToFillAfter[0].makerNode?.order?.orderId).to.equal(6);
|
|
5384
|
+
|
|
5385
|
+
// taker should fill completely with second best maker
|
|
5386
|
+
expect(nodesToFillAfter[1].node.order?.orderId).to.equal(1);
|
|
5387
|
+
expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(5);
|
|
5388
|
+
});
|
|
5389
|
+
|
|
5208
5390
|
it('Test trigger orders', () => {
|
|
5209
5391
|
const vAsk = new BN(15);
|
|
5210
5392
|
const vBid = new BN(8);
|