Skip to content

Commit

Permalink
fix(orderbook): rows missing/duplicated in transition state (#402)
Browse files Browse the repository at this point in the history
* fix orderbook rows missing/duplicated in transition state

* address feedback + fix aggregation issue
  • Loading branch information
aforaleka authored Apr 4, 2024
1 parent 1937a81 commit 21ec348
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 90 deletions.
2 changes: 1 addition & 1 deletion src/constants/orderbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @description Orderbook display constants
*/
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
export const ORDERBOOK_ANIMATION_DURATION = 400;
export const ORDERBOOK_ANIMATION_DURATION = 100;

/**
* @description Orderbook pixel constants
Expand Down
96 changes: 29 additions & 67 deletions src/hooks/Orderbook/useDrawOrderbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ enum OrderbookRowAnimationType {
NONE,
}

export type Rekt = { x1: number; x2: number; y1: number; y2: number };

export const useDrawOrderbook = ({
data,
histogramRange,
Expand Down Expand Up @@ -96,23 +98,17 @@ export const useDrawOrderbook = ({
gradientMultiplier,
histogramAccentColor,
histogramSide,
idx,
rekt,
}: {
barType: 'depth' | 'size';
ctx: CanvasRenderingContext2D;
depthOrSizeValue: number;
gradientMultiplier: number;
histogramAccentColor: string;
histogramSide: 'left' | 'right';
idx: number;
rekt: Rekt;
}) => {
const { x1, x2, y1, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
const { x1, x2, y1, y2 } = rekt;

// X values
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
Expand Down Expand Up @@ -162,25 +158,19 @@ export const useDrawOrderbook = ({
const drawText = ({
animationType = OrderbookRowAnimationType.NONE,
ctx,
idx,
mine,
price,
size,
rekt,
}: {
animationType?: OrderbookRowAnimationType;
ctx: CanvasRenderingContext2D;
idx: number;
mine?: number;
price?: number;
size?: number;
rekt: Rekt;
}) => {
const { y1 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
const { y1 } = rekt;

const { text: y } = getYForElements({ y: y1, rowHeight });

Expand All @@ -189,7 +179,7 @@ export const useDrawOrderbook = ({

switch (animationType) {
case OrderbookRowAnimationType.REMOVE: {
textColor = theme.textSecondary;
textColor = theme.textTertiary;
break;
}

Expand Down Expand Up @@ -250,6 +240,13 @@ export const useDrawOrderbook = ({
if (!rowToRender) return;
const { depth, mine, price, size } = rowToRender;
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
const rekt = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});

// Depth Bar
if (depth) {
Expand All @@ -260,7 +257,7 @@ export const useDrawOrderbook = ({
gradientMultiplier: 1.3,
histogramAccentColor,
histogramSide,
idx,
rekt,
});
}

Expand All @@ -272,17 +269,17 @@ export const useDrawOrderbook = ({
gradientMultiplier: 5,
histogramAccentColor,
histogramSide,
idx,
rekt,
});

// Size, Price, Mine
drawText({
animationType,
ctx,
idx,
mine,
price,
size,
rekt,
});
};

Expand All @@ -298,59 +295,24 @@ export const useDrawOrderbook = ({
// Animate row removal and update
const mapOfOrderbookPriceLevels =
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
const empty: number[] = [];
const removed: number[] = [];
const updated: number[] = [];

prevData.current.forEach((row, idx) => {
if (!row) {
empty.push(idx);
return;
}
if (!row) return;

let animationType = OrderbookRowAnimationType.NEW;

if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
removed.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.REMOVE,
});
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
animationType: OrderbookRowAnimationType.NONE,
});
} else {
updated.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.NEW,
});
animationType = OrderbookRowAnimationType.REMOVE;
} else if (mapOfOrderbookPriceLevels?.[row.price] === row.size) {
animationType = OrderbookRowAnimationType.NONE;
}

drawOrderbookRow({ ctx, idx, rowToRender: row, animationType });
});

setTimeout(() => {
[...empty, ...removed, ...updated].forEach((idx) => {
const { x1, y1, x2, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});

ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
});
});
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
data.forEach((row, idx) => drawOrderbookRow({ ctx, idx, rowToRender: row }));
}, ORDERBOOK_ANIMATION_DURATION);

prevData.current = data;
Expand Down
18 changes: 9 additions & 9 deletions src/hooks/Orderbook/useOrderbookValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,30 @@ import { useMemo } from 'react';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { shallowEqual, useSelector } from 'react-redux';

import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { OrderbookLine, type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';

import { getSubaccountOpenStatusOrdersBySideAndPrice } from '@/state/accountSelectors';
import { getSubaccountOrderSizeBySideAndPrice } from '@/state/accountSelectors';
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';

import { MustBigNumber } from '@/lib/numbers';

export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);

const openOrdersBySideAndPrice =
useSelector(getSubaccountOpenStatusOrdersBySideAndPrice, shallowEqual) || {};
const subaccountOrderSizeBySideAndPrice =
useSelector(getSubaccountOrderSizeBySideAndPrice, shallowEqual) || {};

return useMemo(() => {
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.asks?.toArray() ?? []
)
.map(
(row, idx: number) =>
(row: OrderbookLine, idx: number) =>
({
key: `ask-${idx}`,
side: 'ask',
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
mine: subaccountOrderSizeBySideAndPrice[OrderSide.SELL]?.[row.price],
...row,
} as PerpetualMarketOrderbookLevel)
)
Expand All @@ -36,11 +36,11 @@ export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide:
orderbook?.bids?.toArray() ?? []
)
.map(
(row, idx: number) =>
(row: OrderbookLine, idx: number) =>
({
key: `bid-${idx}`,
side: 'bid',
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
mine: subaccountOrderSizeBySideAndPrice[OrderSide.BUY]?.[row.price],
...row,
} as PerpetualMarketOrderbookLevel)
)
Expand Down Expand Up @@ -87,7 +87,7 @@ export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide:
histogramRange,
hasOrderbook: !!orderbook,
};
}, [orderbook, openOrdersBySideAndPrice]);
}, [orderbook, subaccountOrderSizeBySideAndPrice]);
};

export const useOrderbookValuesForDepthChart = () => {
Expand Down
16 changes: 10 additions & 6 deletions src/state/accountSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,20 @@ export const getSubaccountOpenStatusOrders = createSelector([getSubaccountOrders
orders?.filter((order) => order.status === AbacusOrderStatus.open)
);

export const getSubaccountOpenStatusOrdersBySideAndPrice = createSelector(
export const getSubaccountOrderSizeBySideAndPrice = createSelector(
[getSubaccountOpenStatusOrders],
(openOrders = []) => {
const ordersBySide: Partial<Record<OrderSide, Record<number, SubaccountOrder>>> = {};
openOrders.forEach((order) => {
const orderSizeBySideAndPrice: Partial<Record<OrderSide, Record<number, number>>> = {};
openOrders.forEach((order: SubaccountOrder) => {
const side = ORDER_SIDES[order.side.name];
const byPrice = (ordersBySide[side] ??= {});
byPrice[order.price] = order;
const byPrice = (orderSizeBySideAndPrice[side] ??= {});
if (byPrice[order.price]) {
byPrice[order.price] += order.size;
} else {
byPrice[order.price] = order.size;
}
});
return ordersBySide;
return orderSizeBySideAndPrice;
}
);

Expand Down
13 changes: 6 additions & 7 deletions src/views/tables/Orderbook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { type CustomRowConfig, TableRow } from '@/components/Table';
import { WithTooltip } from '@/components/WithTooltip';

import { calculateCanViewAccount } from '@/state/accountCalculators';
import { getSubaccountOpenStatusOrdersBySideAndPrice } from '@/state/accountSelectors';
import { getSubaccountOrderSizeBySideAndPrice } from '@/state/accountSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { setTradeFormInputs } from '@/state/inputs';
import { getCurrentInput } from '@/state/inputsSelectors';
Expand Down Expand Up @@ -50,8 +50,8 @@ type RowData = Pick<OrderbookLine, 'depth' | 'offset' | 'price' | 'size'> & {
const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);

const openOrdersBySideAndPrice =
useSelector(getSubaccountOpenStatusOrdersBySideAndPrice, shallowEqual) || {};
const subaccountOrderSizeBySideAndPrice =
useSelector(getSubaccountOrderSizeBySideAndPrice, shallowEqual) || {};

return useMemo(() => {
const asks = (orderbook?.asks?.toArray() ?? [])
Expand All @@ -60,8 +60,7 @@ const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number
({
key: `ask-${idx}`,
side: 'ask',
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
...row,
mine: subaccountOrderSizeBySideAndPrice[OrderSide.SELL]?.[row.price],
} as RowData)
)
.slice(0, maxRowsPerSide);
Expand All @@ -72,7 +71,7 @@ const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number
({
key: `bid-${idx}`,
side: 'bid',
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
mine: subaccountOrderSizeBySideAndPrice[OrderSide.BUY]?.[row.price],
...row,
} as RowData)
)
Expand Down Expand Up @@ -141,7 +140,7 @@ const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number
}

return { asks, bids, spread, spreadPercent, histogramRange, hasOrderbook: !!orderbook };
}, [orderbook, openOrdersBySideAndPrice]);
}, [orderbook, subaccountOrderSizeBySideAndPrice]);
};

const OrderbookTable = ({
Expand Down

0 comments on commit 21ec348

Please sign in to comment.