diff --git a/apps/trading/components/market-banner/market-successor-proposal-banner.tsx b/apps/trading/components/market-banner/market-successor-proposal-banner.tsx index 68104fe98..8797909bf 100644 --- a/apps/trading/components/market-banner/market-successor-proposal-banner.tsx +++ b/apps/trading/components/market-banner/market-successor-proposal-banner.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { Fragment, useState } from 'react'; import type { SuccessorProposalListFieldsFragment, NewMarketSuccessorFieldsFragment, @@ -52,7 +52,7 @@ export const MarketSuccessorProposalBanner = ({ TOKEN_PROPOSAL.replace(':id', item.id || '') ); return ( - <> + { (item.terms?.change as NewMarketSuccessorFieldsFragment) @@ -60,7 +60,7 @@ export const MarketSuccessorProposalBanner = ({ } {i < successors.length - 1 && ', '} - + ); })} diff --git a/apps/trading/components/market-header/market-header.tsx b/apps/trading/components/market-header/market-header.tsx index fe7f684b2..178f67e9d 100644 --- a/apps/trading/components/market-header/market-header.tsx +++ b/apps/trading/components/market-header/market-header.tsx @@ -3,7 +3,7 @@ import { Header, HeaderTitle } from '../header'; import { useParams } from 'react-router-dom'; import { MarketSelector } from '../../components/market-selector/market-selector'; import { MarketHeaderStats } from '../../client-pages/market/market-header-stats'; -import { useMarket } from '@vegaprotocol/markets'; +import { useMarket, useMarketList } from '@vegaprotocol/markets'; import { useState } from 'react'; export const MarketHeader = () => { @@ -11,6 +11,10 @@ export const MarketHeader = () => { const { data } = useMarket(marketId); const [open, setOpen] = useState(false); + // Ensure that markets are kept cached so opening the list + // shows all markets instantly + useMarketList(); + if (!data) return null; return ( diff --git a/apps/trading/components/market-selector/market-selector.spec.tsx b/apps/trading/components/market-selector/market-selector.spec.tsx index a3fa3c5c8..faf20a8ff 100644 --- a/apps/trading/components/market-selector/market-selector.spec.tsx +++ b/apps/trading/components/market-selector/market-selector.spec.tsx @@ -132,6 +132,7 @@ describe('MarketSelector', () => { data: markets, loading: false, error: undefined, + reload: jest.fn(), }); it('Button "All" should be selected by default', () => { diff --git a/apps/trading/components/market-selector/market-selector.tsx b/apps/trading/components/market-selector/market-selector.tsx index 59ec08fc5..7750ea33d 100644 --- a/apps/trading/components/market-selector/market-selector.tsx +++ b/apps/trading/components/market-selector/market-selector.tsx @@ -1,6 +1,6 @@ import { t } from '@vegaprotocol/i18n'; import uniqBy from 'lodash/uniqBy'; -import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets'; +import { type MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets'; import { TradingInput, TinyScroll, @@ -8,7 +8,7 @@ import { VegaIconNames, } from '@vegaprotocol/ui-toolkit'; import type { CSSProperties } from 'react'; -import { useCallback, useState, useMemo, useRef } from 'react'; +import { useCallback, useState, useMemo, useRef, useEffect } from 'react'; import { FixedSizeList } from 'react-window'; import { useMarketSelectorList } from './use-market-selector-list'; import type { ProductType } from './product-selector'; @@ -44,7 +44,12 @@ export const MarketSelector = ({ assets: [], }); const allProducts = filter.product === Product.All; - const { markets, data, loading, error } = useMarketSelectorList(filter); + const { markets, data, loading, error, reload } = + useMarketSelectorList(filter); + + useEffect(() => { + reload(); + }, [reload]); return (
diff --git a/apps/trading/components/market-selector/sort-dropdown.tsx b/apps/trading/components/market-selector/sort-dropdown.tsx index fbada5c9f..82e6382ea 100644 --- a/apps/trading/components/market-selector/sort-dropdown.tsx +++ b/apps/trading/components/market-selector/sort-dropdown.tsx @@ -15,6 +15,7 @@ export const Sort = { Gained: 'Gained', Lost: 'Lost', New: 'New', + TopTraded: 'TopTraded', } as const; export type SortType = keyof typeof Sort; @@ -26,6 +27,7 @@ export const SortTypeMapping: { [Sort.Gained]: 'Top gaining', [Sort.Lost]: 'Top losing', [Sort.New]: 'New markets', + [Sort.TopTraded]: 'Top traded', }; const SortIconMapping: { @@ -35,6 +37,7 @@ const SortIconMapping: { [Sort.Gained]: VegaIconNames.TREND_UP, [Sort.Lost]: VegaIconNames.TREND_DOWN, [Sort.New]: VegaIconNames.STAR, + [Sort.TopTraded]: VegaIconNames.ARROW_UP, }; export const SortDropdown = ({ diff --git a/apps/trading/components/market-selector/use-market-selector-list.ts b/apps/trading/components/market-selector/use-market-selector-list.ts index a9626fd66..72bef8f54 100644 --- a/apps/trading/components/market-selector/use-market-selector-list.ts +++ b/apps/trading/components/market-selector/use-market-selector-list.ts @@ -1,7 +1,11 @@ import { useMemo } from 'react'; import orderBy from 'lodash/orderBy'; import { MarketState } from '@vegaprotocol/types'; -import { calcCandleVolume, useMarketList } from '@vegaprotocol/markets'; +import { + calcCandleVolume, + calcTradedFactor, + useMarketList, +} from '@vegaprotocol/markets'; import { priceChangePercentage } from '@vegaprotocol/utils'; import type { Filter } from '../../components/market-selector/market-selector'; import { Sort } from './sort-dropdown'; @@ -20,7 +24,7 @@ export const useMarketSelectorList = ({ sort, searchTerm, }: Filter) => { - const { data, loading, error } = useMarketList(); + const { data, loading, error, reload } = useMarketList(); const markets = useMemo(() => { if (!data?.length) return []; @@ -94,10 +98,14 @@ export const useMarketSelectorList = ({ ); } + if (sort === Sort.TopTraded) { + return orderBy(markets, [(m) => calcTradedFactor(m)], ['desc']); + } + return markets; }, [data, product, searchTerm, assets, sort]); - return { markets, data, loading, error }; + return { markets, data, loading, error, reload }; }; export const isMarketActive = (state: MarketState) => { diff --git a/apps/trading/components/navbar/nav-header.tsx b/apps/trading/components/navbar/nav-header.tsx index ed78eacb1..da12b118a 100644 --- a/apps/trading/components/navbar/nav-header.tsx +++ b/apps/trading/components/navbar/nav-header.tsx @@ -1,6 +1,6 @@ import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { MarketSelector } from '../market-selector'; -import { useMarket } from '@vegaprotocol/markets'; +import { useMarket, useMarketList } from '@vegaprotocol/markets'; import { t } from '@vegaprotocol/i18n'; import { useParams } from 'react-router-dom'; import * as PopoverPrimitive from '@radix-ui/react-popover'; @@ -14,6 +14,10 @@ export const NavHeader = () => { const { data } = useMarket(marketId); const [open, setOpen] = useState(false); + // Ensure that markets are kept cached so opening the list + // shows all markets instantly + useMarketList(); + if (!marketId) return null; return ( diff --git a/libs/markets/src/lib/market-utils.spec.tsx b/libs/markets/src/lib/market-utils.spec.tsx index a58abcffb..b974098c8 100644 --- a/libs/markets/src/lib/market-utils.spec.tsx +++ b/libs/markets/src/lib/market-utils.spec.tsx @@ -1,6 +1,10 @@ import * as Schema from '@vegaprotocol/types'; -import type { Market } from './markets-provider'; -import { filterAndSortMarkets, totalFeesPercentage } from './market-utils'; +import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider'; +import { + calcTradedFactor, + filterAndSortMarkets, + totalFeesPercentage, +} from './market-utils'; const { MarketState, MarketTradingMode } = Schema; const MARKET_A: Partial = { @@ -77,3 +81,52 @@ describe('totalFees', () => { expect(totalFeesPercentage(i)).toEqual(o); }); }); + +describe('calcTradedFactor', () => { + const marketA = { + data: { + markPrice: '10', + }, + candles: [ + { + volume: '1000', + }, + ], + tradableInstrument: { + instrument: { + product: { + settlementAsset: { + decimals: 18, + quantum: '1000000000000000000', // 1 + }, + }, + }, + }, + }; + const marketB = { + data: { + markPrice: '10', + }, + candles: [ + { + volume: '1000', + }, + ], + tradableInstrument: { + instrument: { + product: { + settlementAsset: { + decimals: 18, + quantum: '1', // 0.0000000000000000001 + }, + }, + }, + }, + }; + it('a is "traded" more than b', () => { + const fa = calcTradedFactor(marketA as MarketMaybeWithDataAndCandles); + const fb = calcTradedFactor(marketB as MarketMaybeWithDataAndCandles); + // it should be true because market a's asset is "more valuable" than b's + expect(fa > fb).toBeTruthy(); + }); +}); diff --git a/libs/markets/src/lib/market-utils.ts b/libs/markets/src/lib/market-utils.ts index 871f2afa4..448acf247 100644 --- a/libs/markets/src/lib/market-utils.ts +++ b/libs/markets/src/lib/market-utils.ts @@ -1,8 +1,13 @@ -import { formatNumberPercentage } from '@vegaprotocol/utils'; +import { formatNumberPercentage, toBigNum } from '@vegaprotocol/utils'; import * as Schema from '@vegaprotocol/types'; import BigNumber from 'bignumber.js'; import orderBy from 'lodash/orderBy'; -import type { Market, Candle, MarketMaybeWithData } from '../'; +import type { + Market, + Candle, + MarketMaybeWithData, + MarketMaybeWithDataAndCandles, +} from '../'; const { MarketState, MarketTradingMode } = Schema; export const totalFees = (fees: Market['fees']['factors']) => { @@ -86,3 +91,18 @@ export const calcCandleHigh = (candles: Candle[]): string | undefined => { export const calcCandleVolume = (candles: Candle[]): string | undefined => candles && candles.reduce((acc, c) => new BigNumber(acc).plus(c.volume).toString(), '0'); + +export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => { + const volume = Number(calcCandleVolume(m.candles || []) || 0); + const price = m.data?.markPrice ? Number(m.data.markPrice) : 0; + const quantum = Number( + m.tradableInstrument.instrument.product.settlementAsset.quantum + ); + const decimals = Number( + m.tradableInstrument.instrument.product.settlementAsset.decimals + ); + const fp = toBigNum(price, decimals); + const fq = toBigNum(quantum, decimals); + const factor = fq.multipliedBy(fp).multipliedBy(volume); + return factor.toNumber(); +};