1197 improve depth chart price level data handling (#1198)
* refactor: extract parseLevel and updateLevels into new file * test: add unit tests * Expand unit test
This commit is contained in:
parent
2082d3de0a
commit
036f67a68e
83
libs/market-depth/src/lib/depth-chart-utils.spec.ts
Normal file
83
libs/market-depth/src/lib/depth-chart-utils.spec.ts
Normal file
@ -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 },
|
||||
]);
|
||||
});
|
||||
});
|
79
libs/market-depth/src/lib/depth-chart-utils.ts
Normal file
79
libs/market-depth/src/lib/depth-chart-utils.ts
Normal file
@ -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;
|
||||
};
|
@ -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));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user