Mp 2193 pnl breakdown (#775)

* perps: add additional pnl info

* perps: make realized / unrealized coins
This commit is contained in:
Bob van der Helm 2024-02-06 15:12:55 +01:00 committed by GitHub
parent 9ccb32b743
commit ef9c353b6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 166 additions and 71 deletions

View File

@ -1,5 +1,6 @@
import { cacheFn, positionsCache } from 'api/cache' import { cacheFn, positionsCache } from 'api/cache'
import { getCreditManagerQueryClient } from 'api/cosmwasm-client' import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import getPrices from 'api/prices/getPrices'
import getDepositedVaults from 'api/vaults/getDepositedVaults' import getDepositedVaults from 'api/vaults/getDepositedVaults'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
@ -19,6 +20,8 @@ export default async function getAccount(
`${chainConfig.id}/account/${accountId}`, `${chainConfig.id}/account/${accountId}`,
) )
const prices = await getPrices(chainConfig)
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId }) const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
const depositedVaults = await getDepositedVaults(accountId, chainConfig, accountPosition) const depositedVaults = await getDepositedVaults(accountId, chainConfig, accountPosition)
@ -30,7 +33,7 @@ export default async function getAccount(
lends: accountPosition.lends.map((lend) => new BNCoin(lend)), lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)), deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
vaults: depositedVaults, vaults: depositedVaults,
perps: resolvePerpsPositions(accountPosition.perps), perps: resolvePerpsPositions(accountPosition.perps, prices),
kind: accountKind, kind: accountKind,
} }
} }

View File

