import React, { useCallback, useEffect, useMemo, useState } from 'react' import useMangoStore, { SECONDS } 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 { getPrecisionDigits, patchInternalMarketName, perpContractPrecision, usdFormatter, } from '../utils' import { PerpMarket } from '@blockworks-foundation/mango-client' import BN from 'bn.js' import { useViewport } from '../hooks/useViewport' import { breakpoints } from './TradePageGrid' import { useTranslation } from 'next-i18next' import SwitchMarketDropdown from './SwitchMarketDropdown' import Tooltip from './Tooltip' export function calculateFundingRate(perpStats, perpMarket) { const oldestStat = perpStats[perpStats.length - 1] const latestStat = perpStats[0] if (!latestStat || !(perpMarket instanceof PerpMarket)) return 0.0 // 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 const fundingInQuoteDecimals = fundingDifference / Math.pow(10, perpMarket.quoteDecimals) const avgPrice = (parseFloat(latestStat.baseOraclePrice) + parseFloat(oldestStat.baseOraclePrice)) / 2 const basePriceInBaseLots = avgPrice * perpMarket.baseLotsToNumber(new BN(1)) return (fundingInQuoteDecimals / basePriceInBaseLots) * 100 } export function parseOpenInterest(perpMarket: PerpMarket) { if (!perpMarket || !(perpMarket instanceof PerpMarket)) return 0 return perpMarket.baseLotsToNumber(perpMarket.openInterest) / 2 } const MarketDetails = () => { const { t } = useTranslation('common') const oraclePrice = useOraclePrice() const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) const marketConfig = useMangoStore((s) => s.selectedMarket.config) const selectedMarket = useMangoStore((s) => s.selectedMarket.current) const baseSymbol = marketConfig.baseSymbol const selectedMarketName = marketConfig.name const isPerpMarket = marketConfig.kind === 'perp' const previousMarketName: string = usePrevious(selectedMarketName) const connected = useMangoStore((s) => s.wallet.connected) const { width } = useViewport() const isMobile = width ? width < breakpoints.sm : false const [ohlcv, setOhlcv] = useState(null) const [change24h, setChange24h] = useState(0) const [, setLoading] = useState(false) const [perpStats, setPerpStats] = useState([]) const [perpVolume, setPerpVolume] = useState(0) const fetchMarketInfo = useCallback(async () => { const marketInfo = await fetch( `https://event-history-api-candles.herokuapp.com/markets/${patchInternalMarketName( selectedMarketName )}` ) const parsedMarketInfo = await marketInfo.json() setChange24h(parsedMarketInfo?.change24h) }, [selectedMarketName]) useInterval(() => { fetchMarketInfo() }, 120 * SECONDS) useEffect(() => { fetchMarketInfo() }, [fetchMarketInfo]) const fetchPerpStats = useCallback(async () => { const urlParams = new URLSearchParams({ mangoGroup: groupConfig.name }) urlParams.append('market', selectedMarketName) const perpStats = await fetch( `https://mango-stats-v3.herokuapp.com/perp/funding_rate?` + urlParams ) const parsedPerpStats = await perpStats.json() setPerpStats(parsedPerpStats) const perpVolume = await fetch( `https://event-history-api.herokuapp.com/stats/perps/${marketConfig.publicKey.toString()}` ) const parsedPerpVolume = await perpVolume.json() setPerpVolume(parsedPerpVolume?.data?.volume) }, [selectedMarketName, marketConfig, groupConfig.name]) useInterval(() => { if (isPerpMarket) { fetchPerpStats() } }, 120 * SECONDS) useEffect(() => { if (isPerpMarket) { fetchPerpStats() } }, [isPerpMarket, fetchPerpStats]) 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]) // TODO: don't spam db // useInterval(async () => { // fetchOhlcv() // }, 5000) useMemo(() => { if (previousMarketName !== selectedMarketName) { setLoading(true) fetchOhlcv() } }, [selectedMarketName]) const funding1h = calculateFundingRate(perpStats, selectedMarket) const [funding1hStr, fundingAprStr] = funding1h ? [funding1h.toFixed(4), (funding1h * 24 * 365).toFixed(2)] : ['-', '-'] return (