@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.
- package/abi/Multicall.json +86 -0
- package/abi/central-park/IPerpetualManager.json +7 -2
- package/abi/central-park/LimitOrderBook.json +130 -2
- package/config/defaultConfig.json +2 -2
- package/dist/accountTrade.d.ts +0 -1
- package/dist/accountTrade.js +0 -21
- package/dist/liquidatorTool.d.ts +2 -1
- package/dist/liquidatorTool.js +8 -2
- package/dist/liquidityProviderTool.d.ts +28 -4
- package/dist/liquidityProviderTool.js +40 -5
- package/dist/marketData.d.ts +15 -4
- package/dist/marketData.js +219 -97
- package/dist/perpetualDataHandler.d.ts +3 -2
- package/dist/perpetualDataHandler.js +5 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/writeAccessHandler.d.ts +8 -0
- package/dist/writeAccessHandler.js +27 -0
- package/package.json +1 -1
- package/src/accountTrade.ts +0 -19
- package/src/liquidatorTool.ts +8 -2
- package/src/liquidityProviderTool.ts +39 -5
- package/src/marketData.ts +265 -166
- package/src/perpetualDataHandler.ts +6 -7
- package/src/version.ts +1 -1
- package/src/writeAccessHandler.ts +27 -1
package/dist/marketData.js
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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
|
-
|
|
210
|
-
|
|
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(
|
|
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
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
245
|
-
|
|
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
|
|
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)(
|
|
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 =
|
|
314
|
-
S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralQuanto)(
|
|
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)(
|
|
412
|
+
S2Liq = (0, d8XMath_1.calculateLiquidationPriceCollateralQuote)(lockedInQC, signedPositionBC, marginCashCC, tau);
|
|
318
413
|
}
|
|
319
|
-
|
|
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
|
|
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 /
|
|
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.
|
|
1
|
+
export declare const D8X_SDK_VERSION = "0.1.4";
|
package/dist/version.js
CHANGED
|
@@ -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
package/src/accountTrade.ts
CHANGED
|
@@ -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
|
}
|
package/src/liquidatorTool.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|