@ -1,15 +1,16 @@
import { ReactNode } from 'react' import { ReactNode } from 'react'
import AssetImage from 'components/common/assets/AssetImage'
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/common/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber' import { FormattedNumber } from 'components/common/FormattedNumber'
import Text from 'components/common/Text' import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip' import { Tooltip } from 'components/common/Tooltip'
import AssetImage from 'components/common/assets/AssetImage'
import TradeDirection from 'components/perps/BalancesTable/Columns/TradeDirection' import TradeDirection from 'components/perps/BalancesTable/Columns/TradeDirection'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import usePerpsEnabledAssets from 'hooks/assets/usePerpsEnabledAssets' import usePerpsEnabledAssets from 'hooks/assets/usePerpsEnabledAssets'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { demagnify } from 'utils/formatters' import { demagnify } from 'utils/formatters'
export const ASSET_META = { export const ASSET_META = {
accessorKey: 'symbol', accessorKey: 'symbol',
header: 'Asset', header: 'Asset',
@ -58,7 +59,12 @@ function TooltipContent(props: TooltipProps) {
/> />
</LabelAndValue> </LabelAndValue>
<LabelAndValue label='Unrealized PnL'> <LabelAndValue label='Unrealized PnL'>
<DisplayCurrency coin={row.pnl} options={{ abbreviated: false }} showZero isProfitOrLoss /> <DisplayCurrency
coin={row.pnl.net}
options={{ abbreviated: false }}
showZero
isProfitOrLoss
/>
</LabelAndValue> </LabelAndValue>
</div> </div>
) )

View File

@ -1,10 +1,9 @@
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/common/DisplayCurrency'
import { BNCoin } from 'types/classes/BNCoin'
export const PNL_META = { id: 'pnl', header: 'Total PnL', meta: { className: 'w-30' } } export const PNL_META = { id: 'pnl', header: 'Total PnL', meta: { className: 'w-30' } }
interface Props { interface Props {
pnl: BNCoin pnl: PerpsPnL
} }
export default function TotalPnL(props: Props) { export default function TotalPnL(props: Props) {
@ -13,7 +12,7 @@ export default function TotalPnL(props: Props) {
return ( return (
<DisplayCurrency <DisplayCurrency
className='text-xs text-right number' className='text-xs text-right number'
coin={pnl} coin={pnl.net}
options={{ abbreviated: false }} options={{ abbreviated: false }}
isProfitOrLoss isProfitOrLoss
showZero showZero

View File

@ -6,7 +6,7 @@ export function getAssetAccountPerpRow(
prices: BNCoin[], prices: BNCoin[],
position: PerpsPosition, position: PerpsPosition,
assets: Asset[], assets: Asset[],
prev?: BNCoin, prev?: PerpsPosition,
): AccountPerpRow { ): AccountPerpRow {
const { denom, amount } = position const { denom, amount } = position
const amountChange = !prev ? position.amount : position.amount.minus(prev.amount) const amountChange = !prev ? position.amount : position.amount.minus(prev.amount)

View File

@ -19,10 +19,20 @@ interface Props {
showZero?: boolean showZero?: boolean
options?: FormatOptions options?: FormatOptions
isProfitOrLoss?: boolean isProfitOrLoss?: boolean
showSignPrefix?: boolean
} }
export default function DisplayCurrency(props: Props) { export default function DisplayCurrency(props: Props) {
const { coin, className, isApproximation, parentheses, showZero, options, isProfitOrLoss } = props const {
coin,
className,
isApproximation,
parentheses,
showSignPrefix,
showZero,
options,
isProfitOrLoss,
} = props
const displayCurrencies = useDisplayCurrencyAssets() const displayCurrencies = useDisplayCurrencyAssets()
const assets = useAllAssets() const assets = useAllAssets()
const [displayCurrency] = useDisplayCurrency() const [displayCurrency] = useDisplayCurrency()
@ -59,7 +69,7 @@ export default function DisplayCurrency(props: Props) {
) )
const prefix = useMemo(() => { const prefix = useMemo(() => {
const positiveOrNegativePrefix = isProfitOrLoss const positiveOrNegativePrefix = showSignPrefix
? amount > 0 ? amount > 0
? '+' ? '+'
: amount < 0 : amount < 0
@ -72,7 +82,7 @@ export default function DisplayCurrency(props: Props) {
return isUSD return isUSD
? `${approximationPrefix}${smallerThanPrefix}${positiveOrNegativePrefix}$` ? `${approximationPrefix}${smallerThanPrefix}${positiveOrNegativePrefix}$`
: `${approximationPrefix}${smallerThanPrefix}${positiveOrNegativePrefix}` : `${approximationPrefix}${smallerThanPrefix}${positiveOrNegativePrefix}`
}, [isUSD, isApproximation, showZero, isProfitOrLoss, amount, isLessThanACent]) }, [isUSD, isApproximation, showZero, showSignPrefix, amount, isLessThanACent])
const suffix = isUSD const suffix = isUSD
? '' ? ''

View File

@ -16,8 +16,10 @@ export default function TooltipContent(props: Props) {
<div> <div>
<div <div
className={classNames( className={classNames(
'flex max-w-[320px] flex-1 gap-2 rounded-sm p-3 text-sm shadow-tooltip backdrop-blur-lg', 'flex max-w-[320px] flex-1 gap-2 rounded-lg p-3 text-sm shadow-tooltip backdrop-blur-[100px]',
props.type === 'info' && 'bg-white/20', 'relative isolate max-w-full overflow-hidden',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-lg before:p-[1px] before:border-glas',
props.type === 'info' && 'bg-white/10',
props.type === 'warning' && 'bg-warning', props.type === 'warning' && 'bg-warning',
props.type === 'error' && 'bg-error', props.type === 'error' && 'bg-error',
props.className, props.className,

View File

@ -1,53 +1,77 @@
import classNames from 'classnames'
import DisplayCurrency from 'components/common/DisplayCurrency' import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/common/Divider'
import Text from 'components/common/Text' import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip' import { Tooltip } from 'components/common/Tooltip'
import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
export const PNL_META = { accessorKey: 'pnl', header: 'Total PnL', id: 'pnl' } export const PNL_META = { accessorKey: 'pnl.net.amount', header: 'Total PnL', id: 'pnl' }
type Props = { type Props = {
pnl: BNCoin pnl: PerpsPnL
} }
export default function PnL(props: Props) { export default function PnL(props: Props) {
return ( return (
<Tooltip <Tooltip content={<PnLTooltip {...props} />} type='info' underline>
content={ <DisplayCurrency
<PnLTooltip className='inline text-xs'
realized={BNCoin.fromDenomAndBigNumber('uusd', BN_ZERO)} coin={props.pnl.net}
unrealized={props.pnl} isProfitOrLoss
/> showSignPrefix
} showZero
type='info' />
underline
>
<DisplayCurrency className='inline text-xs' coin={props.pnl} isProfitOrLoss showZero />
</Tooltip> </Tooltip>
) )
} }
type PnLTooltipProps = { function PnLTooltip(props: Props) {
realized: BNCoin
unrealized: BNCoin
}
function PnLTooltip(props: PnLTooltipProps) {
return ( return (
<div className='flex flex-col w-full gap-2'> <div className='flex flex-col w-full gap-2 min-w-[280px]'>
{[props.realized, props.unrealized].map((coin, i) => ( {[props.pnl.realized, props.pnl.unrealized].map((coins, i) => (
<div key={i} className='flex items-center w-full gap-8 space-between'> <>
<Text className='mr-auto text-white/60' size='sm'> <div key={i} className='flex items-center w-full gap-8 space-between'>
{i === 0 ? 'Realized' : 'Unrealized'} PnL <Text className='mr-auto text-white/60 font-bold' size='sm'>
</Text> {i === 0 ? 'Realized' : 'Unrealized'} PnL
<DisplayCurrency </Text>
coin={coin} <DisplayCurrency
className='self-end text-sm text-end' coin={coins.net}
isProfitOrLoss className='self-end text-sm text-end font-bold'
showZero isProfitOrLoss
/> showSignPrefix
</div> showZero
/>
</div>
<PnLRow coin={coins.price} text='Price' />
<PnLRow coin={coins.funding} text='Funding' className='text-white/60' showSignPrefix />
<PnLRow coin={coins.fees} text='Fees' className='text-white/60' showSignPrefix />
{i === 0 && <Divider className='my-2' />}
</>
))} ))}
</div> </div>
) )
} }
type PnLRowProps = {
coin: BNCoin
text: string
showSignPrefix?: boolean
className?: string
}
function PnLRow(props: PnLRowProps) {
return (
<div className='flex items-center w-full gap-8 space-between pl-4'>
<Text className='mr-auto text-white/60' size='sm'>
{props.text}
</Text>
<DisplayCurrency
coin={props.coin}
className={classNames('self-end text-sm text-end', props.className)}
showZero
showSignPrefix={props.showSignPrefix}
/>
</div>
)
}

View File

@ -21,7 +21,8 @@ export default function usePerpsBalancesTable() {
const netValue = getAccountNetValue(currentAccount, prices, allAssets) const netValue = getAccountNetValue(currentAccount, prices, allAssets)
return currentAccount.perps.map((position) => { return currentAccount.perps.map((position) => {
const price = prices.find(byDenom(position.denom))?.amount ?? BN_ZERO const perpPrice = prices.find(byDenom(position.denom))?.amount ?? BN_ZERO
const basePrice = prices.find(byDenom(position.baseDenom))?.amount ?? BN_ZERO
const asset = perpAssets.find(byDenom(position.denom))! const asset = perpAssets.find(byDenom(position.denom))!
return { return {
@ -31,7 +32,11 @@ export default function usePerpsBalancesTable() {
pnl: position.pnl, pnl: position.pnl,
entryPrice: position.entryPrice, entryPrice: position.entryPrice,
liquidationPrice: position.entryPrice, // TODO: 📈 Get actual liquidation price from HC liquidationPrice: position.entryPrice, // TODO: 📈 Get actual liquidation price from HC
leverage: price.times(demagnify(position.amount, asset)).div(netValue).plus(1).toNumber(), leverage: perpPrice
.times(demagnify(position.amount, asset))
.div(netValue)
.plus(1)
.toNumber(),
} as PerpPositionRow } as PerpPositionRow
}) })
}, [allAssets, currentAccount, perpAssets, prices]) }, [allAssets, currentAccount, perpAssets, prices])

View File

@ -7,7 +7,7 @@ interface Account {
debts: BNCoin[] debts: BNCoin[]
lends: BNCoin[] lends: BNCoin[]
vaults: DepositedVault[] vaults: DepositedVault[]
perps: PerpPosition[] perps: PerpsPosition[]
kind: AccountKind kind: AccountKind
} }

View File

@ -12,7 +12,7 @@ interface PerpsPosition {
tradeDirection: TradeDirection tradeDirection: TradeDirection
amount: BigNumber amount: BigNumber
closingFee: BNCoin closingFee: BNCoin
pnl: BNCoin pnl: PerpsPnL
entryPrice: BigNumber entryPrice: BigNumber
} }
@ -21,3 +21,16 @@ interface PerpPositionRow extends PerpsPosition {
liquidationPrice: BigNumber liquidationPrice: BigNumber
leverage: number leverage: number
} }
interface PerpsPnL {
net: BNCoin
realized: PerpsPnLCoins
unrealized: PerpsPnLCoins
}
interface PerpsPnLCoins {
fees: BNCoin
funding: BNCoin
net: BNCoin
price: BNCoin
}

View File

@ -1,6 +1,6 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { BN_ONE } from 'constants/math' import { BN_ONE, BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
export default function getPerpsPosition( export default function getPerpsPosition(
@ -9,15 +9,27 @@ export default function getPerpsPosition(
tradeDirection: TradeDirection, tradeDirection: TradeDirection,
) { ) {
const perpsBaseDenom = 'ibc/F91EA2C0A23697A1048E08C2F787E3A58AC6F706A1CD2257A504925158CFC0F3' const perpsBaseDenom = 'ibc/F91EA2C0A23697A1048E08C2F787E3A58AC6F706A1CD2257A504925158CFC0F3'
const perpsPosition = { return {
amount, amount,
closingFee: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ONE), closingFee: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ONE),
pnl: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ONE.negated()), pnl: {
net: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ONE),
realized: {
net: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO),
price: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO),
funding: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO),
fees: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO.times(-1)),
},
unrealized: {
net: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO),
price: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO),
funding: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO),
fees: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ZERO.times(-1)),
},
},
entryPrice: BN_ONE, entryPrice: BN_ONE,
baseDenom: perpsBaseDenom, baseDenom: perpsBaseDenom,
denom: asset.denom, denom: asset.denom,
tradeDirection, tradeDirection,
} }
return perpsPosition
} }

