@drift-labs/sdk 0.2.0-master.2 → 0.2.0-master.5

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 { 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,24 +121,27 @@ 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 {
126
128
  let limitPrice;
127
129
  if (!order.oraclePriceOffset.eq(ZERO)) {
128
- const floatingPrice = oraclePriceData.price.add(order.oraclePriceOffset);
129
- if (order.postOnly) {
130
- limitPrice = isVariant(order.direction, 'long')
131
- ? BN.min(order.price, floatingPrice)
132
- : BN.max(order.price, floatingPrice);
130
+ limitPrice = oraclePriceData.price.add(order.oraclePriceOffset);
131
+ } else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
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);
133
140
  } else {
134
- limitPrice = floatingPrice;
141
+ const bidPrice = calculateBidPrice(market, oraclePriceData);
142
+ const delta = bidPrice.div(new BN(market.amm.maxSlippageRatio));
143
+ limitPrice = bidPrice.sub(delta);
135
144
  }
136
- } else if (
137
- isVariant(order.orderType, 'market') ||
138
- isVariant(order.orderType, 'triggerMarket')
139
- ) {
140
- limitPrice = getAuctionPrice(order, slot);
141
145
  } else {
142
146
  limitPrice = order.price;
143
147
  }
@@ -153,7 +153,11 @@ export function calculatePositionFundingPNL(
153
153
  }
154
154
 
155
155
  export function positionIsAvailable(position: UserPosition): boolean {
156
- return position.baseAssetAmount.eq(ZERO) && position.openOrders.eq(ZERO);
156
+ return (
157
+ position.baseAssetAmount.eq(ZERO) &&
158
+ position.openOrders.eq(ZERO) &&
159
+ position.unsettledPnl.eq(ZERO)
160
+ );
157
161
  }
158
162
 
