2021-08-24 20:15:57 -07:00
|
|
|
import { useCallback, useState } from 'react'
|
2021-10-02 19:01:06 -07:00
|
|
|
import { useRouter } from 'next/router'
|
2021-10-18 03:34:52 -07:00
|
|
|
import Link from 'next/link'
|
2021-08-29 23:17:36 -07:00
|
|
|
import useMangoStore from '../stores/useMangoStore'
|
2021-12-06 09:50:15 -08:00
|
|
|
import { ExclamationIcon } from '@heroicons/react/outline'
|
|
|
|
import Button from '../components/Button'
|
2021-09-07 18:52:45 -07:00
|
|
|
import { useViewport } from '../hooks/useViewport'
|
|
|
|
import { breakpoints } from './TradePageGrid'
|
2021-12-25 09:23:04 -08:00
|
|
|
import { ExpandableRow, Table, Td, Th, TrBody, TrHead } from './TableElements'
|
2021-10-05 13:22:47 -07:00
|
|
|
import { formatUsdValue } from '../utils'
|
2021-12-06 09:50:15 -08:00
|
|
|
import Loading from './Loading'
|
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-10-05 13:22:47 -07:00
|
|
|
import PerpSideBadge from './PerpSideBadge'
|
|
|
|
import PnlText from './PnlText'
|
2021-12-06 09:50:15 -08:00
|
|
|
import { settlePnl } from './MarketPosition'
|
2021-10-20 05:42:40 -07:00
|
|
|
import { useTranslation } from 'next-i18next'
|
2021-11-03 04:28:58 -07:00
|
|
|
import MobileTableHeader from './mobile/MobileTableHeader'
|
2021-06-27 21:44:48 -07:00
|
|
|
|
|
|
|
const PositionsTable = () => {
|
2021-10-20 05:42:40 -07:00
|
|
|
const { t } = useTranslation('common')
|
2021-12-06 09:50:15 -08:00
|
|
|
const { reloadMangoAccount } = useMangoStore((s) => s.actions)
|
|
|
|
const [settling, setSettling] = useState(false)
|
2021-10-05 13:22:47 -07:00
|
|
|
|
2021-12-25 09:23:04 -08:00
|
|
|
const mangoClient = useMangoStore((s) => s.connection.client)
|
|
|
|
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
|
|
|
|
2021-10-02 19:01:06 -07:00
|
|
|
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
2021-10-05 13:22:47 -07:00
|
|
|
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
|
2021-10-02 19:01:06 -07:00
|
|
|
const price = useMangoStore((s) => s.tradeForm.price)
|
2021-08-24 20:15:57 -07:00
|
|
|
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
|
2021-08-20 04:51:29 -07:00
|
|
|
const setMangoStore = useMangoStore((s) => s.set)
|
2021-12-06 09:50:15 -08:00
|
|
|
const { openPositions, unsettledPositions } = usePerpPositions()
|
2021-09-07 18:52:45 -07:00
|
|
|
const { width } = useViewport()
|
|
|
|
const isMobile = width ? width < breakpoints.md : false
|
2021-10-02 19:01:06 -07:00
|
|
|
const { asPath } = useRouter()
|
2021-06-27 21:44:48 -07:00
|
|
|
|
2021-08-24 20:15:57 -07:00
|
|
|
const handleCloseWarning = useCallback(() => {
|
|
|
|
setShowMarketCloseModal(false)
|
|
|
|
}, [])
|
|
|
|
|
2021-10-05 13:22:47 -07:00
|
|
|
const handleSizeClick = (size, side, indexPrice) => {
|
2021-10-02 19:01:06 -07:00
|
|
|
const step = selectedMarket.minOrderSize
|
2021-10-05 13:22:47 -07:00
|
|
|
const priceOrDefault = price ? price : indexPrice
|
2021-10-02 19:01:06 -07:00
|
|
|
const roundedSize = Math.round(size / step) * step
|
|
|
|
const quoteSize = roundedSize * priceOrDefault
|
2021-08-20 04:51:29 -07:00
|
|
|
setMangoStore((state) => {
|
2021-10-02 19:01:06 -07:00
|
|
|
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-12-06 09:50:15 -08:00
|
|
|
const handleSettleAll = async () => {
|
|
|
|
setSettling(true)
|
2021-12-25 09:23:04 -08:00
|
|
|
const mangoAccounts = await mangoClient.getAllMangoAccounts(mangoGroup)
|
2021-12-29 11:32:41 -08:00
|
|
|
for (const p of unsettledPositions) {
|
|
|
|
await settlePnl(p.perpMarket, p.perpAccount, t, mangoAccounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
reloadMangoAccount()
|
2021-12-06 09:50:15 -08:00
|
|
|
setSettling(false)
|
|
|
|
}
|
2021-10-05 13:22:47 -07:00
|
|
|
|
2021-06-27 21:44:48 -07:00
|
|
|
return (
|
2021-12-07 20:00:02 -08:00
|
|
|
<div className="flex flex-col pb-2">
|
2021-12-06 09:50:15 -08:00
|
|
|
{unsettledPositions.length > 0 ? (
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="border border-th-bkg-4 rounded-lg mb-6 p-4 sm:p-6">
|
2021-11-30 18:46:23 -08:00
|
|
|
<div className="flex items-center justify-between">
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex items-center sm:text-lg">
|
|
|
|
<ExclamationIcon className="flex-shrink-0 h-5 mr-1.5 mt-0.5 text-th-primary w-5" />
|
2021-10-20 05:42:40 -07:00
|
|
|
{t('unsettled-positions')}
|
2021-10-05 13:22:47 -07:00
|
|
|
</div>
|
|
|
|
<Button
|
|
|
|
className="text-xs pt-0 pb-0 h-8 pl-3 pr-3 whitespace-nowrap"
|
|
|
|
onClick={handleSettleAll}
|
|
|
|
>
|
2021-10-20 05:42:40 -07:00
|
|
|
{settling ? <Loading /> : t('settle-all')}
|
2021-10-05 13:22:47 -07:00
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{unsettledPositions.map((p) => {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className="border-b border-th-bkg-4 flex items-center justify-between py-4 last:border-b-0 last:pb-0"
|
|
|
|
key={p.marketConfig.baseSymbol}
|
|
|
|
>
|
|
|
|
<div className="flex items-center">
|
|
|
|
<img
|
|
|
|
alt=""
|
|
|
|
width="20"
|
|
|
|
height="20"
|
|
|
|
src={`/assets/icons/${p.marketConfig.baseSymbol.toLowerCase()}.svg`}
|
|
|
|
className={`mr-2.5`}
|
|
|
|
/>
|
|
|
|
<div>{p.marketConfig.name}</div>
|
|
|
|
</div>
|
|
|
|
<PnlText pnl={p.unsettledPnl} />
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</div>
|
2021-12-06 09:50:15 -08:00
|
|
|
) : null}
|
2021-12-07 20:00:02 -08:00
|
|
|
<div className={`md:overflow-x-auto`}>
|
2021-11-09 01:24:43 -08:00
|
|
|
<div className={`align-middle inline-block min-w-full`}>
|
2021-10-05 13:22:47 -07:00
|
|
|
{openPositions.length ? (
|
2021-09-07 18:52:45 -07:00
|
|
|
!isMobile ? (
|
|
|
|
<Table>
|
|
|
|
<thead>
|
|
|
|
<TrHead>
|
2021-10-20 05:42:40 -07:00
|
|
|
<Th>{t('market')}</Th>
|
|
|
|
<Th>{t('side')}</Th>
|
|
|
|
<Th>{t('position-size')}</Th>
|
|
|
|
<Th>{t('notional-size')}</Th>
|
|
|
|
<Th>{t('average-entry')}</Th>
|
|
|
|
<Th>{t('break-even')}</Th>
|
|
|
|
<Th>{t('unrealized-pnl')}</Th>
|
2021-09-07 18:52:45 -07:00
|
|
|
</TrHead>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
2021-10-05 13:22:47 -07:00
|
|
|
{openPositions.map(
|
|
|
|
(
|
|
|
|
{
|
|
|
|
marketIndex,
|
|
|
|
marketConfig,
|
2021-08-29 23:33:41 -07:00
|
|
|
perpMarket,
|
2021-10-05 13:22:47 -07:00
|
|
|
perpAccount,
|
|
|
|
basePosition,
|
|
|
|
notionalSize,
|
|
|
|
indexPrice,
|
|
|
|
avgEntryPrice,
|
|
|
|
breakEvenPrice,
|
|
|
|
unrealizedPnl,
|
|
|
|
},
|
|
|
|
index
|
|
|
|
) => {
|
|
|
|
return (
|
|
|
|
<TrBody index={index} key={`${marketIndex}`}>
|
|
|
|
<Td>
|
|
|
|
<div className="flex items-center">
|
|
|
|
<img
|
|
|
|
alt=""
|
|
|
|
width="20"
|
|
|
|
height="20"
|
|
|
|
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
|
|
|
|
className={`mr-2.5`}
|
|
|
|
/>
|
2021-11-14 10:37:02 -08:00
|
|
|
{decodeURIComponent(asPath).includes(
|
|
|
|
marketConfig.name
|
2021-10-18 03:34:52 -07:00
|
|
|
) ? (
|
|
|
|
<span>{marketConfig.name}</span>
|
|
|
|
) : (
|
2021-11-14 06:48:09 -08:00
|
|
|
<Link
|
2021-11-14 10:37:02 -08:00
|
|
|
href={{
|
|
|
|
pathname: '/market',
|
|
|
|
query: { name: marketConfig.name },
|
|
|
|
}}
|
2021-11-14 06:48:09 -08:00
|
|
|
shallow={true}
|
|
|
|
>
|
2021-10-18 03:34:52 -07:00
|
|
|
<a className="text-th-fgd-1 underline hover:no-underline hover:text-th-fgd-1">
|
|
|
|
{marketConfig.name}
|
|
|
|
</a>
|
|
|
|
</Link>
|
|
|
|
)}
|
2021-10-05 13:22:47 -07:00
|
|
|
</div>
|
|
|
|
</Td>
|
|
|
|
<Td>
|
|
|
|
<PerpSideBadge perpAccount={perpAccount} />
|
|
|
|
</Td>
|
|
|
|
<Td>
|
|
|
|
{basePosition &&
|
|
|
|
selectedMarketConfig.kind === 'perp' &&
|
2021-10-02 19:01:06 -07:00
|
|
|
asPath.includes(marketConfig.baseSymbol) ? (
|
|
|
|
<span
|
|
|
|
className="cursor-pointer underline hover:no-underline"
|
|
|
|
onClick={() =>
|
|
|
|
handleSizeClick(
|
2021-10-05 13:22:47 -07:00
|
|
|
Math.abs(basePosition),
|
|
|
|
basePosition > 0 ? 'buy' : 'sell',
|
|
|
|
indexPrice
|
2021-08-20 04:51:29 -07:00
|
|
|
)
|
2021-10-02 19:01:06 -07:00
|
|
|
}
|
|
|
|
>
|
2021-10-05 13:22:47 -07:00
|
|
|
{`${Math.abs(basePosition)} ${
|
|
|
|
marketConfig.baseSymbol
|
|
|
|
}`}
|
2021-09-07 18:52:45 -07:00
|
|
|
</span>
|
|
|
|
) : (
|
2021-10-05 13:22:47 -07:00
|
|
|
<span>
|
|
|
|
{`${Math.abs(basePosition)} ${
|
|
|
|
marketConfig.baseSymbol
|
|
|
|
}`}
|
2021-09-07 18:52:45 -07:00
|
|
|
</span>
|
2021-10-05 13:22:47 -07:00
|
|
|
)}
|
|
|
|
</Td>
|
|
|
|
<Td>{formatUsdValue(Math.abs(notionalSize))}</Td>
|
|
|
|
<Td>
|
|
|
|
{avgEntryPrice
|
|
|
|
? formatUsdValue(avgEntryPrice)
|
|
|
|
: '--'}
|
|
|
|
</Td>
|
|
|
|
<Td>
|
|
|
|
{breakEvenPrice
|
|
|
|
? formatUsdValue(breakEvenPrice)
|
|
|
|
: '--'}
|
|
|
|
</Td>
|
|
|
|
<Td>
|
2021-12-17 10:46:03 -08:00
|
|
|
{breakEvenPrice ? (
|
|
|
|
<PnlText pnl={unrealizedPnl} />
|
|
|
|
) : (
|
|
|
|
'--'
|
|
|
|
)}
|
2021-10-05 13:22:47 -07:00
|
|
|
</Td>
|
|
|
|
{showMarketCloseModal ? (
|
|
|
|
<MarketCloseModal
|
|
|
|
isOpen={showMarketCloseModal}
|
|
|
|
onClose={handleCloseWarning}
|
|
|
|
market={perpMarket}
|
|
|
|
marketIndex={marketIndex}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</TrBody>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)}
|
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
|
|
|
) : (
|
2021-11-03 04:28:58 -07:00
|
|
|
<>
|
|
|
|
<MobileTableHeader
|
|
|
|
colOneHeader={t('market')}
|
|
|
|
colTwoHeader={t('unrealized-pnl')}
|
|
|
|
/>
|
|
|
|
{openPositions.map(
|
|
|
|
(
|
|
|
|
{
|
|
|
|
marketConfig,
|
|
|
|
basePosition,
|
|
|
|
notionalSize,
|
|
|
|
avgEntryPrice,
|
|
|
|
breakEvenPrice,
|
|
|
|
unrealizedPnl,
|
|
|
|
},
|
|
|
|
index
|
|
|
|
) => {
|
|
|
|
return (
|
|
|
|
<ExpandableRow
|
|
|
|
buttonTemplate={
|
|
|
|
<>
|
|
|
|
<div className="flex items-center justify-between text-fgd-1 w-full">
|
|
|
|
<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 ${
|
|
|
|
basePosition > 0
|
|
|
|
? 'text-th-green'
|
|
|
|
: 'text-th-red'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
{basePosition > 0
|
|
|
|
? t('long').toUpperCase()
|
|
|
|
: t('short').toUpperCase()}
|
|
|
|
</span>
|
|
|
|
{Math.abs(basePosition)}
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
</div>
|
2021-09-07 18:52:45 -07:00
|
|
|
</div>
|
2021-11-03 04:28:58 -07:00
|
|
|
<PnlText pnl={unrealizedPnl} />
|
2021-09-07 18:52:45 -07:00
|
|
|
</div>
|
2021-11-03 04:28:58 -07:00
|
|
|
</>
|
|
|
|
}
|
|
|
|
key={`${index}`}
|
|
|
|
index={index}
|
|
|
|
panelTemplate={
|
|
|
|
<div className="grid grid-cols-2 grid-flow-row gap-4">
|
|
|
|
<div className="col-span-1 text-left">
|
|
|
|
<div className="pb-0.5 text-th-fgd-3 text-xs">
|
|
|
|
{t('average-entry')}
|
|
|
|
</div>
|
|
|
|
{avgEntryPrice
|
|
|
|
? formatUsdValue(avgEntryPrice)
|
|
|
|
: '--'}
|
2021-10-05 13:22:47 -07:00
|
|
|
</div>
|
2021-11-03 04:28:58 -07:00
|
|
|
<div className="col-span-1 text-left">
|
|
|
|
<div className="pb-0.5 text-th-fgd-3 text-xs">
|
|
|
|
{t('notional-size')}
|
|
|
|
</div>
|
|
|
|
{formatUsdValue(notionalSize)}
|
2021-10-05 13:22:47 -07:00
|
|
|
</div>
|
2021-11-03 04:28:58 -07:00
|
|
|
<div className="col-span-1 text-left">
|
|
|
|
<div className="pb-0.5 text-th-fgd-3 text-xs">
|
|
|
|
{t('break-even')}
|
|
|
|
</div>
|
|
|
|
{breakEvenPrice
|
|
|
|
? formatUsdValue(breakEvenPrice)
|
|
|
|
: '--'}
|
2021-10-05 13:22:47 -07:00
|
|
|
</div>
|
2021-09-07 18:52:45 -07:00
|
|
|
</div>
|
2021-11-03 04:28:58 -07:00
|
|
|
}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)}
|
|
|
|
</>
|
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-10-20 05:42:40 -07:00
|
|
|
{t('no-perp')}
|
2021-06-27 21:44:48 -07:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default PositionsTable
|