Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full orderbook update #162

Merged
merged 12 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions __tests__/orderBookSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ 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),
new OrderbookLine(17, 1, 1, 1, 1, false),
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),
Expand All @@ -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");

Expand All @@ -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");

Expand All @@ -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");
Expand Down
24 changes: 20 additions & 4 deletions src/app/components/OrderBook.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -104,6 +104,7 @@ function CurrentPriceRow() {
}

export function OrderBook() {
const dispatch = useAppDispatch();
const token1Symbol = useAppSelector(
(state) => state.pairSelector.token1.symbol
);
Expand All @@ -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 (
<div className="p-2 text-sx text-primary-content">
<div className="grid grid-cols-2 m-1 text-secondary-content font-bold text-sm uppercase">
<div className="justify-self-start">Order book</div>
<div className="flex justify-end join">
<span className="join-item mr-2">Grouping </span>
<input
className="input-xs w-16 join-item"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const grouping = Number(event.target.value);
dispatch(orderBookSlice.actions.setGrouping(grouping));
}}
></input>
</div>
</div>
<div className="sized-columns">
<div className="sized-columns mx-2 col-span-4 text-sm text-secondary-content">
<div className="sized-columns mx-2 col-span-4 text-sm font-bold text-secondary-content">
<div className="text-start">
Order
<br />
Expand All @@ -135,7 +150,8 @@ export function OrderBook() {
<br />({token1Symbol})
</div>
</div>

</div>
<div className="sized-columns mx-2 col-span-4 text-sm">
{sells.map((props, index) => (
<OrderBookRow key={"sell-" + index} {...props} />
))}
Expand Down
94 changes: 83 additions & 11 deletions src/app/redux/orderBookSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface OrderBookState {
bestBuy: number | null;
spread: number | null;
spreadPercent: number | null;
grouping: number | null;
}

const initialState: OrderBookState = {
Expand All @@ -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

Expand All @@ -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) {
Expand All @@ -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" });
}

Expand All @@ -93,15 +124,18 @@ export const orderBookSlice = createSlice({
reducers: {
updateAdex(state: OrderBookState, action: PayloadAction<adex.StaticState>) {
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;

Expand All @@ -120,6 +154,44 @@ export const orderBookSlice = createSlice({
state.bestSell = bestSell;
state.bestBuy = bestBuy;
},
setGrouping(state, action: PayloadAction<number>) {
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;
},
},
});

Expand Down