@drift-labs/sdk 2.18.0 → 2.19.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.
@@ -47,12 +47,12 @@ exports.DevnetPerpMarkets = [
47
47
  {
48
48
  fullName: 'Bonk',
49
49
  category: ['Meme'],
50
- symbol: 'BONK-PERP',
51
- baseAssetSymbol: 'BONK',
50
+ symbol: '1MBONK-PERP',
51
+ baseAssetSymbol: '1MBONK',
52
52
  marketIndex: 4,
53
53
  oracle: new web3_js_1.PublicKey('6bquU99ktV1VRiHDr8gMhDFt3kMfhCQo5nfNrg2Urvsn'),
54
54
  launchTs: 1677068931000,
55
- oracleSource: __1.OracleSource.PYTH_1000,
55
+ oracleSource: __1.OracleSource.PYTH_1M,
56
56
  },
57
57
  ];
58
58
  exports.MainnetPerpMarkets = [
@@ -96,6 +96,16 @@ exports.MainnetPerpMarkets = [
96
96
  launchTs: 1675802661000,
97
97
  oracleSource: __1.OracleSource.PYTH,
98
98
  },
99
+ {
100
+ fullName: 'Bonk',
101
+ category: ['Meme'],
102
+ symbol: '1MBONK-PERP',
103
+ baseAssetSymbol: '1MBONK',
104
+ marketIndex: 4,
105
+ oracle: new web3_js_1.PublicKey('8ihFLu5FimgTQ1Unh4dVyEHUGodJ5gJQCrQf4KUVB9bN'),
106
+ launchTs: 1677690149000,
107
+ oracleSource: __1.OracleSource.PYTH_1M,
108
+ },
99
109
  ];
100
110
  exports.PerpMarkets = {
101
111
  devnet: exports.DevnetPerpMarkets,
package/lib/dlob/DLOB.js CHANGED
@@ -286,8 +286,9 @@ class DLOB {
286
286
  }
287
287
  }
