2021-08-30 16:47:43 -07:00
|
|
|
import { PerpMarket } from '@blockworks-foundation/mango-client'
|
2021-07-13 06:52:48 -07:00
|
|
|
import { useState } from 'react'
|
2021-08-30 16:47:43 -07:00
|
|
|
import useMangoGroupConfig from '../../hooks/useMangoGroupConfig'
|
|
|
|
import useMangoStore from '../../stores/useMangoStore'
|
2021-07-13 06:52:48 -07:00
|
|
|
import Chart from '../Chart'
|
2021-09-06 10:28:35 -07:00
|
|
|
import BN from 'bn.js'
|
2021-07-13 06:52:48 -07:00
|
|
|
|
|
|
|
const icons = {
|
2021-08-30 16:47:43 -07:00
|
|
|
'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',
|
2021-07-13 06:52:48 -07:00
|
|
|
}
|
|
|
|
|
2021-08-30 16:47:43 -07:00
|
|
|
function calculateFundingRate(
|
|
|
|
oldestLongFunding,
|
|
|
|
oldestShortFunding,
|
|
|
|
latestLongFunding,
|
|
|
|
latestShortFunding,
|
|
|
|
perpMarket,
|
|
|
|
oraclePrice
|
|
|
|
) {
|
|
|
|
if (!perpMarket || !oraclePrice) return 0.0
|
2021-07-13 06:52:48 -07:00
|
|
|
|
2021-08-30 16:47:43 -07:00
|
|
|
// 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
|
2021-09-06 10:28:35 -07:00
|
|
|
const basePriceInBaseLots =
|
|
|
|
oraclePrice * perpMarket.baseLotsToNumber(new BN(1))
|
2021-08-30 16:47:43 -07:00
|
|
|
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)
|
|
|
|
)
|
2021-09-25 03:17:17 -07:00
|
|
|
let selectedStatsData = perpStats.filter(
|
2021-09-01 08:00:38 -07:00
|
|
|
(stat) => stat.name === selectedAsset
|
|
|
|
)
|
2021-09-06 10:28:35 -07:00
|
|
|
|
2021-09-25 03:17:17 -07:00
|
|
|
if (selectedAsset == 'SOL-PERP') {
|
|
|
|
const startTimestamp = 1632160800000
|
|
|
|
selectedStatsData = selectedStatsData.filter(
|
|
|
|
(stat) => new Date(stat.hourly).getTime() >= startTimestamp
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-08-30 16:47:43 -07:00
|
|
|
const perpsData = selectedStatsData.map((x) => {
|
|
|
|
return {
|
|
|
|
fundingRate: calculateFundingRate(
|
|
|
|
x.oldestLongFunding,
|
|
|
|
x.oldestShortFunding,
|
|
|
|
x.latestLongFunding,
|
|
|
|
x.latestShortFunding,
|
|
|
|
selectedMarket,
|
|
|
|
x.baseOraclePrice
|
|
|
|
),
|
2021-08-30 17:24:04 -07:00
|
|
|
openInterest: selectedMarket.baseLotsToNumber(x.openInterest) / 2,
|
2021-08-30 16:47:43 -07:00
|
|
|
time: x.hourly,
|
|
|
|
}
|
|
|
|
})
|
2021-07-13 06:52:48 -07:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2021-09-19 17:36:02 -07:00
|
|
|
<div className="flex items-center justify-between mb-4 w-full">
|
2021-07-13 06:52:48 -07:00
|
|
|
<AssetHeader asset={selectedAsset} />
|
2021-09-19 17:36:02 -07:00
|
|
|
<div className="flex">
|
2021-08-30 16:47:43 -07:00
|
|
|
{marketConfigs.map((market) => (
|
2021-07-13 06:52:48 -07:00
|
|
|
<div
|
2021-09-19 17:36:02 -07:00
|
|
|
className={`bg-th-bkg-3 cursor-pointer default-transition ml-2 px-2 py-1 rounded-md whitespace-nowrap
|
2021-07-13 06:52:48 -07:00
|
|
|
${
|
2021-08-30 16:47:43 -07:00
|
|
|
selectedAsset === market.name
|
2021-07-13 06:52:48 -07:00
|
|
|
? `ring-1 ring-inset ring-th-primary text-th-primary`
|
|
|
|
: `text-th-fgd-1 opacity-50 hover:opacity-100`
|
|
|
|
}
|
|
|
|
`}
|
2021-08-30 16:47:43 -07:00
|
|
|
onClick={() => setSelectedAsset(market.name)}
|
|
|
|
key={market.name as string}
|
2021-07-13 06:52:48 -07:00
|
|
|
>
|
2021-08-30 16:47:43 -07:00
|
|
|
{market.name}
|
2021-07-13 06:52:48 -07:00
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-09-19 17:36:02 -07:00
|
|
|
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 gap-2 sm:gap-4">
|
2021-07-13 06:52:48 -07:00
|
|
|
<div
|
|
|
|
className="border border-th-bkg-3 relative p-4 rounded-md"
|
|
|
|
style={{ height: '300px' }}
|
|
|
|
>
|
|
|
|
<Chart
|
2021-09-06 10:28:35 -07:00
|
|
|
title="Avg. Hourly Funding Rate"
|
2021-07-13 06:52:48 -07:00
|
|
|
xAxis="time"
|
|
|
|
yAxis="fundingRate"
|
2021-08-30 16:47:43 -07:00
|
|
|
data={perpsData}
|
2021-09-06 10:28:35 -07:00
|
|
|
labelFormat={(x) => `${x.toFixed(4)}%`}
|
2021-08-30 16:47:43 -07:00
|
|
|
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 &&
|
2021-08-30 17:24:04 -07:00
|
|
|
x.toLocaleString(undefined, {
|
|
|
|
maximumFractionDigits: selectedMarketConfig.baseDecimals,
|
|
|
|
}) + selectedMarketConfig.baseSymbol
|
2021-08-30 16:47:43 -07:00
|
|
|
}
|
|
|
|
type="area"
|
2021-07-13 06:52:48 -07:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const AssetHeader = ({ asset }) => {
|
|
|
|
switch (asset) {
|
2021-08-30 16:47:43 -07:00
|
|
|
case 'BTC-PERP':
|
2021-07-13 06:52:48 -07:00
|
|
|
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"
|
|
|
|
/>
|
2021-08-30 17:24:04 -07:00
|
|
|
Bitcoin Perpetual Futures
|
2021-07-13 06:52:48 -07:00
|
|
|
</div>
|
|
|
|
)
|
2021-08-30 16:47:43 -07:00
|
|
|
case 'ETH-PERP':
|
2021-07-13 06:52:48 -07:00
|
|
|
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"
|
|
|
|
/>
|
2021-08-30 17:24:04 -07:00
|
|
|
Ethereum Perpetual Futures
|
2021-07-13 06:52:48 -07:00
|
|
|
</div>
|
|
|
|
)
|
2021-08-30 16:47:43 -07:00
|
|
|
case 'SOL-PERP':
|
2021-07-13 06:52:48 -07:00
|
|
|
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"
|
|
|
|
/>
|
2021-08-30 17:24:04 -07:00
|
|
|
Solana Perpetual Futures
|
2021-07-13 06:52:48 -07:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
case 'SRM':
|
|
|
|
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"
|
|
|
|
/>
|
2021-08-30 17:24:04 -07:00
|
|
|
Serum Perpetual Futures
|
2021-07-13 06:52:48 -07:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
default:
|
|
|
|
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"
|
|
|
|
/>
|
2021-08-30 17:24:04 -07:00
|
|
|
Bitcoin Perpetual Futures
|
2021-07-13 06:52:48 -07:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|