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:
John Walley 2022-08-31 09:14:10 +01:00 committed by GitHub
parent 2082d3de0a
commit 036f67a68e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 164 additions and 62 deletions

View 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 },
]);
});
});

View 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;
};

View File

@ -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));