mango-ui-v3/components/PerpPositionsTable.tsx

437 lines
16 KiB
TypeScript
Raw Normal View History

2021-08-24 20:15:57 -07:00
import { useCallback, useState } from 'react'
import { useRouter } from 'next/router'
import useMangoStore from '../stores/useMangoStore'
import {
getMarketByPublicKey,
getMarketIndexBySymbol,
PerpMarket,
2021-08-14 13:05:31 -07:00
ZERO_BN,
} from '@blockworks-foundation/mango-client'
2021-07-24 11:12:52 -07:00
import SideBadge from './SideBadge'
2021-09-07 18:52:45 -07:00
import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid'
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
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-22 05:45:10 -07:00
import usePerpPositions from '../hooks/usePerpPositions'
2021-08-24 20:15:57 -07:00
import MarketCloseModal from './MarketCloseModal'
2021-09-07 18:52:45 -07:00
import { ExpandableRow } from './TableElements'
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-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)
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const price = useMangoStore((s) => s.tradeForm.price)
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-09-07 18:52:45 -07:00
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
const { asPath } = useRouter()
2021-06-27 21:44:48 -07:00
2021-08-24 20:15:57 -07:00
const handleCloseWarning = useCallback(() => {
setShowMarketCloseModal(false)
}, [])
const handleSizeClick = (size, side) => {
const step = selectedMarket.minOrderSize
const marketIndex = getMarketIndexBySymbol(
groupConfig,
marketConfig.baseSymbol
)
const priceOrDefault = price
? price
: mangoGroup.getPrice(marketIndex, mangoCache).toNumber()
const roundedSize = Math.round(size / step) * step
const quoteSize = roundedSize * priceOrDefault
2021-08-20 04:51:29 -07:00
setMangoStore((state) => {
state.tradeForm.baseSize = roundedSize
state.tradeForm.quoteSize = quoteSize.toFixed(2)
state.tradeForm.side = side === 'buy' ? 'sell' : 'buy'
2021-08-20 04:51:29 -07:00
})
}
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-09-07 18:52:45 -07:00
!isMobile ? (
<Table>
<thead>
<TrHead>
<Th>Market</Th>
<Th>Side</Th>
<Th>Position Size</Th>
<Th>Notional Size</Th>
<Th>Avg entry Price</Th>
<Th>Break-even Price</Th>
<Th>PnL</Th>
</TrHead>
</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 perpMarket = allMarkets[
perpMarketInfo.perpMarket.toString()
2021-08-22 05:45:10 -07:00
] as PerpMarket
const perpTradeHistory = tradeHistory.filter(
(t) => t.marketName === marketConfig.name
)
let breakEvenPrice
try {
breakEvenPrice = perpAccount.getBreakEvenPrice(
mangoAccount,
perpMarket,
perpTradeHistory
)
} catch (e) {
breakEvenPrice = null
}
const pnl =
breakEvenPrice !== null
? perpMarket.baseLotsToNumber(
perpAccount.basePosition
) *
(mangoGroup
.getPrice(marketIndex, mangoCache)
.toNumber() -
parseFloat(breakEvenPrice))
: null
2021-07-06 21:34:21 -07:00
const basePosition = Math.abs(
perpMarket.baseLotsToNumber(perpAccount.basePosition)
)
2021-08-22 05:45:10 -07:00
return (
2021-09-07 18:52:45 -07:00
<TrBody index={index} key={`${marketIndex}`}>
<Td>
2021-08-22 05:45:10 -07:00
<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>
2021-09-07 18:52:45 -07:00
<Td>
2021-08-22 05:45:10 -07:00
{!perpAccount.basePosition.eq(ZERO_BN) ? (
<SideBadge
side={
perpAccount.basePosition.gt(ZERO_BN)
? 'long'
: 'short'
}
/>
) : (
'-'
)}
</Td>
2021-09-07 18:52:45 -07:00
<Td>
{perpAccount && basePosition > 0 ? (
marketConfig.kind === 'perp' &&
asPath.includes(marketConfig.baseSymbol) ? (
<span
className="cursor-pointer underline hover:no-underline"
onClick={() =>
handleSizeClick(
basePosition,
perpAccount.basePosition.gt(ZERO_BN)
? 'buy'
: 'sell'
2021-08-20 04:51:29 -07:00
)
}
>
{`${basePosition} ${marketConfig.baseSymbol}`}
</span>
) : (
`${basePosition} ${marketConfig.baseSymbol}`
)
2021-08-22 05:45:10 -07:00
) : (
`0 ${marketConfig.baseSymbol}`
)}
</Td>
2021-09-07 18:52:45 -07:00
<Td>
2021-08-22 05:45:10 -07:00
{usdFormatter(
Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
) *
mangoGroup
.getPrice(marketIndex, mangoCache)
.toNumber()
)
)}
2021-09-07 18:52:45 -07:00
</Td>
<Td>
2021-08-22 05:45:10 -07:00
{getAvgEntryPrice(
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
)}
</Td>
2021-09-07 18:52:45 -07:00
<Td>
{getBreakEvenPrice(
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
)}
2021-08-22 05:45:10 -07:00
</Td>
2021-09-07 18:52:45 -07:00
<Td>
{pnl !== null ? (
pnl > 0 ? (
<span className="text-th-green">
{usdFormatter(pnl)}
</span>
) : (
<span className="text-th-red">
{usdFormatter(pnl)}
</span>
)
) : (
'--'
)}
2021-08-22 05:45:10 -07:00
</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-09-07 18:52:45 -07:00
</TrBody>
2021-08-22 05:45:10 -07:00
)
})}
2021-09-07 18:52:45 -07:00
</tbody>
2021-06-27 21:44:48 -07:00
</Table>
2021-09-07 18:52:45 -07:00
) : (
perpPositions.map(({ perpAccount, marketIndex }, index) => {
const perpMarketInfo = mangoGroup.perpMarkets[marketIndex]
const marketConfig = getMarketByPublicKey(
groupConfig,
perpMarketInfo.perpMarket
)
const perpMarket = allMarkets[
perpMarketInfo.perpMarket.toString()
] as PerpMarket
const perpTradeHistory = tradeHistory.filter(
(t) => t.marketName === marketConfig.name
)
let breakEvenPrice
try {
breakEvenPrice = perpAccount.getBreakEvenPrice(
mangoAccount,
perpMarket,
perpTradeHistory
2021-09-07 18:52:45 -07:00
)
} catch (e) {
breakEvenPrice = null
}
2021-09-07 18:52:45 -07:00
const pnl =
breakEvenPrice !== null
? perpMarket.baseLotsToNumber(perpAccount.basePosition) *
(mangoGroup.getPrice(marketIndex, mangoCache).toNumber() -
parseFloat(breakEvenPrice))
: null
return (
<ExpandableRow
buttonTemplate={
<>
2021-09-28 06:15:36 -07:00
<div className="col-span-11 flex items-center justify-between text-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>
<div className="mb-0.5 text-left">
{marketConfig.name}
</div>
<div className="text-th-fgd-3 text-xs">
<span
className={`mr-1
2021-09-07 18:52:45 -07:00
${
perpAccount.basePosition.gt(ZERO_BN)
? 'text-th-green'
: 'text-th-red'
}
`}
>
{perpAccount.basePosition.gt(ZERO_BN)
? 'LONG'
: 'SHORT'}
</span>
{`${
Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
)
) > 0
? Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
2021-09-07 18:52:45 -07:00
)
)
: 0
2021-09-28 06:15:36 -07:00
}`}
2021-09-07 18:52:45 -07:00
</div>
</div>
</div>
2021-09-28 06:15:36 -07:00
{pnl !== null ? (
<span
className={`mr-1.5 ${
pnl > 0 ? 'text-th-green' : 'text-th-red'
}`}
>
{usdFormatter(pnl)}
</span>
) : (
'--'
)}
</div>
</>
}
key={`${index}`}
index={index}
panelTemplate={
<>
2021-09-28 06:15:36 -07:00
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
Ave Entry Price
</div>
{getAvgEntryPrice(
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
)}
</div>
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
Notional Size
2021-09-07 18:52:45 -07:00
</div>
{usdFormatter(
Math.abs(
perpMarket.baseLotsToNumber(
perpAccount.basePosition
) *
mangoGroup
.getPrice(marketIndex, mangoCache)
.toNumber()
)
)}
</div>
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
Break-even Price
2021-09-07 18:52:45 -07:00
</div>
{getBreakEvenPrice(
mangoAccount,
perpAccount,
perpMarket,
perpTradeHistory
)}
</div>
<div className="col-span-1 text-left">
<div className="pb-0.5 text-th-fgd-3 text-xs">
PnL
2021-09-07 18:52:45 -07:00
</div>
{pnl !== null ? (
2021-09-28 06:15:36 -07:00
<span
className={
pnl > 0 ? 'text-th-green' : 'text-th-red'
}
>
{usdFormatter(pnl)}
</span>
) : (
'--'
)}
</div>
</>
}
/>
)
})
2021-09-07 18:52:45 -07:00
)
2021-06-27 21:44:48 -07:00
) : (
<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