@d8x/perpetuals-sdk 0.1.2 → 0.1.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.
@@ -189,7 +189,8 @@ class MarketData extends perpetualDataHandler_1.default {
189
189
  if (this.proxyContract == null) {
190
190
  throw Error("no proxy contract initialized. Use createProxyInstance().");
191
191
  }
192
- let mgnAcct = yield perpetualDataHandler_1.default.getMarginAccount(traderAddr, symbol, this.symbolToPerpStaticInfo, this.proxyContract);
192
+ let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(symbol);
193
+ let mgnAcct = yield perpetualDataHandler_1.default.getMarginAccount(traderAddr, symbol, this.symbolToPerpStaticInfo, this.proxyContract, [obj.idxPrices[0], obj.idxPrices[1]]);
193
194
  return mgnAcct;
194
195
  });
195
196
  }
@@ -197,32 +198,123 @@ class MarketData extends perpetualDataHandler_1.default {
197
198
  * Estimates what the position risk will be if a given order is executed.
198
199
  * @param traderAddr Address of trader
199
200
  * @param order Order to be submitted
200
- * @param currentPositionRisk Position risk before trade
201
+ * @param account Position risk before trade
202
+ * @param indexPriceInfo Index prices and market status (open/closed)
201
203
  * @returns {MarginAccount} Position risk after trade
202
204
  */
203
- positionRiskOnTrade(traderAddr, order, currentPositionRisk, indexPriceInfo) {
204
- var _a, _b, _c;
205
+ positionRiskOnTrade(traderAddr, order, account, indexPriceInfo) {
206
+ var _a, _b, _c, _d, _e;
205
207
  return __awaiter(this, void 0, void 0, function* () {
206
208
  if (this.proxyContract == null) {
207
209
  throw Error("no proxy contract initialized. Use createProxyInstance().");
208
210
  }
209
- if (currentPositionRisk == undefined) {
210
- currentPositionRisk = yield this.positionRisk(traderAddr, order.symbol);
211
+ // fetch undefined data
212
+ if (account == undefined) {
213
+ account = yield this.positionRisk(traderAddr, order.symbol);
211
214
  }
212
215
  if (indexPriceInfo == undefined) {
213
- let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(currentPositionRisk.symbol);
216
+ let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
214
217
  indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
215
218
  }
216
- let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(order.symbol, this.poolStaticInfos);
217
- // price for this order = limit price (conservative) if given, else the current perp price
218
- let tradeAmount = Math.abs(order.quantity) * (order.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1);
219
- let tradePrice = (_a = order.limitPrice) !== null && _a !== void 0 ? _a : (yield this.getPerpetualPrice(order.symbol, tradeAmount));
220
- // total fee rate = exchange fee + broker fee
221
- let feeRate = ((yield this.proxyContract.queryExchangeFee(poolId, traderAddr, (_b = order.brokerAddr) !== null && _b !== void 0 ? _b : nodeSDKTypes_1.ZERO_ADDRESS)) +
222
- ((_c = order.brokerFeeTbps) !== null && _c !== void 0 ? _c : 0)) /
223
- 100000;
219
+ let lotSizeBC = MarketData._getLotSize(account.symbol, this.symbolToPerpStaticInfo);
220
+ // Too small, no change to account
221
+ if (Math.abs(order.quantity) < lotSizeBC) {
222
+ return { newPositionRisk: account, orderCost: 0 };
223
+ }
224
+ // Current state:
225
+ // perp (for FXs and such)
224
226
  let perpetualState = yield this.getPerpetualState(order.symbol, indexPriceInfo);
225
- return MarketData._positionRiskOnAccountAction(order.symbol, tradeAmount, 0, order.leverage, order.keepPositionLvg, tradePrice, feeRate, perpetualState, currentPositionRisk, this.symbolToPerpStaticInfo);
227
+ let [S2, S3, Sm] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice, perpetualState.markPrice];
228
+ // cash in margin account: upon trading, unpaid funding will be realized
229
+ let currentMarginCashCC = account.collateralCC;
230
+ // signed position, still correct if side is closed (==0)
231
+ let currentPositionBC = (account.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1) * account.positionNotionalBaseCCY;
232
+ // signed locked-in value
233
+ let currentLockedInQC = account.entryPrice * currentPositionBC;
234
+ // New trader state:
235
+ // signed trade amount
236
+ let tradeAmountBC = Math.abs(order.quantity) * (order.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1);
237
+ // signed position
238
+ let newPositionBC = currentPositionBC + tradeAmountBC;
239
+ if (Math.abs(newPositionBC) < 10 * lotSizeBC) {
240
+ // fully closed
241
+ tradeAmountBC = -currentPositionBC;
242
+ newPositionBC = 0;
243
+ }
244
+ let newSide = newPositionBC > 0 ? nodeSDKTypes_1.BUY_SIDE : newPositionBC < 0 ? nodeSDKTypes_1.SELL_SIDE : nodeSDKTypes_1.CLOSED_SIDE;
245
+ // price for this order = limit price (conservative) if given, else the current perp price
246
+ let tradePrice = (_a = order.limitPrice) !== null && _a !== void 0 ? _a : (yield this.getPerpetualPrice(order.symbol, tradeAmountBC));
247
+ // fees
248
+ let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(order.symbol, this.poolStaticInfos);
249
+ let exchangeFeeTbps = yield this.proxyContract.queryExchangeFee(poolId, traderAddr, (_b = order.brokerAddr) !== null && _b !== void 0 ? _b : nodeSDKTypes_1.ZERO_ADDRESS);
250
+ let exchangeFeeCC = (Math.abs(tradeAmountBC) * exchangeFeeTbps * 1e-5 * S2) / S3;
251
+ let brokerFeeCC = (Math.abs(tradeAmountBC) * ((_c = order.brokerFeeTbps) !== null && _c !== void 0 ? _c : 0) * 1e-5 * S2) / S3;
252
+ // Trade type:
253
+ let isClose = newPositionBC == 0 || newPositionBC * tradeAmountBC < 0;
254
+ let isOpen = newPositionBC != 0 && (currentPositionBC == 0 || newPositionBC * currentPositionBC > 0); // regular open, no flip
255
+ let isFlip = Math.abs(newPositionBC) > Math.abs(currentPositionBC) && !isOpen; // flip position sign, not fully closed
256
+ let keepPositionLvgOnClose = ((_d = order.keepPositionLvg) !== null && _d !== void 0 ? _d : false) && !isOpen;
257
+ // Contract: _doMarginCollateralActions
258
+ // No collateral actions if
259
+ // 1) leverage is not set or
260
+ // 2) fully closed after trade or
261
+ // 3) is a partial closing, it doesn't flip, and keep lvg flag is not set
262
+ let traderDepositCC;
263
+ let targetLvg;
264
+ if (order.leverage == undefined || newPositionBC == 0 || (!isOpen && !isFlip && !keepPositionLvgOnClose)) {
265
+ traderDepositCC = 0;
266
+ targetLvg = 0;
267
+ }
268
+ else {
269
+ // 1) opening and flipping trades need to specify a leverage: default to max if not given
270
+ // 2) for others it's ignored, set target to 0
271
+ let initialMarginRate = this.symbolToPerpStaticInfo.get(account.symbol).initialMarginRate;
272
+ targetLvg = isFlip || isOpen ? (_e = order.leverage) !== null && _e !== void 0 ? _e : 1 / initialMarginRate : 0;
273
+ let [b0, pos0] = isOpen ? [0, 0] : [account.collateralCC, currentPositionBC];
274
+ traderDepositCC = (0, d8XMath_1.getDepositAmountForLvgTrade)(b0, pos0, tradeAmountBC, targetLvg, tradePrice, S3, Sm);
275
+ console.log("deposit for trget lvg:", traderDepositCC);
276
+ // fees are paid from wallet in this case
277
+ // referral rebate??
278
+ traderDepositCC += exchangeFeeCC + brokerFeeCC;
279
+ }
280
+ // Contract: _executeTrade
281
+ let deltaCashCC = (-tradeAmountBC * (tradePrice - S2)) / S3;
282
+ let deltaLockedQC = tradeAmountBC * S2;
283
+ if (isClose) {
284
+ let pnl = account.entryPrice * tradeAmountBC - deltaLockedQC;
285
+ deltaLockedQC += pnl;
286
+ deltaCashCC += pnl / S3;
287
+ }
288
+ // funding and fees
289
+ deltaCashCC = deltaCashCC + account.unrealizedFundingCollateralCCY - exchangeFeeCC - brokerFeeCC;
290
+ // New cash, locked-in, entry price & leverage after trade
291
+ let newLockedInValueQC = currentLockedInQC + deltaLockedQC;
292
+ let newMarginCashCC = currentMarginCashCC + deltaCashCC + traderDepositCC;
293
+ let newEntryPrice = newPositionBC == 0 ? 0 : Math.abs(newLockedInValueQC / newPositionBC);
294
+ let newMarginBalanceCC = newMarginCashCC + (newPositionBC * Sm - newLockedInValueQC) / S3;
295
+ let newLeverage = newPositionBC == 0
296
+ ? 0
297
+ : newMarginBalanceCC <= 0
298
+ ? Infinity
299
+ : (Math.abs(newPositionBC) * Sm) / S3 / newMarginBalanceCC;
300
+ // Liquidation params
301
+ let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(account.symbol, newLockedInValueQC, newPositionBC, newMarginCashCC, Sm, S3, this.symbolToPerpStaticInfo);
302
+ // New position risk
303
+ let newPositionRisk = {
304
+ symbol: account.symbol,
305
+ positionNotionalBaseCCY: Math.abs(newPositionBC),
306
+ side: newSide,
307
+ entryPrice: newEntryPrice,
308
+ leverage: newLeverage,
309
+ markPrice: Sm,
310
+ unrealizedPnlQuoteCCY: newPositionBC * Sm - newLockedInValueQC,
311
+ unrealizedFundingCollateralCCY: 0,
312
+ collateralCC: newMarginCashCC,
313
+ collToQuoteConversion: S3,
314
+ liquidationPrice: [S2Liq, S3Liq],
315
+ liquidationLvg: 1 / tau,
316
+ };
317
+ return { newPositionRisk: newPositionRisk, orderCost: traderDepositCC };
226
318
  });
227
319
  }
228
320
  /**
@@ -232,105 +324,94 @@ class MarketData extends perpetualDataHandler_1.default {
232
324
  * @param currentPositionRisk Position risk before
233
325
  * @returns {MarginAccount} Position risk after
234
326
  */
235
- positionRiskOnCollateralAction(deltaCollateral, currentPositionRisk, indexPriceInfo) {
327
+ positionRiskOnCollateralAction(deltaCollateral, account, indexPriceInfo) {
236
328
  return __awaiter(this, void 0, void 0, function* () {
237
329
  if (this.proxyContract == null) {
238
330
  throw Error("no proxy contract initialized. Use createProxyInstance().");
239
331
  }
332
+ if (deltaCollateral + account.collateralCC + account.unrealizedFundingCollateralCCY < 0) {
333
+ throw Error("not enough margin to remove");
334
+ }
240
335
  if (indexPriceInfo == undefined) {
241
- let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(currentPositionRisk.symbol);
336
+ let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
242
337
  indexPriceInfo = [obj.idxPrices[0], obj.idxPrices[1], obj.mktClosed[0], obj.mktClosed[1]];
243
338
  }
244
- let perpetualState = yield this.getPerpetualState(currentPositionRisk.symbol, indexPriceInfo);
245
- return MarketData._positionRiskOnAccountAction(currentPositionRisk.symbol, 0, deltaCollateral, undefined, false, 0, 0, perpetualState, currentPositionRisk, this.symbolToPerpStaticInfo);
339
+ let perpetualState = yield this.getPerpetualState(account.symbol, indexPriceInfo);
340
+ let [S2, S3, Sm] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice, perpetualState.markPrice];
341
+ // no position: just increase collateral and kill liquidation vars
342
+ if (account.positionNotionalBaseCCY == 0) {
343
+ return {
344
+ symbol: account.symbol,
345
+ positionNotionalBaseCCY: account.positionNotionalBaseCCY,
346
+ side: account.side,
347
+ entryPrice: account.entryPrice,
348
+ leverage: account.leverage,
349
+ markPrice: Sm,
350
+ unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
351
+ unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
352
+ collateralCC: account.collateralCC + deltaCollateral,
353
+ collToQuoteConversion: S3,
354
+ liquidationPrice: [0, undefined],
355
+ liquidationLvg: Infinity,
356
+ };
357
+ }
358
+ let positionBC = account.positionNotionalBaseCCY * (account.side == nodeSDKTypes_1.BUY_SIDE ? 1 : -1);
359
+ let lockedInQC = account.entryPrice * positionBC;
360
+ let newMarginCashCC = account.collateralCC + deltaCollateral;
361
+ let newMarginBalanceCC = newMarginCashCC + account.unrealizedFundingCollateralCCY + (positionBC * Sm - lockedInQC) / S3;
362
+ if (newMarginBalanceCC <= 0) {
363
+ return {
364
+ symbol: account.symbol,
365
+ positionNotionalBaseCCY: account.positionNotionalBaseCCY,
366
+ side: account.side,
367
+ entryPrice: account.entryPrice,
368
+ leverage: Infinity,
369
+ markPrice: Sm,
370
+ unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
371
+ unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
372
+ collateralCC: newMarginCashCC,
373
+ collToQuoteConversion: S3,
374
+ liquidationPrice: [S2, S3],
375
+ liquidationLvg: 0,
376
+ };
377
+ }
378
+ let newLeverage = (Math.abs(positionBC) * Sm) / S3 / newMarginBalanceCC;
379
+ // Liquidation params
380
+ let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(account.symbol, lockedInQC, positionBC, newMarginCashCC, Sm, S3, this.symbolToPerpStaticInfo);
381
+ // New position risk
382
+ let newPositionRisk = {
383
+ symbol: account.symbol,
384
+ positionNotionalBaseCCY: account.positionNotionalBaseCCY,
385
+ side: account.side,
386
+ entryPrice: account.entryPrice,
387
+ leverage: newLeverage,
388
+ markPrice: Sm,
389
+ unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
390
+ unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
391
+ collateralCC: newMarginCashCC,
392
+ collToQuoteConversion: S3,
393
+ liquidationPrice: [S2Liq, S3Liq],
394
+ liquidationLvg: 1 / tau,
395
+ };
396
+ return newPositionRisk;
246
397
  });
247
398
  }
248
- static _positionRiskOnAccountAction(symbol, tradeAmount, marginDeposit, tradeLeverage, keepPositionLvg, tradePrice, feeRate, perpetualState, currentPositionRisk, symbolToPerpStaticInfo) {
249
- let currentSide = currentPositionRisk.side;
250
- let currentPosition = (currentSide == nodeSDKTypes_1.BUY_SIDE ? 1 : -1) * currentPositionRisk.positionNotionalBaseCCY;
251
- let newPosition = currentPosition + tradeAmount;
252
- let newSide = newPosition > 0 ? nodeSDKTypes_1.BUY_SIDE : newPosition < 0 ? nodeSDKTypes_1.SELL_SIDE : nodeSDKTypes_1.CLOSED_SIDE;
253
- let lockedInValue = currentPositionRisk.entryPrice * currentPosition;
254
- let newLockedInValue = lockedInValue + tradeAmount * tradePrice;
255
- let newEntryPrice = newPosition == 0 ? 0 : Math.abs(newLockedInValue / newPosition);
256
- if (tradeAmount == 0) {
257
- keepPositionLvg = false;
258
- }
259
- let isOpen = newPosition != 0 && (tradeAmount == 0 || currentPosition == 0 || tradeAmount * currentPosition > 0);
260
- let isFlip = Math.abs(tradeAmount) > Math.abs(currentPosition) && !isOpen;
261
- let keepPositionLvgOnClose = keepPositionLvg && !isOpen;
262
- // need these for leverage/margin calculations
263
- let [markPrice, indexPriceS2, indexPriceS3] = [
264
- perpetualState.markPrice,
265
- perpetualState.indexPrice,
266
- perpetualState.collToQuoteIndexPrice,
267
- ];
268
- let newCollateral;
269
- let newLeverage;
270
- if (keepPositionLvg) {
271
- // we have a target leverage for the resulting position
272
- // this gives us the total margin needed in the account so that it satisfies the leverage condition
273
- newCollateral = (0, d8XMath_1.getMarginRequiredForLeveragedTrade)(currentPositionRisk.leverage, currentPosition, lockedInValue, tradeAmount, markPrice, indexPriceS2, indexPriceS3, tradePrice, feeRate);
274
- // the new leverage follows from the updated margin and position
275
- newLeverage = (0, d8XMath_1.getNewPositionLeverage)(tradeAmount, newCollateral, currentPosition, lockedInValue, tradePrice, indexPriceS3, markPrice);
276
- }
277
- else if (tradeAmount != 0) {
278
- let deltaCash;
279
- if (!isOpen && !isFlip && !keepPositionLvgOnClose) {
280
- // cash comes from realized pnl
281
- deltaCash = (tradeAmount * (tradePrice - currentPositionRisk.entryPrice)) / indexPriceS3;
282
- newLockedInValue = lockedInValue + currentPositionRisk.entryPrice * tradeAmount;
283
- }
284
- else {
285
- // target lvg will default current lvg if not specified
286
- let targetLvg = isFlip || isOpen ? tradeLeverage !== null && tradeLeverage !== void 0 ? tradeLeverage : 0 : 0;
287
- let b0, pos0;
288
- [b0, pos0] = isOpen ? [0, 0] : [currentPositionRisk.collateralCC, currentPosition];
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;
294
- }
295
- newCollateral = currentPositionRisk.collateralCC + deltaCash; // - (feeRate * Math.abs(tradeAmount) * indexPriceS2) / indexPriceS3;
296
- // the new leverage corresponds to increasing the position and collateral according to the order
297
- newLeverage = (0, d8XMath_1.getNewPositionLeverage)(0, newCollateral, newPosition, newLockedInValue, tradePrice, indexPriceS3, markPrice);
298
- }
299
- else {
300
- // there is no order, adding/removing collateral
301
- newCollateral = currentPositionRisk.collateralCC + marginDeposit;
302
- newLeverage = (0, d8XMath_1.getNewPositionLeverage)(0, newCollateral, currentPosition, lockedInValue, indexPriceS2, indexPriceS3, markPrice);
303
- }
304
- // liquidation vars
399
+ static _getLiquidationParams(symbol, lockedInQC, signedPositionBC, marginCashCC, markPrice, collToQuoteConversion, symbolToPerpStaticInfo) {
305
400
  let S2Liq, S3Liq;
306
401
  let tau = symbolToPerpStaticInfo.get(symbol).maintenanceMarginRate;
307
402
  let ccyType = symbolToPerpStaticInfo.get(symbol).collateralCurrencyType;
308
403
  if (ccyType == nodeSDKTypes_1.CollaterlCCY.BASE) {
309
- S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralBase)(newLockedInValue, newPosition, newCollateral, tau);
404
+ S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralBase)(lockedInQC, signedPositionBC, marginCashCC, tau);
310
405
  S3Liq = S2Liq;
311
406
  }
312
407
  else if (ccyType == nodeSDKTypes_1.CollaterlCCY.QUANTO) {
313
- S3Liq = indexPriceS3;
314
- S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralQuanto)(newLockedInValue, newPosition, newCollateral, tau, indexPriceS3, markPrice);
408
+ S3Liq = collToQuoteConversion;
409
+ S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralQuanto)(lockedInQC, signedPositionBC, marginCashCC, tau, collToQuoteConversion, markPrice);
315
410
  }
