From d058d1b6187d8f2832bead6748380ade12109b8a Mon Sep 17 00:00:00 2001 From: bearpong <178402093+bearpong@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:59:20 +0200 Subject: [PATCH] fix: always liq price and borrowing fee for opening positions --- .../components/asset-cards/getAssetCards.tsx | 4 +- .../components/asset-cards/positions.tsx | 4 +- .../app/berpetuals/components/order-chart.tsx | 14 +-- .../components/order-history-header.tsx | 5 +- .../berpetuals/components/order-history.tsx | 44 +++------ .../app/components/close-position-modal.tsx | 4 +- .../components/table-columns/positions.tsx | 12 +-- apps/perp/src/app/components/total-amount.tsx | 3 + .../app/components/update-position-modal.tsx | 4 +- .../src/app/portfolio/components/stats.tsx | 5 +- .../app/portfolio/components/user-assets.tsx | 31 ++---- apps/perp/src/hooks/usePollOpenPositions.ts | 96 ++++++++++++++----- apps/perp/src/types/market.ts | 4 +- apps/perp/src/types/order-history.ts | 4 +- 14 files changed, 134 insertions(+), 100 deletions(-) diff --git a/apps/perp/src/app/berpetuals/components/asset-cards/getAssetCards.tsx b/apps/perp/src/app/berpetuals/components/asset-cards/getAssetCards.tsx index 845e00c71..99a260d7d 100755 --- a/apps/perp/src/app/berpetuals/components/asset-cards/getAssetCards.tsx +++ b/apps/perp/src/app/berpetuals/components/asset-cards/getAssetCards.tsx @@ -4,7 +4,7 @@ import type { IClosedTrade, ILimitOrder, IMarketOrder, - IOpenTradeCalculated, + IOpenTrade, } from "~/types/order-history"; import { getHistoryListItems } from "./history"; import { getLimitListItems } from "./orders"; @@ -18,7 +18,7 @@ export const getAssetCardList = ({ markets, closedTradesItems, }: { - openPositionsItems: IOpenTradeCalculated[]; + openPositionsItems: IOpenTrade[]; openOrderItems: ILimitOrder[]; closedTradesItems: IClosedTrade[]; markets: IMarket[]; diff --git a/apps/perp/src/app/berpetuals/components/asset-cards/positions.tsx b/apps/perp/src/app/berpetuals/components/asset-cards/positions.tsx index ec698de62..8ce1a5327 100755 --- a/apps/perp/src/app/berpetuals/components/asset-cards/positions.tsx +++ b/apps/perp/src/app/berpetuals/components/asset-cards/positions.tsx @@ -9,10 +9,10 @@ import { PositionLiquidationPrice } from "~/app/components/table-columns/positio import { MarketTradePNL } from "~/app/components/market-trade-pnl"; import { UpdatePositionModal } from "~/app/components/update-position-modal"; import { type IMarket } from "~/types/market"; -import type { ICards, IOpenTradeCalculated } from "~/types/order-history"; +import type { ICards, IOpenTrade } from "~/types/order-history"; export const getMarketListItems = ( - marketOrderItems: IOpenTradeCalculated[], + marketOrderItems: IOpenTrade[], markets: IMarket[], ): ICards[] => { const cards = marketOrderItems.map((item) => { diff --git a/apps/perp/src/app/berpetuals/components/order-chart.tsx b/apps/perp/src/app/berpetuals/components/order-chart.tsx index 486355c1f..59cd1797a 100755 --- a/apps/perp/src/app/berpetuals/components/order-chart.tsx +++ b/apps/perp/src/app/berpetuals/components/order-chart.tsx @@ -16,7 +16,7 @@ import { type ResolutionString, } from "~/types/charting-library"; import { type IMarket } from "~/types/market"; -import { ILimitOrder, IOpenTradeCalculated } from "~/types/order-history"; +import { ILimitOrder, IOpenTrade } from "~/types/order-history"; import { type TableStateProps } from "~/types/table"; import { ClosePositionModal } from "../../components/close-position-modal"; import type { ChartProps } from "./TVChartContainer"; @@ -67,16 +67,16 @@ export function OrderChart({ ); const openPositions = useMemo(() => { + if (!openPositionData) { + return []; + } + const positions = generateMarketOrders(openPositionData, markets); return positions.map((position, index) => { return { ...position, - borrowing_fee: - openPositionsLiqFeesData?.at(1)?.at(index)?.toString() ?? "0", - liq_price: - openPositionsLiqFeesData?.at(0)?.at(index)?.toString() ?? "0", }; - }) as IOpenTradeCalculated[]; + }) as IOpenTrade[]; }, [openPositionData, markets, openPositionsLiqFeesData]); const openOrders = useMemo(() => { @@ -85,7 +85,7 @@ export function OrderChart({ const [positionOpenState, setPositionOpenState] = useState(false); const [orderOpenState, setOrderOpenState] = useState(false); - const [position, setPosition] = useState(); + const [position, setPosition] = useState(); const [order, setOrder] = useState(); const defaultWidgetProps: Partial = { diff --git a/apps/perp/src/app/berpetuals/components/order-history-header.tsx b/apps/perp/src/app/berpetuals/components/order-history-header.tsx index e6d8ac749..5b6891ace 100755 --- a/apps/perp/src/app/berpetuals/components/order-history-header.tsx +++ b/apps/perp/src/app/berpetuals/components/order-history-header.tsx @@ -21,7 +21,10 @@ export function OrderHistoryHeader({ markets }: { markets: IMarket[] }) { } = usePollOpenPositions(tableState); const openPositions = useMemo( - () => generateMarketOrders(openPositionsData, markets) as IOpenTrade[], + () => + openPositionsData + ? (generateMarketOrders(openPositionsData, markets) as IOpenTrade[]) + : [], [openPositionsData, markets], ); diff --git a/apps/perp/src/app/berpetuals/components/order-history.tsx b/apps/perp/src/app/berpetuals/components/order-history.tsx index 7ba890b3e..340e084d7 100755 --- a/apps/perp/src/app/berpetuals/components/order-history.tsx +++ b/apps/perp/src/app/berpetuals/components/order-history.tsx @@ -27,7 +27,7 @@ import type { IClosedTrade, ILimitOrder, IMarketOrder, - IOpenTradeCalculated, + IOpenTrade, } from "~/types/order-history"; import { TotalAmount } from "../../components/total-amount"; import { getAssetCardList } from "./asset-cards/getAssetCards"; @@ -41,12 +41,8 @@ export function OrderHistory({ markets: IMarket[]; size: "sm" | "md" | "lg"; }) { - const [updateOpen, setUpdateOpen] = useState( - false, - ); - const [deleteOpen, setDeleteOpen] = useState( - false, - ); + const [updateOpen, setUpdateOpen] = useState(false); + const [deleteOpen, setDeleteOpen] = useState(false); const { tableState, setTableState } = useContext(TableContext); const { isConnected } = useBeraJs(); @@ -98,7 +94,10 @@ export function OrderHistory({ ); const openMarketPositions = useMemo( - () => generateMarketOrders(openPositionData, markets) ?? [], + () => + openPositionData + ? (generateMarketOrders(openPositionData, markets) as IOpenTrade[]) + : [], [markets, openPositionData], ); const openMarketOrders = useMemo( @@ -115,11 +114,11 @@ export function OrderHistory({ ); // edge case for selection when new orders are opened or closed - const prevPositionLength = usePrevious(openPositionData?.length ?? 0); + const prevPositionLength = usePrevious(openPositionData?.result?.length ?? 0); useEffect(() => { if ( tableState.tabType === "positions" && - (openPositionData?.length ?? 0) !== prevPositionLength + (openPositionData?.result?.length ?? 0) !== prevPositionLength ) { setTableState((prev) => ({ ...prev, selection: {} })); } @@ -136,14 +135,9 @@ export function OrderHistory({ // props generation const tableProps = useMemo(() => { - let data: ( - | IOpenTradeCalculated - | ILimitOrder - | IMarketOrder - | IClosedTrade - )[] = []; + let data: (IOpenTrade | ILimitOrder | IMarketOrder | IClosedTrade)[] = []; let columns: - | ColumnDef[] + | ColumnDef[] | ColumnDef[] | ColumnDef[] | ColumnDef[] = []; @@ -154,15 +148,7 @@ export function OrderHistory({ switch (tableState.tabType) { case "positions": - data = openMarketPositions.map((position, index) => { - return { - ...position, - borrowing_fee: - openPositionsLiqFeesData?.at(1)?.at(index)?.toString() ?? "0", - liq_price: - openPositionsLiqFeesData?.at(0)?.at(index)?.toString() ?? "0", - }; - }); + data = openMarketPositions as IOpenTrade[]; columns = markets ? generatePositionColumns(markets, setUpdateOpen, setDeleteOpen) : []; @@ -246,7 +232,7 @@ export function OrderHistory({ const assetCardItems = useMemo(() => { return getAssetCardList({ - openPositionsItems: openMarketPositions as IOpenTradeCalculated[], + openPositionsItems: openMarketPositions as IOpenTrade[], openOrderItems: openMarketOrders as ILimitOrder[], marketOrdersItems: marketOrders as IMarketOrder[], closedTradesItems: closedMarketTrades as IClosedTrade[], @@ -269,12 +255,12 @@ export function OrderHistory({ tabType={tableState.tabType ?? "positions"} /> diff --git a/apps/perp/src/app/components/close-position-modal.tsx b/apps/perp/src/app/components/close-position-modal.tsx index 4efd154d0..e67cc6295 100755 --- a/apps/perp/src/app/components/close-position-modal.tsx +++ b/apps/perp/src/app/components/close-position-modal.tsx @@ -20,7 +20,7 @@ import { TableContext } from "~/context/table-context"; import { usePollMarketOrders } from "~/hooks/usePollMarketOrders"; import { usePollOpenPositions } from "~/hooks/usePollOpenPositions"; import { usePollPrices } from "~/hooks/usePollPrices"; -import type { IOpenTradeCalculated } from "~/types/order-history"; +import type { IOpenTrade } from "~/types/order-history"; import { MarketTradePNL } from "./market-trade-pnl"; export function ClosePositionModal({ @@ -33,7 +33,7 @@ export function ClosePositionModal({ }: { trigger?: any; disabled?: boolean; - openPosition: IOpenTradeCalculated; + openPosition: IOpenTrade; className?: string; controlledOpen?: boolean; onOpenChange?: (state: boolean) => void; diff --git a/apps/perp/src/app/components/table-columns/positions.tsx b/apps/perp/src/app/components/table-columns/positions.tsx index d8d40c09b..8ad78bcbd 100755 --- a/apps/perp/src/app/components/table-columns/positions.tsx +++ b/apps/perp/src/app/components/table-columns/positions.tsx @@ -11,10 +11,10 @@ import { PositionTitle } from "~/app/components/position-title"; import { useCalculateLiqPrice } from "~/hooks/useCalculateLiqPrice"; import { usePollPrices } from "~/hooks/usePollPrices"; import type { IMarket } from "~/types/market"; -import type { IOpenTradeCalculated } from "~/types/order-history"; +import type { IOpenTrade } from "~/types/order-history"; import { MarketTradePNL } from "../market-trade-pnl"; -const MarkPrice = ({ position }: { position: IOpenTradeCalculated }) => { +const MarkPrice = ({ position }: { position: IOpenTrade }) => { const { marketPrices } = usePollPrices(); const price = marketPrices[position?.market?.pair_index ?? ""] ?? "0"; @@ -33,7 +33,7 @@ export const PositionLiquidationPrice = ({ position, className, }: { - position: IOpenTradeCalculated; + position: IOpenTrade; className?: string; }) => { const formattedPrice = formatFromBaseUnit( @@ -62,10 +62,10 @@ export const PositionLiquidationPrice = ({ export const generatePositionColumns = ( markets: IMarket[], - setUpdateOpen: (state: boolean | IOpenTradeCalculated) => void, - setDeleteOpen: (state: boolean | IOpenTradeCalculated) => void, + setUpdateOpen: (state: boolean | IOpenTrade) => void, + setDeleteOpen: (state: boolean | IOpenTrade) => void, ) => { - const positionsColumns: ColumnDef[] = [ + const positionsColumns: ColumnDef[] = [ { header: "Market / Action", cell: ({ row }) => ( diff --git a/apps/perp/src/app/components/total-amount.tsx b/apps/perp/src/app/components/total-amount.tsx index e9f50c97c..0072363a7 100755 --- a/apps/perp/src/app/components/total-amount.tsx +++ b/apps/perp/src/app/components/total-amount.tsx @@ -31,6 +31,9 @@ export function TotalAmount({ const { data: openPositions } = usePollOpenPositions(tableState); const { marketPrices } = usePollPrices(); const unrealizedPnl = useMemo(() => { + if (!openPositions) { + return BigNumber(totalUnrealizedPnl); + } const pnl = calculateUnrealizedPnl(openPositions, marketPrices); return BigNumber(pnl ?? totalUnrealizedPnl); }, [openPositions, marketPrices, totalUnrealizedPnl]); diff --git a/apps/perp/src/app/components/update-position-modal.tsx b/apps/perp/src/app/components/update-position-modal.tsx index b9a6a1c2f..5acf24a36 100755 --- a/apps/perp/src/app/components/update-position-modal.tsx +++ b/apps/perp/src/app/components/update-position-modal.tsx @@ -19,7 +19,7 @@ import { usePriceData, useVaa } from "~/context/price-context"; import { TableContext } from "~/context/table-context"; import { usePollOpenPositions } from "~/hooks/usePollOpenPositions"; import { usePollPrices } from "~/hooks/usePollPrices"; -import type { IOpenTradeCalculated } from "~/types/order-history"; +import type { IOpenTrade } from "~/types/order-history"; import { TPSL } from "../berpetuals/components/tpsl"; import { MarketTradePNL } from "./market-trade-pnl"; @@ -32,7 +32,7 @@ export function UpdatePositionModal({ }: { trigger?: any; disabled?: boolean; - openPosition: IOpenTradeCalculated; + openPosition: IOpenTrade; className?: string; controlledOpen?: boolean; onOpenChange?: (state: boolean) => void; diff --git a/apps/perp/src/app/portfolio/components/stats.tsx b/apps/perp/src/app/portfolio/components/stats.tsx index 85bda023d..b94698bef 100755 --- a/apps/perp/src/app/portfolio/components/stats.tsx +++ b/apps/perp/src/app/portfolio/components/stats.tsx @@ -75,12 +75,13 @@ export default function Stats({ markets }: { markets: IMarket[] }) { onValueChange={(value) => setTabType(value as "Volume" | "PnL")} className="h-full w-full sm:w-fit" > - + {["Volume", "PnL"].map((status) => ( setTabType(status as "Volume" | "PnL")} > {status}{" "} diff --git a/apps/perp/src/app/portfolio/components/user-assets.tsx b/apps/perp/src/app/portfolio/components/user-assets.tsx index 353794bb3..1dfe0c93b 100755 --- a/apps/perp/src/app/portfolio/components/user-assets.tsx +++ b/apps/perp/src/app/portfolio/components/user-assets.tsx @@ -7,8 +7,7 @@ import React, { useMemo, useState, } from "react"; -import { usePollPositionsLiqFeePrices, usePrevious } from "@bera/berajs"; -import type { OpenTrade } from "@bera/proto/src"; +import { usePrevious } from "@bera/berajs"; import { SimpleTable, useAsyncTable } from "@bera/shared-ui"; import { TableState, @@ -27,7 +26,7 @@ import { TableContext } from "~/context/table-context"; import { usePollMarketOrders } from "~/hooks/usePollMarketOrders"; import { usePollOpenPositions } from "~/hooks/usePollOpenPositions"; import type { IMarket } from "~/types/market"; -import type { IOpenTrade, IOpenTradeCalculated } from "~/types/order-history"; +import type { IOpenTrade } from "~/types/order-history"; import { FilterableTableState } from "~/types/table"; import { TotalAmount } from "../../components/total-amount"; @@ -40,30 +39,20 @@ export default function UserOpenPositions({ markets }: { markets: IMarket[] }) { isValidating, refresh: refetchPositions, } = usePollOpenPositions(tableState); - const { data: openPositionsLiqFeesData } = usePollPositionsLiqFeePrices( - data?.result - ? data.result.map((position: OpenTrade) => Number(position.index)) - : [], - ); const { refresh: refetchMarketHistory } = usePollMarketOrders(tableState); - let openPositions = generateMarketOrders(data, markets) as IOpenTrade[]; + let openPositions = data + ? (generateMarketOrders(data, markets) as IOpenTrade[]) + : []; openPositions = openPositions.map((position, index) => { return { ...position, - borrowing_fee: - openPositionsLiqFeesData?.at(1)?.at(index)?.toString() ?? "0", - liq_price: openPositionsLiqFeesData?.at(0)?.at(index)?.toString() ?? "0", }; }); - const [updateOpen, setUpdateOpen] = useState( - false, - ); - const [deleteOpen, setDeleteOpen] = useState( - false, - ); + const [updateOpen, setUpdateOpen] = useState(false); + const [deleteOpen, setDeleteOpen] = useState(false); const prevPositionLength = usePrevious(openPositions?.length ?? 0); useEffect(() => { @@ -140,7 +129,7 @@ export default function UserOpenPositions({ markets }: { markets: IMarket[] }) { ); const table = useAsyncTable({ - data: (openPositions as IOpenTradeCalculated[]) ?? [], + data: (openPositions as IOpenTrade[]) ?? [], columns: markets ? generatePositionColumns(markets, setUpdateOpen, setDeleteOpen) : [], @@ -202,12 +191,12 @@ export default function UserOpenPositions({ markets }: { markets: IMarket[] }) { diff --git a/apps/perp/src/hooks/usePollOpenPositions.ts b/apps/perp/src/hooks/usePollOpenPositions.ts index f78b76d70..445b52a26 100755 --- a/apps/perp/src/hooks/usePollOpenPositions.ts +++ b/apps/perp/src/hooks/usePollOpenPositions.ts @@ -1,11 +1,28 @@ -import { useBeraJs } from "@bera/berajs"; +import { DefaultHookReturnType, useBeraJs } from "@bera/berajs"; import { perpsEndpoint } from "@bera/config"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; +import { usePollPositionsLiqFeePrices } from "@bera/berajs"; import { API_FILTERS, DEFAULT_QUERY, POLLING } from "~/utils/constants"; import type { TableStateProps } from "~/types/table"; +import { OpenTrade, Pagination } from "@bera/proto/src"; +import { OpenPositionData } from "~/types/market"; +import { IOpenTradeWithoutMarket } from "~/types/order-history"; -export const usePollOpenPositions = (props: TableStateProps) => { +export const usePollOpenPositions = ( + props: TableStateProps, +): Omit< + DefaultHookReturnType< + OpenPositionData & { + total: number; + } + >, + "mutate" +> & { + total: number; + pagination?: Pagination; + multiRefresh: () => void; +} => { const { account } = useBeraJs(); const queryString = Object.entries(props.positions ?? {}) @@ -14,25 +31,31 @@ export const usePollOpenPositions = (props: TableStateProps) => { ) .map(([key, value]) => `${key}=${value}`) .join("&") ?? ""; - const QUERY_KEY = ["openPositions", account, queryString]; - const { data, isLoading, isValidating } = useSWR( + const QUERY_KEY = + account && queryString ? ["openPositions", account, queryString] : null; + + const { data, isLoading, isValidating, error, mutate } = useSWR<{ + result: OpenTrade[]; + pagination: Pagination; + total: number; + }>( QUERY_KEY, async () => { - if (account && queryString) { - const res = await fetch( - `${perpsEndpoint}/opentrades/traders/${account}${ - queryString ? `?${queryString}` : "" - }`, - ); - const data = await res.json(); - // TODO: ask backend to provide a total items in the response - const totalRes = await fetch( - `${perpsEndpoint}/opentrades/traders/${account}?${DEFAULT_QUERY}`, - ); - const total = await totalRes.json(); - return { ...data, total: total.pagination.total_items } ?? {}; + if (!account || !queryString) { + throw new Error("No account or query string"); } - return {}; + const res = await fetch( + `${perpsEndpoint}/opentrades/traders/${account}${ + queryString ? `?${queryString}` : "" + }`, + ); + const data = await res.json(); + // TODO: ask backend to provide a total items in the response + const totalRes = await fetch( + `${perpsEndpoint}/opentrades/traders/${account}?${DEFAULT_QUERY}`, + ); + const total = await totalRes.json(); + return { ...data, total: total.pagination.total_items } ?? {}; }, { keepPreviousData: true, @@ -41,14 +64,43 @@ export const usePollOpenPositions = (props: TableStateProps) => { }, ); - const refresh = () => void mutate(QUERY_KEY); + const { + data: openPositionsLiqFeesData, + isLoading: isLoadingLiqFees, + isValidating: isValidatingLiqFees, + } = usePollPositionsLiqFeePrices( + data?.result + ? data.result.map((position: OpenTrade) => Number(position.index)) + : [], + ); + + const refresh = () => void mutate(); return { - data: data ?? { result: [], pagination: {}, total: 0 }, + data: { + result: + (data?.result.map((position, index) => { + return { + ...position, + borrowing_fee: + openPositionsLiqFeesData?.at(1)?.at(index)?.toString() ?? "0", + liq_price: + openPositionsLiqFeesData?.at(0)?.at(index)?.toString() ?? "0", + }; + }) as IOpenTradeWithoutMarket[]) ?? [], + pagination: data?.pagination ?? { + page: 0, + per_page: 0, + total_pages: 0, + total_items: 0, + }, + total: data?.total ?? 0, + }, + error, isLoading, isValidating, total: data?.total ?? 0, - pagination: data?.pagination ?? {}, + pagination: data?.pagination, refresh, multiRefresh: () => { refresh(); diff --git a/apps/perp/src/types/market.ts b/apps/perp/src/types/market.ts index 371b28da2..7bbe69e25 100755 --- a/apps/perp/src/types/market.ts +++ b/apps/perp/src/types/market.ts @@ -3,9 +3,9 @@ import type { Market, MarketOrder, OpenLimitOrder, - OpenTrade, Pagination, } from "@bera/proto/src"; +import { IOpenTradeWithoutMarket } from "./order-history"; export interface IMarket extends Market { imageUri?: string; @@ -15,7 +15,7 @@ export interface IMarket extends Market { dailyNumOfTrades?: number; } export interface OpenPositionData { - result: OpenTrade[]; + result: IOpenTradeWithoutMarket[]; pagination: Pagination; } diff --git a/apps/perp/src/types/order-history.ts b/apps/perp/src/types/order-history.ts index 57e5677aa..e025bda6f 100755 --- a/apps/perp/src/types/order-history.ts +++ b/apps/perp/src/types/order-history.ts @@ -7,11 +7,11 @@ import type { import { type IMarket } from "./market"; -export interface IOpenTrade extends OpenTrade { +export interface IOpenTrade extends IOpenTradeWithoutMarket { market: IMarket; } -export interface IOpenTradeCalculated extends IOpenTrade { +export interface IOpenTradeWithoutMarket extends OpenTrade { liq_price: string; borrowing_fee: string; }