Merge pull request #18 from blockworks-foundation/perp-stats

Add funding rate and open interest graphs
This commit is contained in:
tjshipe 2021-09-06 13:35:40 -04:00 committed by GitHub
commit 4fe6edf64b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 48 deletions

View File

@ -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<string>('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<string>('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 (
<>
<div className="flex flex-col-reverse items-center sm:flex-row sm:justify-between sm:h-12 mb-4 w-full">
<AssetHeader asset={selectedAsset} />
<div className="flex pb-4 sm:pb-0">
{latestStats.map((stat) => (
{marketConfigs.map((market) => (
<div
className={`px-2 py-1 ml-2 rounded-md cursor-pointer default-transition bg-th-bkg-3
${
selectedAsset === stat.name
selectedAsset === market.name
? `ring-1 ring-inset ring-th-primary text-th-primary`
: `text-th-fgd-1 opacity-50 hover:opacity-100`
}
`}
onClick={() => setSelectedAsset(stat.name)}
key={stat.name as string}
onClick={() => setSelectedAsset(market.name)}
key={market.name as string}
>
{stat.name}
{market.name}
</div>
))}
</div>
</div>
<div className="grid grid-flow-col grid-cols-1 grid-rows-1 gap-4 pb-8">
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 gap-4 pb-8">
<div
className="border border-th-bkg-3 relative p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Funding Rate"
title="Avg. Hourly Funding Rate"
xAxis="time"
yAxis="fundingRate"
data={selectedStatsData}
labelFormat={(x) => `${(x * 100).toFixed(5)}%`}
type="bar"
data={perpsData}
labelFormat={(x) => `${x.toFixed(4)}%`}
type="area"
/>
</div>
<div
className="border border-th-bkg-3 relative p-4 rounded-md"
style={{ height: '300px' }}
>
<Chart
title="Open Interest"
xAxis="time"
yAxis="openInterest"
data={perpsData}
labelFormat={(x) =>
x &&
x.toLocaleString(undefined, {
maximumFractionDigits: selectedMarketConfig.baseDecimals,
}) + selectedMarketConfig.baseSymbol
}
type="area"
/>
</div>
</div>
@ -61,7 +133,7 @@ export default function StatsPerps() {
const AssetHeader = ({ asset }) => {
switch (asset) {
case 'BTC':
case 'BTC-PERP':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
@ -71,10 +143,10 @@ const AssetHeader = ({ asset }) => {
height="24"
className="mr-2.5"
/>
Bitcoin
Bitcoin Perpetual Futures
</div>
)
case 'ETH':
case 'ETH-PERP':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
@ -84,10 +156,10 @@ const AssetHeader = ({ asset }) => {
height="24"
className="mr-2.5"
/>
Ethereum
Ethereum Perpetual Futures
</div>
)
case 'SOL':
case 'SOL-PERP':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
@ -97,7 +169,7 @@ const AssetHeader = ({ asset }) => {
height="24"
className="mr-2.5"
/>
Solana
Solana Perpetual Futures
</div>
)
case 'SRM':
@ -110,20 +182,7 @@ const AssetHeader = ({ asset }) => {
height="24"
className="mr-2.5"
/>
Serum
</div>
)
case 'USDC':
return (
<div className="flex items-center text-xl text-th-fgd-1">
<img
src={icons[asset]}
alt={icons[asset]}
width="24"
height="24"
className="mr-2.5"
/>
USD Coin
Serum Perpetual Futures
</div>
)
default:
@ -136,7 +195,7 @@ const AssetHeader = ({ asset }) => {
height="24"
className="mr-2.5"
/>
Bitcoin
Bitcoin Perpetual Futures
</div>
)
}

View File

@ -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<any[]>([])
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

View File

@ -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}
/>
</div>
</PageBodyContainer>
@ -60,14 +61,14 @@ export default function StatsPage() {
)
}
const TabContent = ({ activeTab, latestStats, stats }) => {
const TabContent = ({ activeTab, latestStats, stats, perpStats }) => {
switch (activeTab) {
case 'Totals':
return <StatsTotals latestStats={latestStats} stats={stats} />
case 'Assets':
return <StatsAssets latestStats={latestStats} stats={stats} />
case 'Perps':
return <StatsPerps />
return <StatsPerps perpStats={perpStats} />
case 'Markets':
return <div>Markets</div>
case 'Liquidations':