2021-05-17 22:33:04 -07:00
|
|
|
import React, { useCallback, useMemo, useState } from 'react'
|
2021-04-19 06:45:59 -07:00
|
|
|
import useMangoStore from '../stores/useMangoStore'
|
|
|
|
import usePrevious from '../hooks/usePrevious'
|
2021-05-17 22:33:04 -07:00
|
|
|
import useInterval from '../hooks/useInterval'
|
|
|
|
import ChartApi from '../utils/chartDataConnector'
|
2021-04-19 06:45:59 -07:00
|
|
|
import UiLock from './UiLock'
|
2021-05-17 22:33:04 -07:00
|
|
|
import ManualRefresh from './ManualRefresh'
|
2021-07-05 08:03:57 -07:00
|
|
|
import useOraclePrice from '../hooks/useOraclePrice'
|
2021-07-20 07:21:58 -07:00
|
|
|
import DayHighLow from './DayHighLow'
|
2021-08-13 11:54:09 -07:00
|
|
|
import { useEffect } from 'react'
|
2021-08-15 06:31:59 -07:00
|
|
|
import { formatUsdValue } from '../utils'
|
2021-08-23 10:58:37 -07:00
|
|
|
import { PerpMarket } from '@blockworks-foundation/mango-client'
|
2021-08-13 11:54:09 -07:00
|
|
|
|
2021-08-20 08:01:18 -07:00
|
|
|
function calculateFundingRate(perpStats, perpMarket, oraclePrice) {
|
2021-08-13 11:54:09 -07:00
|
|
|
const oldestStat = perpStats[perpStats.length - 1]
|
|
|
|
const latestStat = perpStats[0]
|
|
|
|
|
2021-08-25 11:30:16 -07:00
|
|
|
if (!latestStat || !perpMarket || !oraclePrice) return 0.0
|
2021-08-13 11:54:09 -07:00
|
|
|
|
2021-08-20 08:01:18 -07:00
|
|
|
// Averaging long and short funding excludes socialized loss
|
|
|
|
const startFunding =
|
|
|
|
(parseFloat(oldestStat.longFunding) + parseFloat(oldestStat.shortFunding)) /
|
|
|
|
2
|
|
|
|
const endFunding =
|
|
|
|
(parseFloat(latestStat.longFunding) + parseFloat(latestStat.shortFunding)) /
|
|
|
|
2
|
|
|
|
const fundingDifference = endFunding - startFunding
|
|
|
|
|
2021-08-13 11:54:09 -07:00
|
|
|
const fundingInQuoteDecimals =
|
|
|
|
fundingDifference / Math.pow(10, perpMarket.quoteDecimals)
|
|
|
|
|
2021-08-20 08:01:18 -07:00
|
|
|
// TODO - use avgPrice and discard oraclePrice once stats are better
|
|
|
|
// const avgPrice = (latestStat.baseOraclePrice + oldestStat.baseOraclePrice) / 2
|
|
|
|
const basePriceInBaseLots = oraclePrice * perpMarket.baseLotsToNumber(1)
|
2021-08-13 11:54:09 -07:00
|
|
|
return (fundingInQuoteDecimals / basePriceInBaseLots) * 100
|
|
|
|
}
|
|
|
|
|
2021-08-23 10:58:37 -07:00
|
|
|
function parseOpenInterest(perpMarket: PerpMarket) {
|
|
|
|
if (!perpMarket) return 0
|
2021-08-13 12:06:17 -07:00
|
|
|
|
2021-08-23 10:58:37 -07:00
|
|
|
return perpMarket.baseLotsToNumber(perpMarket.openInterest) / 2
|
2021-08-13 11:54:09 -07:00
|
|
|
}
|
2021-07-20 07:21:58 -07:00
|
|
|
|
2021-04-19 06:45:59 -07:00
|
|
|
const MarketHeader = () => {
|
2021-07-05 08:03:57 -07:00
|
|
|
const oraclePrice = useOraclePrice()
|
2021-08-20 05:10:59 -07:00
|
|
|
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
2021-06-16 22:37:35 -07:00
|
|
|
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
2021-08-13 11:54:09 -07:00
|
|
|
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
2021-06-23 08:32:33 -07:00
|
|
|
const baseSymbol = marketConfig.baseSymbol
|
|
|
|
const selectedMarketName = marketConfig.name
|
2021-08-20 05:10:59 -07:00
|
|
|
const isPerpMarket = marketConfig.kind === 'perp'
|
2021-05-17 22:33:04 -07:00
|
|
|
const previousMarketName: string = usePrevious(selectedMarketName)
|
2021-06-23 08:32:33 -07:00
|
|
|
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
2021-05-17 22:33:04 -07:00
|
|
|
const connected = useMangoStore((s) => s.wallet.connected)
|
|
|
|
|
|
|
|
const [ohlcv, setOhlcv] = useState(null)
|
|
|
|
const [loading, setLoading] = useState(false)
|
2021-08-13 11:54:09 -07:00
|
|
|
const [perpStats, setPerpStats] = useState([])
|
2021-08-25 14:39:48 -07:00
|
|
|
const change = ohlcv ? ((ohlcv.c[0] - ohlcv.o[0]) / ohlcv.o[0]) * 100 : ''
|
2021-05-17 22:33:04 -07:00
|
|
|
const volume = ohlcv ? ohlcv.v[0] : '--'
|
|
|
|
|
2021-08-13 14:30:39 -07:00
|
|
|
const fetchPerpStats = useCallback(async () => {
|
2021-08-25 10:40:32 -07:00
|
|
|
const urlParams = new URLSearchParams({ mangoGroup: groupConfig.name })
|
|
|
|
urlParams.append('market', selectedMarketName)
|
2021-08-13 11:54:09 -07:00
|
|
|
const perpStats = await fetch(
|
2021-08-25 10:40:32 -07:00
|
|
|
`https://mango-stats-v3.herokuapp.com/perp/funding_rate?` + urlParams
|
2021-08-13 11:54:09 -07:00
|
|
|
)
|
|
|
|
const parsedPerpStats = await perpStats.json()
|
|
|
|
setPerpStats(parsedPerpStats)
|
|
|
|
}, [selectedMarketName])
|
|
|
|
|
2021-08-13 14:30:39 -07:00
|
|
|
useEffect(() => {
|
2021-08-20 05:10:59 -07:00
|
|
|
if (isPerpMarket) {
|
2021-08-13 11:54:09 -07:00
|
|
|
fetchPerpStats()
|
|
|
|
}
|
2021-08-25 14:39:48 -07:00
|
|
|
}, [isPerpMarket, fetchPerpStats])
|
2021-08-13 11:54:09 -07:00
|
|
|
|
2021-05-17 22:33:04 -07:00
|
|
|
const fetchOhlcv = useCallback(async () => {
|
2021-06-12 19:03:05 -07:00
|
|
|
if (!selectedMarketName) return
|
|
|
|
|
2021-05-17 22:33:04 -07:00
|
|
|
// calculate from and to date (0:00UTC to 23:59:59UTC)
|
|
|
|
const date = new Date()
|
2021-05-20 04:21:36 -07:00
|
|
|
const utcFrom = new Date(
|
2021-06-05 11:40:07 -07:00
|
|
|
Date.UTC(
|
|
|
|
date.getFullYear(),
|
|
|
|
date.getUTCMonth(),
|
|
|
|
date.getUTCDate(),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0
|
|
|
|
)
|
2021-05-17 22:33:04 -07:00
|
|
|
)
|
2021-05-20 04:21:36 -07:00
|
|
|
const utcTo = new Date(
|
2021-06-05 11:40:07 -07:00
|
|
|
Date.UTC(
|
|
|
|
date.getFullYear(),
|
|
|
|
date.getUTCMonth(),
|
|
|
|
date.getUTCDate(),
|
|
|
|
23,
|
|
|
|
59,
|
|
|
|
59
|
|
|
|
)
|
2021-05-17 22:33:04 -07:00
|
|
|
)
|
2021-06-05 11:40:07 -07:00
|
|
|
|
2021-05-17 22:33:04 -07:00
|
|
|
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])
|
2021-04-19 06:45:59 -07:00
|
|
|
|
|
|
|
return (
|
2021-05-17 22:33:04 -07:00
|
|
|
<div
|
2021-07-23 07:07:05 -07:00
|
|
|
className={`flex items-end sm:items-center justify-between pt-4 px-6 md:pb-1 md:pt-8 md:px-6`}
|
2021-05-17 22:33:04 -07:00
|
|
|
>
|
|
|
|
<div className="flex flex-col sm:flex-row sm:items-center">
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="pb-3 sm:pb-0 pr-8">
|
2021-05-17 22:33:04 -07:00
|
|
|
<div className="flex items-center">
|
|
|
|
<img
|
|
|
|
alt=""
|
|
|
|
width="24"
|
|
|
|
height="24"
|
2021-06-16 22:37:35 -07:00
|
|
|
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
2021-05-17 22:33:04 -07:00
|
|
|
className={`mr-2.5`}
|
|
|
|
/>
|
|
|
|
|
2021-06-23 06:16:37 -07:00
|
|
|
<div className="font-semibold pr-0.5 text-xl">{baseSymbol}</div>
|
|
|
|
<span className="text-th-fgd-4 text-xl">
|
2021-08-20 05:10:59 -07:00
|
|
|
{isPerpMarket ? '-' : '/'}
|
2021-06-23 06:16:37 -07:00
|
|
|
</span>
|
|
|
|
<div className="font-semibold pl-0.5 text-xl">
|
2021-08-20 05:10:59 -07:00
|
|
|
{isPerpMarket ? 'PERP' : groupConfig.quoteSymbol}
|
2021-05-17 22:33:04 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="pr-6">
|
2021-08-16 06:31:25 -07:00
|
|
|
<div className="text-th-fgd-3 tiny-text pb-0.5">Oracle price</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="font-semibold text-th-fgd-1 text-xs">
|
2021-08-15 06:31:59 -07:00
|
|
|
{oraclePrice ? formatUsdValue(oraclePrice) : '--'}
|
2021-05-17 22:33:04 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="pr-4">
|
2021-08-16 06:31:25 -07:00
|
|
|
<div className="text-th-fgd-3 tiny-text pb-0.5">24h Change</div>
|
2021-08-25 14:39:48 -07:00
|
|
|
{change ? (
|
2021-08-13 14:45:22 -07:00
|
|
|
<div
|
|
|
|
className={`font-semibold text-xs ${
|
2021-08-25 14:39:48 -07:00
|
|
|
change > 0
|
2021-08-13 14:45:22 -07:00
|
|
|
? `text-th-green`
|
2021-08-25 14:39:48 -07:00
|
|
|
: change < 0
|
2021-08-13 14:45:22 -07:00
|
|
|
? `text-th-red`
|
|
|
|
: `text-th-fgd-1`
|
|
|
|
}`}
|
|
|
|
>
|
2021-08-25 14:39:48 -07:00
|
|
|
{change ? change.toFixed(2) + '%' : '--'}
|
2021-08-13 14:45:22 -07:00
|
|
|
</div>
|
2021-05-17 22:33:04 -07:00
|
|
|
) : (
|
|
|
|
<MarketDataLoader />
|
|
|
|
)}
|
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="pr-6">
|
2021-08-16 06:31:25 -07:00
|
|
|
<div className="text-th-fgd-3 tiny-text pb-0.5">24h Vol</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="font-semibold text-th-fgd-1 text-xs">
|
2021-06-17 17:32:14 -07:00
|
|
|
{ohlcv && !loading && volume ? (
|
2021-05-17 22:33:04 -07:00
|
|
|
volume !== '--' ? (
|
|
|
|
<>
|
|
|
|
{volume.toFixed(2)}
|
2021-08-16 06:31:25 -07:00
|
|
|
<span className="ml-1 text-th-fgd-3 tiny-text pb-0.5">
|
2021-07-23 07:07:05 -07:00
|
|
|
{baseSymbol}
|
|
|
|
</span>
|
2021-05-17 22:33:04 -07:00
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
volume
|
|
|
|
)
|
|
|
|
) : (
|
|
|
|
<MarketDataLoader />
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-08-20 05:10:59 -07:00
|
|
|
{isPerpMarket ? (
|
2021-07-20 07:21:58 -07:00
|
|
|
<>
|
|
|
|
<div className="pr-6">
|
2021-08-16 06:31:25 -07:00
|
|
|
<div className="text-th-fgd-3 tiny-text pb-0.5">
|
2021-08-13 12:10:50 -07:00
|
|
|
Avg Funding Rate (1h)
|
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="font-semibold text-th-fgd-1 text-xs">
|
2021-08-25 10:43:17 -07:00
|
|
|
{selectedMarket ? (
|
2021-08-21 06:02:51 -07:00
|
|
|
`${calculateFundingRate(
|
|
|
|
perpStats,
|
|
|
|
selectedMarket,
|
|
|
|
oraclePrice
|
|
|
|
)?.toFixed(4)}%`
|
|
|
|
) : (
|
|
|
|
<MarketDataLoader />
|
|
|
|
)}
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="pr-6">
|
2021-08-16 06:31:25 -07:00
|
|
|
<div className="text-th-fgd-3 tiny-text pb-0.5">
|
|
|
|
Open Interest
|
|
|
|
</div>
|
2021-08-13 11:54:09 -07:00
|
|
|
<div className="font-semibold text-th-fgd-1 text-xs">
|
2021-08-23 10:58:37 -07:00
|
|
|
{selectedMarket ? (
|
2021-08-23 11:18:34 -07:00
|
|
|
`${parseOpenInterest(
|
|
|
|
selectedMarket as PerpMarket
|
|
|
|
)} ${baseSymbol}`
|
2021-08-21 06:02:51 -07:00
|
|
|
) : (
|
|
|
|
<MarketDataLoader />
|
|
|
|
)}
|
2021-08-13 11:54:09 -07:00
|
|
|
</div>
|
2021-07-20 07:21:58 -07:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : null}
|
2021-08-25 11:30:16 -07:00
|
|
|
<DayHighLow
|
|
|
|
high={ohlcv?.h[0]}
|
|
|
|
low={ohlcv?.l[0]}
|
|
|
|
latest={oraclePrice?.toNumber()}
|
|
|
|
/>
|
2021-05-17 22:33:04 -07:00
|
|
|
</div>
|
2021-04-19 06:45:59 -07:00
|
|
|
</div>
|
2021-08-16 06:31:25 -07:00
|
|
|
<div className="flex items-center">
|
2021-04-19 06:45:59 -07:00
|
|
|
<UiLock />
|
2021-06-23 08:32:33 -07:00
|
|
|
{connected && mangoAccount ? <ManualRefresh className="pl-2" /> : null}
|
2021-04-19 06:45:59 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default MarketHeader
|
|
|
|
|
2021-08-21 06:02:51 -07:00
|
|
|
export const MarketDataLoader = () => (
|
2021-07-20 07:21:58 -07:00
|
|
|
<div className="animate-pulse bg-th-bkg-3 h-3.5 mt-0.5 w-10 rounded-sm" />
|
2021-04-19 06:45:59 -07:00
|
|
|
)
|