import { PerpMarket, ZERO_BN } from '@blockworks-foundation/mango-client' import { useState, useMemo } from 'react' import useMangoStore from '../../stores/useMangoStore' import Chart from '../Chart' import BN from 'bn.js' import { perpContractPrecision } from '../../utils' import { useTranslation } from 'next-i18next' import { marketsSelector } from '../../stores/selectors' import dayjs from 'dayjs' import TabButtons from 'components/TabButtons' function calculateFundingRate( oldestLongFunding, oldestShortFunding, latestLongFunding, latestShortFunding, perpMarket: PerpMarket, oraclePrice ) { if (!perpMarket || !oraclePrice) return 0.0 // Averaging long and short funding excludes socialized loss const startFunding = (parseFloat(oldestLongFunding) + parseFloat(oldestShortFunding)) / 2 const endFunding = (parseFloat(latestLongFunding) + parseFloat(latestShortFunding)) / 2 const fundingDifference = endFunding - startFunding const fundingInQuoteDecimals = fundingDifference / Math.pow(10, perpMarket.quoteDecimals) // TODO - use avgPrice and discard oraclePrice once stats are better // const avgPrice = (latestStat.baseOraclePrice + oldestStat.baseOraclePrice) / 2 const basePriceInBaseLots = oraclePrice * perpMarket.baseLotsToNumber(new BN(1)) return (fundingInQuoteDecimals / basePriceInBaseLots) * 100 } export default function StatsPerps({ perpStats, loadPerpStats }) { const { t } = useTranslation('common') const [selectedAsset, setSelectedAsset] = useState('BTC-PERP') const marketConfigs = useMangoStore( (s) => s.selectedMangoGroup.config ).perpMarkets const selectedMarketConfig = marketConfigs?.find( (m) => m.name === selectedAsset ) const marketDirectory = useMangoStore(marketsSelector) const markets = Object.values(marketDirectory) const perpMarkets = useMemo(() => { return markets.filter((m) => m instanceof PerpMarket) as PerpMarket[] }, [markets]) const selectedMarket = useMemo(() => { if (selectedMarketConfig) { return perpMarkets.find((m) => m.publicKey.equals(selectedMarketConfig.publicKey) ) } }, [selectedMarketConfig, perpMarkets]) const perpsData = useMemo(() => { if (!perpStats?.length || !selectedMarket) return [] let selectedStatsData = perpStats.filter( (stat) => stat.name === selectedAsset ) if (selectedAsset == 'SOL-PERP') { const startTimestamp = 1632160800000 selectedStatsData = selectedStatsData.filter( (stat) => new Date(stat.hourly).getTime() >= startTimestamp ) } const perpsData = selectedStatsData.map((x) => { return { fundingRate: calculateFundingRate( x.oldestLongFunding, x.oldestShortFunding, x.latestLongFunding, x.latestShortFunding, selectedMarket, x.baseOraclePrice ), openInterest: selectedMarket.baseLotsToNumber(x.openInterest) / 2, time: x.hourly, } }) if (selectedAsset === 'BTC-PERP') { const index = perpsData.findIndex( (x) => x.time === '2021-09-15T05:00:00.000Z' ) perpsData.splice(index, 1) } return perpsData }, [selectedAsset, perpStats, selectedMarket]) if (!selectedMarket) return null const progress = 1 - selectedMarket.liquidityMiningInfo.mngoLeft.toNumber() / selectedMarket.liquidityMiningInfo.mngoPerPeriod.toNumber() const start = selectedMarket.liquidityMiningInfo.periodStart.toNumber() const now = Date.now() / 1000 const elapsed = now - start const est = start + elapsed / progress const lmi = selectedMarket.liquidityMiningInfo const maxDepthUi = (lmi.maxDepthBps.toNumber() * selectedMarket.baseLotSize.toNumber()) / Math.pow(10, selectedMarket.baseDecimals) return ( <>
({ label: m.baseSymbol, key: m.name, }))} onClick={setSelectedAsset} showSymbolIcon />

{selectedAsset.split(/-|\//)[0]} {t('perpetual-futures')}

`${x.toFixed(4)}%`} tickFormat={(x) => x.toLocaleString(undefined, { maximumFractionDigits: 4 }) } type="area" yAxisWidth={70} loading={loadPerpStats} />
{selectedMarketConfig?.baseSymbol ? ( x && x.toLocaleString(undefined, { maximumFractionDigits: perpContractPrecision[selectedMarketConfig.baseSymbol], }) + ' ' + selectedMarketConfig.baseSymbol } type="area" loading={loadPerpStats} /> ) : null}
{lmi.mngoPerPeriod.gt(ZERO_BN) ? (

{t('liquidity-mining')}

{t('depth-rewarded')}

{maxDepthUi.toLocaleString() + ' '} {selectedMarketConfig?.baseSymbol ? ( {selectedMarketConfig.baseSymbol} ) : null}

{t('target-period-length')}

{( selectedMarket.liquidityMiningInfo.targetPeriodLength.toNumber() / 60 ).toFixed()}{' '} {t('minutes')}

{t('mngo-per-period')}

{( selectedMarket.liquidityMiningInfo.mngoPerPeriod.toNumber() / Math.pow(10, 6) ).toFixed(2)}

{t('mngo-left-period')}

{( selectedMarket.liquidityMiningInfo.mngoLeft.toNumber() / Math.pow(10, 6) ).toFixed(2)}

{t('est-period-end')}

{dayjs(est * 1000).format('DD MMM YYYY')}
{dayjs(est * 1000).format('h:mma')}

{t('period-progress')}

{(progress * 100).toFixed(2)}%
) : null} ) }