mango-v4-ui/components/trade/TradeSummary.tsx

298 lines
9.9 KiB
TypeScript
Raw Normal View History

2022-12-21 20:19:00 -08:00
import {
2023-08-28 04:59:36 -07:00
Bank,
2022-12-21 20:19:00 -08:00
HealthType,
MangoAccount,
PerpMarket,
Serum3Market,
} from '@blockworks-foundation/mango-v4'
2023-01-24 16:54:24 -08:00
import FormatNumericValue from '@components/shared/FormatNumericValue'
2022-12-21 20:19:00 -08:00
import HealthImpact from '@components/shared/HealthImpact'
import Tooltip from '@components/shared/Tooltip'
import mangoStore from '@store/mangoStore'
2023-04-25 05:41:23 -07:00
import Decimal from 'decimal.js'
2022-12-21 20:19:00 -08:00
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useMemo } from 'react'
import Slippage from './Slippage'
import {
floorToDecimal,
formatNumericValue,
getDecimalCount,
} from 'utils/numbers'
import { formatTokenSymbol } from 'utils/tokens'
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
2023-11-05 03:22:05 -08:00
import {
TriggerOrderTypes,
calculateEstPriceForBaseSize,
} from 'utils/tradeForm'
2022-12-21 20:19:00 -08:00
const TradeSummary = ({
2023-08-28 04:59:36 -07:00
balanceBank,
2022-12-21 20:19:00 -08:00
mangoAccount,
}: {
2023-08-28 04:59:36 -07:00
balanceBank: Bank | undefined
2022-12-21 20:19:00 -08:00
mangoAccount: MangoAccount | undefined
}) => {
const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const tradeForm = mangoStore((s) => s.tradeForm)
2023-07-06 07:24:07 -07:00
const orderbook = mangoStore((s) => s.selectedMarket.orderbook)
2023-04-25 05:41:23 -07:00
const { selectedMarket, quoteBank } = useSelectedMarket()
const openPerpPositions = useOpenPerpPositions()
// calc new avg price if an open position exists
const avgEntryPrice = useMemo(() => {
if (
2023-07-06 07:24:07 -07:00
!openPerpPositions?.length ||
!selectedMarket ||
2023-07-06 07:24:07 -07:00
!orderbook ||
selectedMarket instanceof Serum3Market
)
return
const openPosition = openPerpPositions.find(
2023-07-21 11:47:53 -07:00
(pos) => pos.marketIndex === selectedMarket.perpMarketIndex,
)
const { baseSize, price, reduceOnly, side, tradeType } = tradeForm
2023-07-06 07:24:07 -07:00
if (!openPosition || !price || !tradeForm.baseSize) return
let orderPrice = parseFloat(price)
if (tradeType === 'Market') {
orderPrice = calculateEstPriceForBaseSize(
orderbook,
parseFloat(tradeForm.baseSize),
2023-07-21 11:47:53 -07:00
tradeForm.side,
)
}
const currentSize = openPosition.getBasePositionUi(selectedMarket)
const tradeSize =
side === 'buy' ? parseFloat(baseSize) : parseFloat(baseSize) * -1
const newTotalSize = currentSize + tradeSize
const currentAvgPrice = openPosition.getAverageEntryPriceUi(selectedMarket)
// don't calc when closing position
if (newTotalSize === 0) {
return
}
// don't calc when reducing position
if (
(currentSize < 0 !== tradeSize < 0 &&
newTotalSize < 0 === currentSize < 0) ||
reduceOnly
) {
return
}
// if trade side changes with new trade return new trade price
if (currentSize < 0 !== newTotalSize < 0) {
return price
}
const newTotalCost = currentAvgPrice * currentSize + orderPrice * tradeSize
const newAvgEntryPrice = newTotalCost / newTotalSize
return newAvgEntryPrice
2023-07-06 07:24:07 -07:00
}, [openPerpPositions, selectedMarket, tradeForm, orderbook])
2022-12-21 20:19:00 -08:00
const maintProjectedHealth = useMemo(() => {
2023-03-04 10:53:49 -08:00
if (!mangoAccount || !group) return 100
2022-12-21 20:19:00 -08:00
let simulatedHealthRatio = 0
try {
if (selectedMarket instanceof Serum3Market) {
simulatedHealthRatio =
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithSerum3AskUiChanges(
group,
Number(tradeForm.baseSize),
selectedMarket.serumMarketExternal,
2023-07-21 11:47:53 -07:00
HealthType.maint,
2022-12-21 20:19:00 -08:00
)
: mangoAccount.simHealthRatioWithSerum3BidUiChanges(
group,
Number(tradeForm.quoteSize),
selectedMarket.serumMarketExternal,
2023-07-21 11:47:53 -07:00
HealthType.maint,
2022-12-21 20:19:00 -08:00
)
} else if (selectedMarket instanceof PerpMarket) {
simulatedHealthRatio =
tradeForm.side === 'sell'
? mangoAccount.simHealthRatioWithPerpAskUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize) || 0,
2023-07-21 11:47:53 -07:00
HealthType.maint,
2022-12-21 20:19:00 -08:00
)
: mangoAccount.simHealthRatioWithPerpBidUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize) || 0,
2023-07-21 11:47:53 -07:00
HealthType.maint,
2022-12-21 20:19:00 -08:00
)
}
} catch (e) {
console.warn('Error calculating projected health: ', e)
}
return simulatedHealthRatio > 100
? 100
: simulatedHealthRatio < 0
? 0
: Math.trunc(simulatedHealthRatio)
2023-01-02 11:50:09 -08:00
}, [group, mangoAccount, selectedMarket, tradeForm])
2022-12-21 20:19:00 -08:00
const [balance, borrowAmount] = useMemo(() => {
if (!balanceBank || !mangoAccount) return [0, 0]
2023-03-17 05:48:38 -07:00
let borrowAmount
const balance = mangoAccount.getTokenDepositsUi(balanceBank)
if (tradeForm.side === 'buy') {
const remainingBalance = balance - parseFloat(tradeForm.quoteSize)
borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
} else {
const remainingBalance = balance - parseFloat(tradeForm.baseSize)
borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
}
return [balance, borrowAmount]
2023-03-17 05:48:38 -07:00
}, [balanceBank, mangoAccount, tradeForm])
2023-04-25 05:41:23 -07:00
const orderValue = useMemo(() => {
2023-04-28 11:39:22 -07:00
if (
!quoteBank ||
!tradeForm.price ||
2023-05-13 04:41:42 -07:00
!tradeForm.baseSize ||
2023-06-15 05:32:40 -07:00
isNaN(parseFloat(tradeForm.price)) ||
isNaN(parseFloat(tradeForm.baseSize))
2023-04-28 11:39:22 -07:00
)
return 0
2023-04-25 05:41:23 -07:00
const basePriceDecimal = new Decimal(tradeForm.price)
const quotePriceDecimal = new Decimal(quoteBank.uiPrice)
const sizeDecimal = new Decimal(tradeForm.baseSize)
2023-05-13 04:41:42 -07:00
return floorToDecimal(
basePriceDecimal.mul(quotePriceDecimal).mul(sizeDecimal),
2023-07-21 11:47:53 -07:00
2,
2023-05-13 04:41:42 -07:00
)
2023-04-25 05:41:23 -07:00
}, [quoteBank, tradeForm])
2023-11-05 03:22:05 -08:00
const isTriggerOrder =
tradeForm.tradeType === TriggerOrderTypes.STOP_LOSS ||
tradeForm.tradeType === TriggerOrderTypes.TAKE_PROFIT
2022-12-21 20:19:00 -08:00
return (
<div className="space-y-2 px-3 md:px-4">
2022-12-30 10:26:45 -08:00
<div className="flex justify-between text-xs">
<p>{t('trade:order-value')}</p>
<p className="font-mono text-th-fgd-2">
2023-05-13 04:41:42 -07:00
{orderValue ? <FormatNumericValue value={orderValue} isUsd /> : ''}
2022-12-30 10:26:45 -08:00
</p>
</div>
2022-12-21 20:19:00 -08:00
<HealthImpact maintProjectedHealth={maintProjectedHealth} small />
{borrowAmount && balanceBank ? (
<>
<div className="flex justify-between text-xs">
<Tooltip
content={
balance
? t('trade:tooltip-borrow-balance', {
balance: formatNumericValue(balance),
borrowAmount: formatNumericValue(borrowAmount),
token: formatTokenSymbol(balanceBank.name),
rate: formatNumericValue(
balanceBank.getBorrowRateUi(),
2023-07-21 11:47:53 -07:00
2,
),
})
: t('trade:tooltip-borrow-no-balance', {
borrowAmount: formatNumericValue(borrowAmount),
token: formatTokenSymbol(balanceBank.name),
rate: formatNumericValue(
balanceBank.getBorrowRateUi(),
2023-07-21 11:47:53 -07:00
2,
),
})
2023-03-17 05:48:38 -07:00
}
delay={100}
>
<p className="tooltip-underline">{t('borrow-amount')}</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={borrowAmount}
decimals={balanceBank.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{formatTokenSymbol(balanceBank.name)}
</span>
</p>
</div>
<div className="flex justify-between text-xs">
<Tooltip
content={t('loan-origination-fee-tooltip', {
fee: `${(
balanceBank.loanOriginationFeeRate.toNumber() * 100
).toFixed(3)}%`,
})}
delay={100}
>
<p className="tooltip-underline">{t('loan-origination-fee')}</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={
borrowAmount * balanceBank.loanOriginationFeeRate.toNumber()
}
decimals={balanceBank.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{formatTokenSymbol(balanceBank.name)}
</span>
</p>
</div>
</>
2023-03-17 05:48:38 -07:00
) : null}
{/* <div className="flex justify-between text-xs">
2022-12-21 20:19:00 -08:00
<Tooltip content="The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw.">
<p className="tooltip-underline">{t('free-collateral')}</p>
</Tooltip>
<p className="text-th-fgd-2">
2023-01-24 16:54:24 -08:00
{group && mangoAccount ? (
<FormatNumericValue
value={toUiDecimalsForQuote(
mangoAccount.getCollateralValue(group)
)}
decimals={2}
isUsd
/>
) : (
''
)}
2022-12-21 20:19:00 -08:00
</p>
</div> */}
2022-12-21 20:19:00 -08:00
<Slippage />
{avgEntryPrice && selectedMarket instanceof PerpMarket ? (
<div className="flex justify-between text-xs">
<p>{t('trade:avg-entry-price')}</p>
<p className="text-th-fgd-2">
<FormatNumericValue
value={avgEntryPrice}
decimals={getDecimalCount(selectedMarket.tickSize)}
isUsd
/>
</p>
</div>
) : null}
2023-11-05 03:22:05 -08:00
{selectedMarket instanceof Serum3Market && !isTriggerOrder ? (
<div className="flex justify-between text-xs">
<p>{t('common:route')}</p>
<p className="text-th-fgd-2">Openbook</p>
</div>
) : null}
2022-12-21 20:19:00 -08:00
</div>
)
}
export default TradeSummary