mango-ui-v3/components/MarketHeader.tsx

237 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useMemo, useState } from 'react'
import useMangoStore from '../stores/useMangoStore'
import usePrevious from '../hooks/usePrevious'
import useInterval from '../hooks/useInterval'
import ChartApi from '../utils/chartDataConnector'
import UiLock from './UiLock'
import ManualRefresh from './ManualRefresh'
import useOraclePrice from '../hooks/useOraclePrice'
import DayHighLow from './DayHighLow'
import { useEffect } from 'react'
import { formatUsdValue } from '../utils'
function calculateFundingRate(perpStats, perpMarket) {
const oldestStat = perpStats[perpStats.length - 1]
const latestStat = perpStats[0]
if (!latestStat || !perpMarket) return null
const fundingDifference = latestStat.longFunding - oldestStat.longFunding
const fundingInQuoteDecimals =
fundingDifference / Math.pow(10, perpMarket.quoteDecimals)
const basePriceInBaseLots =
latestStat.baseOraclePrice * perpMarket.baseLotsToNumber(1)
return (fundingInQuoteDecimals / basePriceInBaseLots) * 100
}
function parseOpenInterest(perpStats, perpMarket) {
if (!perpStats?.length || !perpMarket) return 0
return perpMarket.baseLotsToNumber(perpStats[0].openInterest / 2)
}
const MarketHeader = () => {
const oraclePrice = useOraclePrice()
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const mangoGroupName = useMangoStore((s) => s.selectedMangoGroup.name)
const baseSymbol = marketConfig.baseSymbol
const selectedMarketName = marketConfig.name
const previousMarketName: string = usePrevious(selectedMarketName)
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
const connected = useMangoStore((s) => s.wallet.connected)
const [ohlcv, setOhlcv] = useState(null)
const [loading, setLoading] = useState(false)
const [perpStats, setPerpStats] = useState([])
const [spotStats, setSpotStats] = useState(null)
// const change = ohlcv ? ((ohlcv.c[0] - ohlcv.o[0]) / ohlcv.o[0]) * 100 : '--'
const volume = ohlcv ? ohlcv.v[0] : '--'
const fetchSpotStats = useCallback(async () => {
const urlParams = new URLSearchParams({ mangoGroup: mangoGroupName })
urlParams.append('market', selectedMarketName)
const spotStats = await fetch(
'https://mango-stats-v3.herokuapp.com/spot/change/24?' + urlParams
)
const parsedSpotStats = await spotStats.json()
setSpotStats(parsedSpotStats)
}, [selectedMarketName, mangoGroupName])
const fetchPerpStats = useCallback(async () => {
const perpStats = await fetch(
`https://mango-stats-v3.herokuapp.com/perp/funding_rate?market=${selectedMarketName}`
)
const parsedPerpStats = await perpStats.json()
setPerpStats(parsedPerpStats)
}, [selectedMarketName])
useEffect(() => {
if (!selectedMarketName.includes('PERP')) {
fetchSpotStats()
}
}, [fetchSpotStats, selectedMarketName])
useEffect(() => {
if (selectedMarketName.includes('PERP')) {
fetchPerpStats()
}
}, [fetchPerpStats, selectedMarketName])
const fetchOhlcv = useCallback(async () => {
if (!selectedMarketName) return
// calculate from and to date (0:00UTC to 23:59:59UTC)
const date = new Date()
const utcFrom = new Date(
Date.UTC(
date.getFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
0,
0,
0
)
)
const utcTo = new Date(
Date.UTC(
date.getFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
23,
59,
59
)
)
const from = utcFrom.getTime() / 1000
const to = utcTo.getTime() / 1000
const ohlcv = await ChartApi.getOhlcv(selectedMarketName, '1D', from, to)
if (ohlcv) {
setOhlcv(ohlcv)
setLoading(false)
}
}, [selectedMarketName])
useInterval(async () => {
fetchOhlcv()
}, 5000)
useMemo(() => {
if (previousMarketName !== selectedMarketName) {
setLoading(true)
}
}, [selectedMarketName])
return (
<div
className={`flex items-end sm:items-center justify-between pt-4 px-6 md:pb-1 md:pt-8 md:px-6`}
>
<div className="flex flex-col sm:flex-row sm:items-center">
<div className="pb-3 sm:pb-0 pr-8">
<div className="flex items-center">
<img
alt=""
width="24"
height="24"
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
<div className="font-semibold pr-0.5 text-xl">{baseSymbol}</div>
<span className="text-th-fgd-4 text-xl">
{selectedMarketName.includes('PERP') ? '' : '/'}
</span>
<div className="font-semibold pl-0.5 text-xl">
{selectedMarketName.split(/\/|-/)[1]}
</div>
</div>
</div>
<div className="flex items-center">
<div className="pr-6">
<div className="text-th-fgd-3 tiny-text pb-0.5">Oracle price</div>
<div className="font-semibold text-th-fgd-1 text-xs">
{oraclePrice ? formatUsdValue(oraclePrice) : '--'}
</div>
</div>
<div className="pr-4">
<div className="text-th-fgd-3 tiny-text pb-0.5">24h Change</div>
{spotStats?.change ? (
<div
className={`font-semibold text-xs ${
spotStats.change > 0
? `text-th-green`
: spotStats.change < 0
? `text-th-red`
: `text-th-fgd-1`
}`}
>
{spotStats.change.toFixed(2) + '%'}
</div>
) : (
<MarketDataLoader />
)}
</div>
<div className="pr-6">
<div className="text-th-fgd-3 tiny-text pb-0.5">24h Vol</div>
<div className="font-semibold text-th-fgd-1 text-xs">
{ohlcv && !loading && volume ? (
volume !== '--' ? (
<>
{volume.toFixed(2)}
<span className="ml-1 text-th-fgd-3 tiny-text pb-0.5">
{baseSymbol}
</span>
</>
) : (
volume
)
) : (
<MarketDataLoader />
)}
</div>
</div>
<DayHighLow
high={spotStats?.high}
low={spotStats?.low}
latest={spotStats?.latest}
/>
{selectedMarketName.includes('PERP') ? (
<>
<div className="pr-6">
<div className="text-th-fgd-3 tiny-text pb-0.5">
Avg Funding Rate (1h)
</div>
<div className="font-semibold text-th-fgd-1 text-xs">
{calculateFundingRate(perpStats, selectedMarket)?.toFixed(6)}%
</div>
</div>
<div className="pr-6">
<div className="text-th-fgd-3 tiny-text pb-0.5">
Open Interest
</div>
<div className="font-semibold text-th-fgd-1 text-xs">
{parseOpenInterest(perpStats, selectedMarket)} {baseSymbol}
</div>
</div>
</>
) : null}
</div>
</div>
<div className="flex items-center">
<UiLock />
{connected && mangoAccount ? <ManualRefresh className="pl-2" /> : null}
</div>
</div>
)
}
export default MarketHeader
const MarketDataLoader = () => (
<div className="animate-pulse bg-th-bkg-3 h-3.5 mt-0.5 w-10 rounded-sm" />
)