@d8x/perpetuals-sdk 0.1.1 → 0.1.2

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/dist/d8XMath.d.ts CHANGED
@@ -96,17 +96,15 @@ export declare function getMaxSignedPositionSize(marginCollateral: number, curre
96
96
  /**
97
97
  * Compute the leverage resulting from a trade
98
98
  * @param tradeAmount Amount to trade, in base currency, signed
99
- * @param marginCollateral Amount of cash in the margin account, after the trade, in collateral currency
99
+ * @param marginCollateral Amount of cash in the margin account, in collateral currency
100
100
  * @param currentPosition Position size before the trade
101
101
  * @param currentLockedInValue Locked-in value before the trade
102
- * @param indexPriceS2 Spot price of the index when the trade happens
102
+ * @param price Price charged to trade tradeAmount
103
103
  * @param indexPriceS3 Spot price of the collateral currency when the trade happens
104
104
  * @param markPrice Mark price of the index when the trade happens
105
- * @param limitPrice Price charged to trade tradeAmount
106
- * @param feeRate Trading fee rate applicable to this trade
107
105
  * @returns Leverage of the resulting position
108
106
  */
109
- export declare function getNewPositionLeverage(tradeAmount: number, marginCollateral: number, currentPosition: number, currentLockedInValue: number, indexPriceS2: number, indexPriceS3: number, markPrice: number, limitPrice: number, feeRate: number): number;
107
+ export declare function getNewPositionLeverage(tradeAmount: number, marginCollateral: number, currentPosition: number, currentLockedInValue: number, price: number, indexPriceS3: number, markPrice: number): number;
110
108
  /**
111
109
  * Determine amount to be deposited into margin account so that the given leverage
112
110
  * is obtained when trading a position pos (trade amount = position)
package/dist/d8XMath.js CHANGED
@@ -224,20 +224,18 @@ exports.getMaxSignedPositionSize = getMaxSignedPositionSize;
224
224
  /**
225
225
  * Compute the leverage resulting from a trade
226
226
  * @param tradeAmount Amount to trade, in base currency, signed
227
- * @param marginCollateral Amount of cash in the margin account, after the trade, in collateral currency
227
+ * @param marginCollateral Amount of cash in the margin account, in collateral currency
228
228
  * @param currentPosition Position size before the trade
229
229
  * @param currentLockedInValue Locked-in value before the trade
230
- * @param indexPriceS2 Spot price of the index when the trade happens
230
+ * @param price Price charged to trade tradeAmount
231
231
  * @param indexPriceS3 Spot price of the collateral currency when the trade happens
232
232
  * @param markPrice Mark price of the index when the trade happens
233
- * @param limitPrice Price charged to trade tradeAmount
234
- * @param feeRate Trading fee rate applicable to this trade
235
233
  * @returns Leverage of the resulting position
236
234
  */
237
- function getNewPositionLeverage(tradeAmount, marginCollateral, currentPosition, currentLockedInValue, indexPriceS2, indexPriceS3, markPrice, limitPrice, feeRate) {
235
+ function getNewPositionLeverage(tradeAmount, marginCollateral, currentPosition, currentLockedInValue, price, indexPriceS3, markPrice) {
238
236
  let newPosition = tradeAmount + currentPosition;
239
- let pnlQC = currentPosition * markPrice - currentLockedInValue + tradeAmount * (markPrice - limitPrice);
240
- return ((Math.abs(newPosition) * indexPriceS2) / (marginCollateral * indexPriceS3 + pnlQC - feeRate * Math.abs(tradeAmount)));
237
+ let pnlQC = currentPosition * markPrice - currentLockedInValue + tradeAmount * (markPrice - price);
238
+ return (Math.abs(newPosition) * markPrice) / (marginCollateral * indexPriceS3 + pnlQC);
241
239
  }
242
240
  exports.getNewPositionLeverage = getNewPositionLeverage;
243
241
  /**
@@ -139,7 +139,7 @@ export default class MarketData extends PerpetualDataHandler {
139
139
  * @param currentPositionRisk Position risk before trade
140
140
  * @returns {MarginAccount} Position risk after trade
141
141
  */
142
- positionRiskOnTrade(traderAddr: string, order: Order, currentPositionRisk?: MarginAccount): Promise<MarginAccount>;
142
+ positionRiskOnTrade(traderAddr: string, order: Order, currentPositionRisk?: MarginAccount, indexPriceInfo?: [number, number, boolean, boolean]): Promise<MarginAccount>;
143
143
  /**
144
144
  * Estimates what the position risk will be if given amount of collateral is added/removed from the account.
145
145
  * @param traderAddr Address of trader
@@ -147,7 +147,7 @@ export default class MarketData extends PerpetualDataHandler {
147
147
  * @param currentPositionRisk Position risk before
148
148
  * @returns {MarginAccount} Position risk after
149
149
  */
150
- positionRiskOnCollateralAction(deltaCollateral: number, currentPositionRisk: MarginAccount): Promise<MarginAccount>;
150
+ positionRiskOnCollateralAction(deltaCollateral: number, currentPositionRisk: MarginAccount, indexPriceInfo?: [number, number, boolean, boolean]): Promise<MarginAccount>;
151
151
  protected static _positionRiskOnAccountAction(symbol: string, tradeAmount: number, marginDeposit: number, tradeLeverage: number | undefined, keepPositionLvg: boolean | undefined, tradePrice: number, feeRate: number, perpetualState: PerpetualState, currentPositionRisk: MarginAccount, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>): MarginAccount;
152
152
  /**
153
153
  * Gets the wallet balance in the collateral currency corresponding to a given perpetual symbol.
@@ -278,6 +278,6 @@ export default class MarketData extends PerpetualDataHandler {
278
278
  * @ignore
279
279
  */
280
280
  static orderIdsOfTrader(traderAddr: string, orderBookContract: ethers.Contract): Promise<string[]>;
281
- getAvailableMargin(traderAddr: string, symbol: string): Promise<number>;
281
+ getAvailableMargin(traderAddr: string, symbol: string, indexPrices?: [number, number]): Promise<number>;
282
282
  static _exchangeInfo(_proxyContract: ethers.Contract, _poolStaticInfos: Array<PoolStaticInfo>, _symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _symbolList: Map<string, string>, _priceFeedGetter: PriceFeeds): Promise<ExchangeInfo>;
283
283
  }
@@ -200,7 +200,7 @@ class MarketData extends perpetualDataHandler_1.default {
200
200
  * @param currentPositionRisk Position risk before trade
201
201
  * @returns {MarginAccount} Position risk after trade
202
202
  */
203
- positionRiskOnTrade(traderAddr, order, currentPositionRisk) {
203
+ positionRiskOnTrade(traderAddr, order, currentPositionRisk, indexPriceInfo) {
204
204
  var _a, _b, _c;
205
205
  return __awaiter(this, void 0, void 0, function* () {
206
206
  if (this.proxyContract == null) {
@@ -209,6 +209,10 @@ class MarketData extends perpetualDataHandler_1.default {
209
209
  if (currentPositionRisk == undefined) {
210
210
  currentPositionRisk = yield this.positionRisk(traderAddr, order.symbol);
211
211
  }
212
+ if (indexPriceInfo == undefined) {
213
+ let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(currentPositionRisk.symbol);
214
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
215
+ }
212
216
  let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(order.symbol, this.poolStaticInfos);
213
217
  // price for this order = limit price (conservative) if given, else the current perp price
214
218
  let tradeAmount = Math.abs(order.quantity) * (order.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1);
@@ -217,7 +221,7 @@ class MarketData extends perpetualDataHandler_1.default {
217
221
  let feeRate = ((yield this.proxyContract.queryExchangeFee(poolId, traderAddr, (_b = order.brokerAddr) !== null && _b !== void 0 ? _b : nodeSDKTypes_1.ZERO_ADDRESS)) +
218
222
  ((_c = order.brokerFeeTbps) !== null && _c !== void 0 ? _c : 0)) /
219
223
  100000;
220
- let perpetualState = yield this.getPerpetualState(order.symbol);
224
+ let perpetualState = yield this.getPerpetualState(order.symbol, indexPriceInfo);
221
225
  return MarketData._positionRiskOnAccountAction(order.symbol, tradeAmount, 0, order.leverage, order.keepPositionLvg, tradePrice, feeRate, perpetualState, currentPositionRisk, this.symbolToPerpStaticInfo);
222
226
  });
223
227
  }
@@ -228,12 +232,16 @@ class MarketData extends perpetualDataHandler_1.default {
228
232
  * @param currentPositionRisk Position risk before
229
233
  * @returns {MarginAccount} Position risk after
230
234
  */
231
- positionRiskOnCollateralAction(deltaCollateral, currentPositionRisk) {
235
+ positionRiskOnCollateralAction(deltaCollateral, currentPositionRisk, indexPriceInfo) {
232
236
  return __awaiter(this, void 0, void 0, function* () {
233
237
  if (this.proxyContract == null) {
234
238
  throw Error("no proxy contract initialized. Use createProxyInstance().");
235
239
  }
236
- let perpetualState = yield this.getPerpetualState(currentPositionRisk.symbol);
240
+ if (indexPriceInfo == undefined) {
241
+ let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(currentPositionRisk.symbol);
242
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
243
+ }
244
+ let perpetualState = yield this.getPerpetualState(currentPositionRisk.symbol, indexPriceInfo);
237
245
  return MarketData._positionRiskOnAccountAction(currentPositionRisk.symbol, 0, deltaCollateral, undefined, false, 0, 0, perpetualState, currentPositionRisk, this.symbolToPerpStaticInfo);
238
246
  });
239
247
  }
@@ -243,6 +251,8 @@ class MarketData extends perpetualDataHandler_1.default {
243
251
  let newPosition = currentPosition + tradeAmount;
244
252
  let newSide = newPosition > 0 ? nodeSDKTypes_1.BUY_SIDE : newPosition < 0 ? nodeSDKTypes_1.SELL_SIDE : nodeSDKTypes_1.CLOSED_SIDE;
245
253
  let lockedInValue = currentPositionRisk.entryPrice * currentPosition;
254
+ let newLockedInValue = lockedInValue + tradeAmount * tradePrice;
255
+ let newEntryPrice = newPosition == 0 ? 0 : Math.abs(newLockedInValue / newPosition);
246
256
  if (tradeAmount == 0) {
247
257
  keepPositionLvg = false;
248
258
  }
@@ -262,32 +272,35 @@ class MarketData extends perpetualDataHandler_1.default {
262
272
  // this gives us the total margin needed in the account so that it satisfies the leverage condition
263
273
  newCollateral = (0, d8XMath_1.getMarginRequiredForLeveragedTrade)(currentPositionRisk.leverage, currentPosition, lockedInValue, tradeAmount, markPrice, indexPriceS2, indexPriceS3, tradePrice, feeRate);
264
274
  // the new leverage follows from the updated margin and position
265
- newLeverage = (0, d8XMath_1.getNewPositionLeverage)(tradeAmount, newCollateral, currentPosition, lockedInValue, indexPriceS2, indexPriceS3, markPrice, tradePrice, feeRate);
275
+ newLeverage = (0, d8XMath_1.getNewPositionLeverage)(tradeAmount, newCollateral, currentPosition, lockedInValue, tradePrice, indexPriceS3, markPrice);
266
276
  }
267
277
  else if (tradeAmount != 0) {
268
- let depositAtTradeTime;
278
+ let deltaCash;
269
279
  if (!isOpen && !isFlip && !keepPositionLvgOnClose) {
270
- // no deposit from trader's wallet, but there is realized pnl
271
- depositAtTradeTime = tradeAmount * (tradePrice - currentPositionRisk.entryPrice);
280
+ // cash comes from realized pnl
281
+ deltaCash = (tradeAmount * (tradePrice - currentPositionRisk.entryPrice)) / indexPriceS3;
282
+ newLockedInValue = lockedInValue + currentPositionRisk.entryPrice * tradeAmount;
272
283
  }
273
284
  else {
274
285
  // target lvg will default current lvg if not specified
275
286
  let targetLvg = isFlip || isOpen ? tradeLeverage !== null && tradeLeverage !== void 0 ? tradeLeverage : 0 : 0;
276
287
  let b0, pos0;
277
288
  [b0, pos0] = isOpen ? [0, 0] : [currentPositionRisk.collateralCC, currentPosition];
278
- depositAtTradeTime = (0, d8XMath_1.getDepositAmountForLvgTrade)(b0, pos0, tradeAmount, targetLvg, tradePrice, indexPriceS2, markPrice);
289
+ // cash comes from trader wallet
290
+ deltaCash = (0, d8XMath_1.getDepositAmountForLvgTrade)(b0, pos0, tradeAmount, targetLvg, tradePrice, indexPriceS3, markPrice);
291
+ // premium/instantaneous pnl
292
+ // deltaCash -= (tradeAmount * (tradePrice - indexPriceS2)) / indexPriceS3;
293
+ newLockedInValue = lockedInValue + tradeAmount * tradePrice;
279
294
  }
280
- newCollateral = currentPositionRisk.collateralCC + depositAtTradeTime;
295
+ newCollateral = currentPositionRisk.collateralCC + deltaCash; // - (feeRate * Math.abs(tradeAmount) * indexPriceS2) / indexPriceS3;
281
296
  // the new leverage corresponds to increasing the position and collateral according to the order
282
- newLeverage = (0, d8XMath_1.getNewPositionLeverage)(tradeAmount, newCollateral, currentPosition, lockedInValue, indexPriceS2, indexPriceS3, markPrice, tradePrice, feeRate);
297
+ newLeverage = (0, d8XMath_1.getNewPositionLeverage)(0, newCollateral, newPosition, newLockedInValue, tradePrice, indexPriceS3, markPrice);
283
298
  }
284
299
  else {
285
300
  // there is no order, adding/removing collateral
286
301
  newCollateral = currentPositionRisk.collateralCC + marginDeposit;
287
- newLeverage = (0, d8XMath_1.getNewPositionLeverage)(0, newCollateral, currentPosition, lockedInValue, indexPriceS2, indexPriceS3, markPrice, 0, 0);
302
+ newLeverage = (0, d8XMath_1.getNewPositionLeverage)(0, newCollateral, currentPosition, lockedInValue, indexPriceS2, indexPriceS3, markPrice);
288
303
  }
289
- let newLockedInValue = lockedInValue + tradeAmount * tradePrice;
290
- let entryPrice = newPosition == 0 ? 0 : Math.abs(newLockedInValue / newPosition);
291
304
  // liquidation vars
292
305
  let S2Liq, S3Liq;
293
306
  let tau = symbolToPerpStaticInfo.get(symbol).maintenanceMarginRate;
@@ -307,7 +320,7 @@ class MarketData extends perpetualDataHandler_1.default {
307
320
  symbol: currentPositionRisk.symbol,
308
321
  positionNotionalBaseCCY: Math.abs(newPosition),
309
322
  side: newSide,
310
- entryPrice: entryPrice,
323
+ entryPrice: newEntryPrice,
311
324
  leverage: newLeverage,
312
325
  markPrice: markPrice,
313
326
  unrealizedPnlQuoteCCY: newPosition * markPrice - newLockedInValue,
@@ -571,14 +584,22 @@ class MarketData extends perpetualDataHandler_1.default {
571
584
  return digests;
572
585
  });
573
586
  }
574
- getAvailableMargin(traderAddr, symbol) {
587
+ getAvailableMargin(traderAddr, symbol, indexPrices) {
575
588
  return __awaiter(this, void 0, void 0, function* () {
576
589
  if (this.proxyContract == null) {
577
590
  throw Error("no proxy contract initialized. Use createProxyInstance().");
578
591
  }
579
592
  let mgnAcct = yield perpetualDataHandler_1.default.getMarginAccount(traderAddr, symbol, this.symbolToPerpStaticInfo, this.proxyContract);
593
+ if (indexPrices == undefined) {
594
+ // fetch from API
595
+ let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(symbol);
596
+ indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
597
+ }
598
+ let S2 = indexPrices[0];
599
+ let ccyType = this.getPerpetualStaticInfo(symbol).collateralCurrencyType;
600
+ let S3 = ccyType == nodeSDKTypes_1.COLLATERAL_CURRENCY_QUANTO ? indexPrices[1] : ccyType == nodeSDKTypes_1.COLLATERAL_CURRENCY_QUOTE ? 1 : S2;
580
601
  let perpInfo = this.symbolToPerpStaticInfo.get(symbol);
581
- let balanceCC = mgnAcct.collateralCC + mgnAcct.unrealizedPnlQuoteCCY / mgnAcct.collToQuoteConversion;
602
+ let balanceCC = mgnAcct.collateralCC + mgnAcct.unrealizedPnlQuoteCCY / S3;
582
603
  let initalMarginCC = Math.abs((perpInfo.initialMarginRate * mgnAcct.positionNotionalBaseCCY * mgnAcct.markPrice) /
583
604
  mgnAcct.collToQuoteConversion);
584
605
  return balanceCC - initalMarginCC;
@@ -595,7 +616,7 @@ class MarketData extends perpetualDataHandler_1.default {
595
616
  for (let perpSymbol of _symbolToPerpStaticInfo.keys()) {
596
617
  let sInfo = _symbolToPerpStaticInfo.get(perpSymbol);
597
618
  allSym.add(sInfo.S2Symbol);
598
- if (sInfo.S3Symbol != '') {
619
+ if (sInfo.S3Symbol != "") {
599
620
  allSym.add(sInfo.S3Symbol);
600
621
  }
601
622
  }
@@ -619,8 +640,12 @@ class MarketData extends perpetualDataHandler_1.default {
619
640
  let poolSymbol = PoolState.poolSymbol;
620
641
  for (var k = 0; k < perpetualIDs.length; k++) {
621
642
  let perp = yield _proxyContract.getPerpetual(perpetualIDs[k]);
622
- let symS2 = (0, utils_1.contractSymbolToSymbol)(perp.S2BaseCCY, _symbolList) + "-" + (0, utils_1.contractSymbolToSymbol)(perp.S2QuoteCCY, _symbolList);
623
- let symS3 = (0, utils_1.contractSymbolToSymbol)(perp.S3BaseCCY, _symbolList) + "-" + (0, utils_1.contractSymbolToSymbol)(perp.S3QuoteCCY, _symbolList);
643
+ let symS2 = (0, utils_1.contractSymbolToSymbol)(perp.S2BaseCCY, _symbolList) +
644
+ "-" +
645
+ (0, utils_1.contractSymbolToSymbol)(perp.S2QuoteCCY, _symbolList);
646
+ let symS3 = (0, utils_1.contractSymbolToSymbol)(perp.S3BaseCCY, _symbolList) +
647
+ "-" +
648
+ (0, utils_1.contractSymbolToSymbol)(perp.S3QuoteCCY, _symbolList);
624
649
  let perpSymbol = symS2 + "-" + poolSymbol;
625
650
  //console.log("perpsymbol=",perpSymbol);
626
651
  let res = idxPriceMap.get(symS2);
@@ -645,7 +670,7 @@ class MarketData extends perpetualDataHandler_1.default {
645
670
  }
646
671
  let fMidPrice = yield _proxyContract.queryPerpetualPrice(perpetualIDs[k], ethers_1.BigNumber.from(0), [
647
672
  (0, d8XMath_1.floatToABK64x64)(indexS2),
648
- (0, d8XMath_1.floatToABK64x64)(indexS3)
673
+ (0, d8XMath_1.floatToABK64x64)(indexS3),
649
674
  ]);
650
675
  let markPremiumRate = (0, d8XMath_1.ABK64x64ToFloat)(perp.currentMarkPremiumRate.fPrice);
651
676
  let currentFundingRateBps = 1e4 * (0, d8XMath_1.ABK64x64ToFloat)(perp.fCurrentFundingRate);
@@ -662,7 +687,7 @@ class MarketData extends perpetualDataHandler_1.default {
662
687
  currentFundingRateBps: currentFundingRateBps,
663
688
  openInterestBC: (0, d8XMath_1.ABK64x64ToFloat)(perp.fOpenInterest),
664
689
  maxPositionBC: Infinity,
665
- isMarketClosed: isS2MktClosed || isS3MktClosed
690
+ isMarketClosed: isS2MktClosed || isS3MktClosed,
666
691
  };
667
692
  PoolState.perpetuals.push(PerpetualState);
668
693
  }
@@ -94,7 +94,7 @@ export default class PerpetualDataHandler {
94
94
  protected static _getSymbolFromPoolId(poolId: number, staticInfos: PoolStaticInfo[]): string;
95
95
  protected static _getPoolIdFromSymbol(symbol: string, staticInfos: PoolStaticInfo[]): number;
96
96
  static getNestedPerpetualIds(_proxyContract: ethers.Contract): Promise<number[][]>;
97
- static getMarginAccount(traderAddr: string, symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _proxyContract: ethers.Contract): Promise<MarginAccount>;
97
+ static getMarginAccount(traderAddr: string, symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _proxyContract: ethers.Contract, _pxS2S3?: [number, number | undefined]): Promise<MarginAccount>;
98
98
  protected static _queryPerpetualPrice(symbol: string, tradeAmount: number, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _proxyContract: ethers.Contract, indexPrices: [number, number]): Promise<number>;
99
99
  protected static _queryPerpetualMarkPrice(symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _proxyContract: ethers.Contract, indexPrices: [number, number]): Promise<number>;
100
100
  protected static _queryPerpetualState(symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _proxyContract: ethers.Contract, indexPrices: [number, number, boolean, boolean]): Promise<PerpetualState>;
@@ -164,4 +164,11 @@ export default class PerpetualDataHandler {
164
164
  */
165
165
  getPoolIndexFromSymbol(symbol: string): number;
166
166
  getMarginTokenFromSymbol(symbol: string): string | undefined;
167
+ /**
168
+ * Performs basic validity checks on a given order
169
+ * @param order Order struct
170
+ * @param traderAccount Trader account
171
+ * @param perpStaticInfo Symbol to perpetual info map
172
+ */
173
+ protected static checkOrder(order: Order, traderAccount: MarginAccount, perpStaticInfo: Map<string, PerpetualStaticInfo>): void;
167
174
  }
@@ -111,23 +111,21 @@ class PerpetualDataHandler {
111
111
  // try to find a limit order book
112
112
  let lobAddr = yield this.lobFactoryContract.getOrderBookAddress(perpetualIDs[k]);
113
113
  currentLimitOrderBookAddr.push(lobAddr);
114
- if (poolCCY == undefined) {
115
- // we find out the pool currency by looking at all perpetuals
116
- // unless for quanto perpetuals, we know the pool currency
117
- // from the perpetual. This fails if we have a pool with only
118
- // quanto perpetuals
119
- if (perp.eCollateralCurrency == nodeSDKTypes_1.COLLATERAL_CURRENCY_BASE) {
120
- poolCCY = base;
121
- ccy.push(nodeSDKTypes_1.CollaterlCCY.BASE);
122
- }
123
- else if (perp.eCollateralCurrency == nodeSDKTypes_1.COLLATERAL_CURRENCY_QUOTE) {
124
- poolCCY = quote;
125
- ccy.push(nodeSDKTypes_1.CollaterlCCY.QUOTE);
126
- }
127
- else {
128
- poolCCY = base3;
129
- ccy.push(nodeSDKTypes_1.CollaterlCCY.QUANTO);
130
- }
114
+ // we find out the pool currency by looking at all perpetuals
115
+ // unless for quanto perpetuals, we know the pool currency
116
+ // from the perpetual. This fails if we have a pool with only
117
+ // quanto perpetuals
118
+ if (perp.eCollateralCurrency == nodeSDKTypes_1.COLLATERAL_CURRENCY_BASE) {
119
+ poolCCY = poolCCY !== null && poolCCY !== void 0 ? poolCCY : base;
120
+ ccy.push(nodeSDKTypes_1.CollaterlCCY.BASE);
121
+ }
122
+ else if (perp.eCollateralCurrency == nodeSDKTypes_1.COLLATERAL_CURRENCY_QUOTE) {
123
+ poolCCY = poolCCY !== null && poolCCY !== void 0 ? poolCCY : quote;
124
+ ccy.push(nodeSDKTypes_1.CollaterlCCY.QUOTE);
125
+ }
126
+ else {
127
+ poolCCY = poolCCY !== null && poolCCY !== void 0 ? poolCCY : base3;
128
+ ccy.push(nodeSDKTypes_1.CollaterlCCY.QUANTO);
131
129
  }
132
130
  }
133
131
  if (perpetualIDs.length == 0) {
@@ -286,12 +284,16 @@ class PerpetualDataHandler {
286
284
  return poolIds;
287
285
  });
288
286
  }
289
- static getMarginAccount(traderAddr, symbol, symbolToPerpStaticInfo, _proxyContract) {
287
+ static getMarginAccount(traderAddr, symbol, symbolToPerpStaticInfo, _proxyContract, _pxS2S3) {
290
288
  return __awaiter(this, void 0, void 0, function* () {
291
289
  let perpId = Number(symbol);
292
290
  if (isNaN(perpId)) {
293
291
  perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
294
292
  }
293
+ // TODO: correct numbers using actual S2, S3
294
+ // if (_pxS2S3 == undefined) {
295
+ // // fetch s2,s3
296
+ // }
295
297
  const idx_cash = 3;
296
298
  const idx_notional = 4;
297
299
  const idx_locked_in = 5;
@@ -342,20 +344,26 @@ class PerpetualDataHandler {
342
344
  });
343
345
  }
344
346
  static _queryPerpetualState(symbol, symbolToPerpStaticInfo, _proxyContract, indexPrices) {
345
- var _a, _b;
346
347
  return __awaiter(this, void 0, void 0, function* () {
347
348
  let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
349
+ let staticInfo = symbolToPerpStaticInfo.get(symbol);
348
350
  let ccy = symbol.split("-");
349
351
  let [S2, S3] = [indexPrices[0], indexPrices[1]];
350
- let ammState = yield _proxyContract.getAMMState(perpId, [S2, S3].map((x) => (0, d8XMath_1.floatToABK64x64)(x == undefined || Number.isNaN(x) ? 0 : x)));
351
- let markPrice = (0, d8XMath_1.ABK64x64ToFloat)(ammState[6].mul(nodeSDKTypes_1.ONE_64x64.add(ammState[8])).div(nodeSDKTypes_1.ONE_64x64));
352
+ if (staticInfo.collateralCurrencyType == nodeSDKTypes_1.CollaterlCCY.BASE) {
353
+ S3 = S2;
354
+ }
355
+ else if (staticInfo.collateralCurrencyType == nodeSDKTypes_1.CollaterlCCY.QUOTE) {
356
+ S3 = 1;
357
+ }
358
+ let ammState = yield _proxyContract.getAMMState(perpId, [S2, S3].map(d8XMath_1.floatToABK64x64));
359
+ let markPrice = S2 * (1 + (0, d8XMath_1.ABK64x64ToFloat)(ammState[8]));
352
360
  let state = {
353
361
  id: perpId,
354
362
  state: nodeSDKTypes_1.PERP_STATE_STR[ammState[13]],
355
363
  baseCurrency: ccy[0],
356
364
  quoteCurrency: ccy[1],
357
- indexPrice: (0, d8XMath_1.ABK64x64ToFloat)(ammState[6]),
358
- collToQuoteIndexPrice: (0, d8XMath_1.ABK64x64ToFloat)(ammState[7]),
365
+ indexPrice: S2,
366
+ collToQuoteIndexPrice: S3,
359
367
  markPrice: markPrice,
360
368
  midPrice: (0, d8XMath_1.ABK64x64ToFloat)(ammState[10]),
361
369
  currentFundingRateBps: (0, d8XMath_1.ABK64x64ToFloat)(ammState[14]) * 1e4,
@@ -363,12 +371,6 @@ class PerpetualDataHandler {
363
371
  maxPositionBC: (0, d8XMath_1.ABK64x64ToFloat)(ammState[12]),
364
372
  isMarketClosed: indexPrices[2] || indexPrices[3],
365
373
  };
366
- if (((_a = symbolToPerpStaticInfo.get(symbol)) === null || _a === void 0 ? void 0 : _a.collateralCurrencyType) == nodeSDKTypes_1.CollaterlCCY.BASE) {
367
- state.collToQuoteIndexPrice = state.indexPrice;
368
- }
369
- else if (((_b = symbolToPerpStaticInfo.get(symbol)) === null || _b === void 0 ? void 0 : _b.collateralCurrencyType) == nodeSDKTypes_1.CollaterlCCY.QUOTE) {
370
- state.collToQuoteIndexPrice = 1;
371
- }
372
374
  return state;
373
375
  });
374
376
  }
@@ -720,5 +722,40 @@ class PerpetualDataHandler {
720
722
  }
721
723
  return undefined;
722
724
  }
725
+ /**
726
+ * Performs basic validity checks on a given order
727
+ * @param order Order struct
728
+ * @param traderAccount Trader account
729
+ * @param perpStaticInfo Symbol to perpetual info map
730
+ */
731
+ static checkOrder(order, traderAccount, perpStaticInfo) {
732
+ // this throws error if not found
733
+ let perpetualId = PerpetualDataHandler.symbolToPerpetualId(order.symbol, perpStaticInfo);
734
+ // check side
735
+ if (order.side != nodeSDKTypes_1.BUY_SIDE && order.side != nodeSDKTypes_1.SELL_SIDE) {
736
+ throw Error(`order side must be ${nodeSDKTypes_1.BUY_SIDE} or ${nodeSDKTypes_1.SELL_SIDE}`);
737
+ }
738
+ // check amount
739
+ let lotSize = perpStaticInfo.get(order.symbol).lotSizeBC;
740
+ let curPos = traderAccount.side == nodeSDKTypes_1.CLOSED_SIDE
741
+ ? 0
742
+ : (traderAccount.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1) * traderAccount.positionNotionalBaseCCY;
743
+ let newPos = curPos + (order.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1) * order.quantity;
744
+ if (Math.abs(order.quantity) < lotSize || (Math.abs(newPos) >= lotSize && Math.abs(newPos) < 10 * lotSize)) {
745
+ throw Error(`trade amount too small: ${order.quantity} ${perpStaticInfo.get(order.symbol).S2Symbol}`);
746
+ }
747
+ // check limit price
748
+ if (order.side == nodeSDKTypes_1.BUY_SIDE && order.limitPrice != undefined && order.limitPrice <= 0) {
749
+ throw Error(`invalid limit price for buy order: ${order.limitPrice}`);
750
+ }
751
+ // broker fee
752
+ if (order.brokerFeeTbps != undefined && order.brokerFeeTbps < 0) {
753
+ throw Error(`invalid broker fee: ${order.brokerFeeTbps / 10} bps`);
754
+ }
755
+ // stop price
756
+ if (order.stopPrice != undefined && order.stopPrice < 0) {
757
+ throw Error(`invalid stop price: ${order.stopPrice}`);
758
+ }
759
+ }
723
760
  }
724
761
  exports.default = PerpetualDataHandler;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const D8X_SDK_VERSION = "0.1.1";
1
+ export declare const D8X_SDK_VERSION = "0.1.2";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.D8X_SDK_VERSION = void 0;
4
- exports.D8X_SDK_VERSION = "0.1.1";
4
+ exports.D8X_SDK_VERSION = "0.1.2";
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "name": "@d8x/perpetuals-sdk",
35
35
  "description": "Node TypeScript SDK for D8X Perpetual Futures",
36
- "version": "0.1.1",
36
+ "version": "0.1.2",
37
37
  "main": "./dist/index.js",
38
38
  "types": "./dist/index.d.ts",
39
39
  "directories": {
package/src/d8XMath.ts CHANGED
@@ -265,14 +265,12 @@ export function getMaxSignedPositionSize(
265
265
  /**
266
266
  * Compute the leverage resulting from a trade
267
267
  * @param tradeAmount Amount to trade, in base currency, signed
268
- * @param marginCollateral Amount of cash in the margin account, after the trade, in collateral currency
268
+ * @param marginCollateral Amount of cash in the margin account, in collateral currency
269
269
  * @param currentPosition Position size before the trade
270
270
  * @param currentLockedInValue Locked-in value before the trade
271
- * @param indexPriceS2 Spot price of the index when the trade happens
271
+ * @param price Price charged to trade tradeAmount
272
272
  * @param indexPriceS3 Spot price of the collateral currency when the trade happens
273
273
  * @param markPrice Mark price of the index when the trade happens
274
- * @param limitPrice Price charged to trade tradeAmount
275
- * @param feeRate Trading fee rate applicable to this trade
276
274
  * @returns Leverage of the resulting position
277
275
  */
278
276
  export function getNewPositionLeverage(
@@ -280,17 +278,13 @@ export function getNewPositionLeverage(
280
278
  marginCollateral: number,
281
279
  currentPosition: number,
282
280
  currentLockedInValue: number,
283
- indexPriceS2: number,
281
+ price: number,
284
282
  indexPriceS3: number,
285
- markPrice: number,
286
- limitPrice: number,
287
- feeRate: number
283
+ markPrice: number
288
284
  ): number {
289
285
  let newPosition = tradeAmount + currentPosition;
290
- let pnlQC = currentPosition * markPrice - currentLockedInValue + tradeAmount * (markPrice - limitPrice);
291
- return (
292
- (Math.abs(newPosition) * indexPriceS2) / (marginCollateral * indexPriceS3 + pnlQC - feeRate * Math.abs(tradeAmount))
293
- );
286
+ let pnlQC = currentPosition * markPrice - currentLockedInValue + tradeAmount * (markPrice - price);
287
+ return (Math.abs(newPosition) * markPrice) / (marginCollateral * indexPriceS3 + pnlQC);
294
288
  }
295
289
 
296
290
  /**
package/src/marketData.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  CLOSED_SIDE,
17
17
  COLLATERAL_CURRENCY_BASE,
18
18
  COLLATERAL_CURRENCY_QUANTO,
19
+ COLLATERAL_CURRENCY_QUOTE,
19
20
  CollaterlCCY,
20
21
  ERC20_ABI,
21
22
  ExchangeInfo,
@@ -146,7 +147,13 @@ export default class MarketData extends PerpetualDataHandler {
146
147
  if (this.proxyContract == null) {
147
148
  throw Error("no proxy contract initialized. Use createProxyInstance().");
148
149
  }
149
- return await MarketData._exchangeInfo(this.proxyContract, this.poolStaticInfos, this.symbolToPerpStaticInfo, this.symbolList, this.priceFeedGetter);
150
+ return await MarketData._exchangeInfo(
151
+ this.proxyContract,
152
+ this.poolStaticInfos,
153
+ this.symbolToPerpStaticInfo,
154
+ this.symbolList,
155
+ this.priceFeedGetter
156
+ );
150
157
  }
151
158
 
152
159
  /**
@@ -224,7 +231,8 @@ export default class MarketData extends PerpetualDataHandler {
224
231
  public async positionRiskOnTrade(
225
232
  traderAddr: string,
226
233
  order: Order,
227
- currentPositionRisk?: MarginAccount
234
+ currentPositionRisk?: MarginAccount,
235
+ indexPriceInfo?: [number, number, boolean, boolean]
228
236
  ): Promise<MarginAccount> {
229
237
  if (this.proxyContract == null) {
230
238
  throw Error("no proxy contract initialized. Use createProxyInstance().");
@@ -232,6 +240,10 @@ export default class MarketData extends PerpetualDataHandler {
232
240
  if (currentPositionRisk == undefined) {
233
241
  currentPositionRisk = await this.positionRisk(traderAddr, order.symbol);
234
242
  }
243
+ if (indexPriceInfo == undefined) {
244
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(currentPositionRisk.symbol);
245
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
246
+ }
235
247
  let poolId = PerpetualDataHandler._getPoolIdFromSymbol(order.symbol, this.poolStaticInfos);
236
248
  // price for this order = limit price (conservative) if given, else the current perp price
237
249
  let tradeAmount = Math.abs(order.quantity) * (order.side == BUY_SIDE ? 1 : -1);
@@ -241,7 +253,7 @@ export default class MarketData extends PerpetualDataHandler {
241
253
  ((await this.proxyContract.queryExchangeFee(poolId, traderAddr, order.brokerAddr ?? ZERO_ADDRESS)) +
242
254
  (order.brokerFeeTbps ?? 0)) /
243
255
  100_000;
244
- let perpetualState = await this.getPerpetualState(order.symbol);
256
+ let perpetualState = await this.getPerpetualState(order.symbol, indexPriceInfo);
245
257
 
246
258
  return MarketData._positionRiskOnAccountAction(
247
259
  order.symbol,
@@ -266,12 +278,17 @@ export default class MarketData extends PerpetualDataHandler {
266
278
  */
267
279
  public async positionRiskOnCollateralAction(
268
280
  deltaCollateral: number,
269
- currentPositionRisk: MarginAccount
281
+ currentPositionRisk: MarginAccount,
282
+ indexPriceInfo?: [number, number, boolean, boolean]
270
283
  ): Promise<MarginAccount> {
271
284
  if (this.proxyContract == null) {
272
285
  throw Error("no proxy contract initialized. Use createProxyInstance().");
273
286
  }
274
- let perpetualState = await this.getPerpetualState(currentPositionRisk.symbol);
287
+ if (indexPriceInfo == undefined) {
288
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(currentPositionRisk.symbol);
289
+ indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
290
+ }
291
+ let perpetualState = await this.getPerpetualState(currentPositionRisk.symbol, indexPriceInfo);
275
292
 
276
293
  return MarketData._positionRiskOnAccountAction(
277
294
  currentPositionRisk.symbol,
@@ -304,6 +321,8 @@ export default class MarketData extends PerpetualDataHandler {
304
321
  let newPosition = currentPosition + tradeAmount;
305
322
  let newSide = newPosition > 0 ? BUY_SIDE : newPosition < 0 ? SELL_SIDE : CLOSED_SIDE;
306
323
  let lockedInValue = currentPositionRisk.entryPrice * currentPosition;
324
+ let newLockedInValue = lockedInValue + tradeAmount * tradePrice;
325
+ let newEntryPrice = newPosition == 0 ? 0 : Math.abs(newLockedInValue / newPosition);
307
326
  if (tradeAmount == 0) {
308
327
  keepPositionLvg = false;
309
328
  }
@@ -338,44 +357,37 @@ export default class MarketData extends PerpetualDataHandler {
338
357
  newCollateral,
339
358
  currentPosition,
340
359
  lockedInValue,
341
- indexPriceS2,
342
- indexPriceS3,
343
- markPrice,
344
360
  tradePrice,
345
- feeRate
361
+ indexPriceS3,
362
+ markPrice
346
363
  );
347
364
  } else if (tradeAmount != 0) {
348
- let depositAtTradeTime: number;
365
+ let deltaCash: number;
349
366
  if (!isOpen && !isFlip && !keepPositionLvgOnClose) {
350
- // no deposit from trader's wallet, but there is realized pnl
351
- depositAtTradeTime = tradeAmount * (tradePrice - currentPositionRisk.entryPrice);
367
+ // cash comes from realized pnl
368
+ deltaCash = (tradeAmount * (tradePrice - currentPositionRisk.entryPrice)) / indexPriceS3;
369
+ newLockedInValue = lockedInValue + currentPositionRisk.entryPrice * tradeAmount;
352
370
  } else {
353
371
  // target lvg will default current lvg if not specified
354
372
  let targetLvg = isFlip || isOpen ? tradeLeverage ?? 0 : 0;
355
373
  let b0, pos0;
356
374
  [b0, pos0] = isOpen ? [0, 0] : [currentPositionRisk.collateralCC, currentPosition];
357
- depositAtTradeTime = getDepositAmountForLvgTrade(
358
- b0,
359
- pos0,
360
- tradeAmount,
361
- targetLvg,
362
- tradePrice,
363
- indexPriceS2,
364
- markPrice
365
- );
375
+ // cash comes from trader wallet
376
+ deltaCash = getDepositAmountForLvgTrade(b0, pos0, tradeAmount, targetLvg, tradePrice, indexPriceS3, markPrice);
377
+ // premium/instantaneous pnl
378
+ // deltaCash -= (tradeAmount * (tradePrice - indexPriceS2)) / indexPriceS3;
379
+ newLockedInValue = lockedInValue + tradeAmount * tradePrice;
366
380
  }
367
- newCollateral = currentPositionRisk.collateralCC + depositAtTradeTime;
381
+ newCollateral = currentPositionRisk.collateralCC + deltaCash; // - (feeRate * Math.abs(tradeAmount) * indexPriceS2) / indexPriceS3;
368
382
  // the new leverage corresponds to increasing the position and collateral according to the order
369
383
  newLeverage = getNewPositionLeverage(
370
- tradeAmount,
384
+ 0,
371
385
  newCollateral,
372
- currentPosition,
373
- lockedInValue,
374
- indexPriceS2,
375
- indexPriceS3,
376
- markPrice,
386
+ newPosition,
387
+ newLockedInValue,
377
388
  tradePrice,
378
- feeRate
389
+ indexPriceS3,
390
+ markPrice
379
391
  );
380
392
  } else {
381
393
  // there is no order, adding/removing collateral
@@ -387,13 +399,10 @@ export default class MarketData extends PerpetualDataHandler {
387
399
  lockedInValue,
388
400
  indexPriceS2,
389
401
  indexPriceS3,
390
- markPrice,
391
- 0,
392
- 0
402
+ markPrice
393
403
  );
394
404
  }
395
- let newLockedInValue = lockedInValue + tradeAmount * tradePrice;
396
- let entryPrice = newPosition == 0 ? 0 : Math.abs(newLockedInValue / newPosition);
405
+
397
406
  // liquidation vars
398
407
  let S2Liq: number, S3Liq: number | undefined;
399
408
  let tau = symbolToPerpStaticInfo.get(symbol)!.maintenanceMarginRate;
@@ -419,7 +428,7 @@ export default class MarketData extends PerpetualDataHandler {
419
428
  symbol: currentPositionRisk.symbol,
420
429
  positionNotionalBaseCCY: Math.abs(newPosition),
421
430
  side: newSide,
422
- entryPrice: entryPrice,
431
+ entryPrice: newEntryPrice,
423
432
  leverage: newLeverage,
424
433
  markPrice: markPrice,
425
434
  unrealizedPnlQuoteCCY: newPosition * markPrice - newLockedInValue,
@@ -549,11 +558,16 @@ export default class MarketData extends PerpetualDataHandler {
549
558
  if (this.proxyContract == null) {
550
559
  throw Error("no proxy contract initialized. Use createProxyInstance().");
551
560
  }
552
- if(indexPrices==undefined) {
561
+ if (indexPrices == undefined) {
553
562
  let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
554
563
  indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
555
564
  }
556
- return await PerpetualDataHandler._queryPerpetualMarkPrice(symbol, this.symbolToPerpStaticInfo, this.proxyContract, indexPrices);
565
+ return await PerpetualDataHandler._queryPerpetualMarkPrice(
566
+ symbol,
567
+ this.symbolToPerpStaticInfo,
568
+ this.proxyContract,
569
+ indexPrices
570
+ );
557
571
  }
558
572
 
559
573
  /**
@@ -576,11 +590,11 @@ export default class MarketData extends PerpetualDataHandler {
576
590
  *
577
591
  * @returns price (number)
578
592
  */
579
- public async getPerpetualPrice(symbol: string, quantity: number, indexPrices?:[number, number]): Promise<number> {
593
+ public async getPerpetualPrice(symbol: string, quantity: number, indexPrices?: [number, number]): Promise<number> {
580
594
  if (this.proxyContract == null) {
581
595
  throw Error("no proxy contract initialized. Use createProxyInstance().");
582
596
  }
583
- if (indexPrices==undefined) {
597
+ if (indexPrices == undefined) {
584
598
  // fetch from API
585
599
  let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
586
600
  indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
@@ -600,11 +614,14 @@ export default class MarketData extends PerpetualDataHandler {
600
614
  * @param indexPrices S2 and S3 prices/isMarketOpen if not provided fetch via REST API
601
615
  * @returns PerpetualState reference
602
616
  */
603
- public async getPerpetualState(symbol: string, indexPriceInfo?: [number, number, boolean, boolean]): Promise<PerpetualState> {
617
+ public async getPerpetualState(
618
+ symbol: string,
619
+ indexPriceInfo?: [number, number, boolean, boolean]
620
+ ): Promise<PerpetualState> {
604
621
  if (this.proxyContract == null) {
605
622
  throw Error("no proxy contract initialized. Use createProxyInstance().");
606
623
  }
607
- if (indexPriceInfo==undefined) {
624
+ if (indexPriceInfo == undefined) {
608
625
  let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
609
626
  indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
610
627
  }
@@ -706,7 +723,7 @@ export default class MarketData extends PerpetualDataHandler {
706
723
  return digests;
707
724
  }
708
725
 
709
- public async getAvailableMargin(traderAddr: string, symbol: string): Promise<number> {
726
+ public async getAvailableMargin(traderAddr: string, symbol: string, indexPrices?: [number, number]): Promise<number> {
710
727
  if (this.proxyContract == null) {
711
728
  throw Error("no proxy contract initialized. Use createProxyInstance().");
712
729
  }
@@ -716,8 +733,16 @@ export default class MarketData extends PerpetualDataHandler {
716
733
  this.symbolToPerpStaticInfo,
717
734
  this.proxyContract
718
735
  );
736
+ if (indexPrices == undefined) {
737
+ // fetch from API
738
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
739
+ indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
740
+ }
741
+ let S2 = indexPrices[0];
742
+ let ccyType = this.getPerpetualStaticInfo(symbol).collateralCurrencyType;
743
+ let S3 = ccyType == COLLATERAL_CURRENCY_QUANTO ? indexPrices[1] : ccyType == COLLATERAL_CURRENCY_QUOTE ? 1 : S2;
719
744
  let perpInfo = this.symbolToPerpStaticInfo.get(symbol);
720
- let balanceCC = mgnAcct.collateralCC + mgnAcct.unrealizedPnlQuoteCCY / mgnAcct.collToQuoteConversion;
745
+ let balanceCC = mgnAcct.collateralCC + mgnAcct.unrealizedPnlQuoteCCY / S3;
721
746
  let initalMarginCC = Math.abs(
722
747
  (perpInfo!.initialMarginRate * mgnAcct.positionNotionalBaseCCY * mgnAcct.markPrice) /
723
748
  mgnAcct.collToQuoteConversion
@@ -728,7 +753,7 @@ export default class MarketData extends PerpetualDataHandler {
728
753
  public static async _exchangeInfo(
729
754
  _proxyContract: ethers.Contract,
730
755
  _poolStaticInfos: Array<PoolStaticInfo>,
731
- _symbolToPerpStaticInfo : Map<string, PerpetualStaticInfo>,
756
+ _symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
732
757
  _symbolList: Map<string, string>,
733
758
  _priceFeedGetter: PriceFeeds
734
759
  ): Promise<ExchangeInfo> {
@@ -736,18 +761,18 @@ export default class MarketData extends PerpetualDataHandler {
736
761
  let factory = await _proxyContract.getOracleFactory();
737
762
  let info: ExchangeInfo = { pools: [], oracleFactoryAddr: factory, proxyAddr: _proxyContract.address };
738
763
  const numPools = nestedPerpetualIDs.length;
739
-
764
+
740
765
  // get all prices
741
766
  let allSym = new Set<string>();
742
- for(let perpSymbol of _symbolToPerpStaticInfo.keys()) {
743
- let sInfo : PerpetualStaticInfo | undefined = _symbolToPerpStaticInfo.get(perpSymbol);
767
+ for (let perpSymbol of _symbolToPerpStaticInfo.keys()) {
768
+ let sInfo: PerpetualStaticInfo | undefined = _symbolToPerpStaticInfo.get(perpSymbol);
744
769
  allSym.add(sInfo!.S2Symbol);
745
- if(sInfo!.S3Symbol!='') {
770
+ if (sInfo!.S3Symbol != "") {
746
771
  allSym.add(sInfo!.S3Symbol);
747
772
  }
748
773
  }
749
774
  let allSymArr = Array.from(allSym.values());
750
- let idxPriceMap : Map<string, [number,boolean]> = await _priceFeedGetter.fetchPrices(allSymArr);
775
+ let idxPriceMap: Map<string, [number, boolean]> = await _priceFeedGetter.fetchPrices(allSymArr);
751
776
 
752
777
  for (var j = 0; j < numPools; j++) {
753
778
  let perpetualIDs = nestedPerpetualIDs[j];
@@ -767,33 +792,39 @@ export default class MarketData extends PerpetualDataHandler {
767
792
  let poolSymbol = PoolState.poolSymbol;
768
793
  for (var k = 0; k < perpetualIDs.length; k++) {
769
794
  let perp = await _proxyContract.getPerpetual(perpetualIDs[k]);
770
- let symS2 = contractSymbolToSymbol(perp.S2BaseCCY, _symbolList)+"-"+contractSymbolToSymbol(perp.S2QuoteCCY, _symbolList);
771
- let symS3 = contractSymbolToSymbol(perp.S3BaseCCY, _symbolList)+"-"+contractSymbolToSymbol(perp.S3QuoteCCY, _symbolList);
772
- let perpSymbol = symS2+"-"+poolSymbol;
795
+ let symS2 =
796
+ contractSymbolToSymbol(perp.S2BaseCCY, _symbolList) +
797
+ "-" +
798
+ contractSymbolToSymbol(perp.S2QuoteCCY, _symbolList);
799
+ let symS3 =
800
+ contractSymbolToSymbol(perp.S3BaseCCY, _symbolList) +
801
+ "-" +
802
+ contractSymbolToSymbol(perp.S3QuoteCCY, _symbolList);
803
+ let perpSymbol = symS2 + "-" + poolSymbol;
773
804
  //console.log("perpsymbol=",perpSymbol);
774
- let res = idxPriceMap.get(symS2)
775
- if (res==undefined) {
805
+ let res = idxPriceMap.get(symS2);
806
+ if (res == undefined) {
776
807
  throw new Error(`Price for index ${symS2} could not be fetched - config issue`);
777
808
  }
778
- let [indexS2, isS2MktClosed] : [number, boolean] = [res[0], res[1]];
809
+ let [indexS2, isS2MktClosed]: [number, boolean] = [res[0], res[1]];
779
810
  let indexS3 = 1;
780
811
  let isS3MktClosed = false;
781
812
  if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_BASE) {
782
813
  indexS3 = indexS2;
783
814
  } else if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_QUANTO) {
784
815
  res = idxPriceMap.get(symS3);
785
- if (res==undefined) {
816
+ if (res == undefined) {
786
817
  throw new Error(`Price for index ${symS3} could not be fetched - config issue`);
787
818
  } else {
788
819
  indexS3 = res[0];
789
- isS3MktClosed = res[1];
820
+ isS3MktClosed = res[1];
790
821
  }
791
822
  }
792
823
  let fMidPrice = await _proxyContract.queryPerpetualPrice(perpetualIDs[k], BigNumber.from(0), [
793
824
  floatToABK64x64(indexS2),
794
- floatToABK64x64(indexS3)
825
+ floatToABK64x64(indexS3),
795
826
  ]);
796
-
827
+
797
828
  let markPremiumRate = ABK64x64ToFloat(perp.currentMarkPremiumRate.fPrice);
798
829
  let currentFundingRateBps = 1e4 * ABK64x64ToFloat(perp.fCurrentFundingRate);
799
830
  let state = PERP_STATE_STR[perp.state];
@@ -809,7 +840,7 @@ export default class MarketData extends PerpetualDataHandler {
809
840
  currentFundingRateBps: currentFundingRateBps,
810
841
  openInterestBC: ABK64x64ToFloat(perp.fOpenInterest),
811
842
  maxPositionBC: Infinity,
812
- isMarketClosed: isS2MktClosed || isS3MktClosed
843
+ isMarketClosed: isS2MktClosed || isS3MktClosed,
813
844
  };
814
845
  PoolState.perpetuals.push(PerpetualState);
815
846
  }
@@ -166,21 +166,20 @@ export default class PerpetualDataHandler {
166
166
  // try to find a limit order book
167
167
  let lobAddr = await this.lobFactoryContract.getOrderBookAddress(perpetualIDs[k]);
168
168
  currentLimitOrderBookAddr.push(lobAddr);
169
- if (poolCCY == undefined) {
170
- // we find out the pool currency by looking at all perpetuals
171
- // unless for quanto perpetuals, we know the pool currency
172
- // from the perpetual. This fails if we have a pool with only
173
- // quanto perpetuals
174
- if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_BASE) {
175
- poolCCY = base;
176
- ccy.push(CollaterlCCY.BASE);
177
- } else if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_QUOTE) {
178
- poolCCY = quote;
179
- ccy.push(CollaterlCCY.QUOTE);
180
- } else {
181
- poolCCY = base3;
182
- ccy.push(CollaterlCCY.QUANTO);
183
- }
169
+
170
+ // we find out the pool currency by looking at all perpetuals
171
+ // unless for quanto perpetuals, we know the pool currency
172
+ // from the perpetual. This fails if we have a pool with only
173
+ // quanto perpetuals
174
+ if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_BASE) {
175
+ poolCCY = poolCCY ?? base;
176
+ ccy.push(CollaterlCCY.BASE);
177
+ } else if (perp.eCollateralCurrency == COLLATERAL_CURRENCY_QUOTE) {
178
+ poolCCY = poolCCY ?? quote;
179
+ ccy.push(CollaterlCCY.QUOTE);
180
+ } else {
181
+ poolCCY = poolCCY ?? base3;
182
+ ccy.push(CollaterlCCY.QUANTO);
184
183
  }
185
184
  }
186
185
  if (perpetualIDs.length == 0) {
@@ -352,12 +351,17 @@ export default class PerpetualDataHandler {
352
351
  traderAddr: string,
353
352
  symbol: string,
354
353
  symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>,
355
- _proxyContract: ethers.Contract
354
+ _proxyContract: ethers.Contract,
355
+ _pxS2S3?: [number, number | undefined]
356
356
  ): Promise<MarginAccount> {
357
357
  let perpId = Number(symbol);
358
358
  if (isNaN(perpId)) {
359
359
  perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
360
360
  }
361
+ // TODO: correct numbers using actual S2, S3
362
+ // if (_pxS2S3 == undefined) {
363
+ // // fetch s2,s3
364
+ // }
361
365
  const idx_cash = 3;
362
366
  const idx_notional = 4;
363
367
  const idx_locked_in = 5;
@@ -434,20 +438,23 @@ export default class PerpetualDataHandler {
434
438
  indexPrices: [number, number, boolean, boolean]
435
439
  ): Promise<PerpetualState> {
436
440
  let perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
441
+ let staticInfo = symbolToPerpStaticInfo.get(symbol)!;
437
442
  let ccy = symbol.split("-");
438
443
  let [S2, S3] = [indexPrices[0], indexPrices[1]];
439
- let ammState = await _proxyContract.getAMMState(
440
- perpId,
441
- [S2, S3].map((x) => floatToABK64x64(x == undefined || Number.isNaN(x) ? 0 : x))
442
- );
443
- let markPrice = ABK64x64ToFloat(ammState[6].mul(ONE_64x64.add(ammState[8])).div(ONE_64x64));
444
+ if (staticInfo.collateralCurrencyType == CollaterlCCY.BASE) {
445
+ S3 = S2;
446
+ } else if (staticInfo.collateralCurrencyType == CollaterlCCY.QUOTE) {
447
+ S3 = 1;
448
+ }
449
+ let ammState = await _proxyContract.getAMMState(perpId, [S2, S3].map(floatToABK64x64));
450
+ let markPrice = S2 * (1 + ABK64x64ToFloat(ammState[8]));
444
451
  let state = {
445
452
  id: perpId,
446
453
  state: PERP_STATE_STR[ammState[13]],
447
454
  baseCurrency: ccy[0],
448
455
  quoteCurrency: ccy[1],
449
- indexPrice: ABK64x64ToFloat(ammState[6]),
450
- collToQuoteIndexPrice: ABK64x64ToFloat(ammState[7]),
456
+ indexPrice: S2,
457
+ collToQuoteIndexPrice: S3,
451
458
  markPrice: markPrice,
452
459
  midPrice: ABK64x64ToFloat(ammState[10]),
453
460
  currentFundingRateBps: ABK64x64ToFloat(ammState[14]) * 1e4,
@@ -455,11 +462,6 @@ export default class PerpetualDataHandler {
455
462
  maxPositionBC: ABK64x64ToFloat(ammState[12]),
456
463
  isMarketClosed: indexPrices[2] || indexPrices[3],
457
464
  };
458
- if (symbolToPerpStaticInfo.get(symbol)?.collateralCurrencyType == CollaterlCCY.BASE) {
459
- state.collToQuoteIndexPrice = state.indexPrice;
460
- } else if (symbolToPerpStaticInfo.get(symbol)?.collateralCurrencyType == CollaterlCCY.QUOTE) {
461
- state.collToQuoteIndexPrice = 1;
462
- }
463
465
  return state;
464
466
  }
465
467
 
@@ -834,4 +836,50 @@ export default class PerpetualDataHandler {
834
836
  }
835
837
  return undefined;
836
838
  }
839
+
840
+ /**
841
+ * Performs basic validity checks on a given order
842
+ * @param order Order struct
843
+ * @param traderAccount Trader account
844
+ * @param perpStaticInfo Symbol to perpetual info map
845
+ */
846
+ protected static checkOrder(
847
+ order: Order,
848
+ traderAccount: MarginAccount,
849
+ perpStaticInfo: Map<string, PerpetualStaticInfo>
850
+ ) {
851
+ // this throws error if not found
852
+ let perpetualId = PerpetualDataHandler.symbolToPerpetualId(order.symbol, perpStaticInfo);
853
+
854
+ // check side
855
+ if (order.side != BUY_SIDE && order.side != SELL_SIDE) {
856
+ throw Error(`order side must be ${BUY_SIDE} or ${SELL_SIDE}`);
857
+ }
858
+
859
+ // check amount
860
+ let lotSize = perpStaticInfo.get(order.symbol)!.lotSizeBC;
861
+ let curPos =
862
+ traderAccount.side == CLOSED_SIDE
863
+ ? 0
864
+ : (traderAccount.side == BUY_SIDE ? 1 : -1) * traderAccount.positionNotionalBaseCCY;
865
+ let newPos = curPos + (order.side == BUY_SIDE ? 1 : -1) * order.quantity;
866
+ if (Math.abs(order.quantity) < lotSize || (Math.abs(newPos) >= lotSize && Math.abs(newPos) < 10 * lotSize)) {
867
+ throw Error(`trade amount too small: ${order.quantity} ${perpStaticInfo.get(order.symbol)!.S2Symbol}`);
868
+ }
869
+
870
+ // check limit price
871
+ if (order.side == BUY_SIDE && order.limitPrice != undefined && order.limitPrice <= 0) {
872
+ throw Error(`invalid limit price for buy order: ${order.limitPrice}`);
873
+ }
874
+
875
+ // broker fee
876
+ if (order.brokerFeeTbps != undefined && order.brokerFeeTbps < 0) {
877
+ throw Error(`invalid broker fee: ${order.brokerFeeTbps / 10} bps`);
878
+ }
879
+
880
+ // stop price
881
+ if (order.stopPrice != undefined && order.stopPrice < 0) {
882
+ throw Error(`invalid stop price: ${order.stopPrice}`);
883
+ }
884
+ }
837
885
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const D8X_SDK_VERSION = "0.1.1";
1
+ export const D8X_SDK_VERSION = "0.1.2";