316
411
  else {
317
- S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralQuote)(newLockedInValue, newPosition, newCollateral, tau);
412
+ S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralQuote)(lockedInQC, signedPositionBC, marginCashCC, tau);
318
413
  }
319
- let newPositionRisk = {
320
- symbol: currentPositionRisk.symbol,
321
- positionNotionalBaseCCY: Math.abs(newPosition),
322
- side: newSide,
323
- entryPrice: newEntryPrice,
324
- leverage: newLeverage,
325
- markPrice: markPrice,
326
- unrealizedPnlQuoteCCY: newPosition * markPrice - newLockedInValue,
327
- unrealizedFundingCollateralCCY: currentPositionRisk.unrealizedFundingCollateralCCY,
328
- collateralCC: newCollateral,
329
- collToQuoteConversion: indexPriceS3,
330
- liquidationPrice: [S2Liq, S3Liq],
331
- liquidationLvg: 1 / tau,
332
- };
333
- return newPositionRisk;
414
+ return [S2Liq, S3Liq, tau];
334
415
  }
335
416
  /**
336
417
  * Gets the wallet balance in the collateral currency corresponding to a given perpetual symbol.
@@ -589,12 +670,12 @@ class MarketData extends perpetualDataHandler_1.default {
589
670
  if (this.proxyContract == null) {
590
671
  throw Error("no proxy contract initialized. Use createProxyInstance().");
591
672
  }
592
- let mgnAcct = yield perpetualDataHandler_1.default.getMarginAccount(traderAddr, symbol, this.symbolToPerpStaticInfo, this.proxyContract);
593
673
  if (indexPrices == undefined) {
594
674
  // fetch from API
595
675
  let obj = yield this.priceFeedGetter.fetchPricesForPerpetual(symbol);
596
676
  indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
597
677
  }
678
+ let mgnAcct = yield perpetualDataHandler_1.default.getMarginAccount(traderAddr, symbol, this.symbolToPerpStaticInfo, this.proxyContract, [indexPrices[0], indexPrices[1]]);
598
679
  let S2 = indexPrices[0];
599
680
  let ccyType = this.getPerpetualStaticInfo(symbol).collateralCurrencyType;
600
681
  let S3 = ccyType == nodeSDKTypes_1.COLLATERAL_CURRENCY_QUANTO ? indexPrices[1] : ccyType == nodeSDKTypes_1.COLLATERAL_CURRENCY_QUOTE ? 1 : S2;
@@ -605,6 +686,47 @@ class MarketData extends perpetualDataHandler_1.default {
605
686
  return balanceCC - initalMarginCC;
606
687
  });
607
688
  }
689
+ /**
690
+ * Calculate a type of exchange loyality score based on trader volume
691
+ * @param traderAddr address of the trader
692
+ * @param brokerAddr address of the trader's broker or undefined
693
+ * @returns a loyality score (4 worst, 1 best)
694
+ */
695
+ getTraderLoyalityScore(traderAddr, brokerAddr) {
696
+ return __awaiter(this, void 0, void 0, function* () {
697
+ if (this.proxyContract == null) {
698
+ throw Error("no proxy contract or wallet initialized. Use createProxyInstance().");
699
+ }
700
+ // loop over all pools and query volumes
701
+ let brokerProm = [];
702
+ let traderProm = [];
703
+ for (let k = 0; k < this.poolStaticInfos.length; k++) {
704
+ if (brokerAddr != "" && brokerAddr != undefined) {
705
+ let brkrVol = this.proxyContract.getCurrentBrokerVolume(this.poolStaticInfos[k].poolId, brokerAddr);
706
+ brokerProm.push(brkrVol);
707
+ }
708
+ let trdrVol = this.proxyContract.getCurrentTraderVolume(this.poolStaticInfos[k].poolId, traderAddr);
709
+ traderProm.push(trdrVol);
710
+ }
711
+ // sum
712
+ let totalBrokerVolume = 0;
713
+ let totalTraderVolume = 0;
714
+ let brkrVol = yield Promise.all(brokerProm);
715
+ let trdrVol = yield Promise.all(traderProm);
716
+ for (let k = 0; k < this.poolStaticInfos.length; k++) {
717
+ if (brokerAddr != "" && brokerAddr != undefined) {
718
+ totalBrokerVolume += (0, d8XMath_1.ABK64x64ToFloat)(brkrVol[k]);
719
+ }
720
+ totalTraderVolume += (0, d8XMath_1.ABK64x64ToFloat)(trdrVol[k]);
721
+ }
722
+ const volumeCap = 500000;
723
+ let score = totalBrokerVolume == 0 ? totalTraderVolume / volumeCap : totalBrokerVolume;
724
+ // 5 different equally spaced categories: (4 is best, 1 worst)
725
+ let rank4 = 1 + Math.floor(Math.min(score, 1 - 1e-15) * 4);
726
+ // desired ranking starts at 4 (worst) and ends at 1 (best)
727
+ return 5 - rank4;
728
+ });
729
+ }
608
730
  static _exchangeInfo(_proxyContract, _poolStaticInfos, _symbolToPerpStaticInfo, _symbolList, _priceFeedGetter) {
609
731
  return __awaiter(this, void 0, void 0, function* () {
610
732
  let nestedPerpetualIDs = yield perpetualDataHandler_1.default.getNestedPerpetualIds(_proxyContract);
@@ -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, _pxS2S3?: [number, number | undefined]): Promise<MarginAccount>;
97
+ static getMarginAccount(traderAddr: string, symbol: string, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>, _proxyContract: ethers.Contract, _pxS2S3: [number, number]): 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>;
@@ -102,10 +102,11 @@ export default class PerpetualDataHandler {
102
102
  * Liquidation price
103
103
  * @param symbol symbol of the form BTC-USD-MATIC
104
104
  * @param traderState BigInt array according to smart contract
105
+ * @param S2 number, index price S2
105
106
  * @param symbolToPerpStaticInfo mapping symbol->PerpStaticInfo
106
107
  * @returns liquidation mark-price, corresponding collateral/quote conversion
107
108
  */
108
- protected static _calculateLiquidationPrice(symbol: string, traderState: BigNumber[], symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>): [number, number, number, number, number];
109
+ protected static _calculateLiquidationPrice(symbol: string, traderState: BigNumber[], S2: number, symbolToPerpStaticInfo: Map<string, PerpetualStaticInfo>): [number, number, number, number, number];
109
110
  /**
110
111
  * Finds the perpetual id for a symbol of the form
111
112
  * <base>-<quote>-<collateral>. The function first converts the
@@ -290,22 +290,18 @@ class PerpetualDataHandler {
290
290
  if (isNaN(perpId)) {
291
291
  perpId = PerpetualDataHandler.symbolToPerpetualId(symbol, symbolToPerpStaticInfo);
292
292
  }
293
- // TODO: correct numbers using actual S2, S3
294
- // if (_pxS2S3 == undefined) {
295
- // // fetch s2,s3
296
- // }
297
293
  const idx_cash = 3;
298
294
  const idx_notional = 4;
299
295
  const idx_locked_in = 5;
300
296
  const idx_mark_price = 8;
301
297
  const idx_lvg = 7;
302
298
  const idx_s3 = 9;
303
- let traderState = yield _proxyContract.getTraderState(perpId, traderAddr);
299
+ let traderState = yield _proxyContract.getTraderState(perpId, traderAddr, _pxS2S3.map(x => (0, d8XMath_1.floatToABK64x64)(x)));
304
300
  let isEmpty = traderState[idx_notional] == 0;
305
301
  let cash = (0, d8XMath_1.ABK64x64ToFloat)(traderState[idx_cash]);
306
302
  let S2Liq = 0, S3Liq = 0, tau = Infinity, pnl = 0, unpaidFundingCC = 0, fLockedIn = ethers_1.BigNumber.from(0), side = nodeSDKTypes_1.CLOSED_SIDE, entryPrice = 0;
307
303
  if (!isEmpty) {
308
- [S2Liq, S3Liq, tau, pnl, unpaidFundingCC] = PerpetualDataHandler._calculateLiquidationPrice(symbol, traderState, symbolToPerpStaticInfo);
304
+ [S2Liq, S3Liq, tau, pnl, unpaidFundingCC] = PerpetualDataHandler._calculateLiquidationPrice(symbol, traderState, _pxS2S3[0], symbolToPerpStaticInfo);
309
305
  fLockedIn = traderState[idx_locked_in];
310
306
  side = traderState[idx_locked_in] > 0 ? nodeSDKTypes_1.BUY_SIDE : nodeSDKTypes_1.SELL_SIDE;
311
307
  entryPrice = (0, d8XMath_1.ABK64x64ToFloat)((0, d8XMath_1.div64x64)(fLockedIn, traderState[idx_notional]));
@@ -378,10 +374,11 @@ class PerpetualDataHandler {
378
374
  * Liquidation price
379
375
  * @param symbol symbol of the form BTC-USD-MATIC
380
376
  * @param traderState BigInt array according to smart contract
377
+ * @param S2 number, index price S2
381
378
  * @param symbolToPerpStaticInfo mapping symbol->PerpStaticInfo
382
379
  * @returns liquidation mark-price, corresponding collateral/quote conversion
383
380
  */
384
- static _calculateLiquidationPrice(symbol, traderState, symbolToPerpStaticInfo) {
381
+ static _calculateLiquidationPrice(symbol, traderState, S2, symbolToPerpStaticInfo) {
385
382
  const idx_availableCashCC = 2;
386
383
  const idx_cash = 3;
387
384
  const idx_notional = 4;
@@ -405,7 +402,7 @@ class PerpetualDataHandler {
405
402
  if (perpInfo.collateralCurrencyType == nodeSDKTypes_1.CollaterlCCY.BASE) {
406
403
  S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralBase)(lockedInValueQC, position, cashCC, tau);
407
404
  S3Liq = S2Liq;
408
- unpaidFunding = unpaidFunding / (0, d8XMath_1.ABK64x64ToFloat)(traderState[idx_s2]);
405
+ unpaidFunding = unpaidFunding / S2;
409
406
  }
410
407
  else if (perpInfo.collateralCurrencyType == nodeSDKTypes_1.CollaterlCCY.QUANTO) {
411
408
  let S3 = S3Liq;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const D8X_SDK_VERSION = "0.1.2";
1
+ export declare const D8X_SDK_VERSION = "0.1.4";
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.2";
4
+ exports.D8X_SDK_VERSION = "0.1.4";
@@ -40,4 +40,12 @@ export default class WriteAccessHandler extends PerpetualDataHandler {
40
40
  * @returns {string} Address of this wallet.
41
41
  */
42
42
  getAddress(): string;
43
+ /**
44
+ * Converts a given amount of chain native currency (test MATIC)
45
+ * into a mock token used for trading on testnet, with a rate of 1:100_000
46
+ * @param symbol Pool margin token e.g. MATIC
47
+ * @param amountToPay Amount in chain currency, e.g. "0.1" for 0.1 MATIC
48
+ * @returns
49
+ */
50
+ swapForMockToken(symbol: string, amountToPay: string): Promise<any>;
43
51
  }
@@ -101,5 +101,32 @@ class WriteAccessHandler extends perpetualDataHandler_1.default {
101
101
  getAddress() {
102
102
  return this.traderAddr;
103
103
  }
104
+ /**
105
+ * Converts a given amount of chain native currency (test MATIC)
106
+ * into a mock token used for trading on testnet, with a rate of 1:100_000
107
+ * @param symbol Pool margin token e.g. MATIC
108
+ * @param amountToPay Amount in chain currency, e.g. "0.1" for 0.1 MATIC
109
+ * @returns
110
+ */
111
+ swapForMockToken(symbol, amountToPay) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ if (this.signer == null) {
114
+ throw Error("no wallet initialized. Use createProxyInstance().");
115
+ }
116
+ let tokenAddress = this.getMarginTokenFromSymbol(symbol);
117
+ if (tokenAddress == undefined) {
118
+ throw Error("symbols not found");
119
+ }
120
+ let tokenToSwap = new Map(Object.entries(require("../config/mockSwap.json")));
121
+ let swapAddress = tokenToSwap.get(tokenAddress);
122
+ if (swapAddress == undefined) {
123
+ throw Error("No swap contract found for symbol.");
124
+ }
125
+ let contract = new ethers_1.ethers.Contract(swapAddress, nodeSDKTypes_1.MOCK_TOKEN_SWAP_ABI, this.signer);
126
+ return yield contract.swapToMockToken({
127
+ value: ethers_1.ethers.utils.parseEther(amountToPay),
128
+ });
129
+ });
130
+ }
104
131
  }
105
132
  exports.default = WriteAccessHandler;
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.2",
36
+ "version": "0.1.4",
37
37
  "main": "./dist/index.js",
38
38
  "types": "./dist/index.d.ts",
39
39
  "directories": {
@@ -359,23 +359,4 @@ export default class AccountTrade extends WriteAccessHandler {
359
359
  value: this.PRICE_UPDATE_FEE_GWEI * priceFeedData.priceFeedVaas.length,
360
360
  });
361
361
  }
362
-
363
- public async swapForMockToken(symbol: string, amountToPay: string) {
364
- if (this.signer == null) {
365
- throw Error("no wallet initialized. Use createProxyInstance().");
366
- }
367
- let tokenAddress = this.getMarginTokenFromSymbol(symbol);
368
- if (tokenAddress == undefined) {
369
- throw Error("symbols not found");
370
- }
371
- let tokenToSwap = new Map<string, string>(Object.entries(require("../config/mockSwap.json")));
372
- let swapAddress = tokenToSwap.get(tokenAddress);
373
- if (swapAddress == undefined) {
374
- throw Error("No swap contract found for symbol.");
375
- }
376
- let contract = new ethers.Contract(swapAddress, MOCK_TOKEN_SWAP_ABI, this.signer.provider);
377
- return await contract.swapToMockToken({
378
- value: ethers.utils.parseEther(amountToPay),
379
- });
380
- }
381
362
  }
@@ -86,6 +86,7 @@ export default class LiquidatorTool extends WriteAccessHandler {
86
86
  * If not, the position can be liquidated.
87
87
  * @param {string} symbol Symbol of the form ETH-USD-MATIC.
88
88
  * @param {string} traderAddr Address of the trader whose position you want to assess.
89
+ * @param {[number,number]} indexPrices optional, index price S2/S3 for which we test
89
90
  * @example
90
91
  * import { LiquidatorTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
91
92
  * async function main() {
@@ -105,13 +106,18 @@ export default class LiquidatorTool extends WriteAccessHandler {
105
106
  * @returns {boolean} True if the trader is maintenance margin safe in the perpetual.
106
107
  * False means that the trader's position can be liquidated.
107
108
  */
108
- public async isMaintenanceMarginSafe(symbol: string, traderAddr: string): Promise<boolean> {
109
+ public async isMaintenanceMarginSafe(symbol: string, traderAddr: string, indexPrices?: [number, number]): Promise<boolean> {
109
110
  if (this.proxyContract == null) {
110
111
  throw Error("no proxy contract initialized. Use createProxyInstance().");
111
112
  }
112
113
  const idx_notional = 4;
113
114
  let perpID = LiquidatorTool.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
114
- let traderState = await this.proxyContract.getTraderState(perpID, traderAddr);
115
+ if (indexPrices == undefined) {
116
+ // fetch from API
117
+ let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
118
+ indexPrices = [obj.idxPrices[0], obj.idxPrices[1]];
119
+ }
120
+ let traderState = await this.proxyContract.getTraderState(perpID, traderAddr, indexPrices);
115
121
  if (traderState[idx_notional] == 0) {
116
122
  // trader does not have open position
117
123
  return false;