UNPKG

@d8x/perpetuals-sdk

Version:

Node TypeScript SDK for D8X Perpetual Futures

374 lines 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const constants_1 = require("./constants"); const d8XMath_1 = require("./d8XMath"); /** * This class handles events and stores relevant variables * as member variables. The events change the state of the member variables: * mktData : MarketData relevant market data with current state (e.g. index price) * ordersInPerpetual: Map<number, OrderStruct> all open orders for the given trader * positionInPerpetual: Map<number, MarginAccount> all open positions for the given trader * * * Get data: * - getPerpetualData(perp id (string) or symbol) : PerpetualState. This is a reference! * - getExchangeInfo() : ExchangeInfo. This is a reference! * - getCurrentPositionRisk(perp id (string) or symbol) : MarginAccount. This is a reference! * - getOrdersInPerpetualMap : Map<number, OrderStruct>. This is a reference! * - getpositionInPerpetualMap : Map<number, MarginAccount>. This is a reference! * * Construct with a trader address and a marketData object * Initialize to gather all the relevant data. * Send event variables to event handler "on<EventName>" - this updates members * - [x] onUpdateMarkPrice : emitted on proxy; updates markprice and index price data * - [x] onUpdateUpdateFundingRate : emitted on proxy; sets funding rate * - [x] onExecutionFailed : emitted on order book; removes an open order * - [x] onPerpetualLimitOrderCancelled : emitted on order book; removes an open order * - [x] onPerpetualLimitOrderCreated : emitted on order book; adds an open order to the data * - [x] async onUpdateMarginAccount : emitted on proxy; updates position data and open interest * - [x] onTrade : emitted on proxy; returns TradeEvent to be displayed */ class PerpetualEventHandler { constructor(mktData, traderAddr) { this.mktData = mktData; this.traderAddr = traderAddr; this.ordersInPerpetual = new Map(); this.positionInPerpetual = new Map(); this.poolIndexForPerpetual = new Map(); } /** * Call this async function to initialize the * market data */ async initialize() { this.exchangeInfo = await this.mktData.exchangeInfo(); // loop through all pools and perpetuals and get // open positions and open orders for (let k = 0; k < this.exchangeInfo.pools.length; k++) { let poolState = this.exchangeInfo.pools[k]; let poolSymbol = poolState.poolSymbol; for (let j = 0; j < poolState.perpetuals.length; j++) { let perpState = poolState.perpetuals[j]; let perpSymbol = perpState.baseCurrency + "-" + perpState.quoteCurrency + "-" + poolSymbol; let orders = await this.mktData.openOrders(this.traderAddr, perpSymbol); let perpId = perpState.id; this.ordersInPerpetual.set(perpId, orders[0]); let position = (await this.mktData.positionRisk(this.traderAddr, perpSymbol))[0]; this.positionInPerpetual.set(perpId, position); this.poolIndexForPerpetual.set(perpId, k); } } } /** * Get the current exchange info * @returns exchange info */ getExchangeInfo() { return this.exchangeInfo; } /** * getOrdersInPerpetualMap * @returns this.ordersInPerpetual */ getOrdersInPerpetualMap() { return this.ordersInPerpetual; } /** * getpositionInPerpetualMap * @returns this.positionInPerpetual */ getpositionInPerpetualMap() { return this.positionInPerpetual; } /** * Get the data for a perpetual with a given index * @param perpetualIdOrSymbol perpetual idx as string or symbol for which we want the data * @returns perpetual data for this idx */ getPerpetualData(perpetualIdOrSymbol) { let perpId = Number(perpetualIdOrSymbol); if (isNaN(perpId)) { perpId = this.mktData.getPerpIdFromSymbol(perpetualIdOrSymbol); } //uint24 perpetualId = uint24(_iPoolId) * 100_000 + iPerpetualIndex; let poolIdx = this.poolIndexForPerpetual.get(perpId); //Math.floor(perpId / 100_000); let perpetuals = this.exchangeInfo?.pools[poolIdx].perpetuals; if (perpetuals == undefined) { console.log(`exchangeInfo not found, initialize perpetualEventHandler`); return undefined; } // find perpetual let k; for (k = 0; k < perpetuals?.length && perpetuals[k].id != perpId; k++) ; if (perpetuals[k].id != perpId) { console.log(`getPerpetualData: perpetual id ${perpId} not found`); return undefined; } return perpetuals[k]; } /** * Get the trader's current position risk (margin account data) * @param perpetualIdOrSymbol perpetual id as string ('100003') or symbol ('BTC-USD-MATIC') * @returns undefined if no position or margin account (='position risk') */ getCurrentPositionRisk(perpetualIdOrSymbol) { let perpId = Number(perpetualIdOrSymbol); if (isNaN(perpId)) { perpId = this.mktData.getPerpIdFromSymbol(perpetualIdOrSymbol); } return this.positionInPerpetual.get(perpId); } /** * Update the following prices: * - index price * - collateral price * - mid-price * @param perpetualIdOrSymbol perpetual id as string ('100003') or symbol ('BTC-USD-MATIC') */ async updatePrices(perpetualIdOrSymbol) { let perpId = Number(perpetualIdOrSymbol); let symbol = perpetualIdOrSymbol; if (!isNaN(perpId)) { let sym = this.mktData.getSymbolFromPerpId(perpId); if (sym == undefined) { throw new Error(`Symbol not found for perpetual ${perpId}`); } symbol = sym; } let perpState = await this.mktData.getPerpetualState(symbol); let perp = this.getPerpetualData(symbol); if (perp == undefined) { throw new Error(`Perpetual not found: ${symbol}`); } perp.state = perpState.state; perp.indexPrice = perpState.indexPrice; perp.collToQuoteIndexPrice = perpState.collToQuoteIndexPrice; perp.markPremium = perpState.markPremium; perp.midPrice = perpState.midPrice; perp.currentFundingRateBps = perpState.currentFundingRateBps; perp.openInterestBC = perpState.openInterestBC; perp.indexPrice = perpState.indexPrice; perp.collToQuoteIndexPrice = perpState.collToQuoteIndexPrice; } /** * Handle the event UpdateMarkPrice and update relevant * data * @param perpetualId perpetual Id * @param fMarkPricePremium premium rate in ABDK format * @param fSpotIndexPrice spot index price in ABDK format * @returns void */ onUpdateMarkPrice(perpetualId, fMidPricePremium, fMarkPricePremium, fSpotIndexPrice) { let [newMidPrice, newMarkPricePrem, newIndexPrice] = PerpetualEventHandler.ConvertUpdateMarkPrice(fMidPricePremium, fMarkPricePremium, fSpotIndexPrice); let perpetual = this.getPerpetualData(perpetualId.toString()); if (perpetual == undefined) { return; } perpetual.midPrice = newMidPrice; perpetual.markPremium = newMarkPricePrem; perpetual.indexPrice = newIndexPrice; } /** * Handle the event UpdateFundingRate and update relevant * data * UpdateFundingRate(uint24 indexed perpetualId, int128 fFundingRate) * @param fFundingRate funding rate in ABDK format */ onUpdateUpdateFundingRate(perpetualId, fFundingRate) { let newRate = (0, d8XMath_1.ABK64x64ToFloat)(fFundingRate); let perpetual = this.getPerpetualData(perpetualId.toString()); if (perpetual == undefined) { return; } perpetual.currentFundingRateBps = newRate * 1e4; } /** * event ExecutionFailed( uint24 indexed perpetualId, address indexed trader, bytes32 digest, string reason ); * @param perpetualId id of the perpetual * @param trader address of the trader * @param digest digest of the order/cancel order * @param reason reason why the execution failed */ onExecutionFailed(perpetualId, trader, digest, reason) { if (trader != this.traderAddr) { console.log(`onExecutionFailed: trader ${trader} not relevant`); return; } // remove order from open orders let orderStructs = this.ordersInPerpetual.get(perpetualId); if (orderStructs == undefined) { console.log(`onExecutionFailed: no order found for perpetual ${perpetualId}`); return; } if (reason == "cancel delay required") { // canceling failed. We don't remove the order return; } PerpetualEventHandler.deleteOrder(orderStructs, digest); } /** * Event emitted by perpetual proxy * event PerpetualLimitOrderCancelled(bytes32 indexed orderHash); * @param orderId string order id/digest */ onPerpetualLimitOrderCancelled(orderId) { // remove order from open orders let perpId = PerpetualEventHandler.findOrderForId(orderId, this.ordersInPerpetual); if (perpId == undefined) { console.log(`onPerpetualLimitOrderCancelled: no order found with id ${orderId}`); return; } let orderStruct = this.ordersInPerpetual.get(perpId); PerpetualEventHandler.deleteOrder(orderStruct, orderId); } /** * event PerpetualLimitOrderCreated( * uint24 indexed perpetualId, * address indexed trader, * address executorAddr, * address brokerAddr, * Order order, * bytes32 digest *) * @param perpetualId id of the perpetual * @param trader address of the trader * @param executorAddr address of the executor * @param brokerAddr address of the broker * @param Order order struct * @param digest order id */ onPerpetualLimitOrderCreated(perpetualId, trader, _executorAddr, _brokerAddr, Order, digest) { if (trader != this.traderAddr) { console.log(`onPerpetualLimitOrderCreated: trader ${trader} not relevant`); return; } let order = this.mktData.smartContractOrderToOrder(Order); let orderStruct = this.ordersInPerpetual.get(perpetualId); if (orderStruct == undefined) { // no order for this perpetual so far this.ordersInPerpetual.set(perpetualId, { orders: [order], orderIds: [digest] }); } else { orderStruct.orderIds.push(digest); orderStruct.orders.push(order); } } /** * This function is async -> queries the margin account * @param perpetualId id of the perpetual * @param trader trader address * @param positionId position id * @param fPositionBC position size in base currency * @param fCashCC margin collateral in margin account * @param fLockedInValueQC pos*average opening price * @param fFundingPaymentCC funding payment made * @param fOpenInterestBC open interest */ async onUpdateMarginAccount(perpetualId, trader, _positionId, _fPositionBC, _fCashCC, _fLockedInValueQC, _fFundingPaymentCC, fOpenInterestBC) { let perpetual = this.getPerpetualData(perpetualId.toString()); if (perpetual == undefined) { console.log(`onUpdateMarginAccount: perpetual ${perpetualId} not found`); return; } perpetual.openInterestBC = (0, d8XMath_1.ABK64x64ToFloat)(fOpenInterestBC); if (trader != this.traderAddr) { return; } let perpetualIdStr = perpetualId.toString(); let margin = (await this.mktData.positionRisk(this.traderAddr, perpetualIdStr))[0]; this.positionInPerpetual.set(perpetualId, margin); } /** * * @param perpetualId perpetual id * @param trader trader address * @param positionId position id * @param order order struct * @param orderDigest order id * @param newPositionSizeBC new pos size in base currency ABDK * @param price price in ABDK format * @returns trade event data in regular number format */ onTrade(perpetualId, _trader, positionId, _order, orderDigest, newPositionSizeBC, _price) { // remove order digest from open orders let orderStructs = this.ordersInPerpetual.get(perpetualId); if (orderStructs == undefined) { console.log(`onTrade: executed order not found ${orderDigest}`); } else { PerpetualEventHandler.deleteOrder(orderStructs, orderDigest); } // return transformed trade info return { perpetualId: perpetualId, positionId: positionId, orderId: orderDigest, newPositionSizeBC: (0, d8XMath_1.ABK64x64ToFloat)(newPositionSizeBC), executionPrice: (0, d8XMath_1.ABK64x64ToFloat)(newPositionSizeBC), }; } /** * static function to find the number of the OrderStruct with given orderId * @param orderId id/digest of order * @param orderMap mapping for perpetualId->OrderStruct * @returns id of perpetual that contains order with id = orderId or undefined */ static findOrderForId(orderId, orderMap) { /*orderMapMap<number, { orders: Order[]; orderIds: string[];*/ for (const perpId of orderMap.keys()) { let orderStructs = orderMap.get(perpId); if (orderStructs?.orderIds.includes(orderId)) { return perpId; } } return undefined; } /** * Delete the order with given id from the class member data * @param orderStructs array of order struct as stored for the trader and a given perpetual * @param orderId digest/order id * @returns void */ static deleteOrder(orderStructs, orderId) { // find order let k; for (k = 0; k < orderStructs.orderIds.length && orderStructs.orderIds[k] != orderId; k++) ; if (orderStructs.orderIds[k] != orderId) { console.log(`deleteOrder: no order found with digest ${orderId}`); return; } // delete element k on reference of orders orderStructs.orders[k] = orderStructs.orders[orderStructs.orders.length - 1]; orderStructs.orders.pop(); orderStructs.orderIds[k] = orderStructs.orderIds[orderStructs.orderIds.length - 1]; orderStructs.orderIds.pop(); } /** * UpdateMarkPrice( * uint24 indexed perpetualId, * int128 fMarkPricePremium, * int128 fSpotIndexPrice * ) * @param fMarkPricePremium premium rate in ABDK format * @param fSpotIndexPrice spot index price in ABDK format * @returns midPrice, markPricePremium, indexPrice in float */ static ConvertUpdateMarkPrice(fMidPricePremium, fMarkPricePremium, fSpotIndexPrice) { let fMidPrice = (0, d8XMath_1.mul64x64)(fSpotIndexPrice, constants_1.ONE_64x64 + fMidPricePremium); let midPrice = (0, d8XMath_1.ABK64x64ToFloat)(fMidPrice); let markPricePremium = (0, d8XMath_1.ABK64x64ToFloat)(fMarkPricePremium); let indexPrice = (0, d8XMath_1.ABK64x64ToFloat)(fSpotIndexPrice); return [midPrice, markPricePremium, indexPrice]; } } exports.default = PerpetualEventHandler; //# sourceMappingURL=perpetualEventHandler.js.map