mango-ui-v2/components/MarginInfo.tsx

241 lines
7.9 KiB
TypeScript

import { useEffect, useState, useMemo } from 'react'
import { useRouter } from 'next/router'
import { nativeToUi } from '@blockworks-foundation/mango-client/lib/utils'
import { groupBy } from '../utils'
import useTradeHistory from '../hooks/useTradeHistory'
import useMangoStore from '../stores/useMangoStore'
import FloatingElement from './FloatingElement'
import Tooltip from './Tooltip'
import Button from './Button'
import AlertsModal from './AlertsModal'
import useMarketList from '../hooks/useMarketList'
const assetIndex = {
'BTC/USDC': 0,
'ETH/USDC': 1,
'SOL/USDC': 2,
'SRM/USDC': 3,
USDC: 4,
}
const calculatePNL = (tradeHistory, prices, mangoGroup) => {
if (!tradeHistory.length) return '0.00'
const profitAndLoss = {}
const groupedTrades = groupBy(tradeHistory, (trade) => trade.marketName)
if (!prices.length) return '-'
groupedTrades.forEach((val, key) => {
profitAndLoss[key] = val.reduce(
(acc, current) =>
(current.side === 'sell' ? current.size * -1 : current.size) + acc,
0
)
})
const totalNativeUSDC = tradeHistory.reduce((acc, current) => {
const usdtAmount =
current.side === 'sell'
? parseInt(current.nativeQuantityReleased)
: parseInt(current.nativeQuantityPaid) * -1
return usdtAmount + acc
}, 0)
profitAndLoss['USDC'] = nativeToUi(
totalNativeUSDC,
mangoGroup.mintDecimals[assetIndex['USDC']]
)
let total = 0
for (const assetName in profitAndLoss) {
total = total + profitAndLoss[assetName] * prices[assetIndex[assetName]]
}
return total.toFixed(2)
}
export default function MarginInfo() {
const connection = useMangoStore((s) => s.connection.current)
const connected = useMangoStore((s) => s.wallet.connected)
const router = useRouter()
const selectedMarginAccount = useMangoStore(
(s) => s.selectedMarginAccount.current
)
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const selectedMarketName = useMangoStore((s) => s.selectedMarket.name)
const { symbols } = useMarketList()
const tradeHistory = useTradeHistory()
const tradeHistoryLength = useMemo(() => tradeHistory.length, [tradeHistory])
const [mAccountInfo, setMAccountInfo] = useState<
| {
label: string
value: string
unit: string
desc: string
currency: string
}[]
| null
>(null)
const [openAlertModal, setOpenAlertModal] = useState(false)
useEffect(() => {
if (selectedMangoGroup && selectedMarketName) {
selectedMangoGroup.getPrices(connection).then((prices) => {
const accountEquity = selectedMarginAccount
? selectedMarginAccount.computeValue(selectedMangoGroup, prices)
: 0
const assetsVal = selectedMarginAccount
? selectedMarginAccount.getAssetsVal(selectedMangoGroup, prices)
: 0
const liabsVal = selectedMarginAccount
? selectedMarginAccount.getLiabsVal(selectedMangoGroup, prices)
: 0
const collateralRatio = selectedMarginAccount
? assetsVal / liabsVal
: 10
const leverage = accountEquity
? (1 / (collateralRatio - 1)).toFixed(2)
: '0'
const assetNames = Object.keys(symbols)
const selectedAssetIndex = assetIndex[selectedMarketName]
const selectedAssetDeposit =
selectedMarginAccount?.getAssets(selectedMangoGroup)[
selectedAssetIndex
]
const selectedAssetBorrow =
selectedMarginAccount?.getLiabs(selectedMangoGroup)[
selectedAssetIndex
]
const selectedAssetPrice = prices[selectedAssetIndex]
let liquidationPrice: number
if (selectedAssetDeposit > selectedAssetBorrow) {
// marginAccount is long the selected asset, so price changes only affect Assets
const fixedAssetsVal =
assetsVal - selectedAssetDeposit * selectedAssetPrice
liquidationPrice =
(1.1 * liabsVal - fixedAssetsVal) / selectedAssetDeposit
if (liquidationPrice < 0) {
liquidationPrice = NaN
}
} else {
// marginAccount is short the selected asset, so price changes only affect Liabilites
const fixedLiabsVal =
liabsVal - selectedAssetBorrow * selectedAssetPrice
liquidationPrice =
(assetsVal / 1.1 - fixedLiabsVal) / selectedAssetBorrow
}
setMAccountInfo([
{
label: 'Equity',
value: accountEquity.toFixed(2),
unit: '',
currency: '$',
desc: 'The value of the account',
},
{
label: 'Leverage',
value: leverage,
unit: 'x',
currency: '',
desc: 'Total position size divided by account value',
},
{
label: 'Total PNL',
value: calculatePNL(tradeHistory, prices, selectedMangoGroup),
unit: '',
currency: '$',
desc: 'Total PNL reflects trades but not liquidations. Visit the Learn link in the top menu for more information.',
},
{
label: 'Current Assets Value',
value: assetsVal.toFixed(2),
unit: '',
currency: '$',
desc: 'The current value of all your assets',
},
{
label: 'Current Liabilities Value',
value: liabsVal.toFixed(2),
unit: '',
currency: '$',
desc: 'The current value of all your liabilities',
},
{
label: 'Collateral Ratio',
value:
collateralRatio > 9.99
? '>999'
: (collateralRatio * 100).toFixed(0),
unit: '%',
currency: '',
desc: 'Keep your collateral ratio above 110% to avoid liquidation and above 120% to open new margin positions',
},
{
label: 'Estimated Liquidation Price',
value: isFinite(liquidationPrice)
? liquidationPrice.toFixed(2)
: 'N/A',
unit: '',
currency: isFinite(liquidationPrice) ? '$' : '',
desc: `Estimated ${assetNames[selectedAssetIndex]} price that will cause liquidation. Calculated with the assumption that all other asset prices are constant`,
},
])
})
}
}, [
selectedMarginAccount,
selectedMangoGroup,
tradeHistoryLength,
selectedMarketName,
])
return (
<FloatingElement showConnect>
<>
{mAccountInfo
? mAccountInfo.map((entry, i) => (
<div className={`flex justify-between pt-2 pb-2`} key={i}>
<Tooltip content={entry.desc}>
<div
className={`cursor-help font-normal text-th-fgd-4 border-b border-th-fgd-4 border-dashed border-opacity-20 leading-4 default-transition hover:border-th-bkg-2 hover:text-th-fgd-3`}
>
{entry.label}
</div>
</Tooltip>
<div className={`text-th-fgd-1`}>
{entry.currency + entry.value}
{entry.unit}
</div>
</div>
))
: null}
<div className={`flex justify-center items-center mt-4`}>
<Button
onClick={() => setOpenAlertModal(true)}
className="w-1/2"
disabled={!connected}
>
Create Alert
</Button>
<Button
onClick={() => router.push('/risk-calculator')}
className="ml-4 w-1/2 whitespace-nowrap"
>
Risk Calculator
</Button>
</div>
{openAlertModal ? (
<AlertsModal
isOpen={openAlertModal}
onClose={() => setOpenAlertModal(false)}
marginAccount={selectedMarginAccount}
/>
) : null}
</>
</FloatingElement>
)
}