View File

@ -1,6 +1,6 @@
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { PnL, Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { import {
AssetParamsBaseForAddr as AssetParams, AssetParamsBaseForAddr as AssetParams,
AssetParamsBaseForAddr, AssetParamsBaseForAddr,
@ -104,8 +104,14 @@ export function resolveHLSStrategies(
return HLSStakingStrategies return HLSStakingStrategies
} }
export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsPosition[] { export function resolvePerpsPositions(
perpPositions: Positions['perps'],
prices: BNCoin[],
): PerpsPosition[] {
if (!perpPositions) return [] if (!perpPositions) return []
const basePrice =
prices.find((price) => price.denom === perpPositions[0].base_denom)?.amount ?? BN_ZERO
return perpPositions.map((position) => { return perpPositions.map((position) => {
return { return {
denom: position.denom, denom: position.denom,
@ -113,22 +119,37 @@ export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsP
amount: BN(position.size as any).abs(), amount: BN(position.size as any).abs(),
tradeDirection: BN(position.size as any).isNegative() ? 'short' : 'long', tradeDirection: BN(position.size as any).isNegative() ? 'short' : 'long',
closingFee: BNCoin.fromCoin(position.pnl.coins.closing_fee), closingFee: BNCoin.fromCoin(position.pnl.coins.closing_fee),
pnl: getPnlCoin(position.pnl.coins.pnl, position.base_denom), pnl: {
net: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.pnl.values.pnl as any).plus(BN_ZERO),
),
realized: {
net: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO),
price: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO),
funding: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO),
fees: BNCoin.fromDenomAndBigNumber(position.base_denom, BN_ZERO.times(-1)),
},
unrealized: {
net: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.pnl.values.pnl as any),
),
price: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.pnl.values.price_pnl as any),
),
funding: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.pnl.values.accrued_funding as any),
),
fees: BNCoin.fromDenomAndBigNumber(
position.base_denom,
BN(position.pnl.values.closing_fee as any).times(-1),
),
},
},
entryPrice: BN(position.entry_price), entryPrice: BN(position.entry_price),
} }
}) })
} }
function getPnlCoin(pnl: PnL, denom: string): BNCoin {
let amount = BN_ZERO
if (pnl === 'break_even') return BNCoin.fromDenomAndBigNumber(denom, amount)
if ('loss' in (pnl as any)) {
amount = BN((pnl as any).loss.amount).times(-1)
} else if ('profit' in (pnl as { profit: Coin })) {
amount = BN((pnl as any).profit.amount)
}
return BNCoin.fromDenomAndBigNumber(denom, amount)
}