diff --git a/__tests__/orderBookSlice.test.ts b/__tests__/orderBookSlice.test.ts index 8937f758..f1c97db8 100644 --- a/__tests__/orderBookSlice.test.ts +++ b/__tests__/orderBookSlice.test.ts @@ -5,6 +5,11 @@ import { } from "../src/app/redux/orderBookSlice"; const MOCK_SELLS = [ + new OrderbookLine(25, 1, 1, 1, 1, false), + new OrderbookLine(24, 1, 1, 1, 1, false), + new OrderbookLine(23, 1, 1, 1, 1, false), + new OrderbookLine(22, 1, 1, 1, 1, false), + new OrderbookLine(21, 1, 1, 1, 1, false), new OrderbookLine(20, 1, 1, 1, 1, false), new OrderbookLine(19, 1, 1, 1, 1, false), new OrderbookLine(18, 1, 1, 1, 1, false), @@ -12,12 +17,12 @@ const MOCK_SELLS = [ new OrderbookLine(16, 1, 1, 1, 1, false), new OrderbookLine(15, 1, 1, 1, 1, false), new OrderbookLine(14, 1, 1, 1, 1, false), - new OrderbookLine(13, 1, 1, 1, 1, false), - new OrderbookLine(12, 1, 1, 1, 1, false), - new OrderbookLine(11, 1, 1, 1, 1, false), ]; const MOCK_BUYS = [ + new OrderbookLine(13, 1, 1, 1, 1, false), + new OrderbookLine(12, 1, 1, 1, 1, false), + new OrderbookLine(11, 1, 1, 1, 1, false), new OrderbookLine(10, 1, 1, 1, 1, false), new OrderbookLine(9, 1, 1, 1, 1, false), new OrderbookLine(8, 1, 1, 1, 1, false), @@ -31,24 +36,24 @@ const MOCK_BUYS = [ ]; describe("toOrderBookRowProps", () => { - it("returns 8 rows if the input is empty", () => { + it("returns 11 rows if the input is empty", () => { const input: OrderbookLine[] = []; - expect(toOrderBookRowProps(input, "sell").length).toBe(8); + expect(toOrderBookRowProps(input, "sell").length).toBe(11); }); - it("returns 8 rows if the input is smaller", () => { + it("returns 11 rows if the input is smaller", () => { const input: OrderbookLine[] = MOCK_SELLS.slice(0, 5); - expect(toOrderBookRowProps(input, "sell").length).toBe(8); + expect(toOrderBookRowProps(input, "sell").length).toBe(11); }); - it("returns 8 rows if the input is larger", () => { - expect(toOrderBookRowProps(MOCK_SELLS, "sell").length).toBe(8); + it("returns 11 rows if the input is larger", () => { + expect(toOrderBookRowProps(MOCK_SELLS, "sell").length).toBe(11); }); it("drops the correct farther away rows for sells", () => { - const expectedSellPrices = [18, 17, 16, 15, 14, 13, 12, 11]; + const expectedSellPrices = [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14]; const sellRowProps = toOrderBookRowProps(MOCK_SELLS, "sell"); @@ -58,7 +63,7 @@ describe("toOrderBookRowProps", () => { }); it("drops the correct farther away rows for buys", () => { - const expectedBuyPrices = [10, 9, 8, 7, 6, 5, 4, 3]; + const expectedBuyPrices = [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3]; const buyRowProps = toOrderBookRowProps(MOCK_BUYS, "buy"); @@ -68,8 +73,8 @@ describe("toOrderBookRowProps", () => { }); it("calculates correct totals", () => { - const expectedSellTotals = [8, 7, 6, 5, 4, 3, 2, 1]; - const expectedBuyTotals = [1, 2, 3, 4, 5, 6, 7, 8]; + const expectedSellTotals = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + const expectedBuyTotals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; const sellRowProps = toOrderBookRowProps(MOCK_SELLS, "sell"); const buyRowProps = toOrderBookRowProps(MOCK_BUYS, "buy"); diff --git a/src/app/components/OrderBook.tsx b/src/app/components/OrderBook.tsx index f11109ec..0de976e4 100644 --- a/src/app/components/OrderBook.tsx +++ b/src/app/components/OrderBook.tsx @@ -1,9 +1,9 @@ import { CSSProperties } from "react"; -import { useAppSelector } from "../hooks"; -import { OrderBookRowProps } from "../redux/orderBookSlice"; import "../styles/orderbook.css"; import * as utils from "../utils"; +import { OrderBookRowProps, orderBookSlice } from "../redux/orderBookSlice"; +import { useAppDispatch, useAppSelector } from "../hooks"; function OrderBookRow(props: OrderBookRowProps) { const { barColor, orderCount, price, size, total, maxTotal } = props; @@ -104,6 +104,7 @@ function CurrentPriceRow() { } export function OrderBook() { + const dispatch = useAppDispatch(); const token1Symbol = useAppSelector( (state) => state.pairSelector.token1.symbol ); @@ -112,11 +113,25 @@ export function OrderBook() { ); const sells = useAppSelector((state) => state.orderBook.sells); const buys = useAppSelector((state) => state.orderBook.buys); + //const grouping = useAppSelector((state) => state.orderBook.grouping); return (
+
+
Order book
+
+ Grouping + ) => { + const grouping = Number(event.target.value); + dispatch(orderBookSlice.actions.setGrouping(grouping)); + }} + > +
+
-
+
Order
@@ -135,7 +150,8 @@ export function OrderBook() {
({token1Symbol})
- +
+
{sells.map((props, index) => ( ))} diff --git a/src/app/components/order_input/LimitOrderInput.tsx b/src/app/components/order_input/LimitOrderInput.tsx index 8133de0d..da8a95a3 100644 --- a/src/app/components/order_input/LimitOrderInput.tsx +++ b/src/app/components/order_input/LimitOrderInput.tsx @@ -18,9 +18,9 @@ import { AiOutlineInfoCircle } from "react-icons/ai"; import { BottomRightErrorLabel } from "components/BottomRightErrorLabel"; const POST_ONLY_TOOLTIP = - "If your price is very close to the current market price, your limit order might fill immediately, " + - "making you pay taker fees. This option prevents your order from being executed immediately, " + - "guarantees that your order will make it to the order book and you will earn the liquidity provider fees."; + "Select 'POST ONLY' to ensure your order is added to the order book without matching existing orders. " + + "If your order can be partially or completely executed immediately, it will not be created. " + + "This option removes trading fees completely and ensures you receive the maker rebate."; function NonTargetToken() { const { token2, validationToken2, side } = useAppSelector( @@ -246,7 +246,7 @@ export function LimitOrderInput() { POST ONLY
diff --git a/src/app/redux/orderBookSlice.ts b/src/app/redux/orderBookSlice.ts index 2187658b..eec8c3e6 100644 --- a/src/app/redux/orderBookSlice.ts +++ b/src/app/redux/orderBookSlice.ts @@ -20,6 +20,7 @@ export interface OrderBookState { bestBuy: number | null; spread: number | null; spreadPercent: number | null; + grouping: number | null; } const initialState: OrderBookState = { @@ -30,15 +31,14 @@ const initialState: OrderBookState = { bestBuy: null, spread: null, spreadPercent: null, + grouping: 0, }; export function toOrderBookRowProps( adexOrderbookLines: adex.OrderbookLine[], - side: "sell" | "buy" + side: "sell" | "buy", + grouping: number ): OrderBookRowProps[] { - // this will drop the rows that do not fit into 8 buys/sells - // TODO: implement pagination or scrolling - const props: OrderBookRowProps[] = []; let adexRows = [...adexOrderbookLines]; // copy the array so we can mutate it @@ -47,8 +47,39 @@ export function toOrderBookRowProps( adexRows.reverse(); barColor = "hsl(var(--erc))"; } - adexRows = adexRows.slice(0, 8); // Limit to 8 rows - + let groupedArray; + if (grouping > 0) { + const roundToGroup = (num: number) => Math.round(num / grouping) * grouping; + + if (adexRows.length > 0) { + groupedArray = adexRows.reduce((result: adex.OrderbookLine[], item) => { + const key = roundToGroup(item.price); + const foundItem = result.find( + (x: adex.OrderbookLine) => x.price === key + ); // note that we don't specify the type here + let existingItem: adex.OrderbookLine; // declare the variable without assigning it + if (foundItem !== undefined) { + // if the foundItem is not undefined, we can assign it safely + existingItem = foundItem; + existingItem.quantityRemaining += item.quantityRemaining; + existingItem.valueRemaining += item.valueRemaining; + existingItem.noOrders += item.noOrders; + existingItem.orders = existingItem.orders.concat(item.orders); + existingItem.total += item.total; + } else { + // If it's a new price, add it to the result + item.price = key; + result.push({ ...item }); + } + return result; + }, []); + } + } + if (groupedArray != null) { + adexRows = groupedArray.slice(0, 11); //adexRows.slice(0, 11); // Limit to 11 rows + } else { + adexRows = adexRows.slice(0, 11); + } let total = 0; let maxTotal = 0; for (let adexRow of adexRows) { @@ -69,8 +100,8 @@ export function toOrderBookRowProps( props[i].maxTotal = maxTotal; } - // If there are fewer than 8 orders, fill the remaining rows with empty values - while (props.length < 8) { + // If there are fewer than 11 orders, fill the remaining rows with empty values + while (props.length < 11) { props.push({ absentOrders: "\u00A0" }); } @@ -93,15 +124,18 @@ export const orderBookSlice = createSlice({ reducers: { updateAdex(state: OrderBookState, action: PayloadAction) { const adexState = action.payload; + const grouping = state.grouping; + const sells = toOrderBookRowProps( adexState.currentPairOrderbook.sells, - "sell" + "sell", + grouping || 0 ); const buys = toOrderBookRowProps( adexState.currentPairOrderbook.buys, - "buy" + "buy", + grouping || 0 ); - let bestSell = sells[sells.length - 1]?.price || null; let bestBuy = buys[0]?.price || null; @@ -120,6 +154,44 @@ export const orderBookSlice = createSlice({ state.bestSell = bestSell; state.bestBuy = bestBuy; }, + setGrouping(state, action: PayloadAction) { + if (action.payload === null) { + state.grouping = 0; + } else if (action.payload < 0) { + state.grouping = 0; + } else { + state.grouping = action.payload; + } + + const sells = toOrderBookRowProps( + adex.clientState.currentPairOrderbook.sells, + "sell", + state.grouping || 0 + ); + const buys = toOrderBookRowProps( + adex.clientState.currentPairOrderbook.buys, + "buy", + state.grouping || 0 + ); + + let bestSell = sells[sells.length - 1]?.price || null; + let bestBuy = buys[0]?.price || null; + + if (bestBuy !== null && bestSell !== null) { + const spread = bestSell - bestBuy; + if (bestBuy + bestSell !== 0) { + const spreadPercent = ((2 * spread) / (bestBuy + bestSell)) * 100; + state.spreadPercent = spreadPercent; + } + state.spread = spread; + } + + state.sells = sells; + state.buys = buys; + state.lastPrice = adex.clientState.currentPairInfo.lastPrice; + state.bestSell = bestSell; + state.bestBuy = bestBuy; + }, }, }); diff --git a/src/app/redux/orderInputSlice.ts b/src/app/redux/orderInputSlice.ts index f48de435..76dc4862 100644 --- a/src/app/redux/orderInputSlice.ts +++ b/src/app/redux/orderInputSlice.ts @@ -89,7 +89,7 @@ export const initialState: OrderInputState = { token2: initialTokenInput, validationToken2: initialValidationResult, tab: OrderTab.MARKET, - postOnly: true, + postOnly: false, side: adex.OrderSide.BUY, price: 0, slippage: 0.01,