Mp 2193 pnl breakdown (#775)
* perps: add additional pnl info * perps: make realized / unrealized coins
This commit is contained in:
parent
9ccb32b743
commit
ef9c353b6b
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
? ''
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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])
|
||||
|
2
src/types/interfaces/account.d.ts
vendored
2
src/types/interfaces/account.d.ts
vendored
@ -7,7 +7,7 @@ interface Account {
|
||||
debts: BNCoin[]
|
||||
lends: BNCoin[]
|
||||
vaults: DepositedVault[]
|
||||
perps: PerpPosition[]
|
||||
perps: PerpsPosition[]
|
||||
kind: AccountKind
|
||||
}
|
||||
|
||||
|
15
src/types/interfaces/perps.d.ts
vendored
15
src/types/interfaces/perps.d.ts
vendored
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user