diff --git a/libs/market-depth/src/lib/market-depth-provider.ts b/libs/market-depth/src/lib/market-depth-provider.ts index bba5153d0..275172d52 100644 --- a/libs/market-depth/src/lib/market-depth-provider.ts +++ b/libs/market-depth/src/lib/market-depth-provider.ts @@ -1,6 +1,8 @@ import { makeDataProvider } from '@vegaprotocol/react-helpers'; import { updateLevels } from './orderbook-data'; import type { Update } from '@vegaprotocol/react-helpers'; +import { captureException } from '@sentry/react'; + import { MarketDepthDocument, MarketDepthUpdateDocument, @@ -23,18 +25,24 @@ const update: Update< if (delta.marketId !== data.id) { continue; } - /* - const sequenceNumber = Number(delta.sequenceNumber); - if (sequenceNumber <= sequenceNumbers[delta.marketId]) { + const currentSequenceNumber = Number(delta.sequenceNumber); + if (currentSequenceNumber <= sequenceNumbers[delta.marketId]) { return data; } - if (sequenceNumber - 1 !== sequenceNumbers[delta.marketId]) { - sequenceNumbers[delta.marketId] = 0; + if ( + delta.previousSequenceNumber !== + sequenceNumbers[delta.marketId].toString() + ) { + captureException( + new Error( + `Sequence number gap in marketsDepthUpdate for {data.id}, {sequenceNumbers[delta.marketId]} - {delta.previousSequenceNumber}` + ) + ); + delete sequenceNumbers[delta.marketId]; reload(); return; } - sequenceNumbers[delta.marketId] = sequenceNumber; - */ + sequenceNumbers[delta.marketId] = Number(currentSequenceNumber); const updatedData = { ...data, depth: { ...data.depth }, diff --git a/libs/market-depth/src/lib/orderbook-data.ts b/libs/market-depth/src/lib/orderbook-data.ts index bd0379c6a..60b56f35c 100644 --- a/libs/market-depth/src/lib/orderbook-data.ts +++ b/libs/market-depth/src/lib/orderbook-data.ts @@ -1,5 +1,7 @@ import groupBy from 'lodash/groupBy'; import uniqBy from 'lodash/uniqBy'; +import reverse from 'lodash/reverse'; +import cloneDeep from 'lodash/cloneDeep'; import { VolumeType } from '@vegaprotocol/react-helpers'; import { MarketTradingMode } from '@vegaprotocol/types'; import type { MarketData } from '@vegaprotocol/market-list'; @@ -56,7 +58,7 @@ const toPercentValue = (value?: number) => Math.ceil((value ?? 0) * 100); const updateRelativeData = (data: OrderbookRowData[]) => { const { bid, ask, cumulativeVol } = getMaxVolumes(data); const maxBidAsk = Math.max(bid, ask); - data.forEach((data) => { + data.forEach((data, i) => { data.relativeAsk = toPercentValue(data.ask / maxBidAsk); data.relativeBid = toPercentValue(data.bid / maxBidAsk); data.cumulativeVol.relativeAsk = toPercentValue( @@ -68,6 +70,20 @@ const updateRelativeData = (data: OrderbookRowData[]) => { }); }; +const updateCumulativeVolume = (data: OrderbookRowData[]) => { + if (data.length > 1) { + const maxIndex = data.length - 1; + for (let i = 0; i <= maxIndex; i++) { + data[i].cumulativeVol.bid = + data[i].bid + (i !== 0 ? data[i - 1].cumulativeVol.bid : 0); + } + for (let i = maxIndex; i >= 0; i--) { + data[i].cumulativeVol.ask = + data[i].ask + (i !== maxIndex ? data[i + 1].cumulativeVol.ask : 0); + } + } +}; + export const createPartialRow = ( price: string, volume = 0, @@ -157,6 +173,7 @@ export const compactRows = ( (i !== maxIndex ? orderbookData[i + 1].cumulativeVol.ask : 0); } } + updateCumulativeVolume(orderbookData); // count relative volumes updateRelativeData(orderbookData); return orderbookData; @@ -175,45 +192,26 @@ const partiallyUpdateCompactedRows = ( dataType: VolumeType, data: OrderbookRowData[], delta: PriceLevelFieldsFragment, - resolution: number, - modifiedIndex: number -): [number, OrderbookRowData[]] => { + resolution: number +) => { const { price } = delta; const volume = Number(delta.volume); const priceLevel = getPriceLevel(price, resolution); const isAskDataType = dataType === VolumeType.ask; const volKey = isAskDataType ? 'ask' : 'bid'; - const oppositeVolKey = isAskDataType ? 'bid' : 'ask'; const volByLevelKey = isAskDataType ? 'askByLevel' : 'bidByLevel'; - const resolveModifiedIndex = isAskDataType ? Math.max : Math.min; let index = data.findIndex((row) => row.price === priceLevel); if (index !== -1) { - modifiedIndex = resolveModifiedIndex(modifiedIndex, index); - data[index] = { - ...data[index], - [volKey]: - data[index][volKey] - (data[index][volByLevelKey][price] || 0) + volume, - [volByLevelKey]: { - ...data[index][volByLevelKey], - [price]: volume, - }, - }; - return [modifiedIndex, [...data]]; + data[index][volKey] = + data[index][volKey] - (data[index][volByLevelKey][price] || 0) + volume; + data[index][volByLevelKey][price] = volume; } else { const newData: OrderbookRowData = createRow(priceLevel, volume, dataType); index = data.findIndex((row) => BigInt(row.price) < BigInt(priceLevel)); if (index !== -1) { - newData.cumulativeVol[oppositeVolKey] = - data[index + (isAskDataType ? 0 : 1)]?.cumulativeVol[oppositeVolKey] ?? - 0; - modifiedIndex = resolveModifiedIndex(modifiedIndex, index); - return [ - modifiedIndex, - [...data.slice(0, index), newData, ...data.slice(index)], - ]; + data.splice(index, 0, newData); } else { - modifiedIndex = data.length - 1; - return [modifiedIndex, [...data, newData]]; + data.push(newData); } } }; @@ -228,57 +226,19 @@ const partiallyUpdateCompactedRows = ( * @returns void */ export const updateCompactedRows = ( - rows: OrderbookRowData[], - sell: PriceLevelFieldsFragment[] | null, - buy: PriceLevelFieldsFragment[] | null, + rows: Readonly, + sell: Readonly | null, + buy: Readonly | null, resolution: number ) => { - let sellModifiedIndex = -1; - let data = [...rows]; - uniqBy(sell?.reverse(), 'price')?.forEach((delta) => { - [sellModifiedIndex, data] = partiallyUpdateCompactedRows( - VolumeType.ask, - data, - delta, - resolution, - sellModifiedIndex - ); + const data = cloneDeep(rows as OrderbookRowData[]); + uniqBy(reverse(sell || []), 'price')?.forEach((delta) => { + partiallyUpdateCompactedRows(VolumeType.ask, data, delta, resolution); }); - let buyModifiedIndex = data.length; - uniqBy(buy?.reverse(), 'price')?.forEach((delta) => { - [buyModifiedIndex, data] = partiallyUpdateCompactedRows( - VolumeType.bid, - data, - delta, - resolution, - buyModifiedIndex - ); + uniqBy(reverse(buy || []), 'price')?.forEach((delta) => { + partiallyUpdateCompactedRows(VolumeType.bid, data, delta, resolution); }); - - // update cumulative ask only below highest modified price level - if (sellModifiedIndex !== -1) { - for (let i = Math.min(sellModifiedIndex, data.length - 2); i >= 0; i--) { - data[i] = { - ...data[i], - cumulativeVol: { - ...data[i].cumulativeVol, - ask: data[i + 1].cumulativeVol.ask + data[i].ask, - }, - }; - } - } - // update cumulative bid only above lowest modified price level - if (buyModifiedIndex !== data.length) { - for (let i = Math.max(buyModifiedIndex, 1), l = data.length; i < l; i++) { - data[i] = { - ...data[i], - cumulativeVol: { - ...data[i].cumulativeVol, - bid: data[i - 1].cumulativeVol.bid + data[i].bid, - }, - }; - } - } + updateCumulativeVolume(data); let index = 0; // remove levels that do not have any volume while (index < data.length) {