diff --git a/libs/market-depth/src/lib/depth-chart-utils.spec.ts b/libs/market-depth/src/lib/depth-chart-utils.spec.ts new file mode 100644 index 000000000..cccd82600 --- /dev/null +++ b/libs/market-depth/src/lib/depth-chart-utils.spec.ts @@ -0,0 +1,83 @@ +import { parseLevel, updateLevels } from './depth-chart-utils'; + +describe('parseLevel', () => { + it('should parse valid raw price levels', () => { + expect(parseLevel({ price: '132', volume: '4567' })).toEqual({ + price: 132, + volume: 4567, + }); + }); + + it('should parse valid raw price levels with non-zero decimal places', () => { + expect(parseLevel({ price: '132', volume: '4567' }, 2, 4)).toEqual({ + price: 1.32, + volume: 0.4567, + }); + }); +}); + +describe('updateLevels', () => { + it('should update by adding price levels', () => { + const priceLevels = [ + { price: 1.28, volume: 100 }, + { price: 1.35, volume: 200 }, + ]; + + const updates = [ + { price: '132', volume: '200' }, + { price: '131', volume: '150' }, + ]; + + expect(updateLevels(priceLevels, updates, 2, 0)).toEqual([ + { price: 1.28, volume: 100 }, + { price: 1.31, volume: 150 }, + { price: 1.32, volume: 200 }, + { price: 1.35, volume: 200 }, + ]); + }); + + it('should update by updating price levels', () => { + const priceLevels = [ + { price: 1.28, volume: 100 }, + { price: 1.3, volume: 200 }, + { price: 1.35, volume: 300 }, + ]; + const updates = [{ price: '135', volume: '200' }]; + + expect(updateLevels(priceLevels, updates, 2, 0)).toEqual([ + { price: 1.28, volume: 100 }, + { price: 1.3, volume: 200 }, + { price: 1.35, volume: 200 }, + ]); + }); + + it('should update by removing price levels', () => { + const priceLevels = [ + { price: 1.28, volume: 100 }, + { price: 1.3, volume: 200 }, + { price: 1.35, volume: 300 }, + ]; + + const updates = [{ price: '130', volume: '0' }]; + + expect(updateLevels(priceLevels, updates, 2, 0)).toEqual([ + { price: 1.28, volume: 100 }, + { price: 1.35, volume: 300 }, + ]); + }); + + it('should update by adding price levels in reverse order', () => { + const priceLevels = [ + { price: 1.35, volume: 200 }, + { price: 1.28, volume: 100 }, + ]; + + const updates = [{ price: '132', volume: '200' }]; + + expect(updateLevels(priceLevels, updates, 2, 0, true)).toEqual([ + { price: 1.35, volume: 200 }, + { price: 1.32, volume: 200 }, + { price: 1.28, volume: 100 }, + ]); + }); +}); diff --git a/libs/market-depth/src/lib/depth-chart-utils.ts b/libs/market-depth/src/lib/depth-chart-utils.ts new file mode 100644 index 000000000..5d4ded542 --- /dev/null +++ b/libs/market-depth/src/lib/depth-chart-utils.ts @@ -0,0 +1,79 @@ +import { addDecimal } from '@vegaprotocol/react-helpers'; + +interface PriceLevel { + price: number; + volume: number; +} + +interface RawPriceLevel { + price: string; + volume: string; +} + +/** + * Parse price level strings to numbers + * + * @param rawPriceLevel Price level represented by string values + * @param priceDecimalPlaces Number of decimal places used to interpret price string + * @param volumeDecimalPlaces Number of decimal places used to interpret volume string + */ +export const parseLevel = ( + rawPriceLevel: RawPriceLevel, + priceDecimalPlaces = 0, + volumeDecimalPlaces = 0 +): PriceLevel => ({ + price: Number(addDecimal(rawPriceLevel.price, priceDecimalPlaces)), + volume: Number(addDecimal(rawPriceLevel.volume, volumeDecimalPlaces)), +}); + +/** + * Apply price level updates to current price levels. This can involve + * adding, updating, or removing single price levels. + * + * @param levels The current price levels + * @param updates Price level updates + * @param decimalPlaces Number of decimal places used to interpret price string + * @param positionDecimalPlaces Number of decimal places used to interpret volume string + * @param reverse If true the price levels are in descending price order, otherwise they are in ascending price order + */ +export const updateLevels = ( + levels: PriceLevel[], + updates: RawPriceLevel[], + decimalPlaces: number, + positionDecimalPlaces: number, + reverse = false +) => { + updates.forEach((update) => { + const updateLevel = parseLevel( + update, + decimalPlaces, + positionDecimalPlaces + ); + + let index = levels.findIndex( + (level) => + Math.abs(level.price - updateLevel.price) < 10 ** -(decimalPlaces + 1) + ); + + if (index !== -1) { + if (update.volume === '0') { + levels.splice(index, 1); + } else { + Object.assign(levels[index], updateLevel); + } + } else if (update.volume !== '0') { + index = levels.findIndex((level) => + reverse + ? level.price < updateLevel.price + : level.price > updateLevel.price + ); + if (index !== -1) { + levels.splice(index, 0, updateLevel); + } else { + levels.push(updateLevel); + } + } + }); + + return levels; +}; diff --git a/libs/market-depth/src/lib/depth-chart.tsx b/libs/market-depth/src/lib/depth-chart.tsx index e052017b3..8b830b49b 100644 --- a/libs/market-depth/src/lib/depth-chart.tsx +++ b/libs/market-depth/src/lib/depth-chart.tsx @@ -16,74 +16,14 @@ import { useState, useContext, } from 'react'; -import type { - MarketDepthSubscription_marketDepthUpdate_buy, - MarketDepthSubscription_marketDepthUpdate_sell, - MarketDepthSubscription_marketDepthUpdate, -} from './__generated__/MarketDepthSubscription'; -import type { - MarketDepth_market_depth_buy, - MarketDepth_market_depth_sell, -} from './__generated__/MarketDepth'; +import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription'; import type { DepthChartProps } from 'pennant'; +import { parseLevel, updateLevels } from './depth-chart-utils'; interface DepthChartManagerProps { marketId: string; } -interface PriceLevel { - price: number; - volume: number; -} - -const parseLevel = ( - priceLevel: MarketDepth_market_depth_buy | MarketDepth_market_depth_sell, - priceDecimalPlaces = 0, - volumeDecimalPlaces = 0 -): PriceLevel => ({ - price: Number(addDecimal(priceLevel.price, priceDecimalPlaces)), - volume: Number(addDecimal(priceLevel.volume, volumeDecimalPlaces)), -}); - -const updateLevels = ( - levels: PriceLevel[], - updates: ( - | MarketDepthSubscription_marketDepthUpdate_buy - | MarketDepthSubscription_marketDepthUpdate_sell - )[], - decimalPlaces: number, - positionDecimalPlaces: number, - reverse = false -) => { - updates.forEach((update) => { - const updateLevel = parseLevel( - update, - decimalPlaces, - positionDecimalPlaces - ); - let index = levels.findIndex((level) => level.price === updateLevel.price); - if (index !== -1) { - if (update.volume === '0') { - levels.splice(index, 1); - } else { - Object.assign(levels[index], updateLevel); - } - } else if (update.volume !== '0') { - index = levels.findIndex((level) => - reverse - ? level.price < updateLevel.price - : level.price > updateLevel.price - ); - if (index !== -1) { - levels.splice(index, 0, updateLevel); - } else { - levels.push(updateLevel); - } - } - }); - return levels; -}; - const formatMidPrice = (midPrice: string, decimalPlaces: number) => Number(addDecimal(midPrice, decimalPlaces));