feat(orderbook): improve data handling performance (#605)
* feat(orderbook): improve data handling performance * feat(orderbook): fix scrolling out of range
This commit is contained in:
parent
f36d3af286
commit
98d3c47808
@ -1,3 +1,4 @@
|
|||||||
|
import produce from 'immer';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
import type {
|
import type {
|
||||||
@ -87,15 +88,16 @@ export const FILTERS_QUERY = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const update = (
|
const update = (
|
||||||
draft: SimpleMarkets_markets[],
|
data: SimpleMarkets_markets[],
|
||||||
delta: SimpleMarketDataSub_marketData
|
delta: SimpleMarketDataSub_marketData
|
||||||
) => {
|
) =>
|
||||||
|
produce(data, (draft) => {
|
||||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
draft[index].data = delta;
|
draft[index].data = delta;
|
||||||
}
|
}
|
||||||
// @TODO - else push new market to draft
|
// @TODO - else push new market to draft
|
||||||
};
|
});
|
||||||
|
|
||||||
const getData = (responseData: SimpleMarkets) => responseData.markets;
|
const getData = (responseData: SimpleMarkets) => responseData.markets;
|
||||||
const getDelta = (
|
const getDelta = (
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import produce from 'immer';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import type {
|
import type {
|
||||||
Accounts,
|
Accounts,
|
||||||
@ -52,9 +53,10 @@ export const getId = (
|
|||||||
) => `${data.type}-${data.asset.symbol}-${data.market?.id ?? 'null'}`;
|
) => `${data.type}-${data.asset.symbol}-${data.market?.id ?? 'null'}`;
|
||||||
|
|
||||||
const update = (
|
const update = (
|
||||||
draft: Accounts_party_accounts[],
|
data: Accounts_party_accounts[],
|
||||||
delta: AccountSubscribe_accounts
|
delta: AccountSubscribe_accounts
|
||||||
) => {
|
) =>
|
||||||
|
produce(data, (draft) => {
|
||||||
const id = getId(delta);
|
const id = getId(delta);
|
||||||
const index = draft.findIndex((a) => getId(a) === id);
|
const index = draft.findIndex((a) => getId(a) === id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
@ -62,7 +64,7 @@ const update = (
|
|||||||
} else {
|
} else {
|
||||||
draft.push(delta);
|
draft.push(delta);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const getData = (responseData: Accounts): Accounts_party_accounts[] | null =>
|
const getData = (responseData: Accounts): Accounts_party_accounts[] | null =>
|
||||||
responseData.party ? responseData.party.accounts : null;
|
responseData.party ? responseData.party.accounts : null;
|
||||||
const getDelta = (
|
const getDelta = (
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { DepthChart } from 'pennant';
|
import { DepthChart } from 'pennant';
|
||||||
import { produce } from 'immer';
|
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
@ -92,28 +91,31 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
|||||||
if (!dataRef.current) {
|
if (!dataRef.current) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
dataRef.current = produce(dataRef.current, (draft) => {
|
dataRef.current = {
|
||||||
if (delta.buy) {
|
...dataRef.current,
|
||||||
draft.data.buy = updateLevels(
|
midPrice: delta.market.data?.staticMidPrice
|
||||||
draft.data.buy,
|
|
||||||
delta.buy,
|
|
||||||
decimalPlacesRef.current
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (delta.sell) {
|
|
||||||
draft.data.sell = updateLevels(
|
|
||||||
draft.data.sell,
|
|
||||||
delta.sell,
|
|
||||||
decimalPlacesRef.current
|
|
||||||
);
|
|
||||||
}
|
|
||||||
draft.midPrice = delta.market.data?.staticMidPrice
|
|
||||||
? formatMidPrice(
|
? formatMidPrice(
|
||||||
delta.market.data?.staticMidPrice,
|
delta.market.data?.staticMidPrice,
|
||||||
decimalPlacesRef.current
|
decimalPlacesRef.current
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined,
|
||||||
});
|
data: {
|
||||||
|
buy: delta.buy
|
||||||
|
? updateLevels(
|
||||||
|
dataRef.current.data.buy,
|
||||||
|
delta.buy,
|
||||||
|
decimalPlacesRef.current
|
||||||
|
)
|
||||||
|
: dataRef.current.data.buy,
|
||||||
|
sell: delta.sell
|
||||||
|
? updateLevels(
|
||||||
|
dataRef.current.data.sell,
|
||||||
|
delta.sell,
|
||||||
|
decimalPlacesRef.current
|
||||||
|
)
|
||||||
|
: dataRef.current.data.sell,
|
||||||
|
},
|
||||||
|
};
|
||||||
setDepthDataThrottledRef.current(dataRef.current);
|
setDepthDataThrottledRef.current(dataRef.current);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -86,27 +86,31 @@ const sequenceNumbers: Record<string, number> = {};
|
|||||||
const update: Update<
|
const update: Update<
|
||||||
MarketDepth_market,
|
MarketDepth_market,
|
||||||
MarketDepthSubscription_marketDepthUpdate
|
MarketDepthSubscription_marketDepthUpdate
|
||||||
> = (draft, delta, reload) => {
|
> = (data, delta, reload) => {
|
||||||
if (delta.market.id !== draft.id) {
|
if (delta.market.id !== data.id) {
|
||||||
return;
|
return data;
|
||||||
}
|
}
|
||||||
const sequenceNumber = Number(delta.sequenceNumber);
|
const sequenceNumber = Number(delta.sequenceNumber);
|
||||||
if (sequenceNumber <= sequenceNumbers[delta.market.id]) {
|
if (sequenceNumber <= sequenceNumbers[delta.market.id]) {
|
||||||
return;
|
return data;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) {
|
if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) {
|
||||||
sequenceNumbers[delta.market.id] = 0;
|
sequenceNumbers[delta.market.id] = 0;
|
||||||
reload();
|
reload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
sequenceNumbers[delta.market.id] = sequenceNumber;
|
sequenceNumbers[delta.market.id] = sequenceNumber;
|
||||||
Object.assign(draft.data, delta.market.data);
|
const updatedData = { ...data };
|
||||||
|
data.data = delta.market.data;
|
||||||
if (delta.buy) {
|
if (delta.buy) {
|
||||||
draft.depth.buy = updateLevels(draft.depth.buy ?? [], delta.buy);
|
updatedData.depth.buy = updateLevels(data.depth.buy ?? [], delta.buy);
|
||||||
}
|
}
|
||||||
if (delta.sell) {
|
if (delta.sell) {
|
||||||
draft.depth.sell = updateLevels(draft.depth.sell ?? [], delta.sell);
|
updatedData.depth.sell = updateLevels(data.depth.sell ?? [], delta.sell);
|
||||||
}
|
}
|
||||||
|
return updatedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = (responseData: MarketDepth) => {
|
const getData = (responseData: MarketDepth) => {
|
||||||
|
@ -55,10 +55,8 @@ describe('compactRows', () => {
|
|||||||
'1097': 3,
|
'1097': 3,
|
||||||
'1098': 2,
|
'1098': 2,
|
||||||
'1099': 1,
|
'1099': 1,
|
||||||
'1100': 0,
|
|
||||||
});
|
});
|
||||||
expect(orderbookRows[orderbookRows.length - 1].bidByLevel).toEqual({
|
expect(orderbookRows[orderbookRows.length - 1].bidByLevel).toEqual({
|
||||||
'901': 0,
|
|
||||||
'902': 1,
|
'902': 1,
|
||||||
'903': 2,
|
'903': 2,
|
||||||
'904': 3,
|
'904': 3,
|
||||||
@ -81,7 +79,7 @@ describe('compactRows', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('updateLevels', () => {
|
describe('updateLevels', () => {
|
||||||
const levels: MarketDepth_market_depth_sell[] = new Array(10)
|
let levels: MarketDepth_market_depth_sell[] = new Array(10)
|
||||||
.fill(null)
|
.fill(null)
|
||||||
.map((n, i) => ({
|
.map((n, i) => ({
|
||||||
__typename: 'PriceLevel',
|
__typename: 'PriceLevel',
|
||||||
@ -96,9 +94,9 @@ describe('updateLevels', () => {
|
|||||||
volume: '0',
|
volume: '0',
|
||||||
numberOfOrders: '0',
|
numberOfOrders: '0',
|
||||||
};
|
};
|
||||||
updateLevels(levels, [removeFirstRow]);
|
levels = updateLevels(levels, [removeFirstRow]);
|
||||||
expect(levels[0].price).toEqual('20');
|
expect(levels[0].price).toEqual('20');
|
||||||
updateLevels(levels, [removeFirstRow]);
|
levels = updateLevels(levels, [removeFirstRow]);
|
||||||
expect(levels[0].price).toEqual('20');
|
expect(levels[0].price).toEqual('20');
|
||||||
expect(updateLevels([], [removeFirstRow])).toEqual([]);
|
expect(updateLevels([], [removeFirstRow])).toEqual([]);
|
||||||
const addFirstRow: MarketDepthSubscription_marketDepthUpdate_sell = {
|
const addFirstRow: MarketDepthSubscription_marketDepthUpdate_sell = {
|
||||||
@ -107,7 +105,7 @@ describe('updateLevels', () => {
|
|||||||
volume: '10',
|
volume: '10',
|
||||||
numberOfOrders: '10',
|
numberOfOrders: '10',
|
||||||
};
|
};
|
||||||
updateLevels(levels, [addFirstRow]);
|
levels = updateLevels(levels, [addFirstRow]);
|
||||||
expect(levels[0].price).toEqual('10');
|
expect(levels[0].price).toEqual('10');
|
||||||
const addBeforeLastRow: MarketDepthSubscription_marketDepthUpdate_sell = {
|
const addBeforeLastRow: MarketDepthSubscription_marketDepthUpdate_sell = {
|
||||||
__typename: 'PriceLevel',
|
__typename: 'PriceLevel',
|
||||||
@ -115,7 +113,7 @@ describe('updateLevels', () => {
|
|||||||
volume: '95',
|
volume: '95',
|
||||||
numberOfOrders: '95',
|
numberOfOrders: '95',
|
||||||
};
|
};
|
||||||
updateLevels(levels, [addBeforeLastRow]);
|
levels = updateLevels(levels, [addBeforeLastRow]);
|
||||||
expect(levels[levels.length - 2].price).toEqual('95');
|
expect(levels[levels.length - 2].price).toEqual('95');
|
||||||
const addAtTheEnd: MarketDepthSubscription_marketDepthUpdate_sell = {
|
const addAtTheEnd: MarketDepthSubscription_marketDepthUpdate_sell = {
|
||||||
__typename: 'PriceLevel',
|
__typename: 'PriceLevel',
|
||||||
@ -123,7 +121,7 @@ describe('updateLevels', () => {
|
|||||||
volume: '115',
|
volume: '115',
|
||||||
numberOfOrders: '115',
|
numberOfOrders: '115',
|
||||||
};
|
};
|
||||||
updateLevels(levels, [addAtTheEnd]);
|
levels = updateLevels(levels, [addAtTheEnd]);
|
||||||
expect(levels[levels.length - 1].price).toEqual('115');
|
expect(levels[levels.length - 1].price).toEqual('115');
|
||||||
const updateLastRow: MarketDepthSubscription_marketDepthUpdate_sell = {
|
const updateLastRow: MarketDepthSubscription_marketDepthUpdate_sell = {
|
||||||
__typename: 'PriceLevel',
|
__typename: 'PriceLevel',
|
||||||
@ -131,7 +129,7 @@ describe('updateLevels', () => {
|
|||||||
volume: '116',
|
volume: '116',
|
||||||
numberOfOrders: '115',
|
numberOfOrders: '115',
|
||||||
};
|
};
|
||||||
updateLevels(levels, [updateLastRow]);
|
levels = updateLevels(levels, [updateLastRow]);
|
||||||
expect(levels[levels.length - 1]).toEqual(updateLastRow);
|
expect(levels[levels.length - 1]).toEqual(updateLastRow);
|
||||||
expect(updateLevels([], [updateLastRow])).toEqual([updateLastRow]);
|
expect(updateLevels([], [updateLastRow])).toEqual([updateLastRow]);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import produce from 'immer';
|
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import { VolumeType } from '@vegaprotocol/react-helpers';
|
import { VolumeType } from '@vegaprotocol/react-helpers';
|
||||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
@ -31,6 +30,8 @@ export interface OrderbookRowData {
|
|||||||
cumulativeVol: CumulativeVol;
|
cumulativeVol: CumulativeVol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartialOrderbookRowData = Pick<OrderbookRowData, 'price' | 'ask' | 'bid'>;
|
||||||
|
|
||||||
export type OrderbookData = Partial<
|
export type OrderbookData = Partial<
|
||||||
Omit<MarketDepth_market_data, '__typename' | 'market'>
|
Omit<MarketDepth_market_data, '__typename' | 'market'>
|
||||||
> & { rows: OrderbookRowData[] | null };
|
> & { rows: OrderbookRowData[] | null };
|
||||||
@ -75,21 +76,31 @@ const updateRelativeData = (data: OrderbookRowData[]) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createPartialRow = (
|
||||||
|
price: string,
|
||||||
|
volume = 0,
|
||||||
|
dataType?: VolumeType
|
||||||
|
): PartialOrderbookRowData => ({
|
||||||
|
price,
|
||||||
|
ask: dataType === VolumeType.ask ? volume : 0,
|
||||||
|
bid: dataType === VolumeType.bid ? volume : 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const extendRow = (row: PartialOrderbookRowData): OrderbookRowData =>
|
||||||
|
Object.assign(row, {
|
||||||
|
cumulativeVol: {
|
||||||
|
ask: 0,
|
||||||
|
bid: 0,
|
||||||
|
},
|
||||||
|
askByLevel: row.ask ? { [row.price]: row.ask } : {},
|
||||||
|
bidByLevel: row.bid ? { [row.price]: row.bid } : {},
|
||||||
|
});
|
||||||
|
|
||||||
export const createRow = (
|
export const createRow = (
|
||||||
price: string,
|
price: string,
|
||||||
volume = 0,
|
volume = 0,
|
||||||
dataType?: VolumeType
|
dataType?: VolumeType
|
||||||
): OrderbookRowData => ({
|
): OrderbookRowData => extendRow(createPartialRow(price, volume, dataType));
|
||||||
price,
|
|
||||||
ask: dataType === VolumeType.ask ? volume : 0,
|
|
||||||
bid: dataType === VolumeType.bid ? volume : 0,
|
|
||||||
cumulativeVol: {
|
|
||||||
ask: dataType === VolumeType.ask ? volume : 0,
|
|
||||||
bid: dataType === VolumeType.bid ? volume : 0,
|
|
||||||
},
|
|
||||||
askByLevel: dataType === VolumeType.ask ? { [price]: volume } : {},
|
|
||||||
bidByLevel: dataType === VolumeType.bid ? { [price]: volume } : {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapRawData =
|
const mapRawData =
|
||||||
(dataType: VolumeType.ask | VolumeType.bid) =>
|
(dataType: VolumeType.ask | VolumeType.bid) =>
|
||||||
@ -99,8 +110,8 @@ const mapRawData =
|
|||||||
| MarketDepthSubscription_marketDepthUpdate_sell
|
| MarketDepthSubscription_marketDepthUpdate_sell
|
||||||
| MarketDepth_market_depth_buy
|
| MarketDepth_market_depth_buy
|
||||||
| MarketDepthSubscription_marketDepthUpdate_buy
|
| MarketDepthSubscription_marketDepthUpdate_buy
|
||||||
): OrderbookRowData =>
|
): PartialOrderbookRowData =>
|
||||||
createRow(data.price, Number(data.volume), dataType);
|
createPartialRow(data.price, Number(data.volume), dataType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary merges sell amd buy data, orders by price desc, group by price level, counts cumulative and relative values
|
* @summary merges sell amd buy data, orders by price desc, group by price level, counts cumulative and relative values
|
||||||
@ -121,37 +132,38 @@ export const compactRows = (
|
|||||||
resolution: number
|
resolution: number
|
||||||
) => {
|
) => {
|
||||||
// map raw sell data to OrderbookData
|
// map raw sell data to OrderbookData
|
||||||
const askOrderbookData = [...(sell ?? [])].map<OrderbookRowData>(
|
const askOrderbookData = [...(sell ?? [])].map<PartialOrderbookRowData>(
|
||||||
mapRawData(VolumeType.ask)
|
mapRawData(VolumeType.ask)
|
||||||
);
|
);
|
||||||
// map raw buy data to OrderbookData
|
// map raw buy data to OrderbookData
|
||||||
const bidOrderbookData = [...(buy ?? [])].map<OrderbookRowData>(
|
const bidOrderbookData = [...(buy ?? [])].map<PartialOrderbookRowData>(
|
||||||
mapRawData(VolumeType.bid)
|
mapRawData(VolumeType.bid)
|
||||||
);
|
);
|
||||||
|
|
||||||
// group by price level
|
// group by price level
|
||||||
const groupedByLevel = groupBy<OrderbookRowData>(
|
const groupedByLevel = groupBy<PartialOrderbookRowData>(
|
||||||
[...askOrderbookData, ...bidOrderbookData],
|
[...askOrderbookData, ...bidOrderbookData],
|
||||||
(row) => getPriceLevel(row.price, resolution)
|
(row) => getPriceLevel(row.price, resolution)
|
||||||
);
|
);
|
||||||
|
const orderbookData: OrderbookRowData[] = [];
|
||||||
// create single OrderbookData from grouped OrderbookData[], sum volumes and atore volume by level
|
Object.keys(groupedByLevel).forEach((price) => {
|
||||||
const orderbookData = Object.keys(groupedByLevel).reduce<OrderbookRowData[]>(
|
const row = extendRow(
|
||||||
(rows, price) =>
|
groupedByLevel[price].pop() as PartialOrderbookRowData
|
||||||
rows.concat(
|
|
||||||
groupedByLevel[price].reduce<OrderbookRowData>(
|
|
||||||
(a, c) => ({
|
|
||||||
...a,
|
|
||||||
ask: a.ask + c.ask,
|
|
||||||
askByLevel: Object.assign(a.askByLevel, c.askByLevel),
|
|
||||||
bid: (a.bid ?? 0) + (c.bid ?? 0),
|
|
||||||
bidByLevel: Object.assign(a.bidByLevel, c.bidByLevel),
|
|
||||||
}),
|
|
||||||
createRow(price)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
row.price = price;
|
||||||
|
let subRow: PartialOrderbookRowData | undefined;
|
||||||
|
// eslint-disable-next-line no-cond-assign
|
||||||
|
while ((subRow = groupedByLevel[price].pop())) {
|
||||||
|
row.ask += subRow.ask;
|
||||||
|
row.bid += subRow.bid;
|
||||||
|
if (subRow.ask) {
|
||||||
|
row.askByLevel[subRow.price] = subRow.ask;
|
||||||
|
}
|
||||||
|
if (subRow.bid) {
|
||||||
|
row.bidByLevel[subRow.price] = subRow.bid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderbookData.push(row);
|
||||||
|
});
|
||||||
// order by price, it's safe to cast to number price diff should not exceed Number.MAX_SAFE_INTEGER
|
// order by price, it's safe to cast to number price diff should not exceed Number.MAX_SAFE_INTEGER
|
||||||
orderbookData.sort((a, b) => Number(BigInt(b.price) - BigInt(a.price)));
|
orderbookData.sort((a, b) => Number(BigInt(b.price) - BigInt(a.price)));
|
||||||
// count cumulative volumes
|
// count cumulative volumes
|
||||||
@ -163,13 +175,11 @@ export const compactRows = (
|
|||||||
(i !== 0 ? orderbookData[i - 1].cumulativeVol.bid : 0);
|
(i !== 0 ? orderbookData[i - 1].cumulativeVol.bid : 0);
|
||||||
}
|
}
|
||||||
for (let i = maxIndex; i >= 0; i--) {
|
for (let i = maxIndex; i >= 0; i--) {
|
||||||
if (!orderbookData[i].cumulativeVol.ask) {
|
|
||||||
orderbookData[i].cumulativeVol.ask =
|
orderbookData[i].cumulativeVol.ask =
|
||||||
orderbookData[i].ask +
|
orderbookData[i].ask +
|
||||||
(i !== maxIndex ? orderbookData[i + 1].cumulativeVol.ask : 0);
|
(i !== maxIndex ? orderbookData[i + 1].cumulativeVol.ask : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// count relative volumes
|
// count relative volumes
|
||||||
updateRelativeData(orderbookData);
|
updateRelativeData(orderbookData);
|
||||||
return orderbookData;
|
return orderbookData;
|
||||||
@ -186,13 +196,13 @@ export const compactRows = (
|
|||||||
*/
|
*/
|
||||||
const partiallyUpdateCompactedRows = (
|
const partiallyUpdateCompactedRows = (
|
||||||
dataType: VolumeType,
|
dataType: VolumeType,
|
||||||
draft: OrderbookRowData[],
|
data: OrderbookRowData[],
|
||||||
delta:
|
delta:
|
||||||
| MarketDepthSubscription_marketDepthUpdate_sell
|
| MarketDepthSubscription_marketDepthUpdate_sell
|
||||||
| MarketDepthSubscription_marketDepthUpdate_buy,
|
| MarketDepthSubscription_marketDepthUpdate_buy,
|
||||||
resolution: number,
|
resolution: number,
|
||||||
modifiedIndex: number
|
modifiedIndex: number
|
||||||
) => {
|
): [number, OrderbookRowData[]] => {
|
||||||
const { price } = delta;
|
const { price } = delta;
|
||||||
const volume = Number(delta.volume);
|
const volume = Number(delta.volume);
|
||||||
const priceLevel = getPriceLevel(price, resolution);
|
const priceLevel = getPriceLevel(price, resolution);
|
||||||
@ -201,28 +211,36 @@ const partiallyUpdateCompactedRows = (
|
|||||||
const oppositeVolKey = isAskDataType ? 'bid' : 'ask';
|
const oppositeVolKey = isAskDataType ? 'bid' : 'ask';
|
||||||
const volByLevelKey = isAskDataType ? 'askByLevel' : 'bidByLevel';
|
const volByLevelKey = isAskDataType ? 'askByLevel' : 'bidByLevel';
|
||||||
const resolveModifiedIndex = isAskDataType ? Math.max : Math.min;
|
const resolveModifiedIndex = isAskDataType ? Math.max : Math.min;
|
||||||
let index = draft.findIndex((data) => data.price === priceLevel);
|
let index = data.findIndex((row) => row.price === priceLevel);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
||||||
draft[index][volKey] =
|
data[index] = {
|
||||||
draft[index][volKey] - (draft[index][volByLevelKey][price] || 0) + volume;
|
...data[index],
|
||||||
draft[index][volByLevelKey][price] = volume;
|
[volKey]:
|
||||||
|
data[index][volKey] - (data[index][volByLevelKey][price] || 0) + volume,
|
||||||
|
[volByLevelKey]: {
|
||||||
|
...data[index][volByLevelKey],
|
||||||
|
[price]: volume,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return [modifiedIndex, [...data]];
|
||||||
} else {
|
} else {
|
||||||
const newData: OrderbookRowData = createRow(priceLevel, volume, dataType);
|
const newData: OrderbookRowData = createRow(priceLevel, volume, dataType);
|
||||||
index = draft.findIndex((data) => BigInt(data.price) < BigInt(priceLevel));
|
index = data.findIndex((row) => BigInt(row.price) < BigInt(priceLevel));
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
draft.splice(index, 0, newData);
|
|
||||||
newData.cumulativeVol[oppositeVolKey] =
|
newData.cumulativeVol[oppositeVolKey] =
|
||||||
draft[index + (isAskDataType ? -1 : 1)]?.cumulativeVol[
|
data[index + (isAskDataType ? 0 : 1)]?.cumulativeVol[oppositeVolKey] ??
|
||||||
oppositeVolKey
|
0;
|
||||||
] ?? 0;
|
|
||||||
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
||||||
|
return [
|
||||||
|
modifiedIndex,
|
||||||
|
[...data.slice(0, index), newData, ...data.slice(index)],
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
draft.push(newData);
|
modifiedIndex = data.length - 1;
|
||||||
modifiedIndex = draft.length - 1;
|
return [modifiedIndex, [...data, newData]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modifiedIndex;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -239,23 +257,23 @@ export const updateCompactedRows = (
|
|||||||
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null,
|
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null,
|
||||||
buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null,
|
buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null,
|
||||||
resolution: number
|
resolution: number
|
||||||
) =>
|
) => {
|
||||||
produce(rows, (draft) => {
|
|
||||||
let sellModifiedIndex = -1;
|
let sellModifiedIndex = -1;
|
||||||
|
let data = [...rows];
|
||||||
sell?.forEach((delta) => {
|
sell?.forEach((delta) => {
|
||||||
sellModifiedIndex = partiallyUpdateCompactedRows(
|
[sellModifiedIndex, data] = partiallyUpdateCompactedRows(
|
||||||
VolumeType.ask,
|
VolumeType.ask,
|
||||||
draft,
|
data,
|
||||||
delta,
|
delta,
|
||||||
resolution,
|
resolution,
|
||||||
sellModifiedIndex
|
sellModifiedIndex
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let buyModifiedIndex = draft.length;
|
let buyModifiedIndex = data.length;
|
||||||
buy?.forEach((delta) => {
|
buy?.forEach((delta) => {
|
||||||
buyModifiedIndex = partiallyUpdateCompactedRows(
|
[buyModifiedIndex, data] = partiallyUpdateCompactedRows(
|
||||||
VolumeType.bid,
|
VolumeType.bid,
|
||||||
draft,
|
data,
|
||||||
delta,
|
delta,
|
||||||
resolution,
|
resolution,
|
||||||
buyModifiedIndex
|
buyModifiedIndex
|
||||||
@ -264,34 +282,41 @@ export const updateCompactedRows = (
|
|||||||
|
|
||||||
// update cummulative ask only below hihgest modified price level
|
// update cummulative ask only below hihgest modified price level
|
||||||
if (sellModifiedIndex !== -1) {
|
if (sellModifiedIndex !== -1) {
|
||||||
for (let i = Math.min(sellModifiedIndex, draft.length - 2); i >= 0; i--) {
|
for (let i = Math.min(sellModifiedIndex, data.length - 2); i >= 0; i--) {
|
||||||
draft[i].cumulativeVol.ask =
|
data[i] = {
|
||||||
draft[i + 1].cumulativeVol.ask + draft[i].ask;
|
...data[i],
|
||||||
|
cumulativeVol: {
|
||||||
|
...data[i].cumulativeVol,
|
||||||
|
ask: data[i + 1].cumulativeVol.ask + data[i].ask,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update cummulative bid only above lowest modified price level
|
// update cummulative bid only above lowest modified price level
|
||||||
if (buyModifiedIndex !== draft.length) {
|
if (buyModifiedIndex !== data.length) {
|
||||||
for (
|
for (let i = Math.max(buyModifiedIndex, 1), l = data.length; i < l; i++) {
|
||||||
let i = Math.max(buyModifiedIndex, 1), l = draft.length;
|
data[i] = {
|
||||||
i < l;
|
...data[i],
|
||||||
i++
|
cumulativeVol: {
|
||||||
) {
|
...data[i].cumulativeVol,
|
||||||
draft[i].cumulativeVol.bid =
|
bid: data[i - 1].cumulativeVol.bid + data[i].bid,
|
||||||
draft[i - 1].cumulativeVol.bid + draft[i].bid;
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let index = 0;
|
let index = 0;
|
||||||
// remove levels that do not have any volume
|
// remove levels that do not have any volume
|
||||||
while (index < draft.length) {
|
while (index < data.length) {
|
||||||
if (!draft[index].ask && !draft[index].bid) {
|
if (!data[index].ask && !data[index].bid) {
|
||||||
draft.splice(index, 1);
|
data.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// count relative volumes
|
// count relative volumes
|
||||||
updateRelativeData(draft);
|
updateRelativeData(data);
|
||||||
});
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const mapMarketData = (
|
export const mapMarketData = (
|
||||||
data:
|
data:
|
||||||
@ -319,23 +344,24 @@ export const mapMarketData = (
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateLevels = (
|
export const updateLevels = (
|
||||||
levels: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[],
|
draft: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[],
|
||||||
updates: (
|
updates: (
|
||||||
| MarketDepthSubscription_marketDepthUpdate_buy
|
| MarketDepthSubscription_marketDepthUpdate_buy
|
||||||
| MarketDepthSubscription_marketDepthUpdate_sell
|
| MarketDepthSubscription_marketDepthUpdate_sell
|
||||||
)[]
|
)[]
|
||||||
) => {
|
) => {
|
||||||
|
const levels = [...draft];
|
||||||
updates.forEach((update) => {
|
updates.forEach((update) => {
|
||||||
let index = levels.findIndex((level) => level.price === update.price);
|
let index = levels.findIndex((level) => level.price === update.price);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
if (update.volume === '0') {
|
if (update.volume === '0') {
|
||||||
levels.splice(index, 1);
|
levels.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
Object.assign(levels[index], update);
|
levels[index] = update;
|
||||||
}
|
}
|
||||||
} else if (update.volume !== '0') {
|
} else if (update.volume !== '0') {
|
||||||
index = levels.findIndex(
|
index = levels.findIndex(
|
||||||
(level) => Number(level.price) > Number(update.price)
|
(level) => BigInt(level.price) > BigInt(update.price)
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
levels.splice(index, 0, update);
|
levels.splice(index, 0, update);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import produce from 'immer';
|
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Orderbook } from './orderbook';
|
import { Orderbook } from './orderbook';
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
@ -25,27 +24,52 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
|
|||||||
rows: null,
|
rows: null,
|
||||||
});
|
});
|
||||||
const dataRef = useRef<OrderbookData>({ rows: null });
|
const dataRef = useRef<OrderbookData>({ rows: null });
|
||||||
const setOrderbookDataThrottled = useRef(throttle(setOrderbookData, 1000));
|
const deltaRef = useRef<MarketDepthSubscription_marketDepthUpdate>();
|
||||||
|
const updateOrderbookData = useRef(
|
||||||
|
throttle(() => {
|
||||||
|
if (!deltaRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dataRef.current = {
|
||||||
|
...deltaRef.current.market.data,
|
||||||
|
...mapMarketData(deltaRef.current.market.data, resolutionRef.current),
|
||||||
|
rows: updateCompactedRows(
|
||||||
|
dataRef.current.rows ?? [],
|
||||||
|
deltaRef.current.sell,
|
||||||
|
deltaRef.current.buy,
|
||||||
|
resolutionRef.current
|
||||||
|
),
|
||||||
|
};
|
||||||
|
deltaRef.current = undefined;
|
||||||
|
setOrderbookData(dataRef.current);
|
||||||
|
}, 1000)
|
||||||
|
);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(delta: MarketDepthSubscription_marketDepthUpdate) => {
|
(delta: MarketDepthSubscription_marketDepthUpdate) => {
|
||||||
if (!dataRef.current.rows) {
|
if (!dataRef.current.rows) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
dataRef.current = produce(dataRef.current, (draft) => {
|
if (deltaRef.current) {
|
||||||
Object.assign(draft, delta.market.data);
|
deltaRef.current.market = delta.market;
|
||||||
draft.rows = updateCompactedRows(
|
if (delta.sell) {
|
||||||
draft.rows ?? [],
|
if (deltaRef.current.sell) {
|
||||||
delta.sell,
|
deltaRef.current.sell.push(...delta.sell);
|
||||||
delta.buy,
|
} else {
|
||||||
resolutionRef.current
|
deltaRef.current.sell = delta.sell;
|
||||||
);
|
}
|
||||||
Object.assign(
|
}
|
||||||
draft,
|
if (delta.buy) {
|
||||||
mapMarketData(delta.market.data, resolutionRef.current)
|
if (deltaRef.current.buy) {
|
||||||
);
|
deltaRef.current.buy.push(...delta.buy);
|
||||||
});
|
} else {
|
||||||
setOrderbookDataThrottled.current(dataRef.current);
|
deltaRef.current.buy = delta.buy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deltaRef.current = delta;
|
||||||
|
}
|
||||||
|
updateOrderbookData.current();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change
|
// using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change
|
||||||
|
@ -176,7 +176,10 @@ export const Orderbook = ({
|
|||||||
// adjust to current rows position
|
// adjust to current rows position
|
||||||
scrollTop +=
|
scrollTop +=
|
||||||
(scrollTopRef.current % rowHeight) - (scrollTop % rowHeight);
|
(scrollTopRef.current % rowHeight) - (scrollTop % rowHeight);
|
||||||
const priceCenterScrollOffset = Math.max(0, Math.min(scrollTop));
|
const priceCenterScrollOffset = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(scrollTop, numberOfRows * rowHeight - viewportHeight)
|
||||||
|
);
|
||||||
if (scrollTopRef.current !== priceCenterScrollOffset) {
|
if (scrollTopRef.current !== priceCenterScrollOffset) {
|
||||||
updateScrollOffset(priceCenterScrollOffset);
|
updateScrollOffset(priceCenterScrollOffset);
|
||||||
scrollTopRef.current = priceCenterScrollOffset;
|
scrollTopRef.current = priceCenterScrollOffset;
|
||||||
@ -184,7 +187,13 @@ export const Orderbook = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[maxPriceLevel, resolution, viewportHeight, updateScrollOffset]
|
[
|
||||||
|
maxPriceLevel,
|
||||||
|
resolution,
|
||||||
|
viewportHeight,
|
||||||
|
numberOfRows,
|
||||||
|
updateScrollOffset,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -199,23 +208,36 @@ export const Orderbook = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
priceInCenter.current = undefined;
|
priceInCenter.current = undefined;
|
||||||
setLockOnMidPrice(true);
|
let midPrice = getPriceLevel(
|
||||||
scrollToPrice(
|
|
||||||
getPriceLevel(
|
|
||||||
BigInt(bestStaticOfferPrice) +
|
BigInt(bestStaticOfferPrice) +
|
||||||
(BigInt(bestStaticBidPrice) - BigInt(bestStaticOfferPrice)) /
|
(BigInt(bestStaticBidPrice) - BigInt(bestStaticOfferPrice)) / BigInt(2),
|
||||||
BigInt(2),
|
|
||||||
resolution
|
resolution
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}, [bestStaticOfferPrice, bestStaticBidPrice, scrollToPrice, resolution]);
|
if (BigInt(midPrice) > BigInt(maxPriceLevel)) {
|
||||||
|
midPrice = maxPriceLevel;
|
||||||
|
} else {
|
||||||
|
const minPriceLevel =
|
||||||
|
BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution));
|
||||||
|
if (BigInt(midPrice) < minPriceLevel) {
|
||||||
|
midPrice = minPriceLevel.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scrollToPrice(midPrice);
|
||||||
|
setLockOnMidPrice(true);
|
||||||
|
}, [
|
||||||
|
bestStaticOfferPrice,
|
||||||
|
bestStaticBidPrice,
|
||||||
|
scrollToPrice,
|
||||||
|
resolution,
|
||||||
|
maxPriceLevel,
|
||||||
|
numberOfRows,
|
||||||
|
]);
|
||||||
|
|
||||||
// adjust scroll position to keep selected price in center
|
// adjust scroll position to keep selected price in center
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (resolutionRef.current !== resolution) {
|
if (resolutionRef.current !== resolution) {
|
||||||
priceInCenter.current = undefined;
|
priceInCenter.current = undefined;
|
||||||
resolutionRef.current = resolution;
|
resolutionRef.current = resolution;
|
||||||
setLockOnMidPrice(true);
|
|
||||||
}
|
}
|
||||||
if (priceInCenter.current) {
|
if (priceInCenter.current) {
|
||||||
scrollToPrice(priceInCenter.current);
|
scrollToPrice(priceInCenter.current);
|
||||||
@ -238,7 +260,6 @@ export const Orderbook = ({
|
|||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderedRows = useMemo(() => {
|
|
||||||
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
|
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
|
||||||
const prependingBufferSize = Math.min(bufferSize, offset);
|
const prependingBufferSize = Math.min(bufferSize, offset);
|
||||||
offset -= prependingBufferSize;
|
offset -= prependingBufferSize;
|
||||||
@ -247,12 +268,11 @@ export const Orderbook = ({
|
|||||||
prependingBufferSize + viewportSize + bufferSize,
|
prependingBufferSize + viewportSize + bufferSize,
|
||||||
numberOfRows - offset
|
numberOfRows - offset
|
||||||
);
|
);
|
||||||
return {
|
const renderedRows = {
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
data: getRowsToRender(rows, resolution, offset, limit),
|
data: getRowsToRender(rows, resolution, offset, limit),
|
||||||
};
|
};
|
||||||
}, [rows, scrollOffset, resolution, viewportHeight, numberOfRows]);
|
|
||||||
|
|
||||||
const paddingTop = renderedRows.offset * rowHeight;
|
const paddingTop = renderedRows.offset * rowHeight;
|
||||||
const paddingBottom =
|
const paddingBottom =
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import produce from 'immer';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import type {
|
import type {
|
||||||
Markets,
|
Markets,
|
||||||
@ -90,13 +91,14 @@ const MARKET_DATA_SUB = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => {
|
const update = (data: Markets_markets[], delta: MarketDataSub_marketData) =>
|
||||||
|
produce(data, (draft) => {
|
||||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
draft[index].data = delta;
|
draft[index].data = delta;
|
||||||
}
|
}
|
||||||
// @TODO - else push new market to draft
|
// @TODO - else push new market to draft
|
||||||
};
|
});
|
||||||
const getData = (responseData: Markets): Markets_markets[] | null =>
|
const getData = (responseData: Markets): Markets_markets[] | null =>
|
||||||
responseData.markets;
|
responseData.markets;
|
||||||
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import produce from 'immer';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
import type { OrderFields } from './__generated__/OrderFields';
|
import type { OrderFields } from './__generated__/OrderFields';
|
||||||
@ -78,7 +79,8 @@ export const prepareIncomingOrders = (delta: OrderFields[]) => {
|
|||||||
return incoming;
|
return incoming;
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (draft: OrderFields[], delta: OrderFields[]) => {
|
const update = (data: OrderFields[], delta: OrderFields[]) =>
|
||||||
|
produce(data, (draft) => {
|
||||||
const incoming = prepareIncomingOrders(delta);
|
const incoming = prepareIncomingOrders(delta);
|
||||||
|
|
||||||
// Add or update incoming orders
|
// Add or update incoming orders
|
||||||
@ -90,7 +92,7 @@ const update = (draft: OrderFields[], delta: OrderFields[]) => {
|
|||||||
draft[index] = order;
|
draft[index] = order;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
const getData = (responseData: Orders): Orders_party_orders[] | null =>
|
const getData = (responseData: Orders): Orders_party_orders[] | null =>
|
||||||
responseData?.party?.orders || null;
|
responseData?.party?.orders || null;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import produce from 'immer';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import type {
|
import type {
|
||||||
Positions,
|
Positions,
|
||||||
@ -75,16 +76,17 @@ export const POSITIONS_SUB = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const update = (
|
const update = (
|
||||||
draft: Positions_party_positions[],
|
data: Positions_party_positions[],
|
||||||
delta: PositionSubscribe_positions
|
delta: PositionSubscribe_positions
|
||||||
) => {
|
) =>
|
||||||
|
produce(data, (draft) => {
|
||||||
const index = draft.findIndex((m) => m.market.id === delta.market.id);
|
const index = draft.findIndex((m) => m.market.id === delta.market.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
draft[index] = delta;
|
draft[index] = delta;
|
||||||
} else {
|
} else {
|
||||||
draft.push(delta);
|
draft.push(delta);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const getData = (responseData: Positions): Positions_party_positions[] | null =>
|
const getData = (responseData: Positions): Positions_party_positions[] | null =>
|
||||||
responseData.party ? responseData.party.positions : null;
|
responseData.party ? responseData.party.positions : null;
|
||||||
const getDelta = (
|
const getDelta = (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { produce } from 'immer';
|
|
||||||
import type { Draft } from 'immer';
|
|
||||||
import type {
|
import type {
|
||||||
ApolloClient,
|
ApolloClient,
|
||||||
DocumentNode,
|
DocumentNode,
|
||||||
@ -34,11 +32,7 @@ export interface Subscribe<Data, Delta> {
|
|||||||
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
||||||
|
|
||||||
export interface Update<Data, Delta> {
|
export interface Update<Data, Delta> {
|
||||||
(
|
(data: Data, delta: Delta, reload: (forceReset?: boolean) => void): Data;
|
||||||
draft: Draft<Data>,
|
|
||||||
delta: Delta,
|
|
||||||
reload: (forceReset?: boolean) => void
|
|
||||||
): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetData<QueryData, Data> {
|
interface GetData<QueryData, Data> {
|
||||||
@ -105,14 +99,12 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
data = getData(res.data);
|
data = getData(res.data);
|
||||||
// if there was some updates received from subscription during initial query loading apply them on just received data
|
// if there was some updates received from subscription during initial query loading apply them on just received data
|
||||||
if (data && updateQueue && updateQueue.length > 0) {
|
if (data && updateQueue && updateQueue.length > 0) {
|
||||||
data = produce(data, (draft) => {
|
|
||||||
while (updateQueue.length) {
|
while (updateQueue.length) {
|
||||||
const delta = updateQueue.shift();
|
const delta = updateQueue.shift();
|
||||||
if (delta) {
|
if (delta) {
|
||||||
update(draft, delta, reload);
|
data = update(data, delta, reload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// if error will occur data provider stops subscription
|
// if error will occur data provider stops subscription
|
||||||
@ -168,9 +160,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
updateQueue.push(delta);
|
updateQueue.push(delta);
|
||||||
} else {
|
} else {
|
||||||
const newData = produce(data, (draft) => {
|
const newData = update(data, delta, reload);
|
||||||
update(draft, delta, reload);
|
|
||||||
});
|
|
||||||
if (newData === data) {
|
if (newData === data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import type { TradeFields } from './__generated__/TradeFields';
|
|||||||
import type { Trades } from './__generated__/Trades';
|
import type { Trades } from './__generated__/Trades';
|
||||||
import type { TradesSub } from './__generated__/TradesSub';
|
import type { TradesSub } from './__generated__/TradesSub';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import produce from 'immer';
|
||||||
|
|
||||||
export const MAX_TRADES = 50;
|
export const MAX_TRADES = 50;
|
||||||
|
|
||||||
@ -52,7 +53,8 @@ export const sortTrades = (trades: TradeFields[]) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (draft: TradeFields[], delta: TradeFields[]) => {
|
const update = (data: TradeFields[], delta: TradeFields[]) =>
|
||||||
|
produce(data, (draft) => {
|
||||||
const incoming = sortTrades(delta);
|
const incoming = sortTrades(delta);
|
||||||
|
|
||||||
// Add new trades to the top
|
// Add new trades to the top
|
||||||
@ -62,7 +64,7 @@ const update = (draft: TradeFields[], delta: TradeFields[]) => {
|
|||||||
if (draft.length > MAX_TRADES) {
|
if (draft.length > MAX_TRADES) {
|
||||||
draft.splice(MAX_TRADES, draft.length - MAX_TRADES);
|
draft.splice(MAX_TRADES, draft.length - MAX_TRADES);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
const getData = (responseData: Trades): TradeFields[] | null =>
|
const getData = (responseData: Trades): TradeFields[] | null =>
|
||||||
responseData.market ? responseData.market.trades : null;
|
responseData.market ? responseData.market.trades : null;
|
||||||
|
Loading…
Reference in New Issue
Block a user