Skip to content

Commit

Permalink
Track orderbook for ws events
Browse files Browse the repository at this point in the history
  • Loading branch information
maxima-net committed Aug 16, 2022
1 parent 86290df commit 7f3ba91
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 69 deletions.
10 changes: 8 additions & 2 deletions src/clients/dtos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ export interface WebSocketTopOfBookResponseDto extends WebSocketResponseBaseDto
data: QuoteDto[];
}

export interface WebSocketOrderBookResponseDto extends WebSocketResponseBaseDto {
export interface WebSocketOrderBookSnapshotResponseDto extends WebSocketResponseBaseDto {
event: 'snapshot';
data: OrderBookDto;
}

export interface WebSocketOrderBookEntriesResponseDto extends WebSocketResponseBaseDto {
event: 'entries';
data: WebSocketOrderBookEntryDto[];
}
Expand All @@ -160,4 +165,5 @@ export type WebSocketResponseDto =
| WebSocketOrderResponseDto
| WebSocketSwapResponseDto
| WebSocketTopOfBookResponseDto
| WebSocketOrderBookResponseDto;
| WebSocketOrderBookSnapshotResponseDto
| WebSocketOrderBookEntriesResponseDto;
61 changes: 36 additions & 25 deletions src/clients/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import BigNumber from 'bignumber.js';

import type { Transaction } from '../blockchain/models/index';
import type { CurrenciesProvider } from '../common/index';
import { ExchangeSymbol, ExchangeSymbolsProvider, NewOrderRequest, Order, OrderBook, OrderPreview, Quote, symbolsHelper } from '../exchange/index';
import type { DeepMutable } from '../core';
import { ExchangeSymbol, ExchangeSymbolsProvider, NewOrderRequest, Order, OrderBook, OrderBookEntry, OrderPreview, Quote, symbolsHelper } from '../exchange/index';
import type { Swap, SwapParticipantTrade } from '../swaps/index';
import type {
OrderBookDto, OrderDto, QuoteDto, SwapDto, SymbolDto,
OrderBookDto, OrderBookEntryDto, OrderDto, QuoteDto, SwapDto, SymbolDto,
TradeDto, TransactionDto, WebSocketOrderBookEntryDto, WebSocketOrderDataDto
} from './dtos';

Expand Down Expand Up @@ -72,36 +73,46 @@ export const mapOrderBookDtoToOrderBook = (orderBookDto: OrderBookDto): OrderBoo
symbol: orderBookDto.symbol,
quoteCurrency,
baseCurrency,
entries: orderBookDto.entries.map(orderBookEntryDto => ({
side: orderBookEntryDto.side,
price: new BigNumber(orderBookEntryDto.price),
qtyProfile: orderBookEntryDto.qtyProfile
}))
entries: orderBookDto.entries.map(orderBookEntryDto => mapOrderBookEntryDtoToOrderBookEntry(orderBookEntryDto))
};

return orderBook;
};

