diff --git a/v4-client-js/examples/websocket_orderbook_example.ts b/v4-client-js/examples/websocket_orderbook_example.ts new file mode 100644 index 00000000..80e2ef72 --- /dev/null +++ b/v4-client-js/examples/websocket_orderbook_example.ts @@ -0,0 +1,246 @@ +import { Network } from '../src/clients/constants'; +import { SocketClient } from '../src/clients/socket-client'; + +function test(): void { + let orderBookBidList: [number, number, number][] = []; + let orderBookAskList: [number, number, number][] = []; + + const mySocket = new SocketClient( + Network.mainnet().indexerConfig, + () => { + console.log('socket opened'); + + mySocket.subscribeToOrderbook('ETH-USD'); + }, + () => { + console.log('socket closed'); + }, + (message) => { + try { + if (typeof message.data === "string") { + const jsonString = message.data as string; + const parsingData = JSON.parse(jsonString); + const messageId = parsingData.message_id; + const orderBookDataList = parsingData.contents; + + if (orderBookDataList instanceof Array) { + // common orderBook data; + [orderBookBidList, orderBookAskList] = updateOrderBook( + orderBookDataList, + orderBookBidList, + orderBookAskList, + messageId, + ); + + // sort + orderBookBidList = sortByNthElementDesc( + orderBookBidList, + 0, + ); + orderBookAskList = sortByNthElementAsc( + orderBookAskList, + 0, + ); + + // resolving crossed orderBook + if (orderBookBidList[0][0] >= orderBookAskList[0][0]) { + [orderBookBidList, orderBookAskList] = + resolveCrossedOrderBook( + orderBookBidList, + orderBookAskList, + ); + } + + printOrderBook(orderBookBidList, orderBookAskList); + + if (orderBookBidList.length > 300) { + orderBookBidList.splice(50); + } + + if (orderBookAskList.length > 300) { + orderBookAskList.splice(50); + } + } else if ( + orderBookDataList !== null && + orderBookDataList !== undefined + ) { + // initial OrderBook data + setInitialOrderBook( + orderBookDataList, + orderBookBidList, + orderBookAskList, + messageId, + ); + } + } + } catch (e) { + console.error('Error parsing JSON message:', e); + } + }, + (event) => { + console.error('Encountered error:', event.message); + }, + ); + + mySocket.connect(); +} + +const sortByNthElementAsc = (arr: [number, number, number][], n: number): [number, number, number][] => { + return arr.sort((a, b) => { + if (a[n] < b[n]) return -1; + if (a[n] > b[n]) return 1; + return 0; + }); +}; +const sortByNthElementDesc = (arr: [number, number, number][], n: number): [number, number, number][] => { + return arr.sort((a, b) => { + if (a[n] > b[n]) return -1; + if (a[n] < b[n]) return 1; + return 0; + }); +}; + +const printOrderBook = ( + orderBookBidList: [number, number, number][], + orderBookAskList: [number, number, number][], +): void => { + // print + console.log(`OrderBook for ETH-USD:`); + console.log(`Price Qty`); + for (let i = 4; i > -1; i--) { + const priceStr = String(orderBookAskList[i][0]); + const spaces = createSpaces(10 - priceStr.length); + console.log(`${priceStr}${spaces}${orderBookAskList[i][1]}`); + } + console.log('---------------------'); + for (let i = 0; i < 5; i++) { + const priceStr = String(orderBookAskList[i][0]); + const spaces = createSpaces(10 - priceStr.length); + console.log(`${priceStr}${spaces}${orderBookBidList[i][1]}`); + } + console.log(''); +}; + +function createSpaces(count: number): string { + if(count <= 0 ){ + return ""; + } + + let spaces = ""; + for (let i = 0; i < count; i++) { + spaces += " "; + } + return spaces; +} + +const resolveCrossedOrderBook = ( + orderBookBidList: [number, number, number][], + orderBookAskList: [number, number, number][], +): [[number, number, number][], [number, number, number][]] => { + while (orderBookBidList[0][0] >= orderBookAskList[0][0]) { + if (orderBookBidList[0][2] < orderBookAskList[0][2]) { + orderBookBidList.shift(); + } else if (orderBookBidList[0][2] > orderBookAskList[0][2]) { + orderBookAskList.shift(); + } else { + if (orderBookBidList[0][1] > orderBookAskList[0][1]) { + orderBookBidList[0][1] -= orderBookAskList[0][1]; + orderBookAskList.shift(); + } else if (orderBookBidList[0][1] < orderBookAskList[0][1]) { + orderBookAskList[0][1] -= orderBookBidList[0][1]; + orderBookBidList.shift(); + } else { + orderBookAskList.shift(); + orderBookBidList.shift(); + } + } + } + + return [orderBookBidList, orderBookAskList]; +}; +const setInitialOrderBook = ( + orderBookDataList: { bids: []; asks: [] }, + orderBookBidList: [number, number, number][], + orderBookAskList: [number, number, number][], + messageId: number, +): void => { + orderBookDataList.bids.forEach((item: { price: string; size: string }) => { + orderBookBidList.push([Number(item.price), Number(item.size), messageId]); + }); + + orderBookDataList.asks.forEach((item: { price: string; size: string }) => { + orderBookAskList.push([Number(item.price), Number(item.size), messageId]); + }); +}; + +const updateOrderBook = ( + orderBookDataList: { bids: [[]]; asks: [[]] }[], + orderBookBidList: [number, number, number][], + orderBookAskList: [number, number, number][], + messageId: number, +): [[number, number, number][], [number, number, number][]] => { + orderBookDataList.forEach((entry: { bids: [[]]; asks: [[]] }) => { + if (entry.bids !== null && entry.bids !== undefined) { + const flattened = entry.bids.reduce((acc: any[], val: any[]) => acc.concat(val), []); + const entryBidPrice = Number(flattened[0]); + const entryBidSize = Number(flattened[1]); + + // remove prices with zero Qty + if (entryBidSize === 0) { + for (let i = orderBookBidList.length - 1; i >= 0; i--) { + if (orderBookBidList[i][0] === entryBidPrice) { + orderBookBidList.splice(i, 1); + } + } + } else { + // The price that already exists in the order book is modified only Qty + if ( + orderBookBidList.some((innerArray) => innerArray[0] === entryBidPrice) + ) { + orderBookBidList.forEach((item, index) => { + if (item[0] === entryBidPrice) { + orderBookBidList[index][1] = entryBidSize; + orderBookBidList[index][2] = messageId; + } + }); + } else { + // Add new data to order book + orderBookBidList.push([entryBidPrice, entryBidSize, messageId]); + } + } + } + if (entry.asks !== null && entry.asks !== undefined) { + const flattened = entry.asks.reduce((acc: any[], val: any[]) => acc.concat(val), []); + const entryAskPrice = Number(flattened[0]); + const entryAskSize = Number(flattened[1]); + + if (entryAskSize === 0) { + // remove prices with zero Qty + for (let i = orderBookAskList.length - 1; i >= 0; i--) { + if (orderBookAskList[i][0] === entryAskPrice) { + orderBookAskList.splice(i, 1); + } + } + } else { + // The price that already exists in the order book is modified only Qty + if ( + orderBookAskList.some((innerArray) => innerArray[0] === entryAskPrice) + ) { + orderBookAskList.forEach((item, index) => { + if (item[0] === entryAskPrice) { + orderBookAskList[index][1] = entryAskSize; + orderBookAskList[index][2] = messageId; + } + }); + } else { + // Add new data to order book + orderBookAskList.push([entryAskPrice, entryAskSize, messageId]); + } + } + } + }); + + return [orderBookBidList, orderBookAskList]; +}; + +test(); \ No newline at end of file