@d8x/perpetuals-sdk
Version:
Node TypeScript SDK for D8X Perpetual Futures
999 lines • 98 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ethers_1 = require("ethers");
const constants_1 = require("./constants");
const contracts_1 = require("./contracts");
const d8XMath_1 = require("./d8XMath");
const perpetualDataHandler_1 = __importDefault(require("./perpetualDataHandler"));
const utils_1 = require("./utils");
/**
* Functions to access market data (e.g., information on open orders, information on products that can be traded).
* This class requires no private key and is blockchain read-only.
* No gas required for the queries here.
* @extends PerpetualDataHandler
*/
class MarketData extends perpetualDataHandler_1.default {
/**
* Constructor
* @param {NodeSDKConfig} config Configuration object, see
* PerpetualDataHandler.readSDKConfig.
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // load configuration for Polygon zkEVM (testnet)
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* // MarketData (read only, no authentication needed)
* let mktData = new MarketData(config);
* // Create a proxy instance to access the blockchain
* await mktData.createProxyInstance();
* }
* main();
*
*/
constructor(config) {
super(config);
}
/**
* Initialize the marketData-Class with this function
* to create instance of D8X perpetual contract and gather information
* about perpetual currencies
* @param providerOrMarketData optional provider or existing market data instance
*/
async createProxyInstance(providerOrMarketData, overrides) {
await this.priceFeedGetter.init();
if (providerOrMarketData == undefined || !("createProxyInstance" in providerOrMarketData)) {
this.provider = providerOrMarketData ?? new ethers_1.JsonRpcProvider(this.nodeURL);
await this.initContractsAndData(this.provider, overrides);
}
else {
const mktData = providerOrMarketData;
this.nodeURL = mktData.config.nodeURL;
this.provider = new ethers_1.JsonRpcProvider(mktData.config.nodeURL, mktData.network, { staticNetwork: true });
this.proxyContract = contracts_1.IPerpetualManager__factory.connect(mktData.getProxyAddress(), this.provider);
this.multicall = contracts_1.Multicall3__factory.connect(this.config.multicall ?? constants_1.MULTICALL_ADDRESS, this.provider);
({
nestedPerpetualIDs: this.nestedPerpetualIDs,
poolStaticInfos: this.poolStaticInfos,
symbolToTokenAddrMap: this.symbolToTokenAddrMap,
symbolToPerpStaticInfo: this.symbolToPerpStaticInfo,
perpetualIdToSymbol: this.perpetualIdToSymbol,
} = mktData.getAllMappings());
this.priceFeedGetter.setTriangulations(mktData.getTriangulations());
this.signerOrProvider = this.provider;
}
}
/**
* Get the proxy address
* @returns {string} Address of the perpetual proxy contract
*/
getProxyAddress() {
if (this.proxyContract == null) {
throw Error("no proxy contract initialized. Use createProxyInstance().");
}
return this.proxyContract.target.toString();
}
/**
* Get the pre-computed triangulations
* @returns Triangulations
*/
getTriangulations() {
return this.priceFeedGetter.getTriangulations();
}
/**
* Convert the smart contract output of an order into a convenient format of type "Order"
* @param {SmartContractOrder} smOrder SmartContractOrder, as obtained e.g., by PerpetualLimitOrderCreated event
* @returns {Order} more convenient format of order, type "Order"
*/
smartContractOrderToOrder(smOrder) {
return perpetualDataHandler_1.default.fromSmartContractOrder(smOrder, this.symbolToPerpStaticInfo);
}
/**
* Get contract instance. Useful for event listening.
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let mktData = new MarketData(config);
* await mktData.createProxyInstance();
* // Get contract instance
* let proxy = await mktData.getReadOnlyProxyInstance();
* console.log(proxy);
* }
* main();
*
* @returns read-only proxy instance
*/
getReadOnlyProxyInstance() {
if (this.proxyContract == null) {
throw Error("no proxy contract initialized. Use createProxyInstance().");
}
return this.proxyContract;
}
/**
* Information about the products traded in the exchange.
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let mktData = new MarketData(config);
* await mktData.createProxyInstance();
* // Get exchange info
* let info = await mktData.exchangeInfo();
* console.log(info);
* }
* main();
*
* @returns {ExchangeInfo} Array of static data for all the pools and perpetuals in the system.
*/
async exchangeInfo(overrides) {
if (this.proxyContract == null || this.multicall == null) {
throw Error("no proxy contract initialized. Use createProxyInstance().");
}
let rpcURL;
if (overrides) {
({ rpcURL, ...overrides } = overrides);
}
const provider = new ethers_1.JsonRpcProvider(rpcURL ?? this.nodeURL, this.network, { staticNetwork: true });
return await MarketData._exchangeInfo(new ethers_1.Contract(this.proxyAddr, this.config.proxyABI, provider), contracts_1.Multicall3__factory.connect(this.config.multicall ?? constants_1.MULTICALL_ADDRESS, provider), this.poolStaticInfos, this.symbolToPerpStaticInfo, this.perpetualIdToSymbol, this.nestedPerpetualIDs, this.symbolList, this.priceFeedGetter, this.oraclefactoryAddr, // not undefined if proxy contract was initialized
overrides);
}
/**
* All open orders for a trader-address and a symbol.
* @param {string} traderAddr Address of the trader for which we get the open orders.
* @param {string} symbol Symbol of the form ETH-USD-MATIC or a pool symbol, or undefined.
* If a poolSymbol is provided, the response includes orders in all perpetuals of the given pool.
* If no symbol is provided, the response includes orders from all perpetuals in all pools.
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let mktData = new MarketData(config);
* await mktData.createProxyInstance();
* // Get all open orders for a trader/symbol
* let opOrder = await mktData.openOrders("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
* "ETH-USD-MATIC");
* console.log(opOrder);
* }
* main();
*
* @returns For each perpetual an array of open orders and corresponding order-ids.
*/
async openOrders(traderAddr, symbol, overrides) {
// open orders requested only for given symbol
let resArray = [];
let symbols;
if (symbol) {
symbols = symbol.split("-").length == 1 ? this.getPerpetualSymbolsInPool(symbol) : [symbol];
}
else {
symbols = this.poolStaticInfos.reduce((syms, pool) => syms.concat(this.getPerpetualSymbolsInPool(pool.poolMarginSymbol)), new Array());
}
let rpcURL;
if (overrides) {
({ rpcURL, ...overrides } = overrides);
}
const provider = new ethers_1.JsonRpcProvider(rpcURL ?? this.nodeURL, this.network, { staticNetwork: true });
if (symbols.length < 1) {
throw new Error(`No perpetuals found for symbol ${symbol}`);
}
else if (symbols.length < 2) {
let res = await this._openOrdersOfPerpetual(traderAddr, symbols[0], provider, overrides);
resArray.push(res);
}
else {
resArray = await this._openOrdersOfPerpetuals(traderAddr, symbols, provider, overrides);
}
return resArray;
}
/**
* All open orders for a trader-address and a given perpetual symbol.
* @param {string} traderAddr Address of the trader for which we get the open orders.
* @param {string} symbol perpetual-symbol of the form ETH-USD-MATIC
* @returns open orders and order ids
* @ignore
*/
async _openOrdersOfPerpetual(traderAddr, symbol, provider, overrides) {
// open orders requested only for given symbol
const orderBookContract = this.getOrderBookContract(symbol, provider);
const orders = await MarketData.openOrdersOnOrderBook(traderAddr, orderBookContract, this.symbolToPerpStaticInfo, overrides);
const digests = await MarketData.orderIdsOfTrader(traderAddr, orderBookContract, overrides);
return { orders: orders, orderIds: digests };
}
/**
* All open orders for a trader-address and a given perpetual symbol.
* @param {string} traderAddr Address of the trader for which we get the open orders.
* @param {string} symbol perpetual-symbol of the form ETH-USD-MATIC
* @returns open orders and order ids
* @ignore
*/
async _openOrdersOfPerpetuals(traderAddr, symbols, provider, overrides) {
// filter by perpetuals with valid order book
symbols = symbols.filter((symbol) => this.symbolToPerpStaticInfo.get(symbol)?.limitOrderBookAddr !== constants_1.ZERO_ADDRESS);
// open orders requested only for given symbol
const orderBookContracts = symbols.map((symbol) => this.getOrderBookContract(symbol, provider), this);
const multicall = contracts_1.Multicall3__factory.connect(this.config.multicall ?? constants_1.MULTICALL_ADDRESS, provider);
const { orders, digests } = await MarketData._openOrdersOnOrderBooks(traderAddr, orderBookContracts, multicall, this.symbolToPerpStaticInfo, overrides);
return symbols.map((_symbol, i) => ({ orders: orders[i], orderIds: digests[i] }));
}
/**
* Information about the position open by a given trader in a given perpetual contract, or
* for all perpetuals in a pool
* @param {string} traderAddr Address of the trader for which we get the position risk.
* @param {string} symbol Symbol of the form ETH-USD-MATIC,
* or pool symbol ("MATIC") to get all positions in a given pool,
* or no symbol to get all positions in all pools.
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let mktData = new MarketData(config);
* await mktData.createProxyInstance();
* // Get position risk info
* let posRisk = await mktData.positionRisk("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
* "ETH-USD-MATIC");
* console.log(posRisk);
* }
* main();
*
* @returns {Array<MarginAccount>} Array of position risks of trader.
*/
async positionRisk(traderAddr, symbol, overrides) {
if (this.proxyContract == null) {
throw Error("no proxy contract initialized. Use createProxyInstance().");
}
let resArray = [];
let symbols;
if (symbol) {
symbols = symbol.split("-").length == 1 ? this.getPerpetualSymbolsInPool(symbol) : [symbol];
}
else {
symbols = this.poolStaticInfos.reduce((syms, pool) => syms.concat(this.getPerpetualSymbolsInPool(pool.poolMarginSymbol)), new Array());
}
let rpcURL;
if (overrides) {
({ rpcURL, ...overrides } = overrides);
}
const provider = new ethers_1.JsonRpcProvider(rpcURL ?? this.nodeURL, this.network, { staticNetwork: true });
if (symbols.length < 1) {
throw new Error(`No perpetuals found for symbol ${symbol}`);
}
else if (symbols.length < 2) {
let res = await this._positionRiskForTraderInPerpetual(traderAddr, symbols[0], provider, overrides);
resArray.push(res);
}
else {
resArray = await this._positionRiskForTraderInPerpetuals(traderAddr, symbols, provider, overrides);
}
return resArray;
}
/**
* Information about the position open by a given trader in a given perpetual contract.
* @param {string} traderAddr Address of the trader for which we get the position risk.
* @param {string} symbol perpetual symbol of the form ETH-USD-MATIC
* @returns MarginAccount struct for the trader
* @ignore
*/
async _positionRiskForTraderInPerpetual(traderAddr, symbol, provider, overrides) {
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
const isPred = this.isPredictionMarket(symbol);
let mgnAcct = await perpetualDataHandler_1.default.getMarginAccount(traderAddr, symbol, this.symbolToPerpStaticInfo, new ethers_1.Contract(this.proxyAddr, this.config.proxyABI, provider), obj, isPred, overrides);
return mgnAcct;
}
/**
* Information about the position open by a given trader in a given perpetual contract.
* @param {string} traderAddr Address of the trader for which we get the position risk.
* @param {string} symbol perpetual symbol of the form ETH-USD-MATIC
* @returns MarginAccount struct for the trader
* @ignore
*/
async _positionRiskForTraderInPerpetuals(traderAddr, symbols, provider, overrides) {
const MAX_SYMBOLS_PER_CALL = 10;
const pxInfo = new Array();
for (let i = 0; i < symbols.length; i++) {
let obj = await this.priceFeedGetter.fetchPricesForPerpetual(symbols[i]);
pxInfo.push(obj);
}
let mgnAcct = [];
let callSymbols = symbols.slice(0, MAX_SYMBOLS_PER_CALL);
let _px = pxInfo.slice(0, MAX_SYMBOLS_PER_CALL);
while (callSymbols.length > 0) {
const isPred = callSymbols.map((_sym) => this.isPredictionMarket(_sym));
let acc = await perpetualDataHandler_1.default.getMarginAccounts(Array(callSymbols.length).fill(traderAddr), callSymbols, this.symbolToPerpStaticInfo, contracts_1.Multicall3__factory.connect(this.config.multicall ?? constants_1.MULTICALL_ADDRESS, provider), new ethers_1.Contract(this.proxyAddr, this.config.proxyABI, provider), _px, isPred, overrides);
mgnAcct = mgnAcct.concat(acc);
callSymbols = symbols.slice(mgnAcct.length, mgnAcct.length + MAX_SYMBOLS_PER_CALL);
_px = pxInfo.slice(mgnAcct.length, mgnAcct.length + MAX_SYMBOLS_PER_CALL);
}
return mgnAcct;
}
async dataForPositionRiskOnTrade(symbol, traderAddr, tradeAmountBC, indexPriceInfo, signedPositionNotionalBaseCCY, overrides) {
if (this.proxyContract == null || this.multicall == null) {
throw Error("no proxy contract initialized. Use createProxyInstance().");
}
const isPredMkt = this.isPredictionMarket(symbol);
// create all calls
const perpId = perpetualDataHandler_1.default.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
const [fS2, fS3, fEma] = [indexPriceInfo.s2, indexPriceInfo.s3 ?? 0, indexPriceInfo.ema].map((x) => (0, d8XMath_1.floatToABK64x64)(x));
const proxyCalls = [
// 0: traderState
{
target: this.proxyContract.target,
allowFailure: true,
callData: this.proxyContract.interface.encodeFunctionData("getTraderState", [perpId, traderAddr, [fEma, fS3]]),
},
// 1: perpetual price
{
target: this.proxyContract.target,
allowFailure: true,
callData: this.proxyContract.interface.encodeFunctionData("queryPerpetualPrice", [
perpId,
(0, d8XMath_1.floatToABK64x64)(tradeAmountBC),
[fS2, fS3],
indexPriceInfo.conf * 10n,
indexPriceInfo.predMktCLOBParams,
]),
},
// 2: max long pos
{
target: this.proxyContract.target,
allowFailure: false,
callData: this.proxyContract.interface.encodeFunctionData("getMaxSignedOpenTradeSizeForPos", [
perpId,
(0, d8XMath_1.floatToABK64x64)(signedPositionNotionalBaseCCY),
true,
]),
},
// 3: max short pos
{
target: this.proxyContract.target,
allowFailure: false,
callData: this.proxyContract.interface.encodeFunctionData("getMaxSignedOpenTradeSizeForPos", [
perpId,
(0, d8XMath_1.floatToABK64x64)(signedPositionNotionalBaseCCY),
false,
]),
},
];
// multicall
const encodedResults = await this.multicall.aggregate3.staticCall(proxyCalls, (overrides || {}));
// positionRisk to apply this trade on: if not given, defaults to the current trader's position
let traderState;
if (encodedResults[0].success) {
traderState = this.proxyContract.interface.decodeFunctionResult("getTraderState", encodedResults[0].returnData)[0];
}
else {
traderState = await this.proxyContract.getTraderState(perpId, traderAddr, [fEma, fS3]);
}
const account = MarketData.buildMarginAccountFromState(symbol, traderState, this.symbolToPerpStaticInfo, indexPriceInfo, isPredMkt);
// amm price for this trade amount
let ammPrice;
{
let fPrice;
if (encodedResults[1].success) {
fPrice = this.proxyContract.interface.decodeFunctionResult("queryPerpetualPrice", encodedResults[1].returnData)[0];
}
else {
fPrice = await this.proxyContract.queryPerpetualPrice(perpId, (0, d8XMath_1.floatToABK64x64)(tradeAmountBC), [(0, d8XMath_1.floatToABK64x64)(indexPriceInfo.s2), (0, d8XMath_1.floatToABK64x64)(indexPriceInfo.s3 ?? 0)], indexPriceInfo.conf * 10n, indexPriceInfo.predMktCLOBParams);
}
ammPrice = (0, d8XMath_1.ABK64x64ToFloat)(fPrice);
}
// max buy
const fMaxLong = this.proxyContract.interface.decodeFunctionResult("getMaxSignedOpenTradeSizeForPos", encodedResults[2].returnData)[0];
const maxLongTrade = (0, d8XMath_1.ABK64x64ToFloat)(fMaxLong);
// max sell
const fMaxShort = this.proxyContract.interface.decodeFunctionResult("getMaxSignedOpenTradeSizeForPos", encodedResults[3].returnData)[0];
const maxShortTrade = (0, d8XMath_1.ABK64x64ToFloat)(fMaxShort);
return { account: account, ammPrice: ammPrice, maxShortTrade: maxShortTrade, maxLongTrade: maxLongTrade };
}
/**
* Estimates what the position risk will be if a given order is executed.
* @param traderAddr Address of trader
* @param order Order to be submitted
* @param signedPositionNotionalBaseCCY signed position notional of current position (before trade)
* @param tradingFeeTbps trading fee in tenth of basis points (exchange fee and broker fee)
* @param indexPriceInfo Index prices and market status (open/closed). Defaults to current market status if not given.
* @returns Position risk after trade, including order cost and maximal trade sizes for position
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* const mktData = new MarketData(config);
* await mktData.createProxyInstance();
* const order: Order = {
* symbol: "MATIC-USD-MATIC",
* side: "BUY",
* type: "MARKET",
* quantity: 100,
* leverage: 2,
* executionTimestamp: Date.now()/1000,
* };
* // Get position risk conditional on this order being executed
* const posRisk = await mktData.positionRiskOnTrade("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", order, 0, 60);
* console.log(posRisk);
* }
* main();
*/
async positionRiskOnTrade(traderAddr, order, signedPositionNotionalBaseCCY, tradingFeeTbps, indexPriceInfo, overrides) {
if (this.proxyContract == null || this.multicall == null) {
throw Error("no proxy contract initialized. Use createProxyInstance().");
}
const isPredMkt = this.isPredictionMarket(order.symbol);
// fetch prices
if (indexPriceInfo == undefined) {
indexPriceInfo = await this.priceFeedGetter.fetchPricesForPerpetual(order.symbol);
}
// signed trade amount
let tradeAmountBC = Math.abs(order.quantity) * (order.side == constants_1.BUY_SIDE ? 1 : -1);
const symbol = order.symbol;
let obj = await this.dataForPositionRiskOnTrade(symbol, traderAddr, tradeAmountBC, indexPriceInfo, signedPositionNotionalBaseCCY, overrides);
const account = obj.account;
const maxLongTrade = obj.maxLongTrade;
const maxShortTrade = obj.maxShortTrade;
const ammPrice = obj.ammPrice;
let Sm = account.markPrice;
let S2 = indexPriceInfo.s2;
let S3 = account.collToQuoteConversion;
// price for this order = amm price if no limit given, else conservatively adjusted
let tradePrice;
if (order.limitPrice == undefined) {
tradePrice = ammPrice;
}
else {
if (order.type == constants_1.ORDER_TYPE_MARKET) {
if (order.side == constants_1.BUY_SIDE) {
// limit price > amm price --> likely not binding, use avg, less conservative
// limit price < amm price --> likely fails due to slippage, use limit price to get actual max cost
tradePrice = 0.5 * (order.limitPrice + Math.min(order.limitPrice, ammPrice));
}
else {
tradePrice = 0.5 * (order.limitPrice + Math.max(order.limitPrice, ammPrice));
}
}
else {
// limit orders either get executed now (at ammPrice) or later (at limit price)
if ((order.side == constants_1.BUY_SIDE && order.limitPrice > ammPrice) ||
(order.side == constants_1.SELL_SIDE && order.limitPrice < ammPrice)) {
// can be executed now at ammPrice
tradePrice = ammPrice;
}
else {
// will execute in the future at limitPrice -> assume prices converge proportionally
const slippage = ammPrice / S2;
Sm = (Sm / S2) * (order.limitPrice / slippage);
S2 = order.limitPrice / slippage;
tradePrice = order.limitPrice;
if (this.getPerpetualStaticInfo(order.symbol).collateralCurrencyType == constants_1.COLLATERAL_CURRENCY_BASE) {
S3 = S2;
}
}
}
}
// Current state:
let lotSizeBC = MarketData._getLotSize(order.symbol, this.symbolToPerpStaticInfo);
// Too small, no change to account
if (Math.abs(order.quantity) < lotSizeBC) {
return {
newPositionRisk: account,
orderCost: 0,
maxLongTrade: maxLongTrade,
maxShortTrade: maxShortTrade,
ammPrice: obj.ammPrice,
};
}
// cash in margin account: upon trading, unpaid funding will be realized
let currentMarginCashCC = account.collateralCC + account.unrealizedFundingCollateralCCY;
// signed position, still correct if side is closed (==0)
let currentPositionBC = (account.side == constants_1.BUY_SIDE ? 1 : -1) * account.positionNotionalBaseCCY;
// signed locked-in value
let currentLockedInQC = account.entryPrice * currentPositionBC;
// New trader state:
// signed position
let newPositionBC = currentPositionBC + tradeAmountBC;
if (Math.abs(newPositionBC) < 10 * lotSizeBC) {
// fully closed
tradeAmountBC = -currentPositionBC;
newPositionBC = 0;
}
let newSide = newPositionBC > 0 ? constants_1.BUY_SIDE : newPositionBC < 0 ? constants_1.SELL_SIDE : constants_1.CLOSED_SIDE;
let tradingFeeCC = isPredMkt
? (Math.abs(tradeAmountBC) * tradingFeeTbps * 1e-5) / S3
: (Math.abs(tradeAmountBC) * tradingFeeTbps * 1e-5 * S2) / S3;
let referralFeeCC = this.symbolToPerpStaticInfo.get(account.symbol).referralRebate;
// Trade type:
let isClose = newPositionBC == 0 || newPositionBC * tradeAmountBC < 0;
let isOpen = newPositionBC != 0 && (currentPositionBC == 0 || tradeAmountBC * currentPositionBC > 0); // regular open, no flip
let isFlip = Math.abs(newPositionBC) > Math.abs(currentPositionBC) && !isOpen; // flip position sign, not fully closed
let keepPositionLvgOnClose = (order.keepPositionLvg ?? false) && !isOpen;
// Contract: _doMarginCollateralActions
// No collateral actions if
// 1) leverage is not set or
// 2) fully closed after trade or
// 3) is a partial closing, it doesn't flip, and keep lvg flag is not set
let traderDepositCC;
let targetLvg;
if (order.leverage == undefined || newPositionBC == 0 || (!isOpen && !isFlip && !keepPositionLvgOnClose)) {
traderDepositCC = 0;
targetLvg = 0;
}
else {
// 1) opening and flipping trades need to specify a leverage: default to max if not given
// 2) for others it's ignored, set target to 0
let initialMarginRate = this.symbolToPerpStaticInfo.get(account.symbol).initialMarginRate;
targetLvg = isFlip || isOpen ? order.leverage ?? 1 / initialMarginRate : 0;
let b0 = currentMarginCashCC + (currentPositionBC * Sm - currentLockedInQC) / S3;
let pos0 = currentPositionBC;
if (isOpen) {
b0 -= (initialMarginRate * Math.abs(currentPositionBC) * Sm) / S3;
b0 = b0 < 0 ? b0 : 0;
pos0 = 0;
}
traderDepositCC = (0, d8XMath_1.getDepositAmountForLvgTrade)(pos0, b0, tradeAmountBC, targetLvg, tradePrice, S3, Sm, isPredMkt);
// fees are paid from wallet in this case
traderDepositCC += tradingFeeCC + referralFeeCC;
}
// Contract: _executeTrade
let deltaCashCC = (-tradeAmountBC * (tradePrice - S2)) / S3;
let deltaLockedQC = tradeAmountBC * S2;
if (isClose) {
let pnl = account.entryPrice * tradeAmountBC - deltaLockedQC;
deltaLockedQC += pnl;
deltaCashCC += pnl / S3;
}
// funding and fees
deltaCashCC = deltaCashCC - tradingFeeCC - referralFeeCC;
// New cash, locked-in, entry price & leverage after trade
let newLockedInValueQC = currentLockedInQC + deltaLockedQC;
let newMarginCashCC = currentMarginCashCC + deltaCashCC + traderDepositCC;
let newEntryPrice = newPositionBC == 0 ? 0 : Math.abs(newLockedInValueQC / newPositionBC);
let newMarginBalanceCC = newMarginCashCC + (newPositionBC * Sm - newLockedInValueQC) / S3;
let newLeverage;
if (newPositionBC === 0) {
newLeverage = 0;
}
else if (newMarginBalanceCC <= 0) {
newLeverage = Infinity;
}
else {
let p = Sm;
if (isPredMkt) {
p -= 1;
p = newPositionBC > 0 ? p : 1 - p;
}
newLeverage = Math.abs(newPositionBC * p) / S3 / newMarginBalanceCC;
}
// Liquidation params
let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(account.symbol, newLockedInValueQC, newPositionBC, newMarginCashCC, Sm, S3, S2, this.symbolToPerpStaticInfo);
// New position risk
let newPositionRisk = {
symbol: account.symbol,
positionNotionalBaseCCY: Math.abs(newPositionBC),
side: newSide,
entryPrice: newEntryPrice,
leverage: newLeverage,
markPrice: Sm,
unrealizedPnlQuoteCCY: newPositionBC * Sm - newLockedInValueQC,
unrealizedFundingCollateralCCY: 0,
collateralCC: newMarginCashCC,
collToQuoteConversion: S3,
liquidationPrice: [S2Liq, S3Liq],
liquidationLvg: 1 / tau,
};
return {
ammPrice: obj.ammPrice,
newPositionRisk: newPositionRisk,
orderCost: traderDepositCC,
maxLongTrade: maxLongTrade,
maxShortTrade: maxShortTrade,
};
}
/**
* Fee is relative to base-currency amount (=trade amount)
* @param maxMaintMgnRate maintenance margin rate param for pred mkts
* @param Sm Mark price
* @param tradeAmtBC signed trade amount
* @param tradeMgnRate margin rate param from perpetual
* @returns relative exchange fee in decimals
*/
static exchangeFeePrdMkts(maxMaintMgnRate, Sm, tradeAmtBC, traderPosBC, tradeMgnRate) {
const isClose = Math.sign(traderPosBC) != Math.sign(tradeAmtBC);
const isFlip = Math.abs(traderPosBC + tradeAmtBC) > 0.01 && Math.sign(traderPosBC + tradeAmtBC) != Math.sign(traderPosBC);
if (isClose && !isFlip) {
// 0.1cents if is close
return 0.001;
}
return (0, d8XMath_1.pmExchangeFee)(Sm - 1, maxMaintMgnRate, tradeAmtBC, tradeMgnRate);
}
/**
* Estimates what the position risk will be if given amount of collateral is added/removed from the account.
* @param {number} deltaCollateral Amount of collateral to add or remove (signed)
* @param {MarginAccount} account Position risk before collateral is added or removed
* @returns {MarginAccount} Position risk after collateral has been added/removed
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* const mktData = new MarketData(config);
* await mktData.createProxyInstance();
* // Get position risk conditional on removing 3.14 MATIC
* const traderAddr = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B";
* const curPos = await mktData.positionRisk("traderAddr", "BTC-USD-MATIC");
* const posRisk = await mktData.positionRiskOnCollateralAction(-3.14, curPos);
* console.log(posRisk);
* }
* main();
*/
async positionRiskOnCollateralAction(deltaCollateral, account, indexPriceInfo, overrides) {
if (this.proxyContract == null) {
throw new Error("no proxy contract initialized. Use createProxyInstance().");
}
if (deltaCollateral + account.collateralCC + account.unrealizedFundingCollateralCCY < 0) {
throw new Error("not enough margin to remove");
}
if (indexPriceInfo == undefined) {
indexPriceInfo = await this.priceFeedGetter.fetchPricesForPerpetual(account.symbol);
}
let perpetualState = await this.getPerpetualState(account.symbol, indexPriceInfo, overrides);
let Sm; //mark price
if (this.isPredictionMarket(account.symbol)) {
Sm = indexPriceInfo.ema + perpetualState.markPremium;
}
else {
Sm = perpetualState.indexPrice * (1 + perpetualState.markPremium);
}
let [S2, S3] = [perpetualState.indexPrice, perpetualState.collToQuoteIndexPrice];
// no position: just increase collateral and kill liquidation vars
if (account.positionNotionalBaseCCY == 0) {
return {
symbol: account.symbol,
positionNotionalBaseCCY: account.positionNotionalBaseCCY,
side: account.side,
entryPrice: account.entryPrice,
leverage: account.leverage,
markPrice: Sm,
unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
collateralCC: account.collateralCC + deltaCollateral,
collToQuoteConversion: S3,
liquidationPrice: [0, undefined],
liquidationLvg: Infinity,
};
}
let positionBC = account.positionNotionalBaseCCY * (account.side == constants_1.BUY_SIDE ? 1 : -1);
let lockedInQC = account.entryPrice * positionBC;
let newMarginCashCC = account.collateralCC + deltaCollateral;
let newMarginBalanceCC = newMarginCashCC + account.unrealizedFundingCollateralCCY + (positionBC * Sm - lockedInQC) / S3;
if (newMarginBalanceCC <= 0) {
return {
symbol: account.symbol,
positionNotionalBaseCCY: account.positionNotionalBaseCCY,
side: account.side,
entryPrice: account.entryPrice,
leverage: Infinity,
markPrice: Sm,
unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
collateralCC: newMarginCashCC,
collToQuoteConversion: S3,
liquidationPrice: [S2, S3],
liquidationLvg: 0,
};
}
let newLeverage = (Math.abs(positionBC) * Sm) / S3 / newMarginBalanceCC;
// Liquidation params
let [S2Liq, S3Liq, tau] = MarketData._getLiquidationParams(account.symbol, lockedInQC, positionBC, newMarginCashCC, Sm, S3, S2, this.symbolToPerpStaticInfo);
// New position risk
let newPositionRisk = {
symbol: account.symbol,
positionNotionalBaseCCY: account.positionNotionalBaseCCY,
side: account.side,
entryPrice: account.entryPrice,
leverage: newLeverage,
markPrice: Sm,
unrealizedPnlQuoteCCY: account.unrealizedPnlQuoteCCY,
unrealizedFundingCollateralCCY: account.unrealizedFundingCollateralCCY,
collateralCC: newMarginCashCC,
collToQuoteConversion: S3,
liquidationPrice: [S2Liq, S3Liq],
liquidationLvg: 1 / tau,
};
return newPositionRisk;
}
/**
* Calculates liquidation prices for a position
* constructed in positionRiskOnTrade/positionRiskOnCollateralAction
* @param symbol Perpetual symbol
* @param lockedInQC Locked in value
* @param signedPositionBC Signed position size
* @param marginCashCC Available cash in margin account (includes unpaid funding)
* @param markPrice Mark price
* @param collToQuoteConversion Collateral index price (S3)
* @param S2 index price
* @param symbolToPerpStaticInfo Symbol-to-perp static info mapping
* @returns [Base index price, Collateral index price, Maintenance margin rate]
* @ignore
*/
static _getLiquidationParams(symbol, lockedInQC, signedPositionBC, marginCashCC, markPrice, collToQuoteConversion, S2, symbolToPerpStaticInfo) {
let S2Liq, S3Liq;
const staticInfo = symbolToPerpStaticInfo.get(symbol);
let ccyType = staticInfo.collateralCurrencyType;
const isPred = MarketData.isPredictionMarketStatic(staticInfo);
const idx_availableCashCC = 2;
const idx_cash = 3;
const idx_notional = 4;
const idx_locked_in = 5;
const idx_mark_price = 8;
const idx_s3 = 9;
const idx_maint_mgn_rate = 10;
let traderState = new Array(11);
traderState[idx_availableCashCC] = (0, d8XMath_1.floatToABK64x64)(marginCashCC);
traderState[idx_cash] = traderState[idx_availableCashCC];
traderState[idx_notional] = (0, d8XMath_1.floatToABK64x64)(signedPositionBC);
traderState[idx_locked_in] = (0, d8XMath_1.floatToABK64x64)(lockedInQC);
traderState[idx_mark_price] = (0, d8XMath_1.floatToABK64x64)(markPrice);
traderState[idx_s3] = (0, d8XMath_1.floatToABK64x64)(collToQuoteConversion);
traderState[idx_maint_mgn_rate] = (0, d8XMath_1.floatToABK64x64)(perpetualDataHandler_1.default.getMaintenanceMarginRate(staticInfo));
let tau;
[S2Liq, S3Liq, tau, ,] = MarketData._calculateLiquidationPrice(symbol, traderState, S2, symbolToPerpStaticInfo, isPred);
return [S2Liq, S3Liq, tau];
}
/**
* Gets the wallet balance in the settlement currency corresponding to a given perpetual symbol.
* The settlement currency is usually the same as the collateral currency.
* @param address Address to check
* @param symbol Symbol of the form ETH-USD-MATIC.
* @returns Perpetual's collateral token balance of the given address.
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup (authentication required, PK is an environment variable with a private key)
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let md = new MarketData(config);
* await md.createProxyInstance();
* // get MATIC balance of address
* let marginTokenBalance = await md.getWalletBalance(myaddress, "BTC-USD-MATIC");
* console.log(marginTokenBalance);
* }
* main();
*/
async getWalletBalance(address, symbol, overrides) {
let poolIdx = this.getPoolStaticInfoIndexFromSymbol(symbol);
let settleTokenAddr = this.poolStaticInfos[poolIdx].poolSettleTokenAddr;
let token = contracts_1.ERC20__factory.connect(settleTokenAddr, this.provider);
let walletBalance = await token.balanceOf(address, overrides || {});
let decimals = this.poolStaticInfos[poolIdx].poolSettleTokenDecimals;
return Number((0, ethers_1.formatUnits)(walletBalance, decimals));
}
/**
* Get the address' balance of the pool share token
* @param {string} address address of the liquidity provider
* @param {string | number} symbolOrId Symbol of the form ETH-USD-MATIC, or MATIC (collateral only), or Pool-Id
* @returns {number} Pool share token balance of the given address (e.g. dMATIC balance)
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup (authentication required, PK is an environment variable with a private key)
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let md = new MarketData(config);
* await md.createProxyInstance();
* // get dMATIC balance of address
* let shareTokenBalance = await md.getPoolShareTokenBalance(myaddress, "MATIC");
* console.log(shareTokenBalance);
* }
* main();
*/
async getPoolShareTokenBalance(address, symbolOrId, overrides) {
let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
return this._getPoolShareTokenBalanceFromId(address, poolId, overrides);
}
/**
* Query the pool share token holdings of address
* @param address address of token holder
* @param poolId pool id
* @returns pool share token balance of address
* @ignore
*/
async _getPoolShareTokenBalanceFromId(address, poolId, overrides) {
let shareTokenAddr = this.poolStaticInfos[poolId - 1].shareTokenAddr;
let shareToken = contracts_1.ERC20__factory.connect(shareTokenAddr, this.provider);
let d18ShareTokenBalanceOfAddr = await shareToken.balanceOf(address, overrides || {});
return (0, d8XMath_1.dec18ToFloat)(d18ShareTokenBalanceOfAddr);
}
/**
* Value of pool token in collateral currency
* @param {string | number} symbolOrId symbol of the form ETH-USD-MATIC, MATIC (collateral), or poolId
* @returns {number} current pool share token price in collateral currency
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup (authentication required, PK is an environment variable with a private key)
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let md = new MarketData(config);
* await md.createProxyInstance();
* // get price of 1 dMATIC in MATIC
* let shareTokenPrice = await md.getShareTokenPrice(myaddress, "MATIC");
* console.log(shareTokenPrice);
* }
* main();
*/
async getShareTokenPrice(symbolOrId, overrides) {
let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
const priceDec18 = await this.proxyContract.getShareTokenPriceD18(poolId, overrides || {});
const price = (0, d8XMath_1.dec18ToFloat)(priceDec18);
return price;
}
/**
* Value of the pool share tokens for this liquidity provider
* in poolSymbol-currency (e.g. MATIC, USDC).
* @param {string} address address of liquidity provider
* @param {string | number} symbolOrId symbol of the form ETH-USD-MATIC, MATIC (collateral), or poolId
* @returns the value (in collateral tokens) of the pool share, #share tokens, shareTokenAddress
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup (authentication required, PK is an environment variable with a private key)
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let md = new MarketData(config);
* await md.createProxyInstance();
* // get value of pool share token
* let shareToken = await md.getParticipationValue(myaddress, "MATIC");
* console.log(shareToken);
* }
* main();
*/
async getParticipationValue(address, symbolOrId, overrides) {
let poolId = this._poolSymbolOrIdToPoolId(symbolOrId);
const shareTokens = await this._getPoolShareTokenBalanceFromId(address, poolId, overrides);
const priceDec18 = await this.proxyContract.getShareTokenPriceD18(poolId, overrides || {});
const price = (0, d8XMath_1.dec18ToFloat)(priceDec18);
const value = price * shareTokens;
const shareTokenAddr = this.poolStaticInfos[poolId - 1].shareTokenAddr;
return {
value: value,
shareTokenBalance: shareTokens,
poolShareToken: shareTokenAddr,
};
}
/**
* Get pool id from symbol
* @param poolSymbolOrId Pool symbol or pool Id
* @returns Pool Id
* @ignore
*/
_poolSymbolOrIdToPoolId(poolSymbolOrId) {
if (this.proxyContract == null || this.poolStaticInfos.length == 0) {
throw Error("no proxy contract or wallet or data initialized. Use createProxyInstance().");
}
let poolId;
if (isNaN(Number(poolSymbolOrId))) {
poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolOrId, this.poolStaticInfos);
}
else {
poolId = Number(poolSymbolOrId);
}
return poolId;
}
/**
* Gets the maximal order sizes to open/close/flip positions, both long and short,
* considering the existing position, state of the perpetual
* Accounts for user's wallet balance only in rmMaxOrderSizeForTrader case.
* @param {string} traderAddr Address of trader
* @param {symbol} symbol Symbol of the form ETH-USD-MATIC
* @returns Maximal buy and sell trade sizes (positive)
* @example
* import { MarketData, PerpetualDataHandler } from '@d8x/perpetuals-sdk';
* async function main() {
* console.log(MarketData);
* // setup (authentication required, PK is an environment variable with a private key)
* const config = PerpetualDataHandler.readSDKConfig("cardona");
* let md = new MarketData(config);
* await md.createProxyInstance();
* // max order sizes
* let shareToken = await md.maxOrderSizeForTrader(myaddress, "BTC-USD-MATIC");
* console.log(shareToken); // {buy: 314, sell: 415}
* }
* main();
*/
async maxOrderSizeForTrader(traderAddr, symbol, overrides) {
if (!this.proxyContract || !this.multicall) {
throw new Error("proxy contract not initialized");
}
if (this.isPredictionMarket(symbol)) {
// prediction markets: also works for closing positions
return this.pmMaxOrderSizeForTrader(traderAddr, symbol, overrides);
}
// regular markets: also works for closing positions
return this.rmMaxOrderSizeForTrader(traderAddr, symbol, overrides);
}
/**
* pmMaxOrderSizeForTrader returns the max order size for the
* trader that is possible from AMM perspective (agnostic about wallet
* balance and leverage, also correct if trader is shrinking their position)
* @param traderAddr address of trader
* @param symbol perp symbol
* @param overrides optional
* @returns buy: number; sell: number absolute
*/
async pmMaxOrderSizeForTrader(traderAddr, symbol, overrides) {
if (!this.proxyContract || !this.multicall) {
throw new Error("proxy contract not initialized");
}
const perpId = perpetualDataHandler_1.default.symbolToPerpetualId(symbol, this.symbolToPerpStaticInfo);
const indexPriceInfo = await this.priceFeedGetter.fetchPricesForPerpetual(symbol);
const [fS3, fEma] = [indexPriceInfo.s3 ?? 0, indexPriceInfo.ema].map((x) => (0, d8XMath_1.floatToABK64x64)(x));
const traderState = await this.proxyContract.getTraderState(perpId, traderAddr, [fEma, fS3], overrides || {});
const idxNotional = 4;
const [maxShortTrade, maxLongTrade] = await this.getMaxShortLongTrade(perpId, traderState[idxNotional], overrides);
return { buy: maxLongTrade, sell: Math.abs(maxShortTrade) };
}
/**
* Returns the maximal allowed short trade and long trade (signed) for a trader
* that has a given notional (in ABDK format) in the perpetual, ignoring the traders wallet balance
* @param perpId
* @param currentTraderPos ABDK64x64 signed notional position of trader
* @param overrides
* @returns [maxShortPos, maxLongPos] signed maximal trade sizes
*/
async getMaxShortLongTrade(perpId, currentTraderPos, overrides) {
if (!this.proxyContract || !this.multicall) {
throw new Error("proxy contract not initialized");
}
const proxyCalls2 = [
// 0: max long
{
target: this.proxyContract.target,
allowFailure: false,
callData: this.proxyContract.interface.encodeFunctionData("getMaxSignedOpenTradeSizeForPos", [
perpId,
currentTraderPos,
true,
]),
},
// 1: max short
{
target: this.proxyContract.target,
allowFailure: false,
callData: this.proxyContract.interface.encodeFunctionData("getMaxSignedOpenTradeSizeForPos", [
perpId,
currentTraderPos,
false,
]),
},
];
// multicall
const encodedResults2 = await this.multicall.aggregate3.staticCall(proxyCalls2, overrides || {});
// Max based on perp:
// max buy
let maxLongOrderPerp = (0, d8XMath_1.ABK64x64ToFloat)(this.proxyContract.interface.decodeFunctionResult("getMaxSignedOpenTradeSizeForPos", encodedResults2[0].returnData)[0]);
// max short
let maxShortOrderPerp = (0, d8XMath_1.ABK64x64ToFloat)(this.proxyContract.interface.decodeFunctionResult("getMaxSignedOpenTradeSizeForPos", encodedResults2[1].returnData)[0]);
// now we ensure that closing direction can at least close position (always possible in smart contract)
if (currentTraderPos > 0n) {
// pos > 0, so short is closing direction
maxShortOrderPerp = -Math.max(Math.abs(maxShortOrderPerp), (0, d8XMath_1.ABK64x64ToFloat)(currentTraderPos));
}
else {
// pos <=0, so long is closing direction
maxLongOrderPerp = Math.max(maxLongOrderPerp, Math.abs((0, d8XMath_1.ABK64x64ToFloat)(currentTraderPos)));
}
return [maxShortOrderPerp, maxLongOrderPerp];
}
/**