288
288
  getOrder(orderId, userAccount) {
289
+ const orderSignature = NodeList_1.getOrderSignature(orderId, userAccount);
289
290
  for (const nodeList of this.getNodeLists()) {
290
- const node = nodeList.get(orderId, userAccount);
291
+ const node = nodeList.get(orderSignature);
291
292
  if (node) {
292
293
  return node.order;
293
294
  }
@@ -761,20 +762,32 @@ class DLOB {
761
762
  determineMakerAndTaker(askNode, bidNode) {
762
763
  const askSlot = askNode.order.slot.add(new __1.BN(askNode.order.auctionDuration));
763
764
  const bidSlot = bidNode.order.slot.add(new __1.BN(bidNode.order.auctionDuration));
764
- if (askSlot.lte(bidSlot) && !bidNode.order.postOnly) {
765
+ if (bidNode.order.postOnly && askNode.order.postOnly) {
766
+ return undefined;
767
+ }
768
+ else if (bidNode.order.postOnly) {
769
+ return {
770
+ takerNode: askNode,
771
+ makerNode: bidNode,
772
+ };
773
+ }
774
+ else if (askNode.order.postOnly) {
765
775
  return {
766
776
  takerNode: bidNode,
767
777
  makerNode: askNode,
768
778
  };
769
779
  }
770
- else if (bidSlot.lte(askSlot) && !askNode.order.postOnly) {
780
+ else if (askSlot.lte(bidSlot)) {
771
781
  return {
772
- takerNode: askNode,
773
- makerNode: bidNode,
782
+ takerNode: bidNode,
783
+ makerNode: askNode,
774
784
  };
775
785
  }
776
786
  else {
777
- return undefined;
787
+ return {
788
+ takerNode: askNode,
789
+ makerNode: bidNode,
790
+ };
778
791
  }
779
792
  }
780
793
  getBestAsk(marketIndex, fallbackAsk, slot, marketType, oraclePriceData) {
@@ -21,7 +21,7 @@ export declare class NodeList<NodeType extends keyof DLOBNodeMap> implements DLO
21
21
  remove(order: Order, userAccount: PublicKey): void;
22
22
  getGenerator(): Generator<DLOBNode>;
23
23
  has(order: Order, userAccount: PublicKey): boolean;
24
- get(orderId: number, userAccount: PublicKey): DLOBNodeMap[NodeType] | undefined;
24
+ get(orderSignature: string): DLOBNodeMap[NodeType] | undefined;
25
25
  print(): void;
26
26
  printTop(): void;
27
27
  }
@@ -104,8 +104,8 @@ class NodeList {
104
104
  has(order, userAccount) {
105
105
  return this.nodeMap.has(getOrderSignature(order.orderId, userAccount));
106
106
  }
107
- get(orderId, userAccount) {
108
- return this.nodeMap.get(getOrderSignature(orderId, userAccount));
107
+ get(orderSignature) {
108
+ return this.nodeMap.get(orderSignature);
109
109
  }
110
110
  print() {
111
111
  let currentNode = this.head;
@@ -10,9 +10,12 @@ function getOracleClient(oracleSource, connection) {
10
10
  if (types_1.isVariant(oracleSource, 'pyth')) {
11
11
  return new pythClient_1.PythClient(connection);
12
12
  }
13
- if (types_1.isVariant(oracleSource, 'pyth1000')) {
13
+ if (types_1.isVariant(oracleSource, 'pyth1K')) {
14
14
  return new pythClient_1.PythClient(connection, new anchor_1.BN(1000));
15
15
  }
16
+ if (types_1.isVariant(oracleSource, 'pyth1M')) {
17
+ return new pythClient_1.PythClient(connection, new anchor_1.BN(1000000));
18
+ }
16
19
  // if (isVariant(oracleSource, 'switchboard')) {
17
20
  // return new SwitchboardClient(connection);
18
21
  // }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.18.0",
2
+ "version": "2.19.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -6237,14 +6237,17 @@
6237
6237
  {
6238
6238
  "name": "Pyth"
6239
6239
  },
6240
- {
6241
- "name": "Pyth1000"
6242
- },
6243
6240
  {
6244
6241
  "name": "Switchboard"
6245
6242
  },
6246
6243
  {
6247
6244
  "name": "QuoteAsset"
6245
+ },
6246
+ {
6247
+ "name": "Pyth1K"
6248
+ },
6249
+ {
6250
+ "name": "Pyth1M"
6248
6251
  }
6249
6252
  ]
6250
6253
  }
package/lib/math/amm.js CHANGED
@@ -203,26 +203,21 @@ function calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBas
203
203
  exports.calculateMarketOpenBidAsk = calculateMarketOpenBidAsk;
204
204
  function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, directionalSpread, maxSpread) {
205
205
  if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
206
- return 0;
206
+ return 1;
207
207
  }
208
- const defaultLargeBidAskFactor = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
208
+ const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
209
209
  // inventory skew
210
210
  const [openBids, openAsks] = calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
211
- const minSideLiquidity = anchor_1.BN.max(new anchor_1.BN(1), anchor_1.BN.min(openBids.abs(), openAsks.abs()));
212
- const inventoryScaleMaxBN = anchor_1.BN.max(defaultLargeBidAskFactor, new anchor_1.BN(maxSpread / 2)
211
+ const minSideLiquidity = anchor_1.BN.min(openBids.abs(), openAsks.abs());
212
+ const inventoryScaleBN = anchor_1.BN.min(baseAssetAmountWithAmm
213
+ .mul(numericConstants_1.PERCENTAGE_PRECISION)
214
+ .div(anchor_1.BN.max(minSideLiquidity, numericConstants_1.ONE))
215
+ .abs(), numericConstants_1.PERCENTAGE_PRECISION);
216
+ const inventoryScaleMaxBN = anchor_1.BN.max(MAX_BID_ASK_INVENTORY_SKEW_FACTOR, new anchor_1.BN(maxSpread)
213
217
  .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION)
214
218
  .div(new anchor_1.BN(Math.max(directionalSpread, 1))));
215
- const inventoryScaleBN = baseAssetAmountWithAmm
216
- .mul(anchor_1.BN.max(baseAssetAmountWithAmm.abs(), numericConstants_1.BASE_PRECISION))
217
- .div(numericConstants_1.BASE_PRECISION)
218
- .mul(defaultLargeBidAskFactor)
219
- .div(minSideLiquidity)
220
- .abs();
221
- const inventoryScale = anchor_1.BN.min(inventoryScaleMaxBN, inventoryScaleBN).toNumber() /
222
- numericConstants_1.BID_ASK_SPREAD_PRECISION.toNumber();
223
- const inventoryScaleMax = inventoryScaleMaxBN.toNumber() / numericConstants_1.BID_ASK_SPREAD_PRECISION.toNumber();
224
- const inventorySpreadScale = Math.min(inventoryScaleMax, 1 + inventoryScale);
225
- return inventorySpreadScale;
219
+ const inventoryScaleCapped = anchor_1.BN.min(inventoryScaleMaxBN, numericConstants_1.BID_ASK_SPREAD_PRECISION.add(inventoryScaleMaxBN.mul(inventoryScaleBN).div(numericConstants_1.PERCENTAGE_PRECISION))).toNumber() / numericConstants_1.BID_ASK_SPREAD_PRECISION.toNumber();
220
+ return inventoryScaleCapped;
226
221
  }
227
222
  exports.calculateInventoryScale = calculateInventoryScale;
228
223
  function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, netBaseAssetAmount, reservePrice, totalFeeMinusDistributions) {
package/lib/math/trade.js CHANGED
@@ -499,7 +499,7 @@ function calculateEstimatedSpotEntryPrice(assetType, amount, direction, market,
499
499
  .div(basePrecision);
500
500
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
501
501
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
502
- worstPrice = dlobLimitOrder;
502
+ worstPrice = dlobLimitOrderPrice;
503
503
  dlobLimitOrder = dlobLimitOrders.next().value;
504
504
  }
505
505
  else {
@@ -8,16 +8,17 @@ export declare class SerumSubscriber {
8
8
  connection: Connection;
9
9
  programId: PublicKey;
10
10
  marketAddress: PublicKey;
11
- accountLoader: BulkAccountLoader;
11
+ subscriptionType: 'polling' | 'websocket';
12
+ accountLoader: BulkAccountLoader | undefined;
12
13
  market: Market;
13
14
  subscribed: boolean;
14
15
  asksAddress: PublicKey;
15
16
  asks: Orderbook;
16
- asksCallbackId: string;
17
+ asksCallbackId: string | number;
17
18
  lastAsksSlot: number;
18
19
  bidsAddress: PublicKey;
19
20
  bids: Orderbook;
20
- bidsCallbackId: string;
21
+ bidsCallbackId: string | number;
21
22
  lastBidsSlot: number;
22
23
  constructor(config: SerumMarketSubscriberConfig);
23
24
  subscribe(): Promise<void>;
@@ -9,7 +9,13 @@ class SerumSubscriber {
9
9
  this.connection = config.connection;
10
10
  this.programId = config.programId;
11
11
  this.marketAddress = config.marketAddress;
12
- this.accountLoader = config.accountSubscription.accountLoader;
12
+ if (config.accountSubscription.type === 'polling') {
13
+ this.subscriptionType = 'polling';
14
+ this.accountLoader = config.accountSubscription.accountLoader;
15
+ }
16
+ else {
17
+ this.subscriptionType = 'websocket';
18
+ }
13
19
  }
14
20
  async subscribe() {
15
21
  if (this.subscribed) {
@@ -18,16 +24,32 @@ class SerumSubscriber {
18
24
  this.market = await serum_1.Market.load(this.connection, this.marketAddress, undefined, this.programId);
19
25
  this.asksAddress = this.market.asksAddress;
20
26
  this.asks = await this.market.loadAsks(this.connection);
21
- this.asksCallbackId = await this.accountLoader.addAccount(this.asksAddress, (buffer, slot) => {
22
- this.lastAsksSlot = slot;
23
- this.asks = serum_1.Orderbook.decode(this.market, buffer);
24
- });
27
+ if (this.subscriptionType === 'websocket') {
28
+ this.asksCallbackId = this.connection.onAccountChange(this.asksAddress, (accountInfo, ctx) => {
29
+ this.lastAsksSlot = ctx.slot;
30
+ this.asks = serum_1.Orderbook.decode(this.market, accountInfo.data);
31
+ });
32
+ }
33
+ else {
34
+ this.asksCallbackId = await this.accountLoader.addAccount(this.asksAddress, (buffer, slot) => {
35
+ this.lastAsksSlot = slot;
36
+ this.asks = serum_1.Orderbook.decode(this.market, buffer);
37
+ });
38
+ }
25
39
  this.bidsAddress = this.market.bidsAddress;
26
40
  this.bids = await this.market.loadBids(this.connection);
27
- this.bidsCallbackId = await this.accountLoader.addAccount(this.bidsAddress, (buffer, slot) => {
28
- this.lastBidsSlot = slot;
29
- this.bids = serum_1.Orderbook.decode(this.market, buffer);
30
- });
41
+ if (this.subscriptionType === 'websocket') {
42
+ this.bidsCallbackId = this.connection.onAccountChange(this.bidsAddress, (accountInfo, ctx) => {
43
+ this.lastBidsSlot = ctx.slot;
44
+ this.bids = serum_1.Orderbook.decode(this.market, accountInfo.data);
45
+ });
46
+ }
47
+ else {
48
+ this.bidsCallbackId = await this.accountLoader.addAccount(this.bidsAddress, (buffer, slot) => {
49
+ this.lastBidsSlot = slot;
50
+ this.bids = serum_1.Orderbook.decode(this.market, buffer);
51
+ });
52
+ }
31
53
  this.subscribed = true;
32
54
  }
33
55
  getBestBid() {
@@ -48,8 +70,15 @@ class SerumSubscriber {
48
70
  if (!this.subscribed) {
49
71
  return;
50
72
  }
51
- this.accountLoader.removeAccount(this.asksAddress, this.asksCallbackId);
52
- this.accountLoader.removeAccount(this.bidsAddress, this.bidsCallbackId);
73
+ // remove listeners
74
+ if (this.subscriptionType === 'websocket') {
75
+ await this.connection.removeAccountChangeListener(this.asksCallbackId);
76
+ await this.connection.removeAccountChangeListener(this.bidsCallbackId);
77
+ }
78
+ else {
79
+ this.accountLoader.removeAccount(this.asksAddress, this.asksCallbackId);
80
+ this.accountLoader.removeAccount(this.bidsAddress, this.bidsCallbackId);
81
+ }
53
82
  this.subscribed = false;
54
83
  }
55
84
  }
@@ -7,5 +7,7 @@ export declare type SerumMarketSubscriberConfig = {
7
7
  accountSubscription: {
8
8
  type: 'polling';
9
9
  accountLoader: BulkAccountLoader;
10
+ } | {
11
+ type: 'websocket';
10
12
  };
11
13
  };
package/lib/types.d.ts CHANGED
@@ -130,8 +130,11 @@ export declare class OracleSource {
130
130
  static readonly PYTH: {
131
131
  pyth: {};
132
132
  };
133
- static readonly PYTH_1000: {
134
- pyth1000: {};
133
+ static readonly PYTH_1K: {
134
+ pyth1K: {};
135
+ };
136
+ static readonly PYTH_1M: {
137
+ pyth1M: {};
135
138
  };
136
139
  static readonly QUOTE_ASSET: {
137
140
  quoteAsset: {};
package/lib/types.js CHANGED
@@ -78,7 +78,8 @@ class OracleSource {
78
78
  }
79
79
  exports.OracleSource = OracleSource;
80
80
  OracleSource.PYTH = { pyth: {} };
81
- OracleSource.PYTH_1000 = { pyth1000: {} };
81
+ OracleSource.PYTH_1K = { pyth1K: {} };
82
+ OracleSource.PYTH_1M = { pyth1M: {} };
82
83
  // static readonly SWITCHBOARD = { switchboard: {} };
83
84
  OracleSource.QUOTE_ASSET = { quoteAsset: {} };
84
85
  class OrderType {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.18.0",
3
+ "version": "2.19.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -57,12 +57,12 @@ export const DevnetPerpMarkets: PerpMarketConfig[] = [
57
57
  {
58
58
  fullName: 'Bonk',
59
59
  category: ['Meme'],
60
- symbol: 'BONK-PERP',
61
- baseAssetSymbol: 'BONK',
60
+ symbol: '1MBONK-PERP',
61
+ baseAssetSymbol: '1MBONK',
62
62
  marketIndex: 4,
63
63
  oracle: new PublicKey('6bquU99ktV1VRiHDr8gMhDFt3kMfhCQo5nfNrg2Urvsn'),
64
64
  launchTs: 1677068931000,
65
- oracleSource: OracleSource.PYTH_1000,
65
+ oracleSource: OracleSource.PYTH_1M,
66
66
  },
67
67
  ];
68
68
 
@@ -107,6 +107,16 @@ export const MainnetPerpMarkets: PerpMarketConfig[] = [
107
107
  launchTs: 1675802661000,
108
108
  oracleSource: OracleSource.PYTH,
109
109
  },
110
+ {
111
+ fullName: 'Bonk',
112
+ category: ['Meme'],
113
+ symbol: '1MBONK-PERP',
114
+ baseAssetSymbol: '1MBONK',
115
+ marketIndex: 4,
116
+ oracle: new PublicKey('8ihFLu5FimgTQ1Unh4dVyEHUGodJ5gJQCrQf4KUVB9bN'),
117
+ launchTs: 1677690149000,
118
+ oracleSource: OracleSource.PYTH_1M,
119
+ },
110
120
  ];
111
121
 
112
122
  export const PerpMarkets: { [key in DriftEnv]: PerpMarketConfig[] } = {
package/src/dlob/DLOB.ts CHANGED
@@ -450,8 +450,9 @@ export class DLOB {
450
450
  }
451
451
 
452
452
  public getOrder(orderId: number, userAccount: PublicKey): Order | undefined {
453
+ const orderSignature = getOrderSignature(orderId, userAccount);
453
454
  for (const nodeList of this.getNodeLists()) {
454
- const node = nodeList.get(orderId, userAccount);
455
+ const node = nodeList.get(orderSignature);
455
456
  if (node) {
456
457
  return node.order;
457
458
  }
@@ -1383,18 +1384,29 @@ export class DLOB {
1383
1384
  const bidSlot = bidNode.order.slot.add(
1384
1385
  new BN(bidNode.order.auctionDuration)
1385
1386
  );
1386
- if (askSlot.lte(bidSlot) && !bidNode.order.postOnly) {
1387
+
1388
+ if (bidNode.order.postOnly && askNode.order.postOnly) {
1389
+ return undefined;
1390
+ } else if (bidNode.order.postOnly) {
1391
+ return {
1392
+ takerNode: askNode,
1393
+ makerNode: bidNode,
1394
+ };
1395
+ } else if (askNode.order.postOnly) {
1387
1396
  return {
1388
1397
  takerNode: bidNode,
1389
1398
  makerNode: askNode,
1390
1399
  };
1391
- } else if (bidSlot.lte(askSlot) && !askNode.order.postOnly) {
1400
+ } else if (askSlot.lte(bidSlot)) {
1401
+ return {
1402
+ takerNode: bidNode,
1403
+ makerNode: askNode,
1404
+ };
1405
+ } else {
1392
1406
  return {
1393
1407
  takerNode: askNode,
1394
1408
  makerNode: bidNode,
1395
1409
  };
1396
- } else {
1397
- return undefined;
1398
1410
  }
1399
1411
  }
1400
1412
 
@@ -146,11 +146,8 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
146
146
  return this.nodeMap.has(getOrderSignature(order.orderId, userAccount));
147
147
  }
148
148
 
149
- public get(
150
- orderId: number,
151
- userAccount: PublicKey
152
- ): DLOBNodeMap[NodeType] | undefined {
153
- return this.nodeMap.get(getOrderSignature(orderId, userAccount));
149
+ public get(orderSignature: string): DLOBNodeMap[NodeType] | undefined {
150
+ return this.nodeMap.get(orderSignature);
154
151
  }
155
152
 
156
153
  public print(): void {
@@ -14,10 +14,14 @@ export function getOracleClient(
14
14
  return new PythClient(connection);
15
15
  }
16
16
 
17
- if (isVariant(oracleSource, 'pyth1000')) {
17
+ if (isVariant(oracleSource, 'pyth1K')) {
18
18
  return new PythClient(connection, new BN(1000));
19
19
  }
20
20
 
21
+ if (isVariant(oracleSource, 'pyth1M')) {
22
+ return new PythClient(connection, new BN(1000000));
23
+ }
24
+
21
25
  // if (isVariant(oracleSource, 'switchboard')) {
22
26
  // return new SwitchboardClient(connection);
23
27
  // }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.18.0",
2
+ "version": "2.19.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -6237,14 +6237,17 @@
6237
6237
  {
6238
6238
  "name": "Pyth"
6239
6239
  },
6240
- {
6241
- "name": "Pyth1000"
6242
- },
6243
6240
  {
6244
6241
  "name": "Switchboard"
6245
6242
  },
6246
6243
  {
6247
6244
  "name": "QuoteAsset"
6245
+ },
6246
+ {
6247
+ "name": "Pyth1K"
6248
+ },
6249
+ {
6250
+ "name": "Pyth1M"
6248
6251
  }
6249
6252
  ]
6250
6253
  }
package/src/math/amm.ts CHANGED
@@ -11,7 +11,6 @@ import {
11
11
  MARGIN_PRECISION,
12
12
  PRICE_DIV_PEG,
13
13
  PERCENTAGE_PRECISION,
14
- BASE_PRECISION,
15
14
  DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
16
15
  TWO,
17
16
  } from '../constants/numericConstants';
@@ -362,10 +361,12 @@ export function calculateInventoryScale(
362
361
  maxSpread: number
363
362
  ): number {
364
363
  if (baseAssetAmountWithAmm.eq(ZERO)) {
365
- return 0;
364
+ return 1;
366
365
  }
367
366
 
368
- const defaultLargeBidAskFactor = BID_ASK_SPREAD_PRECISION.mul(new BN(10));
367
+ const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
368
+ new BN(10)
369
+ );
369
370
  // inventory skew
370
371
  const [openBids, openAsks] = calculateMarketOpenBidAsk(
371
372
  baseAssetReserve,
@@ -373,34 +374,32 @@ export function calculateInventoryScale(
373
374
  maxBaseAssetReserve
374
375
  );
375
376
 
376
- const minSideLiquidity = BN.max(
377
- new BN(1),
378
- BN.min(openBids.abs(), openAsks.abs())
377
+ const minSideLiquidity = BN.min(openBids.abs(), openAsks.abs());
378
+
379
+ const inventoryScaleBN = BN.min(
380
+ baseAssetAmountWithAmm
381
+ .mul(PERCENTAGE_PRECISION)
382
+ .div(BN.max(minSideLiquidity, ONE))
383
+ .abs(),
384
+ PERCENTAGE_PRECISION
379
385
  );
380
386
 
381
387
  const inventoryScaleMaxBN = BN.max(
382
- defaultLargeBidAskFactor,
383
- new BN(maxSpread / 2)
388
+ MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
389
+ new BN(maxSpread)
384
390
  .mul(BID_ASK_SPREAD_PRECISION)
385
391
  .div(new BN(Math.max(directionalSpread, 1)))
386
392
  );
387
393
 
388
- const inventoryScaleBN = baseAssetAmountWithAmm
389
- .mul(BN.max(baseAssetAmountWithAmm.abs(), BASE_PRECISION))
390
- .div(BASE_PRECISION)
391
- .mul(defaultLargeBidAskFactor)
392
- .div(minSideLiquidity)
393
- .abs();
394
-
395
- const inventoryScale =
396
- BN.min(inventoryScaleMaxBN, inventoryScaleBN).toNumber() /
397
- BID_ASK_SPREAD_PRECISION.toNumber();
398
-
399
- const inventoryScaleMax =
400
- inventoryScaleMaxBN.toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
401
- const inventorySpreadScale = Math.min(inventoryScaleMax, 1 + inventoryScale);
394
+ const inventoryScaleCapped =
395
+ BN.min(
396
+ inventoryScaleMaxBN,
397
+ BID_ASK_SPREAD_PRECISION.add(
398
+ inventoryScaleMaxBN.mul(inventoryScaleBN).div(PERCENTAGE_PRECISION)
399
+ )
400
+ ).toNumber() / BID_ASK_SPREAD_PRECISION.toNumber();
402
401
 
403
- return inventorySpreadScale;
402
+ return inventoryScaleCapped;
404
403
  }
405
404
 
406
405
  export function calculateEffectiveLeverage(
package/src/math/trade.ts CHANGED
@@ -782,7 +782,7 @@ export function calculateEstimatedSpotEntryPrice(
782
782
  cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
783
783
  cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
784
784
 
785
- worstPrice = dlobLimitOrder;
785
+ worstPrice = dlobLimitOrderPrice;
786
786
 
787
787
  dlobLimitOrder = dlobLimitOrders.next().value;
788
788
  } else {
@@ -9,26 +9,32 @@ export class SerumSubscriber {
9
9
  connection: Connection;
10
10
  programId: PublicKey;
11
11
  marketAddress: PublicKey;
12
- accountLoader: BulkAccountLoader;
12
+ subscriptionType: 'polling' | 'websocket';
13
+ accountLoader: BulkAccountLoader | undefined;
13
14
  market: Market;
14
15
 
15
16
  subscribed: boolean;
16
17
 
17
18
  asksAddress: PublicKey;
18
19
  asks: Orderbook;
19
- asksCallbackId: string;
20
+ asksCallbackId: string | number;
20
21
  lastAsksSlot: number;
21
22
 
22
23
  bidsAddress: PublicKey;
23
24
  bids: Orderbook;
24
- bidsCallbackId: string;
25
+ bidsCallbackId: string | number;
25
26
  lastBidsSlot: number;
26
27
 
27
28
  public constructor(config: SerumMarketSubscriberConfig) {
28
29
  this.connection = config.connection;
29
30
  this.programId = config.programId;
30
31
  this.marketAddress = config.marketAddress;
31
- this.accountLoader = config.accountSubscription.accountLoader;
32
+ if (config.accountSubscription.type === 'polling') {
33
+ this.subscriptionType = 'polling';
34
+ this.accountLoader = config.accountSubscription.accountLoader;
35
+ } else {
36
+ this.subscriptionType = 'websocket';
37
+ }
32
38
  }
33
39
 
34
40
  public async subscribe(): Promise<void> {
@@ -46,24 +52,44 @@ export class SerumSubscriber {
46
52
  this.asksAddress = this.market.asksAddress;
47
53
  this.asks = await this.market.loadAsks(this.connection);
48
54
 
49
- this.asksCallbackId = await this.accountLoader.addAccount(
50
- this.asksAddress,
51
- (buffer, slot) => {
52
- this.lastAsksSlot = slot;
53
- this.asks = Orderbook.decode(this.market, buffer);
54
- }
55
- );
55
+ if (this.subscriptionType === 'websocket') {
56
+ this.asksCallbackId = this.connection.onAccountChange(
57
+ this.asksAddress,
58
+ (accountInfo, ctx) => {
59
+ this.lastAsksSlot = ctx.slot;
60
+ this.asks = Orderbook.decode(this.market, accountInfo.data);
61
+ }
62
+ );
63
+ } else {
64
+ this.asksCallbackId = await this.accountLoader.addAccount(
65
+ this.asksAddress,
66
+ (buffer, slot) => {
67
+ this.lastAsksSlot = slot;
68
+ this.asks = Orderbook.decode(this.market, buffer);
69
+ }
70
+ );
71
+ }
56
72
 
57
73
  this.bidsAddress = this.market.bidsAddress;
58
74
  this.bids = await this.market.loadBids(this.connection);
59
75
 
60
- this.bidsCallbackId = await this.accountLoader.addAccount(
61
- this.bidsAddress,
62
- (buffer, slot) => {
63
- this.lastBidsSlot = slot;
64
- this.bids = Orderbook.decode(this.market, buffer);
65
- }
66
- );
76
+ if (this.subscriptionType === 'websocket') {
77
+ this.bidsCallbackId = this.connection.onAccountChange(
78
+ this.bidsAddress,
79
+ (accountInfo, ctx) => {
80
+ this.lastBidsSlot = ctx.slot;
81
+ this.bids = Orderbook.decode(this.market, accountInfo.data);
82
+ }
83
+ );
84
+ } else {
85
+ this.bidsCallbackId = await this.accountLoader.addAccount(
86
+ this.bidsAddress,
87
+ (buffer, slot) => {
88
+ this.lastBidsSlot = slot;
89
+ this.bids = Orderbook.decode(this.market, buffer);
90
+ }
91
+ );
92
+ }
67
93
 
68
94
  this.subscribed = true;
69
95
  }
@@ -91,8 +117,24 @@ export class SerumSubscriber {
91
117
  return;
92
118
  }
93
119
 
94
- this.accountLoader.removeAccount(this.asksAddress, this.asksCallbackId);
95
- this.accountLoader.removeAccount(this.bidsAddress, this.bidsCallbackId);
120
+ // remove listeners
121
+ if (this.subscriptionType === 'websocket') {
122
+ await this.connection.removeAccountChangeListener(
123
+ this.asksCallbackId as number
124
+ );
125
+ await this.connection.removeAccountChangeListener(
126
+ this.bidsCallbackId as number
127
+ );
128
+ } else {
129
+ this.accountLoader.removeAccount(
130
+ this.asksAddress,
131
+ this.asksCallbackId as string
132
+ );
133
+ this.accountLoader.removeAccount(
134
+ this.bidsAddress,
135
+ this.bidsCallbackId as string
136
+ );
137
+ }
96
138
 
97
139
  this.subscribed = false;
98
140
  }
@@ -5,9 +5,13 @@ export type SerumMarketSubscriberConfig = {
5
5
  connection: Connection;
6
6
  programId: PublicKey;
7
7
  marketAddress: PublicKey;
8
- accountSubscription: {
9
- // enables use to add web sockets in the future
10
- type: 'polling';
11
- accountLoader: BulkAccountLoader;
12
- };
8
+ accountSubscription:
9
+ | {
10
+ // enables use to add web sockets in the future
11
+ type: 'polling';
12
+ accountLoader: BulkAccountLoader;
13
+ }
14
+ | {
15
+ type: 'websocket';
16
+ };
13
17
  };
package/src/types.ts CHANGED
@@ -76,7 +76,8 @@ export class DepositDirection {
76
76
 
77
77
  export class OracleSource {
78
78
  static readonly PYTH = { pyth: {} };
79
- static readonly PYTH_1000 = { pyth1000: {} };
79
+ static readonly PYTH_1K = { pyth1K: {} };
80
+ static readonly PYTH_1M = { pyth1M: {} };
80
81
  // static readonly SWITCHBOARD = { switchboard: {} };
81
82
  static readonly QUOTE_ASSET = { quoteAsset: {} };
82
83
  }
package/tests/amm/test.ts CHANGED
@@ -6,8 +6,10 @@ import {
6
6
  QUOTE_PRECISION,
7
7
  calculateSpreadBN,
8
8
  ZERO,
9
+ ONE,
9
10
  calculateLiveOracleStd,
10
11
  calculateLiveOracleTwap,
12
+ calculateInventoryScale,
11
13
  } from '../../src';
12
14
  import { mockPerpMarkets } from '../dlob/helpers';
13
15
 
@@ -36,6 +38,176 @@ class AMMSpreadTerms {
36
38
  }
37
39
 
38
40
  describe('AMM Tests', () => {
41
+ it('Spread Maths', () => {
42
+ let iscale = calculateInventoryScale(
43
+ ZERO,
44
+ AMM_RESERVE_PRECISION,
45
+ AMM_RESERVE_PRECISION.div(new BN(2)),
46
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
47
+ 250,
48
+ 30000,
49
+ );
50
+ assert(iscale == 1);
51
+
52
+ iscale = calculateInventoryScale(
53
+ ONE,
54
+ AMM_RESERVE_PRECISION,
55
+ AMM_RESERVE_PRECISION.div(new BN(2)),
56
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
57
+ 250,
58
+ 30000,
59
+ );
60
+ assert(iscale == 1);
61
+
62
+ let baa = new BN(1000);
63
+ iscale = calculateInventoryScale(
64
+ baa,
65
+ AMM_RESERVE_PRECISION.add(baa),
66
+ AMM_RESERVE_PRECISION.div(new BN(2)),
67
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
68
+ 250,
69
+ 30000,
70
+ );
71
+ console.log(iscale);
72
+ assert(iscale == 1.00024);
73
+
74
+ baa = new BN(100000);
75
+ iscale = calculateInventoryScale(
76
+ baa,
77
+ AMM_RESERVE_PRECISION.add(baa),
78
+ AMM_RESERVE_PRECISION.div(new BN(2)),
79
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
80
+ 250,
81
+ 30000,
82
+ );
83
+ console.log(iscale);
84
+ assert(iscale == 1.024);
85
+
86
+ baa = new BN(1000000);
87
+ iscale = calculateInventoryScale(
88
+ baa,
89
+ AMM_RESERVE_PRECISION.add(baa),
90
+ AMM_RESERVE_PRECISION.div(new BN(2)),
91
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
92
+ 250,
93
+ 30000,
94
+ );
95
+ console.log(iscale);
96
+ assert(iscale == 1.24048);
97
+
98
+ baa = new BN(10000000); // 2%
99
+ iscale = calculateInventoryScale(
100
+ baa,
101
+ AMM_RESERVE_PRECISION.add(baa),
102
+ AMM_RESERVE_PRECISION.div(new BN(2)),
103
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
104
+ 250,
105
+ 30000,
106
+ );
107
+ console.log(iscale);
108
+ assert(iscale == 3.44896);
109
+
110
+ baa = new BN(50000000); // 10%
111
+ iscale = calculateInventoryScale(
112
+ baa,
113
+ AMM_RESERVE_PRECISION.add(baa),
114
+ AMM_RESERVE_PRECISION.div(new BN(2)),
115
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
116
+ 250,
117
+ 30000,
118
+ );
119
+ console.log(iscale);
120
+ assert(iscale == 14.33332);
121
+
122
+
123
+ baa = AMM_RESERVE_PRECISION.div(new BN(4)); // 50%
124
+ iscale = calculateInventoryScale(
125
+ baa,
126
+ AMM_RESERVE_PRECISION.add(baa),
127
+ AMM_RESERVE_PRECISION.div(new BN(2)),
128
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
129
+ 250,
130
+ 30000,
131
+ );
132
+ console.log(iscale);
133
+ assert(iscale == 120); //100%
134
+
135
+ baa = AMM_RESERVE_PRECISION.div(new BN(4)); // 50%
136
+ iscale = calculateInventoryScale(
137
+ baa,
138
+ AMM_RESERVE_PRECISION.add(baa),
139
+ AMM_RESERVE_PRECISION.div(new BN(2)),
140
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
141
+ 250,
142
+ 30000 * 2,
143
+ );
144
+ console.log(iscale);
145
+ assert(iscale == 120 * 2); //100%
146
+
147
+ baa = AMM_RESERVE_PRECISION.div(new BN(5)); // <50%
148
+ iscale = calculateInventoryScale(
149
+ baa,
150
+ AMM_RESERVE_PRECISION.add(baa),
151
+ AMM_RESERVE_PRECISION.div(new BN(2)),
152
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
153
+ 250,
154
+ 30000 * 2,
155
+ );
156
+ assert(iscale == 160.99984);
157
+
158
+ baa = new BN(855329058);
159
+ iscale = calculateInventoryScale(
160
+ baa,
161
+ AMM_RESERVE_PRECISION.add(baa),
162
+ AMM_RESERVE_PRECISION.div(new BN(2)),
163
+ AMM_RESERVE_PRECISION,
164
+ 250,
165
+ 30000,
166
+ ); // >100%
167
+ assert(iscale == 120);
168
+ assert(250*iscale == 30000);
169
+
170
+ iscale = calculateInventoryScale(
171
+ baa,
172
+ AMM_RESERVE_PRECISION.add(baa), // ~85%
173
+ AMM_RESERVE_PRECISION.div(new BN(2)),
174
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
175
+ 250,
176
+ 30000,
177
+ );
178
+ assert(iscale == 120);
179
+ assert(250*iscale == 30000);
180
+
181
+ baa = new BN(-855329058); // ~85%
182
+ iscale = calculateInventoryScale(
183
+ baa,
184
+ AMM_RESERVE_PRECISION.add(baa),
185
+ AMM_RESERVE_PRECISION.div(new BN(2)),
186
+ AMM_RESERVE_PRECISION.mul(new BN(3)).div(new BN(2)),
187
+ 250,
188
+ 30000,
189
+ );
190
+ assert(iscale == 120);
191
+ assert(250*iscale == 30000);
192
+
193
+ // 'bonk' scale
194
+ iscale = calculateInventoryScale(
195
+ new BN('30228000000000000'),
196
+ new BN('2496788386034912600'),
197
+ new BN('2443167585342470000'),
198
+ new BN('2545411471321696000'),
199
+ 3500,
200
+ 100000,
201
+ );
202
+ console.log(iscale);
203
+ console.log(3500*iscale/1e6);
204
+ assert(iscale == 18.762285);
205
+ assert(3500*iscale/1e6 == 0.06566799749999999); //6.5%
206
+
207
+
208
+
209
+ });
210
+
39
211
  it('Various Spreads', () => {
40
212
  const baseSpread: number = 0.025 * 1e6;
41
213
  const lastOracleReservePriceSpreadPct: BN = ZERO;
@@ -150,9 +322,9 @@ describe('AMM Tests', () => {
150
322
 
151
323
  console.log(terms2);
152
324
  assert(terms2.effectiveLeverageCapped >= 1.0002);
153
- assert(terms2.inventorySpreadScale == 10);
154
- assert(terms2.longSpread == 798);
155
- assert(terms2.shortSpread == 46702);
325
+ assert(terms2.inventorySpreadScale == 1.73492);
326
+ assert(terms2.longSpread == 4262);
327
+ assert(terms2.shortSpread == 43238);
156
328
  });
157
329
 
158
330
  it('Corner Case Spreads', () => {
@@ -183,8 +355,8 @@ describe('AMM Tests', () => {
183
355
 
184
356
  console.log(terms2);
185
357
  assert(terms2.effectiveLeverageCapped <= 1.000001);
186
- assert(terms2.inventorySpreadScale == 1.117371);
187
- assert(terms2.longSpread == 1263);
358
+ assert(terms2.inventorySpreadScale == 1.013527);
359
+ assert(terms2.longSpread == 1146);
188
360
  assert(terms2.shortSpread == 6686);
189
361
  });
190
362
 
@@ -5207,7 +5207,8 @@ describe('DLOB Spot Tests', () => {
5207
5207
  expect(nodesToFillAfter[1].makerNode?.order?.orderId).to.equal(3);
5208
5208
  });
5209
5209
 
5210
- it('Test limit orders skipping more recent post onlys', () => {
5210
+ // add back if dlob checks limit order age again
5211
+ it.skip('Test limit orders skipping more recent post onlys', () => {
5211
5212
  const vAsk = new BN(15);
5212
5213
  const vBid = new BN(8);
5213
5214