UNPKG

hyperliquid-sdk

Version:

<<< Typescript SDK for the Hyperliquid API >>>

217 lines (185 loc) 6.1 kB
// src/rest/custom.ts import { AbstractSigner } from 'ethers'; import { InfoAPI } from './info'; import { ExchangeAPI } from './exchange'; import { ApiResponseWithStatus, CommonSuccessOrErrorResponse, UserOpenOrders, } from '../types'; import { OrderResponse, CancelOrderRequest, OrderRequest, OrderType, } from '../types/index'; import { SymbolConversion } from '../utils/symbolConversion'; export class CustomOperations { private exchange: ExchangeAPI; private infoApi: InfoAPI; private wallet: AbstractSigner; private symbolConversion: SymbolConversion; constructor( exchange: ExchangeAPI, infoApi: InfoAPI, wallet: AbstractSigner, symbolConversion: SymbolConversion, ) { this.exchange = exchange; this.infoApi = infoApi; this.wallet = wallet; this.symbolConversion = symbolConversion; } async cancelAllOrders( symbol?: string, ): Promise<ApiResponseWithStatus<CommonSuccessOrErrorResponse>> { try { const address = await this.wallet.getAddress(); const openOrders: UserOpenOrders = await this.infoApi.getUserOpenOrders(address); let ordersToCancel: UserOpenOrders; for (let order of openOrders) { order.coin = await this.symbolConversion.convertSymbol(order.coin); } if (symbol) { ordersToCancel = openOrders.filter((order) => order.coin === symbol); } else { ordersToCancel = openOrders; } if (ordersToCancel.length === 0) { throw new Error('No orders to cancel'); } const cancelRequests: CancelOrderRequest[] = ordersToCancel.map( (order) => ({ coin: order.coin, o: order.oid, }), ); return this.exchange.cancelOrder(cancelRequests); } catch (error) { throw error; } } async getAllAssets(): Promise<{ perp: string[]; spot: string[] }> { return await this.symbolConversion.getAllAssets(); } private DEFAULT_SLIPPAGE = 0.05; private async getSlippagePrice( symbol: string, isBuy: boolean, slippage: number, px?: number, ): Promise<number> { const convertedSymbol = await this.symbolConversion.convertSymbol(symbol); if (!px) { const allMids = await this.infoApi.getAllMids(); px = Number(allMids[convertedSymbol]); } const isSpot = symbol.includes('-SPOT'); //If not isSpot count how many decimals price has to use the same amount for rounding const decimals = px.toString().split('.')[1]?.length || 0; console.log(decimals); px *= isBuy ? 1 + slippage : 1 - slippage; const spotDecimals = px < 1 ? 5 : 8; // TODO: Check the calculation here: https://github.com/ccxt/ccxt/issues/23516 and here: https://github.com/gaardiolor/ccxt/commit/de43976f408007bf2a8555141b13936735645702#diff-bafe810770b7e09d82844d4f5b0efc778f4e2024f8dfb7973faa9cf603d0ca0eR456 return Number(px.toFixed(isSpot ? spotDecimals : decimals - 1)); } async marketOpen( symbol: string, isBuy: boolean, size: number, px?: number, slippage: number = this.DEFAULT_SLIPPAGE, cloid?: string, ): Promise<ApiResponseWithStatus<OrderResponse | string>> { const convertedSymbol = await this.symbolConversion.convertSymbol(symbol); const slippagePrice = await this.getSlippagePrice( convertedSymbol, isBuy, slippage, px, ); const orderRequest: OrderRequest = { coin: convertedSymbol, is_buy: isBuy, sz: size, limit_px: slippagePrice, order_type: { limit: { tif: 'Ioc' } } as OrderType, reduce_only: false, }; if (cloid) { orderRequest.cloid = cloid; } console.debug('Order Request payload: ', orderRequest); return this.exchange.placeOrder(orderRequest); } async marketClose( symbol: string, size?: number, px?: number, slippage: number = this.DEFAULT_SLIPPAGE, cloid?: string, ): Promise<ApiResponseWithStatus<OrderResponse | string>> { const convertedSymbol = await this.symbolConversion.convertSymbol(symbol); const address = await this.wallet.getAddress(); const positions = await this.infoApi.perpetuals.getClearinghouseState(address); for (const position of positions.assetPositions) { const item = position.position; if (convertedSymbol !== item.coin) { continue; } const szi = parseFloat(item.szi); const closeSize = size || Math.abs(szi); const isBuy = szi < 0; // Get aggressive Market Price const slippagePrice = await this.getSlippagePrice( convertedSymbol, isBuy, slippage, px, ); // Market Order is an aggressive Limit Order IoC const orderRequest: OrderRequest = { coin: convertedSymbol, is_buy: isBuy, sz: closeSize, limit_px: slippagePrice, order_type: { limit: { tif: 'Ioc' } } as OrderType, reduce_only: true, }; if (cloid) { orderRequest.cloid = cloid; } return this.exchange.placeOrder(orderRequest); } throw new Error(`No position found for ${convertedSymbol}`); } async closeAllPositions( slippage: number = this.DEFAULT_SLIPPAGE, ): Promise<ApiResponseWithStatus<OrderResponse | string>[]> { try { const address = await this.wallet.getAddress(); const positions = await this.infoApi.perpetuals.getClearinghouseState(address); const closeOrders: Promise< ApiResponseWithStatus<OrderResponse | string> >[] = []; console.log(positions); for (const position of positions.assetPositions) { const item = position.position; if (parseFloat(item.szi) !== 0) { const symbol = await this.symbolConversion.convertSymbol( item.coin, 'forward', ); closeOrders.push( this.marketClose(symbol, undefined, undefined, slippage), ); } } return await Promise.all(closeOrders); } catch (error) { throw error; } } }