mango-ui-v3/components/PerpPositionsTable.tsx

345 lines
13 KiB
TypeScript
Raw Normal View History

2021-08-24 20:15:57 -07:00
import { useCallback, useState } from 'react'
import useMangoStore, { mangoClient } from '../stores/useMangoStore'
2021-06-27 21:44:48 -07:00
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
import {
getMarketByPublicKey,
nativeI80F48ToUi,
2021-07-24 11:12:52 -07:00
PerpAccount,
PerpMarket,
2021-08-03 16:32:24 -07:00
QUOTE_INDEX,
2021-08-14 13:05:31 -07:00
ZERO_BN,
} from '@blockworks-foundation/mango-client'
2021-07-24 11:12:52 -07:00
import Button from './Button'
import { notify } from '../utils/notifications'
import SideBadge from './SideBadge'
import Loading from './Loading'
2021-08-15 06:31:59 -07:00
import { formatUsdValue, usdFormatter } from '../utils'
2021-08-14 11:16:15 -07:00
import useTradeHistory from '../hooks/useTradeHistory'
2021-08-16 14:35:16 -07:00
import Tooltip from './Tooltip'
import { SettlePnlTooltip } from './MarketPosition'
2021-08-22 05:45:10 -07:00
import usePerpPositions from '../hooks/usePerpPositions'
2021-08-24 20:15:57 -07:00
import MarketCloseModal from './MarketCloseModal'
2021-08-14 11:16:15 -07:00
2021-08-14 13:05:31 -07:00
export function getAvgEntryPrice(
2021-08-14 11:16:15 -07:00
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
) {
let avgEntryPrice = '--'
if (perpTradeHistory.length) {
try {
2021-08-15 06:31:59 -07:00
avgEntryPrice = formatUsdValue(
perpAccount.getAverageOpenPrice(
mangoAccount,
perpMarket,
perpTradeHistory
)
)
2021-08-14 11:16:15 -07:00
} catch {
avgEntryPrice = '--'
}
}
return avgEntryPrice
}
2021-08-14 13:05:31 -07:00
export function getBreakEvenPrice(
2021-08-14 11:16:15 -07:00
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
) {
let breakEvenPrice = '--'
if (perpTradeHistory.length) {
try {
2021-08-15 06:31:59 -07:00
breakEvenPrice = formatUsdValue(
perpAccount.getBreakEvenPrice(
mangoAccount,
perpMarket,
perpTradeHistory
)
)
2021-08-14 11:16:15 -07:00
} catch {
breakEvenPrice = '--'
}
}
return breakEvenPrice
}
2021-06-27 21:44:48 -07:00
const PositionsTable = () => {
2021-07-24 11:12:52 -07:00
const actions = useMangoStore((s) => s.actions)
2021-07-06 21:34:21 -07:00
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
const allMarkets = useMangoStore((s) => s.selectedMangoGroup.markets)
2021-07-24 11:12:52 -07:00
const [settlingPerpAcc, setSettlingPerpAcc] = useState(null)
2021-08-24 20:15:57 -07:00
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
2021-08-14 11:16:15 -07:00
const tradeHistory = useTradeHistory()
2021-08-20 04:51:29 -07:00
const setMangoStore = useMangoStore((s) => s.set)
2021-08-22 05:45:10 -07:00
const perpPositions = usePerpPositions()
2021-06-27 21:44:48 -07:00
2021-08-24 20:15:57 -07:00
const handleCloseWarning = useCallback(() => {
setShowMarketCloseModal(false)
}, [])
2021-07-24 11:12:52 -07:00
const handleSettlePnl = async (
perpMarket: PerpMarket,
perpAccount: PerpAccount
) => {
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
const wallet = useMangoStore.getState().wallet.current
const marketIndex = mangoGroup.getPerpMarketIndex(perpMarket.publicKey)
setSettlingPerpAcc(perpAccount)
try {
const txid = await mangoClient.settlePnl(
mangoGroup,
mangoCache,
2021-07-24 11:12:52 -07:00
mangoAccount,
perpMarket,
mangoGroup.rootBankAccounts[QUOTE_INDEX],
mangoCache.priceCache[marketIndex].price,
wallet
)
actions.fetchMangoAccounts()
notify({
title: 'Successfully settled PNL',
description: '',
txid,
})
} catch (e) {
console.log('Error settling PNL: ', `${e}`, `${perpAccount}`)
notify({
title: 'Error settling PNL',
description: e.message,
txid: e.txid,
type: 'error',
})
} finally {
setSettlingPerpAcc(null)
}
}
2021-06-27 21:44:48 -07:00
2021-08-20 04:51:29 -07:00
const handleSizeClick = (size) => {
setMangoStore((state) => {
state.tradeForm.baseSize = size
})
}
2021-06-27 21:44:48 -07:00
return (
2021-08-23 07:14:03 -07:00
<div className="flex flex-col pb-2 pt-4">
2021-07-24 11:12:52 -07:00
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="align-middle inline-block min-w-full sm:px-6 lg:px-8">
2021-08-22 05:45:10 -07:00
{perpPositions.length ? (
2021-07-24 11:12:52 -07:00
<div className="overflow-hidden border-b border-th-bkg-2 sm:rounded-m">
<Table className="min-w-full divide-y divide-th-bkg-2">
2021-06-27 21:44:48 -07:00
<Thead>
2021-07-29 06:19:32 -07:00
<Tr className="text-th-fgd-3 text-xs">
2021-07-24 11:12:52 -07:00
<Th scope="col" className="px-6 py-2 text-left font-normal">
2021-07-29 06:19:32 -07:00
Market
2021-06-27 21:44:48 -07:00
</Th>
2021-07-24 11:12:52 -07:00
<Th scope="col" className="px-2 py-2 text-left font-normal">
Side
</Th>
<Th scope="col" className="px-2 py-2 text-left font-normal">
2021-08-14 13:05:31 -07:00
Position size
2021-08-14 11:16:15 -07:00
</Th>
<Th scope="col" className="px-2 py-2 text-left font-normal">
2021-08-14 13:05:31 -07:00
Notional size
2021-06-27 21:44:48 -07:00
</Th>
2021-07-24 11:12:52 -07:00
<Th scope="col" className="px-2 py-2 text-left font-normal">
2021-08-14 11:16:15 -07:00
Avg entry price
2021-06-27 21:44:48 -07:00
</Th>
2021-07-24 11:12:52 -07:00
<Th scope="col" className="px-2 py-2 text-left font-normal">
2021-08-14 11:16:15 -07:00
Break-even price
</Th>
<Th scope="col" className="px-2 py-2 text-left font-normal">
2021-08-16 14:35:16 -07:00
<Tooltip content={<SettlePnlTooltip />}>
<Tooltip.Content>Unsettled PnL</Tooltip.Content>
</Tooltip>
2021-06-27 21:44:48 -07:00
</Th>
2021-07-24 11:12:52 -07:00
<Th scope="col" className={`relative px-6 py-2.5`}>
<span className={`sr-only`}>Edit</span>
</Th>
2021-06-27 21:44:48 -07:00
</Tr>
</Thead>
<Tbody>
2021-08-22 05:45:10 -07:00
{perpPositions.map(({ perpAccount, marketIndex }, index) => {
const perpMarketInfo = mangoGroup.perpMarkets[marketIndex]
const marketConfig = getMarketByPublicKey(
groupConfig,
perpMarketInfo.perpMarket
)
const perpMarketCache =
mangoCache.perpMarketCache[marketIndex]
const price = mangoCache.priceCache[marketIndex].price
const perpMarket = allMarkets[
perpMarketInfo.perpMarket.toString()
2021-08-22 05:45:10 -07:00
] as PerpMarket
const perpTradeHistory = tradeHistory.filter(
(t) => t.marketName === marketConfig.name
)
2021-07-06 21:34:21 -07:00
2021-08-22 05:45:10 -07:00
return (
<Tr
key={`${marketIndex}`}
className={`border-b border-th-bkg-3
2021-07-24 13:15:27 -07:00
${index % 2 === 0 ? `bg-th-bkg-3` : `bg-th-bkg-2`}
2021-06-27 21:44:48 -07:00
`}
2021-08-22 05:45:10 -07:00
>
<Td className="px-6 py-2 whitespace-nowrap text-sm text-th-fgd-1">
<div className="flex items-center">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
<div>{marketConfig.name}</div>
</div>
</Td>
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
{!perpAccount.basePosition.eq(ZERO_BN) ? (
<SideBadge
side={
perpAccount.basePosition.gt(ZERO_BN)
? 'long'
: 'short'
}
/>
) : (
'-'
)}
</Td>
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
{perpAccount &&
Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
)
) > 0 ? (
<span
className="cursor-pointer underline hover:no-underline"
onClick={() =>
handleSizeClick(
Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
2021-08-20 04:51:29 -07:00
)
)
2021-08-22 05:45:10 -07:00
)
}
>
{`${Math.abs(
2021-08-19 17:14:04 -07:00
perpMarket.baseLotsToNumber(
perpAccount.basePosition
2021-08-22 05:45:10 -07:00
)
)} ${marketConfig.baseSymbol}`}
</span>
) : (
`0 ${marketConfig.baseSymbol}`
)}
</Td>
<Th
scope="col"
className="px-2 py-2 text-left font-normal"
>
{usdFormatter(
Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
) *
mangoGroup
.getPrice(marketIndex, mangoCache)
.toNumber()
)
)}
</Th>
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
{getAvgEntryPrice(
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
)}
</Td>
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
{getBreakEvenPrice(
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
)}
</Td>
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
{usdFormatter(
+nativeI80F48ToUi(
perpAccount.getPnl(
perpMarketInfo,
perpMarketCache,
price
),
marketConfig.quoteDecimals
),
marketConfig.quoteDecimals
2021-08-22 05:45:10 -07:00
)}
</Td>
<Td className="px-2 py-2 whitespace-nowrap text-sm text-th-fgd-1">
<div className="flex justify-end">
2021-08-24 20:15:57 -07:00
{Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
)
) > 0 ? (
<Button
onClick={() => setShowMarketCloseModal(true)}
className="ml-3 text-xs pt-0 pb-0 h-8 pl-3 pr-3"
>
<span>Market Close</span>
</Button>
) : null}
2021-08-22 05:45:10 -07:00
<Button
onClick={() =>
handleSettlePnl(perpMarket, perpAccount)
}
className="ml-3 text-xs pt-0 pb-0 h-8 pl-3 pr-3"
>
{settlingPerpAcc == perpAccount ? (
<Loading />
) : (
<span>Settle PNL</span>
)}
</Button>
</div>
</Td>
2021-08-24 20:15:57 -07:00
{showMarketCloseModal ? (
<MarketCloseModal
isOpen={showMarketCloseModal}
onClose={handleCloseWarning}
market={perpMarket}
2021-08-25 11:30:16 -07:00
marketIndex={marketIndex}
2021-08-24 20:15:57 -07:00
/>
) : null}
2021-08-22 05:45:10 -07:00
</Tr>
)
})}
2021-06-27 21:44:48 -07:00
</Tbody>
</Table>
</div>
) : (
<div
className={`w-full text-center py-6 bg-th-bkg-1 text-th-fgd-3 rounded-md`}
>
2021-08-02 05:44:58 -07:00
No perp positions
2021-06-27 21:44:48 -07:00
</div>
)}
</div>
</div>
</div>
)
}
export default PositionsTable