import useInterval from '@components/shared/useInterval' import mangoStore from '@store/mangoStore' import { useEffect, useMemo, useState } from 'react' import { formatNumericValue, getDecimalCount } from 'utils/numbers' import { ChartTradeType } from 'types' import { useTranslation } from 'next-i18next' import useSelectedMarket from 'hooks/useSelectedMarket' import { Howl } from 'howler' import { IconButton } from '@components/shared/Button' import useLocalStorageState from 'hooks/useLocalStorageState' import { SOUND_SETTINGS_KEY, TRADE_VOLUME_ALERT_KEY } from 'utils/constants' import { BellAlertIcon, BellSlashIcon } from '@heroicons/react/20/solid' import Tooltip from '@components/shared/Tooltip' import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings' import TradeVolumeAlertModal, { DEFAULT_VOLUME_ALERT_SETTINGS, } from '@components/modals/TradeVolumeAlertModal' import dayjs from 'dayjs' import { PerpMarket } from '@blockworks-foundation/mango-v4' const volumeAlertSound = new Howl({ src: ['/sounds/trade-buy.mp3'], volume: 0.8, }) const RecentTrades = () => { const { t } = useTranslation(['common', 'trade']) const fills = mangoStore((s) => s.selectedMarket.fills) const [latestFillId, setLatestFillId] = useState('') const [soundSettings] = useLocalStorageState( SOUND_SETTINGS_KEY, INITIAL_SOUND_SETTINGS ) const [alertSettings] = useLocalStorageState( TRADE_VOLUME_ALERT_KEY, DEFAULT_VOLUME_ALERT_SETTINGS ) const [showVolumeAlertModal, setShowVolumeAlertModal] = useState(false) const { selectedMarket, serumOrPerpMarket: market, baseSymbol, quoteBank, quoteSymbol, } = useSelectedMarket() useEffect(() => { if (!fills.length) return const latesetFill = fills[0] if (!latestFillId) { const fillId = selectedMarket instanceof PerpMarket ? latesetFill.takerClientOrderId : latesetFill.orderId setLatestFillId(fillId.toString()) } }, [fills]) useInterval(() => { if (!soundSettings['recent-trades'] || !quoteBank || !fills.length) return const latesetFill = fills[0] const fillId = selectedMarket instanceof PerpMarket ? latesetFill.takerClientOrderId : latesetFill.orderId setLatestFillId(fillId.toString()) const fillsLimitIndex = fills.findIndex((f) => { const id = selectedMarket instanceof PerpMarket ? f.takerClientOrderId : f.orderId return id.toString() === fillId.toString() }) const newFillsVolumeValue = fills .slice(0, fillsLimitIndex) .reduce((a, c) => a + c.size * c.price, 0) if (newFillsVolumeValue * quoteBank.uiPrice > Number(alertSettings.value)) { volumeAlertSound.play() } }, alertSettings.seconds * 1000) // const fetchRecentTrades = useCallback(async () => { // if (!market) return // try { // const response = await fetch( // `https://event-history-api-candles.herokuapp.com/trades/address/${market.publicKey}` // ) // const parsedResp = await response.json() // const newTrades = parsedResp.data // if (!newTrades) return null // if (newTrades.length && trades.length === 0) { // setTrades(newTrades) // } else if (newTrades?.length && !isEqual(newTrades[0], trades[0])) { // setTrades(newTrades) // } // } catch (e) { // console.error('Unable to fetch recent trades', e) // } // }, [market, trades]) useEffect(() => { // if (CLUSTER === 'mainnet-beta') { // fetchRecentTrades() // } const actions = mangoStore.getState().actions actions.loadMarketFills() }, [selectedMarket]) useInterval(async () => { // if (CLUSTER === 'mainnet-beta') { // fetchRecentTrades() // } const actions = mangoStore.getState().actions actions.loadMarketFills() }, 5000) const [buyRatio, sellRatio] = useMemo(() => { if (!fills.length) return [0, 0] const vol = fills.reduce( (a: { buys: number; sells: number }, c: any) => { if (c.side === 'buy' || c.takerSide === 1) { a.buys = a.buys + c.size } else { a.sells = a.sells + c.size } return a }, { buys: 0, sells: 0 } ) const totalVol = vol.buys + vol.sells return [vol.buys / totalVol, vol.sells / totalVol] }, [fills]) return ( <>
setShowVolumeAlertModal(true)} size="small" hideBg > {soundSettings['recent-trades'] ? ( ) : ( )} {t('trade:buys')}:{' '} {(buyRatio * 100).toFixed(1)}% | {t('trade:sells')}:{' '} {(sellRatio * 100).toFixed(1)}%
{!!fills.length && fills.map((trade: ChartTradeType, i: number) => { const side = trade.side || (trade.takerSide === 0 ? 'bid' : 'ask') const formattedPrice = market?.tickSize && trade.price ? formatNumericValue( trade.price, getDecimalCount(market.tickSize) ) : trade?.price || 0 const formattedSize = market?.minOrderSize && trade.size ? formatNumericValue( trade.size, getDecimalCount(market.minOrderSize) ) : trade?.size || 0 return ( ) })}
{`${t( 'price' )} (${quoteSymbol})`} {t('trade:size')} ({baseSymbol}) {t('time')}
{formattedPrice} {formattedSize} {trade.time ? new Date(trade.time).toLocaleTimeString() : trade.timestamp ? dayjs(trade.timestamp.toNumber() * 1000).format( 'hh:mma' ) : '-'}
{showVolumeAlertModal ? ( setShowVolumeAlertModal(false)} /> ) : null} ) } export default RecentTrades