export const mapWebSocketOrderBookEntryDtoToOrderBook = (orderBookEntryDtos: WebSocketOrderBookEntryDto[]): OrderBook => {
const firstOrderBookEntry = orderBookEntryDtos[0];
if (!firstOrderBookEntry)
throw new Error('Unexpected dto');

const [quoteCurrency, baseCurrency] = symbolsHelper.getQuoteBaseCurrenciesBySymbol(firstOrderBookEntry.symbol);

const orderBook: OrderBook = {
updateId: firstOrderBookEntry.updateId,
symbol: firstOrderBookEntry.symbol,
quoteCurrency,
baseCurrency,
entries: orderBookEntryDtos.map(orderBookEntryDto => ({
side: orderBookEntryDto.side,
price: new BigNumber(orderBookEntryDto.price),
qtyProfile: orderBookEntryDto.qtyProfile
}))
export const mapOrderBookEntryDtoToOrderBookEntry = (entryDto: OrderBookEntryDto): OrderBookEntry => {
const entry: OrderBookEntry = {
side: entryDto.side,
price: new BigNumber(entryDto.price),
qtyProfile: entryDto.qtyProfile
};

return orderBook;
return entry;
};

export const mapWebSocketOrderBookEntryDtoToOrderBooks = (
orderBookEntryDtos: WebSocketOrderBookEntryDto[],
orderBooksMap: ReadonlyMap<OrderBook['symbol'], OrderBook>
): OrderBook[] => {
const updatedOrderBooks: Map<OrderBook['symbol'], OrderBook> = new Map();

for (const entryDto of orderBookEntryDtos) {
const orderBook = updatedOrderBooks.get(entryDto.symbol) || orderBooksMap.get(entryDto.symbol);
if (!orderBook || orderBook.updateId >= entryDto.updateId)
continue;

const entry = mapOrderBookEntryDtoToOrderBookEntry(entryDto);
const updatedOrderBook: OrderBook = {
...orderBook,
updateId: entryDto.updateId,
entries: entryDto.qtyProfile.length ?
[...orderBook.entries, entry]
: orderBook.entries.filter(e => e.side !== entry.side && e.price !== entry.price)
};

updatedOrderBooks.set(updatedOrderBook.symbol, updatedOrderBook);
}

return Array.from(updatedOrderBooks.values());
};

export const mapOrderDtoToOrder = (orderDto: OrderDto, exchangeSymbolsProvider: ExchangeSymbolsProvider): Order => {
Expand Down
36 changes: 30 additions & 6 deletions src/clients/webSocket/webSocketAtomexClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type {
} from '../../exchange/index';
import type { Swap } from '../../swaps/index';
import type { AtomexClient } from '../atomexClient';
import type { WebSocketResponseDto } from '../dtos';
import { mapQuoteDtosToQuotes, mapSwapDtoToSwap, mapWebSocketOrderBookEntryDtoToOrderBook, mapWebSocketOrderDtoToOrder } from '../helpers';
import type { OrderBookDto, WebSocketOrderBookEntryDto, WebSocketResponseDto } from '../dtos';
import { mapOrderBookDtoToOrderBook, mapQuoteDtosToQuotes, mapSwapDtoToSwap, mapWebSocketOrderBookEntryDtoToOrderBooks, mapWebSocketOrderDtoToOrder } from '../helpers';
import { ExchangeWebSocketClient } from './exchangeWebSocketClient';
import { MarketDataWebSocketClient } from './marketDataWebSocketClient';

Expand Down Expand Up @@ -38,6 +38,7 @@ export class WebSocketAtomexClient implements AtomexClient {
protected readonly marketDataWebSocketClient: MarketDataWebSocketClient;
protected readonly exchangeWebSocketClient: ExchangeWebSocketClient;

private readonly _orderBookCache: Map<OrderBook['symbol'], OrderBook> = new Map();
private _isStarted = false;

constructor(options: WebSocketAtomexClientOptions) {
Expand Down Expand Up @@ -138,20 +139,43 @@ export class WebSocketAtomexClient implements AtomexClient {
protected readonly onSocketMessageReceived = (message: WebSocketResponseDto) => {
switch (message.event) {
case 'order':
(this.events.orderUpdated as ToEventEmitter<typeof this.events.orderUpdated>).emit(mapWebSocketOrderDtoToOrder(message.data, this.exchangeSymbolsProvider));
(this.events.orderUpdated as ToEventEmitter<typeof this.events.orderUpdated>).emit(
mapWebSocketOrderDtoToOrder(message.data, this.exchangeSymbolsProvider)
);
break;

case 'swap':
(this.events.swapUpdated as ToEventEmitter<typeof this.events.swapUpdated>).emit(mapSwapDtoToSwap(message.data, this.exchangeSymbolsProvider));
(this.events.swapUpdated as ToEventEmitter<typeof this.events.swapUpdated>).emit(
mapSwapDtoToSwap(message.data, this.exchangeSymbolsProvider)
);
break;

case 'topOfBook':
(this.events.topOfBookUpdated as ToEventEmitter<typeof this.events.topOfBookUpdated>).emit(mapQuoteDtosToQuotes(message.data));
(this.events.topOfBookUpdated as ToEventEmitter<typeof this.events.topOfBookUpdated>).emit(
mapQuoteDtosToQuotes(message.data)
);
break;

case 'snapshot':
this.onOrderBookSnapshotReceived(message.data);
break;

case 'entries':
(this.events.orderBookUpdated as ToEventEmitter<typeof this.events.orderBookUpdated>).emit(mapWebSocketOrderBookEntryDtoToOrderBook(message.data));
this.onOrderBookEntriesReceived(message.data);
break;
}
};

protected onOrderBookSnapshotReceived(orderBookDto: OrderBookDto) {
const orderBook = mapOrderBookDtoToOrderBook(orderBookDto);
this._orderBookCache.set(orderBook.symbol, orderBook);
}

protected onOrderBookEntriesReceived(entryDtos: WebSocketOrderBookEntryDto[]) {
const updatedOrderBooks = mapWebSocketOrderBookEntryDtoToOrderBooks(entryDtos, this._orderBookCache);
for (const updatedOrderBook of updatedOrderBooks) {
this._orderBookCache.set(updatedOrderBook.symbol, updatedOrderBook);
(this.events.orderBookUpdated as ToEventEmitter<typeof this.events.orderBookUpdated>).emit(updatedOrderBook);
}
}
}
15 changes: 3 additions & 12 deletions src/exchange/exchangeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,8 @@ export class ExchangeManager implements AtomexService {
(this.events.orderUpdated as ToEventEmitter<typeof this.events.orderUpdated>).emit(updatedOrder);
};

protected handleExchangeServiceOrderBookUpdated = (orderBookUpdates: OrderBook) => {
// TODO: temporary
this.getOrderBook(orderBookUpdates.symbol)
.then(updatedOrderBook => {
if (!updatedOrderBook)
return;

this._orderBookCache.set(updatedOrderBook.symbol, updatedOrderBook);
(this.events.orderBookUpdated as ToEventEmitter<typeof this.events.orderBookUpdated>).emit(updatedOrderBook);
})
.catch(error => console.error(error));
protected handleExchangeServiceOrderBookUpdated = async (updatedOrderBook: OrderBook) => {
(this.events.orderBookUpdated as ToEventEmitter<typeof this.events.orderBookUpdated>).emit(updatedOrderBook);
};

protected handleExchangeServiceTopOfBookUpdated = (updatedQuotes: readonly Quote[]) => {
Expand Down Expand Up @@ -259,7 +250,7 @@ export class ExchangeManager implements AtomexService {
}
}

protected getCachedOrderBook(symbol: string) {
protected getCachedOrderBook(symbol: string): Promise<OrderBook | undefined> {
const cachedOrderBook = this._orderBookCache.get(symbol);

return cachedOrderBook ? Promise.resolve(cachedOrderBook) : this.getOrderBook(symbol);
Expand Down
Loading

0 comments on commit 7f3ba91

Please sign in to comment.