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,
|
useState,
|
||||||
useContext,
|
useContext,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type {
|
import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription';
|
||||||
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 { DepthChartProps } from 'pennant';
|
import type { DepthChartProps } from 'pennant';
|
||||||
|
import { parseLevel, updateLevels } from './depth-chart-utils';
|
||||||
|
|
||||||
interface DepthChartManagerProps {
|
interface DepthChartManagerProps {
|
||||||
marketId: string;
|
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) =>
|
const formatMidPrice = (midPrice: string, decimalPlaces: number) =>
|
||||||
Number(addDecimal(midPrice, decimalPlaces));
|
Number(addDecimal(midPrice, decimalPlaces));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user