159
163
  /**
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/orders.ts CHANGED
@@ -186,14 +186,7 @@ export function calculateAmountToTradeForLimit(
186
186
  'Cant calculate limit price for oracle offset oracle without OraclePriceData'
187
187
  );
188
188
  }
189
- const floatingPrice = oraclePriceData.price.add(order.oraclePriceOffset);
190
- if (order.postOnly) {
191
- limitPrice = isVariant(order.direction, 'long')
192
- ? BN.min(order.price, floatingPrice)
193
- : BN.max(order.price, floatingPrice);
194
- } else {
195
- limitPrice = floatingPrice;
196
- }
189
+ limitPrice = oraclePriceData.price.add(order.oraclePriceOffset);
197
190
  }
198
191
 
199
192
  const [maxAmountToTrade, direction] = calculateMaxBaseAssetAmountToTrade(
package/src/tx/utils.ts CHANGED
@@ -8,7 +8,7 @@ const COMPUTE_UNITS_DEFAULT = 200_000;
8
8
 
9
9
  export function wrapInTx(
10
10
  instruction: TransactionInstruction,
11
- computeUnits = 500_000 // TODO, requires less code change
11
+ computeUnits = 600_000 // TODO, requires less code change
12
12
  ): Transaction {
13
13
  const tx = new Transaction();
14
14
  if (computeUnits != COMPUTE_UNITS_DEFAULT) {
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 {
@@ -17,6 +17,11 @@ export class PositionDirection {
17
17
  static readonly SHORT = { short: {} };
18
18
  }
19
19
 
20
+ export class DepositDirection {
21
+ static readonly DEPOSIT = { deposit: {} };
22
+ static readonly WITHDRAW = { withdraw: {} };
23
+ }
24
+
20
25
  export class OracleSource {
21
26
  static readonly PYTH = { pyth: {} };
22
27
  static readonly SWITCHBOARD = { switchboard: {} };
@@ -51,6 +56,19 @@ export class OrderAction {
51
56
  static readonly TRIGGER = { trigger: {} };
52
57
  }
53
58
 
59
+ export class OrderActionExplanation {
60
+ static readonly NONE = { none: {} };
61
+ static readonly BREACHED_MARGIN_REQUIREMENT = {
62
+ breachedMarginRequirement: {},
63
+ };
64
+ static readonly ORACLE_PRICE_BREACHED_LIMIT_PRICE = {
65
+ oraclePriceBreachedLimitPrice: {},
66
+ };
67
+ static readonly MARKET_ORDER_FILLED_TO_LIMIT_PRICE = {
68
+ marketOrderFilledToLimitPrice: {},
69
+ };
70
+ }
71
+
54
72
  export class OrderTriggerCondition {
55
73
  static readonly ABOVE = { above: {} };
56
74
  static readonly BELOW = { below: {} };
@@ -92,6 +110,7 @@ export type DepositRecord = {
92
110
  };
93
111
  bankIndex: BN;
94
112
  amount: BN;
113
+ oraclePrice: BN;
95
114
  from?: PublicKey;
96
115
  to?: PublicKey;
97
116
  };
@@ -164,7 +183,10 @@ export type OrderRecord = {
164
183
  maker: PublicKey;
165
184
  takerOrder: Order;
166
185
  makerOrder: Order;
186
+ takerUnsettledPnl: BN;
187
+ makerUnsettledPnl: BN;
167
188
  action: OrderAction;
189
+ actionExplanation: OrderActionExplanation;
168
190
  filler: PublicKey;
169
191
  fillRecordId: BN;
170
192
  marketIndex: BN;
@@ -261,6 +283,8 @@ export type AMM = {
261
283
  lastMarkPriceTwapTs: BN;
262
284
  lastOraclePriceTwap: BN;
263
285
  lastOraclePriceTwapTs: BN;
286
+ lastOracleMarkSpreadPct: BN;
287
+ lastOracleConfPct: BN;
264
288
  oracle: PublicKey;
265
289
  oracleSource: OracleSource;
266
290
  fundingPeriod: BN;
@@ -275,6 +299,8 @@ export type AMM = {
275
299
  totalFee: BN;
276
300
  minimumQuoteAssetTradeSize: BN;
277
301
  baseAssetAmountStepSize: BN;
302
+ maxBaseAssetAmountRatio: number;
303
+ maxSlippageRatio: number;
278
304
  lastOraclePrice: BN;
279
305
  baseSpread: number;
280
306
  curveUpdateIntensity: number;
@@ -365,7 +391,6 @@ export type OrderParams = {
365
391
  orderType: OrderType;
366
392
  userOrderId: number;
367
393
  direction: PositionDirection;
368
- quoteAssetAmount: BN;
369
394
  baseAssetAmount: BN;
370
395
  price: BN;
371
396
  marketIndex: BN;
@@ -384,11 +409,49 @@ export type OrderParams = {
384
409
  };
385
410
  };
386
411
 
412
+ export type NecessaryOrderParams = {
413
+ orderType: OrderType;
414
+ marketIndex: BN;
415
+ baseAssetAmount: BN;
416
+ direction: PositionDirection;
417
+ };
418
+
419
+ export type OptionalOrderParams = {
420
+ [Property in keyof OrderParams]?: OrderParams[Property];
421
+ } & NecessaryOrderParams;
422
+
423
+ export const DefaultOrderParams = {
424
+ orderType: OrderType.MARKET,
425
+ userOrderId: 0,
426
+ direction: PositionDirection.LONG,
427
+ baseAssetAmount: ZERO,
428
+ price: ZERO,
429
+ marketIndex: ZERO,
430
+ reduceOnly: false,
431
+ postOnly: false,
432
+ immediateOrCancel: false,
433
+ triggerPrice: ZERO,
434
+ triggerCondition: OrderTriggerCondition.ABOVE,
435
+ positionLimit: ZERO,
436
+ oraclePriceOffset: ZERO,
437
+ padding0: ZERO,
438
+ padding1: ZERO,
439
+ optionalAccounts: {
440
+ discountToken: false,
441
+ referrer: false,
442
+ },
443
+ };
444
+
387
445
  export type MakerInfo = {
388
446
  maker: PublicKey;
389
447
  order: Order;
390
448
  };
391
449
 
450
+ export type TakerInfo = {
451
+ taker: PublicKey;
452
+ order: Order;
453
+ };
454
+
392
455
  // # Misc Types
393
456
  export interface IWallet {
394
457
  signTransaction(tx: Transaction): Promise<Transaction>;