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

View File

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

View File

@ -1,10 +1,9 @@
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' } }
interface Props {
pnl: BNCoin
pnl: PerpsPnL
}
export default function TotalPnL(props: Props) {
@ -13,7 +12,7 @@ export default function TotalPnL(props: Props) {
return (
<DisplayCurrency
className='text-xs text-right number'
coin={pnl}
coin={pnl.net}
options={{ abbreviated: false }}
isProfitOrLoss
showZero

View File

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

View File

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

View File

@ -16,8 +16,10 @@ export default function TooltipContent(props: Props) {
<div>
<div
className={classNames(
'flex max-w-[320px] flex-1 gap-2 rounded-sm p-3 text-sm shadow-tooltip backdrop-blur-lg',
props.type === 'info' && 'bg-white/20',
'flex max-w-[320px] flex-1 gap-2 rounded-lg p-3 text-sm shadow-tooltip backdrop-blur-[100px]',
'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 === 'error' && 'bg-error',
props.className,

View File

@ -1,53 +1,77 @@
import classNames from 'classnames'
import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/common/Divider'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import { BN_ZERO } from 'constants/math'
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 = {
pnl: BNCoin
pnl: PerpsPnL
}
export default function PnL(props: Props) {
return (
<Tooltip
content={
<PnLTooltip
realized={BNCoin.fromDenomAndBigNumber('uusd', BN_ZERO)}
unrealized={props.pnl}
/>
}
type='info'
underline
>
<DisplayCurrency className='inline text-xs' coin={props.pnl} isProfitOrLoss showZero />
<Tooltip content={<PnLTooltip {...props} />} type='info' underline>
<DisplayCurrency
className='inline text-xs'
coin={props.pnl.net}
isProfitOrLoss
showSignPrefix
showZero
/>
</Tooltip>
)
}
type PnLTooltipProps = {
realized: BNCoin
unrealized: BNCoin
}
function PnLTooltip(props: PnLTooltipProps) {
function PnLTooltip(props: Props) {
return (
<div className='flex flex-col w-full gap-2'>
{[props.realized, props.unrealized].map((coin, i) => (
<div key={i} className='flex items-center w-full gap-8 space-between'>
<Text className='mr-auto text-white/60' size='sm'>
{i === 0 ? 'Realized' : 'Unrealized'} PnL
</Text>
<DisplayCurrency
coin={coin}
className='self-end text-sm text-end'
isProfitOrLoss
showZero
/>
</div>
<div className='flex flex-col w-full gap-2 min-w-[280px]'>
{[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 font-bold' size='sm'>
{i === 0 ? 'Realized' : 'Unrealized'} PnL
</Text>
<DisplayCurrency
coin={coins.net}
className='self-end text-sm text-end font-bold'
isProfitOrLoss
showSignPrefix
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>
)
}
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)
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))!
return {
@ -31,7 +32,11 @@ export default function usePerpsBalancesTable() {
pnl: position.pnl,
entryPrice: position.entryPrice,
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
})
}, [allAssets, currentAccount, perpAssets, prices])

View File

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

View File

@ -12,7 +12,7 @@ interface PerpsPosition {
tradeDirection: TradeDirection
amount: BigNumber
closingFee: BNCoin
pnl: BNCoin
pnl: PerpsPnL
entryPrice: BigNumber
}
@ -21,3 +21,16 @@ interface PerpPositionRow extends PerpsPosition {
liquidationPrice: BigNumber
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 { BN_ONE } from 'constants/math'
import { BN_ONE, BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
export default function getPerpsPosition(
@ -9,15 +9,27 @@ export default function getPerpsPosition(
tradeDirection: TradeDirection,
) {
const perpsBaseDenom = 'ibc/F91EA2C0A23697A1048E08C2F787E3A58AC6F706A1CD2257A504925158CFC0F3'
const perpsPosition = {
return {
amount,
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,
baseDenom: perpsBaseDenom,
denom: asset.denom,
tradeDirection,
}
return perpsPosition
}

View File

@ -1,6 +1,6 @@
import { BN_ZERO } from 'constants/math'
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 {
AssetParamsBaseForAddr as AssetParams,
AssetParamsBaseForAddr,
@ -104,8 +104,14 @@ export function resolveHLSStrategies(
return HLSStakingStrategies
}
export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsPosition[] {
export function resolvePerpsPositions(
perpPositions: Positions['perps'],
prices: BNCoin[],
): PerpsPosition[] {
if (!perpPositions) return []
const basePrice =
prices.find((price) => price.denom === perpPositions[0].base_denom)?.amount ?? BN_ZERO
return perpPositions.map((position) => {
return {
denom: position.denom,
@ -113,22 +119,37 @@ export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsP
amount: BN(position.size as any).abs(),
tradeDirection: BN(position.size as any).isNegative() ? 'short' : 'long',
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),
}
})
}
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)
}