diff --git a/components/stats-page/StatsPerps.tsx b/components/stats-page/StatsPerps.tsx index 7b9db1a8..5769aad9 100644 --- a/components/stats-page/StatsPerps.tsx +++ b/components/stats-page/StatsPerps.tsx @@ -1,57 +1,129 @@ +import { PerpMarket } from '@blockworks-foundation/mango-client' import { useState } from 'react' -import useMangoStats from '../../hooks/useMangoStats' +import useMangoGroupConfig from '../../hooks/useMangoGroupConfig' +import useMangoStore from '../../stores/useMangoStore' import Chart from '../Chart' +import BN from 'bn.js' const icons = { - BTC: '/assets/icons/btc.svg', - ETH: '/assets/icons/eth.svg', - SOL: '/assets/icons/sol.svg', - SRM: '/assets/icons/srm.svg', - USDT: '/assets/icons/usdt.svg', - USDC: '/assets/icons/usdc.svg', - WUSDT: '/assets/icons/usdt.svg', + 'BTC-PERP': '/assets/icons/btc.svg', + 'ETH-PERP': '/assets/icons/eth.svg', + 'SOL-PERP': '/assets/icons/sol.svg', + 'SRM-PERP': '/assets/icons/srm.svg', + 'USDT-PERP': '/assets/icons/usdt.svg', + 'MNGO-PERP': '/assets/icons/mngo.svg', } -export default function StatsPerps() { - const [selectedAsset, setSelectedAsset] = useState('BTC') - const { latestStats, stats } = useMangoStats() +function calculateFundingRate( + oldestLongFunding, + oldestShortFunding, + latestLongFunding, + latestShortFunding, + perpMarket, + oraclePrice +) { + if (!perpMarket || !oraclePrice) return 0.0 - const selectedStatsData = stats.filter((stat) => stat.name === selectedAsset) + // 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 }) { + const [selectedAsset, setSelectedAsset] = useState('BTC-PERP') + const marketConfigs = useMangoGroupConfig().perpMarkets + const selectedMarketConfig = marketConfigs.find( + (m) => m.name === selectedAsset + ) + const markets = Object.values( + useMangoStore.getState().selectedMangoGroup.markets + ).filter((m) => m instanceof PerpMarket) as PerpMarket[] + const selectedMarket = markets.find((m) => + m.publicKey.equals(selectedMarketConfig.publicKey) + ) + const selectedStatsData = perpStats.filter( + (stat) => stat.name === selectedAsset + ) + + 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, + } + }) return ( <>
- {latestStats.map((stat) => ( + {marketConfigs.map((market) => (
setSelectedAsset(stat.name)} - key={stat.name as string} + onClick={() => setSelectedAsset(market.name)} + key={market.name as string} > - {stat.name} + {market.name}
))}
-
+
`${(x * 100).toFixed(5)}%`} - type="bar" + data={perpsData} + labelFormat={(x) => `${x.toFixed(4)}%`} + type="area" + /> +
+
+ + x && + x.toLocaleString(undefined, { + maximumFractionDigits: selectedMarketConfig.baseDecimals, + }) + selectedMarketConfig.baseSymbol + } + type="area" />
@@ -61,7 +133,7 @@ export default function StatsPerps() { const AssetHeader = ({ asset }) => { switch (asset) { - case 'BTC': + case 'BTC-PERP': return (
{ height="24" className="mr-2.5" /> - Bitcoin + Bitcoin Perpetual Futures
) - case 'ETH': + case 'ETH-PERP': return (
{ height="24" className="mr-2.5" /> - Ethereum + Ethereum Perpetual Futures
) - case 'SOL': + case 'SOL-PERP': return (
{ height="24" className="mr-2.5" /> - Solana + Solana Perpetual Futures
) case 'SRM': @@ -110,20 +182,7 @@ const AssetHeader = ({ asset }) => { height="24" className="mr-2.5" /> - Serum -
- ) - case 'USDC': - return ( -
- {icons[asset]} - USD Coin + Serum Perpetual Futures
) default: @@ -136,7 +195,7 @@ const AssetHeader = ({ asset }) => { height="24" className="mr-2.5" /> - Bitcoin + Bitcoin Perpetual Futures ) } diff --git a/hooks/useMangoStats.tsx b/hooks/useMangoStats.tsx index 5e825432..c9d8f584 100644 --- a/hooks/useMangoStats.tsx +++ b/hooks/useMangoStats.tsx @@ -17,6 +17,18 @@ const useMangoStats = () => { utilization: '0', }, ]) + const [perpStats, setPerpStats] = useState([ + { + name: '', + hourly: '', + oldestLongFunding: 0, + oldestShortFunding: 0, + latestLongFunding: 0, + latestShortFunding: 0, + openInterest: 0, + baseOraclePrice: 0, + }, + ]) const [latestStats, setLatestStats] = useState([]) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoGroupName = useMangoStore((s) => s.selectedMangoGroup.name) @@ -34,6 +46,17 @@ const useMangoStats = () => { fetchHistoricalStats() }, [mangoGroupName]) + useEffect(() => { + const fetchHistoricalPerpStats = async () => { + const response = await fetch( + `https://mango-stats-v3.herokuapp.com/perp?mangoGroup=${mangoGroupName}` + ) + const stats = await response.json() + setPerpStats(stats) + } + fetchHistoricalPerpStats() + }, [mangoGroupName]) + useEffect(() => { const getLatestStats = async () => { if (mangoGroup) { @@ -75,7 +98,7 @@ const useMangoStats = () => { getLatestStats() }, [mangoGroup]) - return { latestStats, stats } + return { latestStats, stats, perpStats } } export default useMangoStats diff --git a/pages/stats.tsx b/pages/stats.tsx index bb96fd5a..2199d71f 100644 --- a/pages/stats.tsx +++ b/pages/stats.tsx @@ -9,13 +9,13 @@ import useMangoStats from '../hooks/useMangoStats' const TABS = [ 'Totals', 'Assets', - // 'Perps', + 'Perps', // 'Markets', // 'Liquidations', ] export default function StatsPage() { - const { latestStats, stats } = useMangoStats() + const { latestStats, stats, perpStats } = useMangoStats() const [activeTab, setActiveTab] = useState(TABS[0]) const handleTabChange = (tabName) => { @@ -53,6 +53,7 @@ export default function StatsPage() { activeTab={activeTab} latestStats={latestStats} stats={stats} + perpStats={perpStats} /> @@ -60,14 +61,14 @@ export default function StatsPage() { ) } -const TabContent = ({ activeTab, latestStats, stats }) => { +const TabContent = ({ activeTab, latestStats, stats, perpStats }) => { switch (activeTab) { case 'Totals': return case 'Assets': return case 'Perps': - return + return case 'Markets': return
Markets
case 'Liquidations':