@drift-labs/sdk 0.2.0-master.3 → 0.2.0-master.4

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/src/math/amm.ts CHANGED
@@ -68,6 +68,7 @@ export function calculateOptimalPegAndBudget(
68
68
  let newOptimalPeg: BN;
69
69
  let newBudget: BN;
70
70
  const targetPriceGap = markPriceBefore.sub(targetPrice);
71
+
71
72
  if (targetPriceGap.abs().gt(maxPriceSpread)) {
72
73
  const markAdj = targetPriceGap.abs().sub(maxPriceSpread);
73
74
 
@@ -411,10 +412,8 @@ export function calculateSpread(
411
412
  direction: PositionDirection,
412
413
  oraclePriceData: OraclePriceData
413
414
  ): number {
414
- let spread = amm.baseSpread / 2;
415
-
416
415
  if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
417
- return spread;
416
+ return amm.baseSpread / 2;
418
417
  }
419
418
 
420
419
  const markPrice = calculatePrice(
@@ -435,54 +434,25 @@ export function calculateSpread(
435
434
  .mul(BID_ASK_SPREAD_PRECISION)
436
435
  .div(markPrice);
437
436
 
438
- // oracle retreat
439
- if (
440
- (isVariant(direction, 'long') && targetMarkSpreadPct.lt(ZERO)) ||
441
- (isVariant(direction, 'short') && targetMarkSpreadPct.gt(ZERO))
442
- ) {
443
- spread = Math.max(
444
- spread,
445
- targetMarkSpreadPct.abs().toNumber() + confIntervalPct.abs().toNumber()
446
- );
447
- }
448
-
449
- // inventory skew
450
- const MAX_INVENTORY_SKEW = 5;
451
- if (
452
- (amm.netBaseAssetAmount.gt(ZERO) && isVariant(direction, 'long')) ||
453
- (amm.netBaseAssetAmount.lt(ZERO) && isVariant(direction, 'short')) ||
454
- amm.totalFeeMinusDistributions.eq(ZERO)
455
- ) {
456
- const netCostBasis = amm.quoteAssetAmountLong.sub(
457
- amm.quoteAssetAmountShort
458
- );
459
- const netBaseAssetValue = amm.quoteAssetReserve
460
- .sub(amm.terminalQuoteAssetReserve)
461
- .mul(amm.pegMultiplier)
462
- .div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
463
-
464
- const localBaseAssetValue = amm.netBaseAssetAmount
465
- .mul(markPrice)
466
- .div(AMM_TO_QUOTE_PRECISION_RATIO.mul(MARK_PRICE_PRECISION));
467
- const netPnl = netBaseAssetValue.sub(netCostBasis);
468
- const localPnl = localBaseAssetValue.sub(netCostBasis);
469
-
470
- let effectiveLeverage = MAX_INVENTORY_SKEW;
471
- if (amm.totalFeeMinusDistributions.gt(ZERO)) {
472
- effectiveLeverage =
473
- localPnl.sub(netPnl).toNumber() /
474
- (amm.totalFeeMinusDistributions.toNumber() + 1);
475
- }
437
+ const [longSpread, shortSpread] = calculateSpreadBN(
438
+ amm.baseSpread,
439
+ targetMarkSpreadPct,
440
+ confIntervalPct,
441
+ amm.maxSpread,
442
+ amm.quoteAssetReserve,
443
+ amm.terminalQuoteAssetReserve,
444
+ amm.pegMultiplier,
445
+ amm.netBaseAssetAmount,
446
+ markPrice,
447
+ amm.totalFeeMinusDistributions
448
+ );
476
449
 
477
- let spreadScale = Math.min(MAX_INVENTORY_SKEW, 1 + effectiveLeverage);
478
- const maxTargetSpread = BID_ASK_SPREAD_PRECISION.toNumber() / 50; // 2%
479
- // cap the scale to attempt to only scale up to maxTargetSpread
480
- // always let the oracle retreat methods go through 100%
481
- if (spreadScale * spread > maxTargetSpread) {
482
- spreadScale = Math.max(1.05, maxTargetSpread / spread);
483
- }
450
+ let spread: number;
484
451
 
485
- spread *= spreadScale;
452
+ if (isVariant(direction, 'long')) {
453
+ spread = longSpread;
454
+ } else {
455
+ spread = shortSpread;
486
456
  }
487
457
 
488
458
  return spread;
@@ -2,7 +2,11 @@ import { isVariant, Order } from '../types';
2
2
  import { BN, ZERO } from '../.';
3
3
 
4
4
  export function isAuctionComplete(order: Order, slot: number): boolean {
5
- return new BN(slot).sub(order.slot).gte(new BN(order.auctionDuration));
5
+ if (order.auctionDuration === 0) {
6
+ return true;
7
+ }
8
+
9
+ return new BN(slot).sub(order.slot).gt(new BN(order.auctionDuration));
6
10
  }
7
11
 
8
12
  export function getAuctionPrice(order: Order, slot: number): BN {
@@ -1,9 +1,10 @@
1
1
  import { ClearingHouseUser } from '../clearingHouseUser';
2
- import { isOneOfVariant, isVariant, Order } from '../types';
2
+ import { isOneOfVariant, isVariant, MarketAccount, Order } from '../types';
3
3
  import { ZERO, TWO } from '../constants/numericConstants';
4
4
  import { BN } from '@project-serum/anchor';
5
5
  import { OraclePriceData } from '../oracles/types';
6
- import { getAuctionPrice } from './auction';
6
+ import { getAuctionPrice, isAuctionComplete } from './auction';
7
+ import { calculateAskPrice, calculateBidPrice } from './market';
7
8
 
8
9
  export function isOrderRiskIncreasing(
9
10
  user: ClearingHouseUser,
@@ -120,6 +121,7 @@ export function standardizeBaseAssetAmount(
120
121
 
121
122
  export function getLimitPrice(
122
123
  order: Order,
124
+ market: MarketAccount,
123
125
  oraclePriceData: OraclePriceData,
124
126
  slot: number
125
127
  ): BN {
@@ -127,7 +129,19 @@ export function getLimitPrice(
127
129
  if (!order.oraclePriceOffset.eq(ZERO)) {
128
130
  limitPrice = oraclePriceData.price.add(order.oraclePriceOffset);
129
131
  } else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
130
- limitPrice = getAuctionPrice(order, slot);
132
+ if (isAuctionComplete(order, slot)) {
133
+ limitPrice = getAuctionPrice(order, slot);
134
+ } else if (!order.price.eq(ZERO)) {
135
+ limitPrice = order.price;
136
+ } else if (isVariant(order.direction, 'long')) {
137
+ const askPrice = calculateAskPrice(market, oraclePriceData);
138
+ const delta = askPrice.div(new BN(market.amm.maxSlippageRatio));
139
+ limitPrice = askPrice.add(delta);
140
+ } else {
141
+ const bidPrice = calculateBidPrice(market, oraclePriceData);
142
+ const delta = bidPrice.div(new BN(market.amm.maxSlippageRatio));
143
+ limitPrice = bidPrice.sub(delta);
144
+ }
131
145
  } else {
132
146
  limitPrice = order.price;
133
147
  }
package/src/math/trade.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MarketAccount, PositionDirection, SwapDirection } from '../types';
1
+ import { MarketAccount, PositionDirection } from '../types';
2
2
  import { BN } from '@project-serum/anchor';
3
3
  import { assert } from '../assert/assert';
4
4
  import {
@@ -78,28 +78,20 @@ export function calculateTradeSlippage(
78
78
  if (amount.eq(ZERO)) {
79
79
  return [ZERO, ZERO, oldPrice, oldPrice];
80
80
  }
81
- const [acquiredBase, acquiredQuote] = calculateTradeAcquiredAmounts(
82
- direction,
83
- amount,
84
- market,
85
- inputAssetType,
86
- oraclePriceData,
87
- useSpread
88
- );
89
-
90
- const swapDirection = isVariant(direction, 'long')
91
- ? SwapDirection.REMOVE
92
- : SwapDirection.ADD;
93
- const quoteAssetAmountAcquired = calculateQuoteAssetAmountSwapped(
94
- acquiredQuote.abs(),
95
- market.amm.pegMultiplier,
96
- swapDirection
97
- );
81
+ const [acquiredBaseReserve, acquiredQuoteReserve, acquiredQuoteAssetAmount] =
82
+ calculateTradeAcquiredAmounts(
83
+ direction,
84
+ amount,
85
+ market,
86
+ inputAssetType,
87
+ oraclePriceData,
88
+ useSpread
89
+ );
98
90
 
99
- const entryPrice = quoteAssetAmountAcquired
91
+ const entryPrice = acquiredQuoteAssetAmount
100
92
  .mul(AMM_TO_QUOTE_PRECISION_RATIO)
101
93
  .mul(MARK_PRICE_PRECISION)
102
- .div(acquiredBase.abs());
94
+ .div(acquiredBaseReserve.abs());
103
95
 
104
96
  let amm: Parameters<typeof calculateAmmReservesAfterSwap>[0];
105
97
  if (useSpread && market.amm.baseSpread > 0) {
@@ -116,8 +108,8 @@ export function calculateTradeSlippage(
116
108
  }
117
109
 
118
110
  const newPrice = calculatePrice(
119
- amm.baseAssetReserve.sub(acquiredBase),
120
- amm.quoteAssetReserve.sub(acquiredQuote),
111
+ amm.baseAssetReserve.sub(acquiredBaseReserve),
112
+ amm.quoteAssetReserve.sub(acquiredQuoteReserve),
121
113
  amm.pegMultiplier
122
114
  );
123
115
 
@@ -159,9 +151,9 @@ export function calculateTradeAcquiredAmounts(
159
151
  inputAssetType: AssetType = 'quote',
160
152
  oraclePriceData: OraclePriceData,
161
153
  useSpread = true
162
- ): [BN, BN] {
154
+ ): [BN, BN, BN] {
163
155
  if (amount.eq(ZERO)) {
164
- return [ZERO, ZERO];
156
+ return [ZERO, ZERO, ZERO];
165
157
  }
166
158
 
167
159
  const swapDirection = getSwapDirection(inputAssetType, direction);
@@ -185,7 +177,13 @@ export function calculateTradeAcquiredAmounts(
185
177
 
186
178
  const acquiredBase = amm.baseAssetReserve.sub(newBaseAssetReserve);
187
179
  const acquiredQuote = amm.quoteAssetReserve.sub(newQuoteAssetReserve);
188
- return [acquiredBase, acquiredQuote];
180
+ const acquiredQuoteAssetamount = calculateQuoteAssetAmountSwapped(
181
+ acquiredQuote.abs(),
182
+ amm.pegMultiplier,
183
+ swapDirection
184
+ );
185
+
186
+ return [acquiredBase, acquiredQuote, acquiredQuoteAssetamount];
189
187
  }
190
188
 
191
189
  /**
@@ -1,154 +1,33 @@
1
- import {
2
- OrderParams,
3
- OrderTriggerCondition,
4
- OrderType,
5
- PositionDirection,
6
- } from './types';
1
+ import { OptionalOrderParams, OrderTriggerCondition, OrderType } from './types';
7
2
  import { BN } from '@project-serum/anchor';
8
- import { ZERO } from './constants/numericConstants';
9
3
 
10
4
  export function getLimitOrderParams(
11
- marketIndex: BN,
12
- direction: PositionDirection,
13
- baseAssetAmount: BN,
14
- price: BN,
15
- reduceOnly: boolean,
16
- discountToken = false,
17
- referrer = false,
18
- userOrderId = 0,
19
- postOnly = false,
20
- oraclePriceOffset = ZERO,
21
- immediateOrCancel = false
22
- ): OrderParams {
23
- return {
24
- orderType: OrderType.LIMIT,
25
- userOrderId,
26
- marketIndex,
27
- direction,
28
- quoteAssetAmount: ZERO,
29
- baseAssetAmount,
30
- price,
31
- reduceOnly,
32
- postOnly,
33
- immediateOrCancel,
34
- positionLimit: ZERO,
35
- padding0: true,
36
- padding1: ZERO,
37
- optionalAccounts: {
38
- discountToken,
39
- referrer,
40
- },
41
- triggerCondition: OrderTriggerCondition.ABOVE,
42
- triggerPrice: ZERO,
43
- oraclePriceOffset,
44
- };
5
+ params: Omit<OptionalOrderParams, 'orderType'> & { price: BN }
6
+ ): OptionalOrderParams {
7
+ return Object.assign({}, params, { orderType: OrderType.LIMIT });
45
8
  }
46
9
 
47
10
  export function getTriggerMarketOrderParams(
48
- marketIndex: BN,
49
- direction: PositionDirection,
50
- baseAssetAmount: BN,
51
- triggerPrice: BN,
52
- triggerCondition: OrderTriggerCondition,
53
- reduceOnly: boolean,
54
- discountToken = false,
55
- referrer = false,
56
- userOrderId = 0
57
- ): OrderParams {
58
- return {
59
- orderType: OrderType.TRIGGER_MARKET,
60
- userOrderId,
61
- marketIndex,
62
- direction,
63
- quoteAssetAmount: ZERO,
64
- baseAssetAmount,
65
- price: ZERO,
66
- reduceOnly,
67
- postOnly: false,
68
- immediateOrCancel: false,
69
- positionLimit: ZERO,
70
- padding0: true,
71
- padding1: ZERO,
72
- optionalAccounts: {
73
- discountToken,
74
- referrer,
75
- },
76
- triggerCondition,
77
- triggerPrice,
78
- oraclePriceOffset: ZERO,
79
- };
11
+ params: Omit<OptionalOrderParams, 'orderType'> & {
12
+ triggerCondition: OrderTriggerCondition;
13
+ triggerPrice: BN;
14
+ }
15
+ ): OptionalOrderParams {
16
+ return Object.assign({}, params, { orderType: OrderType.TRIGGER_MARKET });
80
17
  }
81
18
 
82
19
  export function getTriggerLimitOrderParams(
83
- marketIndex: BN,
84
- direction: PositionDirection,
85
- baseAssetAmount: BN,
86
- price: BN,
87
- triggerPrice: BN,
88
- triggerCondition: OrderTriggerCondition,
89
- reduceOnly: boolean,
90
- discountToken = false,
91
- referrer = false,
92
- userOrderId = 0
93
- ): OrderParams {
94
- return {
95
- orderType: OrderType.TRIGGER_LIMIT,
96
- userOrderId,
97
- marketIndex,
98
- direction,
99
- quoteAssetAmount: ZERO,
100
- baseAssetAmount,
101
- price,
102
- reduceOnly,
103
- postOnly: false,
104
- immediateOrCancel: false,
105
- positionLimit: ZERO,
106
- padding0: true,
107
- padding1: ZERO,
108
- optionalAccounts: {
109
- discountToken,
110
- referrer,
111
- },
112
- triggerCondition,
113
- triggerPrice,
114
- oraclePriceOffset: ZERO,
115
- };
20
+ params: Omit<OptionalOrderParams, 'orderType'> & {
21
+ triggerCondition: OrderTriggerCondition;
22
+ triggerPrice: BN;
23
+ price: BN;
24
+ }
25
+ ): OptionalOrderParams {
26
+ return Object.assign({}, params, { orderType: OrderType.TRIGGER_LIMIT });
116
27
  }
117
28
 
118
29
  export function getMarketOrderParams(
119
- marketIndex: BN,
120
- direction: PositionDirection,
121
- quoteAssetAmount: BN,
122
- baseAssetAmount: BN,
123
- reduceOnly: boolean,
124
- price = ZERO,
125
- discountToken = false,
126
- referrer = false
127
- ): OrderParams {
128
- if (baseAssetAmount.eq(ZERO) && quoteAssetAmount.eq(ZERO)) {
129
- throw Error('baseAssetAmount or quoteAssetAmount must be zero');
130
- }
131
-
132
- return {
133
- orderType: OrderType.MARKET,
134
- userOrderId: 0,
135
- marketIndex,
136
- direction,
137
- quoteAssetAmount,
138
- baseAssetAmount,
139
- price,
140
- reduceOnly,
141
- postOnly: false,
142
- immediateOrCancel: false,
143
- positionLimit: ZERO,
144
- padding0: true,
145
- padding1: ZERO,
146
- optionalAccounts: {
147
- discountToken,
148
- referrer,
149
- },
150
- triggerCondition: OrderTriggerCondition.ABOVE,
151
- triggerPrice: ZERO,
152
- oraclePriceOffset: ZERO,
153
- };
30
+ params: Omit<OptionalOrderParams, 'orderType'>
31
+ ): OptionalOrderParams {
32
+ return Object.assign({}, params, { orderType: OrderType.MARKET });
154
33
  }
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PublicKey, Transaction } from '@solana/web3.js';
2
- import { BN } from '.';
2
+ import { BN, ZERO } from '.';
3
3
 
4
4
  // # Utility Types / Enums / Constants
5
5
  export class SwapDirection {
@@ -51,6 +51,19 @@ export class OrderAction {
51
51
  static readonly TRIGGER = { trigger: {} };
52
52
  }
53
53
 
54
+ export class OrderActionExplanation {
55
+ static readonly NONE = { none: {} };
56
+ static readonly BREACHED_MARGIN_REQUIREMENT = {
57
+ breachedMarginRequirement: {},
58
+ };
59
+ static readonly ORACLE_PRICE_BREACHED_LIMIT_PRICE = {
60
+ oraclePriceBreachedLimitPrice: {},
61
+ };
62
+ static readonly MARKET_ORDER_FILLED_TO_LIMIT_PRICE = {
63
+ marketOrderFilledToLimitPrice: {},
64
+ };
65
+ }
66
+
54
67
  export class OrderTriggerCondition {
55
68
  static readonly ABOVE = { above: {} };
56
69
  static readonly BELOW = { below: {} };
@@ -164,7 +177,10 @@ export type OrderRecord = {
164
177
  maker: PublicKey;
165
178
  takerOrder: Order;
166
179
  makerOrder: Order;
180
+ takerUnsettledPnl: BN;
181
+ makerUnsettledPnl: BN;
167
182
  action: OrderAction;
183
+ actionExplanation: OrderActionExplanation;
168
184
  filler: PublicKey;
169
185
  fillRecordId: BN;
170
186
  marketIndex: BN;
@@ -261,6 +277,8 @@ export type AMM = {
261
277
  lastMarkPriceTwapTs: BN;
262
278
  lastOraclePriceTwap: BN;
263
279
  lastOraclePriceTwapTs: BN;
280
+ lastOracleMarkSpreadPct: BN;
281
+ lastOracleConfPct: BN;
264
282
  oracle: PublicKey;
265
283
  oracleSource: OracleSource;
266
284
  fundingPeriod: BN;
@@ -275,6 +293,8 @@ export type AMM = {
275
293
  totalFee: BN;
276
294
  minimumQuoteAssetTradeSize: BN;
277
295
  baseAssetAmountStepSize: BN;
296
+ maxBaseAssetAmountRatio: number;
297
+ maxSlippageRatio: number;
278
298
  lastOraclePrice: BN;
279
299
  baseSpread: number;
280
300
  curveUpdateIntensity: number;
@@ -365,7 +385,6 @@ export type OrderParams = {
365
385
  orderType: OrderType;
366
386
  userOrderId: number;
367
387
  direction: PositionDirection;
368
- quoteAssetAmount: BN;
369
388
  baseAssetAmount: BN;
370
389
  price: BN;
371
390
  marketIndex: BN;
@@ -384,6 +403,39 @@ export type OrderParams = {
384
403
  };
385
404
  };
386
405
 
406
+ export type NecessaryOrderParams = {
407
+ orderType: OrderType;
408
+ marketIndex: BN;
409
+ baseAssetAmount: BN;
410
+ direction: PositionDirection;
411
+ };
412
+
413
+ export type OptionalOrderParams = {
414
+ [Property in keyof OrderParams]?: OrderParams[Property];
415
+ } & NecessaryOrderParams;
416
+
417
+ export const DefaultOrderParams = {
418
+ orderType: OrderType.MARKET,
419
+ userOrderId: 0,
420
+ direction: PositionDirection.LONG,
421
+ baseAssetAmount: ZERO,
422
+ price: ZERO,
423
+ marketIndex: ZERO,
424
+ reduceOnly: false,
425
+ postOnly: false,
426
+ immediateOrCancel: false,
427
+ triggerPrice: ZERO,
428
+ triggerCondition: OrderTriggerCondition.ABOVE,
429
+ positionLimit: ZERO,
430
+ oraclePriceOffset: ZERO,
431
+ padding0: ZERO,
432
+ padding1: ZERO,
433
+ optionalAccounts: {
434
+ discountToken: false,
435
+ referrer: false,
436
+ },
437
+ };
438
+
387
439
  export type MakerInfo = {
388
440
  maker: PublicKey;
389
441
  order: Order;