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

174 lines
5.1 KiB
TypeScript
Raw Normal View History

2023-04-17 09:25:59 -07:00
import { BookSide, PerpMarket } from '@blockworks-foundation/mango-v4'
2023-04-17 14:34:33 -07:00
import { InformationCircleIcon } from '@heroicons/react/24/outline'
2022-12-05 18:54:11 -08:00
import { useQuery } from '@tanstack/react-query'
import useMangoGroup from 'hooks/useMangoGroup'
2022-11-30 07:46:20 -08:00
import useSelectedMarket from 'hooks/useSelectedMarket'
2022-12-05 18:54:11 -08:00
import { useMemo } from 'react'
import { MANGO_DATA_API_URL } from 'utils/constants'
2023-04-17 09:25:59 -07:00
import Tooltip from '@components/shared/Tooltip'
import { useTranslation } from 'next-i18next'
import mangoStore from '@store/mangoStore'
2023-04-24 11:03:11 -07:00
import { OrderbookL2 } from 'types'
2022-12-05 18:54:11 -08:00
const fetchFundingRate = async (groupPk: string | undefined) => {
const res = await fetch(
`${MANGO_DATA_API_URL}/one-hour-funding-rate?mango-group=${groupPk}`
2022-12-05 18:54:11 -08:00
)
return await res.json()
}
export const usePerpFundingRate = () => {
const { group } = useMangoGroup()
2023-02-27 23:20:11 -08:00
const res = useQuery<
{ market_index: number; funding_rate_hourly: number }[],
Error
>(['funding-rate'], () => fetchFundingRate(group?.publicKey?.toString()), {
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
enabled: !!group,
})
2022-12-05 18:54:11 -08:00
2022-12-09 13:37:44 -08:00
return Array.isArray(res?.data) ? res : { isSuccess: false, data: null }
2022-12-05 18:54:11 -08:00
}
2022-11-30 07:46:20 -08:00
2023-03-27 04:25:23 -07:00
export const formatFunding = Intl.NumberFormat('en', {
minimumSignificantDigits: 1,
2023-04-17 09:25:59 -07:00
maximumSignificantDigits: 2,
style: 'percent',
})
2023-04-24 11:03:11 -07:00
function getImpactPriceL2(
bookside: number[][],
baseDepth: number
): number | undefined {
let total = 0
for (const level of bookside) {
total += level[1]
if (total >= baseDepth) {
return level[0]
}
}
return undefined
}
function getInstantaneousFundingRateL2(
market: PerpMarket,
orderbook: OrderbookL2
) {
const MIN_FUNDING = market.minFunding.toNumber()
const MAX_FUNDING = market.maxFunding.toNumber()
const bid = getImpactPriceL2(
orderbook.bids,
market.baseLotsToUi(market.impactQuantity)
)
const ask = getImpactPriceL2(
orderbook.asks,
market.baseLotsToUi(market.impactQuantity)
)
const indexPrice = market._uiPrice
let funding
if (bid !== undefined && ask !== undefined) {
const bookPrice = (bid + ask) / 2
funding = Math.min(
Math.max(bookPrice / indexPrice - 1, MIN_FUNDING),
MAX_FUNDING
)
} else if (bid !== undefined) {
funding = MAX_FUNDING
} else if (ask !== undefined) {
funding = MIN_FUNDING
} else {
funding = 0
}
return funding
}
2022-11-30 07:46:20 -08:00
const PerpFundingRate = () => {
const { selectedMarket } = useSelectedMarket()
2022-12-05 18:54:11 -08:00
const rate = usePerpFundingRate()
2023-04-17 09:25:59 -07:00
const { t } = useTranslation(['common', 'trade'])
2022-12-09 13:37:44 -08:00
2023-04-17 09:25:59 -07:00
const bids = mangoStore((s) => s.selectedMarket.bidsAccount)
const asks = mangoStore((s) => s.selectedMarket.asksAccount)
2023-04-24 11:03:11 -07:00
const orderbook = mangoStore((s) => s.selectedMarket.orderbook)
2022-11-30 07:46:20 -08:00
2022-12-05 18:54:11 -08:00
const fundingRate = useMemo(() => {
if (rate.isSuccess && selectedMarket instanceof PerpMarket) {
2022-12-09 13:37:44 -08:00
const marketRate = rate?.data?.find(
2022-12-05 18:54:11 -08:00
(r) => r.market_index === selectedMarket.perpMarketIndex
)
2023-03-07 08:27:46 -08:00
const funding = marketRate?.funding_rate_hourly
2023-04-17 09:25:59 -07:00
return typeof funding === 'number' ? funding : undefined
2022-12-05 18:54:11 -08:00
}
2022-12-09 13:37:44 -08:00
}, [rate, selectedMarket])
2022-12-05 18:54:11 -08:00
2023-04-24 11:03:11 -07:00
const instantaneousRate = useMemo(() => {
if (!(selectedMarket instanceof PerpMarket)) return undefined
if (bids instanceof BookSide && asks instanceof BookSide) {
return selectedMarket.getInstantaneousFundingRateUi(bids, asks).toFixed(4)
}
if (orderbook.asks.length && orderbook.bids.length) {
return (
getInstantaneousFundingRateL2(selectedMarket, orderbook) * 100
).toFixed(4)
}
return undefined
}, [orderbook, bids, asks, selectedMarket])
2022-11-30 07:46:20 -08:00
return (
2023-04-17 09:25:59 -07:00
<>
<div className="ml-6 flex-col whitespace-nowrap">
<Tooltip
content={
<>
<div>
Funding is paid continuously. The 1hr rate displayed is a
rolling average of the past 60 mins.
</div>
<div className="mt-2">
When positive, longs will pay shorts and when negative shorts
pay longs.
</div>
{typeof fundingRate === 'number' ? (
<div className="mt-2">
The 1hr rate as an APR is{' '}
{formatFunding.format(fundingRate * 8760)}.
</div>
) : null}
2023-04-24 11:03:11 -07:00
{instantaneousRate ? (
2023-04-17 09:25:59 -07:00
<div className="mt-1">
2023-04-24 11:03:11 -07:00
The latest instantaneous rate is {instantaneousRate}%
2023-04-17 09:25:59 -07:00
</div>
) : null}
</>
}
>
<div className="flex items-center">
<div className="text-xs text-th-fgd-4">
{t('trade:funding-rate')}
</div>
<InformationCircleIcon className="ml-1 h-4 w-4 text-th-fgd-4" />
</div>
</Tooltip>
<p className="font-mono text-xs text-th-fgd-2">
{selectedMarket instanceof PerpMarket &&
typeof fundingRate === 'number' ? (
<span>{formatFunding.format(fundingRate)}</span>
) : (
<span className="text-th-fgd-4">-</span>
)}
</p>
</div>
</>
2022-11-30 07:46:20 -08:00
)
}
export default PerpFundingRate