2021-08-24 20:15:57 -07:00
|
|
|
import { useCallback, useMemo, useState } from 'react'
|
2021-07-20 07:21:58 -07:00
|
|
|
import { ElementTitle } from './styles'
|
2021-09-13 14:14:59 -07:00
|
|
|
import useMangoStore from '../stores/useMangoStore'
|
2021-09-19 17:36:02 -07:00
|
|
|
import { formatUsdValue } from '../utils/index'
|
2021-08-24 20:15:57 -07:00
|
|
|
import Button, { LinkButton } from './Button'
|
2021-07-20 07:21:58 -07:00
|
|
|
import Tooltip from './Tooltip'
|
2021-10-05 13:22:47 -07:00
|
|
|
import PerpSideBadge from './PerpSideBadge'
|
2021-08-14 13:05:31 -07:00
|
|
|
import {
|
|
|
|
getMarketIndexBySymbol,
|
|
|
|
PerpAccount,
|
|
|
|
PerpMarket,
|
|
|
|
QUOTE_INDEX,
|
|
|
|
} from '@blockworks-foundation/mango-client'
|
|
|
|
import useTradeHistory from '../hooks/useTradeHistory'
|
|
|
|
import { notify } from '../utils/notifications'
|
2021-08-24 20:15:57 -07:00
|
|
|
import MarketCloseModal from './MarketCloseModal'
|
2021-10-05 13:22:47 -07:00
|
|
|
import PnlText from './PnlText'
|
2021-09-02 14:35:37 -07:00
|
|
|
import Loading from './Loading'
|
2021-09-19 17:36:02 -07:00
|
|
|
import { useViewport } from '../hooks/useViewport'
|
|
|
|
import { breakpoints } from './TradePageGrid'
|
2021-10-05 13:22:47 -07:00
|
|
|
import { collectPerpPosition } from '../hooks/usePerpPositions'
|
2021-08-14 13:05:31 -07:00
|
|
|
|
2021-10-05 13:22:47 -07:00
|
|
|
export const settlePnl = async (
|
|
|
|
perpMarket: PerpMarket,
|
|
|
|
perpAccount: PerpAccount
|
|
|
|
) => {
|
2021-08-14 13:05:31 -07:00
|
|
|
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
|
|
|
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
|
|
|
const mangoCache = useMangoStore.getState().selectedMangoGroup.cache
|
|
|
|
const wallet = useMangoStore.getState().wallet.current
|
2021-09-03 09:14:11 -07:00
|
|
|
const actions = useMangoStore.getState().actions
|
2021-08-14 13:05:31 -07:00
|
|
|
const marketIndex = mangoGroup.getPerpMarketIndex(perpMarket.publicKey)
|
2021-09-13 14:14:59 -07:00
|
|
|
const mangoClient = useMangoStore.getState().connection.client
|
2021-08-14 13:05:31 -07:00
|
|
|
|
|
|
|
try {
|
|
|
|
const txid = await mangoClient.settlePnl(
|
|
|
|
mangoGroup,
|
2021-08-20 13:39:44 -07:00
|
|
|
mangoCache,
|
2021-08-14 13:05:31 -07:00
|
|
|
mangoAccount,
|
|
|
|
perpMarket,
|
|
|
|
mangoGroup.rootBankAccounts[QUOTE_INDEX],
|
|
|
|
mangoCache.priceCache[marketIndex].price,
|
|
|
|
wallet
|
|
|
|
)
|
2021-09-03 09:14:11 -07:00
|
|
|
actions.reloadMangoAccount()
|
2021-08-14 13:05:31 -07:00
|
|
|
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',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-07-20 07:21:58 -07:00
|
|
|
|
2021-08-16 14:35:16 -07:00
|
|
|
export function SettlePnlTooltip() {
|
|
|
|
return (
|
|
|
|
<div>
|
2021-08-23 11:36:02 -07:00
|
|
|
Settling will update your USDC balance to reflect the unsettled PnL
|
|
|
|
amount.{' '}
|
2021-08-16 14:35:16 -07:00
|
|
|
<a
|
|
|
|
href="https://docs.mango.markets/mango-v3/overview#settle-pnl"
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
>
|
|
|
|
Learn more
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-07-20 07:21:58 -07:00
|
|
|
export default function MarketPosition() {
|
2021-08-14 13:05:31 -07:00
|
|
|
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
|
|
|
const mangoGroupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
2021-10-05 13:22:47 -07:00
|
|
|
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
2021-08-14 13:05:31 -07:00
|
|
|
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
|
|
|
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
2021-07-20 07:21:58 -07:00
|
|
|
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
2021-08-14 13:05:31 -07:00
|
|
|
const connected = useMangoStore((s) => s.wallet.connected)
|
2021-08-20 06:17:02 -07:00
|
|
|
const isLoading = useMangoStore((s) => s.selectedMangoAccount.initialLoad)
|
2021-08-20 04:51:29 -07:00
|
|
|
const setMangoStore = useMangoStore((s) => s.set)
|
2021-10-02 19:01:06 -07:00
|
|
|
const price = useMangoStore((s) => s.tradeForm.price)
|
2021-07-20 07:21:58 -07:00
|
|
|
const baseSymbol = marketConfig.baseSymbol
|
2021-08-14 13:05:31 -07:00
|
|
|
const marketName = marketConfig.name
|
|
|
|
const tradeHistory = useTradeHistory()
|
2021-10-05 13:22:47 -07:00
|
|
|
|
2021-08-24 20:15:57 -07:00
|
|
|
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
|
2021-09-02 14:35:37 -07:00
|
|
|
const [settling, setSettling] = useState(false)
|
2021-09-19 17:36:02 -07:00
|
|
|
const { width } = useViewport()
|
|
|
|
const isMobile = width ? width < breakpoints.sm : false
|
2021-07-20 07:21:58 -07:00
|
|
|
|
2021-08-14 13:05:31 -07:00
|
|
|
const marketIndex = useMemo(() => {
|
|
|
|
return getMarketIndexBySymbol(mangoGroupConfig, baseSymbol)
|
|
|
|
}, [mangoGroupConfig, baseSymbol])
|
|
|
|
|
2021-10-05 13:22:47 -07:00
|
|
|
let perpAccount
|
2021-08-31 16:28:39 -07:00
|
|
|
if (marketName.includes('PERP') && mangoAccount) {
|
|
|
|
perpAccount = mangoAccount.perpAccounts[marketIndex]
|
|
|
|
}
|
2021-08-14 13:05:31 -07:00
|
|
|
|
2021-10-02 19:01:06 -07:00
|
|
|
const handleSizeClick = (size, side) => {
|
|
|
|
const step = selectedMarket.minOrderSize
|
|
|
|
|
|
|
|
const priceOrDefault = price
|
|
|
|
? price
|
2021-10-05 13:22:47 -07:00
|
|
|
: mangoGroup.getPrice(marketIndex, mangoCache).toNumber()
|
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-08-24 20:15:57 -07:00
|
|
|
const handleCloseWarning = useCallback(() => {
|
|
|
|
setShowMarketCloseModal(false)
|
|
|
|
}, [])
|
|
|
|
|
2021-09-02 14:35:37 -07:00
|
|
|
const handleSettlePnl = (perpMarket, perpAccount) => {
|
|
|
|
setSettling(true)
|
|
|
|
settlePnl(perpMarket, perpAccount).then(() => {
|
|
|
|
setSettling(false)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-05 13:22:47 -07:00
|
|
|
if (!mangoGroup || !selectedMarket || !(selectedMarket instanceof PerpMarket))
|
|
|
|
return null
|
2021-10-02 19:01:06 -07:00
|
|
|
|
2021-10-05 13:22:47 -07:00
|
|
|
const {
|
|
|
|
basePosition,
|
|
|
|
avgEntryPrice,
|
|
|
|
breakEvenPrice,
|
|
|
|
notionalSize,
|
|
|
|
unsettledPnl,
|
|
|
|
} = collectPerpPosition(
|
|
|
|
mangoAccount,
|
|
|
|
mangoGroup,
|
|
|
|
mangoCache,
|
|
|
|
marketConfig,
|
|
|
|
selectedMarket,
|
|
|
|
tradeHistory
|
|
|
|
)
|
2021-08-17 08:10:32 -07:00
|
|
|
|
2021-10-05 13:22:47 -07:00
|
|
|
return (
|
2021-09-19 17:36:02 -07:00
|
|
|
<>
|
|
|
|
<div className={!connected && !isMobile ? 'filter blur-sm' : null}>
|
|
|
|
{!isMobile ? (
|
2021-09-20 05:24:42 -07:00
|
|
|
<ElementTitle>{marketConfig.name} Position</ElementTitle>
|
2021-09-19 17:36:02 -07:00
|
|
|
) : null}
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex items-center justify-between pb-3">
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="font-normal text-th-fgd-3 leading-4">Side</div>
|
2021-08-20 06:17:02 -07:00
|
|
|
{isLoading ? (
|
2021-08-21 06:02:51 -07:00
|
|
|
<DataLoader />
|
2021-08-14 13:05:31 -07:00
|
|
|
) : (
|
2021-10-05 13:22:47 -07:00
|
|
|
<PerpSideBadge perpAccount={perpAccount}></PerpSideBadge>
|
2021-08-14 13:05:31 -07:00
|
|
|
)}
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex justify-between pb-3">
|
2021-08-14 13:05:31 -07:00
|
|
|
<div className="font-normal text-th-fgd-3 leading-4">
|
|
|
|
Position size
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="text-th-fgd-1">
|
2021-08-20 06:17:02 -07:00
|
|
|
{isLoading ? (
|
2021-08-21 06:02:51 -07:00
|
|
|
<DataLoader />
|
2021-10-05 13:22:47 -07:00
|
|
|
) : basePosition ? (
|
2021-08-20 04:51:29 -07:00
|
|
|
<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'
|
2021-08-20 04:51:29 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
2021-10-05 13:22:47 -07:00
|
|
|
{`${Math.abs(basePosition)} ${baseSymbol}`}
|
2021-08-20 04:51:29 -07:00
|
|
|
</span>
|
|
|
|
) : (
|
|
|
|
`0 ${baseSymbol}`
|
|
|
|
)}
|
2021-08-14 13:05:31 -07:00
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex justify-between pb-3">
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="font-normal text-th-fgd-3 leading-4">
|
2021-08-14 13:05:31 -07:00
|
|
|
Notional size
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="text-th-fgd-1">
|
2021-08-20 06:17:02 -07:00
|
|
|
{isLoading ? (
|
2021-08-21 06:02:51 -07:00
|
|
|
<DataLoader />
|
2021-08-20 06:17:02 -07:00
|
|
|
) : (
|
2021-10-05 13:22:47 -07:00
|
|
|
formatUsdValue(Math.abs(notionalSize))
|
2021-08-20 06:17:02 -07:00
|
|
|
)}
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex justify-between pb-3">
|
2021-08-14 13:05:31 -07:00
|
|
|
<div className="font-normal text-th-fgd-3 leading-4">
|
|
|
|
Avg entry price
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="text-th-fgd-1">
|
|
|
|
{isLoading ? <DataLoader /> : formatUsdValue(avgEntryPrice)}
|
2021-08-14 13:05:31 -07:00
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex justify-between pb-3">
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="font-normal text-th-fgd-3 leading-4">
|
2021-08-14 13:05:31 -07:00
|
|
|
Break-even price
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="text-th-fgd-1">
|
|
|
|
{isLoading ? <DataLoader /> : formatUsdValue(breakEvenPrice)}
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex justify-between pb-3">
|
2021-08-16 14:35:16 -07:00
|
|
|
<Tooltip content={<SettlePnlTooltip />}>
|
2021-08-16 10:00:43 -07:00
|
|
|
<Tooltip.Content className="font-normal text-th-fgd-3 leading-4">
|
2021-10-05 13:22:47 -07:00
|
|
|
Unsettled Balance
|
2021-08-16 10:00:43 -07:00
|
|
|
</Tooltip.Content>
|
|
|
|
</Tooltip>
|
2021-10-05 13:22:47 -07:00
|
|
|
<div className="flex items-center">
|
|
|
|
{isLoading ? <DataLoader /> : <PnlText pnl={unsettledPnl} />}
|
2021-09-02 14:35:37 -07:00
|
|
|
{settling ? (
|
|
|
|
<Loading className="ml-2" />
|
|
|
|
) : (
|
|
|
|
<LinkButton
|
|
|
|
onClick={() => handleSettlePnl(selectedMarket, perpAccount)}
|
|
|
|
className="ml-2 text-th-primary text-xs disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:underline"
|
2021-10-05 13:22:47 -07:00
|
|
|
disabled={unsettledPnl === 0}
|
2021-09-02 14:35:37 -07:00
|
|
|
>
|
|
|
|
Settle
|
|
|
|
</LinkButton>
|
|
|
|
)}
|
2021-08-16 06:31:25 -07:00
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
2021-10-05 13:22:47 -07:00
|
|
|
{basePosition ? (
|
2021-08-24 20:15:57 -07:00
|
|
|
<Button
|
|
|
|
onClick={() => setShowMarketCloseModal(true)}
|
2021-09-21 02:50:53 -07:00
|
|
|
className="mt-2.5 w-full"
|
2021-08-24 20:15:57 -07:00
|
|
|
>
|
|
|
|
<span>Market Close</span>
|
|
|
|
</Button>
|
|
|
|
) : null}
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
2021-08-24 20:15:57 -07:00
|
|
|
{showMarketCloseModal ? (
|
|
|
|
<MarketCloseModal
|
|
|
|
isOpen={showMarketCloseModal}
|
|
|
|
onClose={handleCloseWarning}
|
|
|
|
market={selectedMarket}
|
2021-08-25 11:30:16 -07:00
|
|
|
marketIndex={marketIndex}
|
2021-08-24 20:15:57 -07:00
|
|
|
/>
|
|
|
|
) : null}
|
2021-07-20 07:21:58 -07:00
|
|
|
</>
|
2021-10-05 13:22:47 -07:00
|
|
|
)
|
2021-07-20 07:21:58 -07:00
|
|
|
}
|
2021-08-21 06:02:51 -07:00
|
|
|
|
|
|
|
export const DataLoader = () => (
|
|
|
|
<div className="animate-pulse bg-th-bkg-3 h-5 w-10 rounded-sm" />
|
|
|
|
)
|