feat(trading): sort by top traded in market selector (#4574)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
28f7bd36e7
commit
570472b739
@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import type {
|
import type {
|
||||||
SuccessorProposalListFieldsFragment,
|
SuccessorProposalListFieldsFragment,
|
||||||
NewMarketSuccessorFieldsFragment,
|
NewMarketSuccessorFieldsFragment,
|
||||||
@ -52,7 +52,7 @@ export const MarketSuccessorProposalBanner = ({
|
|||||||
TOKEN_PROPOSAL.replace(':id', item.id || '')
|
TOKEN_PROPOSAL.replace(':id', item.id || '')
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<Fragment key={i}>
|
||||||
<ExternalLink href={externalLink} key={i}>
|
<ExternalLink href={externalLink} key={i}>
|
||||||
{
|
{
|
||||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
||||||
@ -60,7 +60,7 @@ export const MarketSuccessorProposalBanner = ({
|
|||||||
}
|
}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
{i < successors.length - 1 && ', '}
|
{i < successors.length - 1 && ', '}
|
||||||
</>
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { Header, HeaderTitle } from '../header';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||||
import { useMarket } from '@vegaprotocol/markets';
|
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export const MarketHeader = () => {
|
export const MarketHeader = () => {
|
||||||
@ -11,6 +11,10 @@ export const MarketHeader = () => {
|
|||||||
const { data } = useMarket(marketId);
|
const { data } = useMarket(marketId);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
// Ensure that markets are kept cached so opening the list
|
||||||
|
// shows all markets instantly
|
||||||
|
useMarketList();
|
||||||
|
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -132,6 +132,7 @@ describe('MarketSelector', () => {
|
|||||||
data: markets,
|
data: markets,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
reload: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Button "All" should be selected by default', () => {
|
it('Button "All" should be selected by default', () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import uniqBy from 'lodash/uniqBy';
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
import { type MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||||
import {
|
import {
|
||||||
TradingInput,
|
TradingInput,
|
||||||
TinyScroll,
|
TinyScroll,
|
||||||
@ -8,7 +8,7 @@ import {
|
|||||||
VegaIconNames,
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type { CSSProperties } from 'react';
|
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 { FixedSizeList } from 'react-window';
|
||||||
import { useMarketSelectorList } from './use-market-selector-list';
|
import { useMarketSelectorList } from './use-market-selector-list';
|
||||||
import type { ProductType } from './product-selector';
|
import type { ProductType } from './product-selector';
|
||||||
@ -44,7 +44,12 @@ export const MarketSelector = ({
|
|||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
const allProducts = filter.product === Product.All;
|
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 (
|
return (
|
||||||
<div data-testid="market-selector">
|
<div data-testid="market-selector">
|
||||||
|
@ -15,6 +15,7 @@ export const Sort = {
|
|||||||
Gained: 'Gained',
|
Gained: 'Gained',
|
||||||
Lost: 'Lost',
|
Lost: 'Lost',
|
||||||
New: 'New',
|
New: 'New',
|
||||||
|
TopTraded: 'TopTraded',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type SortType = keyof typeof Sort;
|
export type SortType = keyof typeof Sort;
|
||||||
@ -26,6 +27,7 @@ export const SortTypeMapping: {
|
|||||||
[Sort.Gained]: 'Top gaining',
|
[Sort.Gained]: 'Top gaining',
|
||||||
[Sort.Lost]: 'Top losing',
|
[Sort.Lost]: 'Top losing',
|
||||||
[Sort.New]: 'New markets',
|
[Sort.New]: 'New markets',
|
||||||
|
[Sort.TopTraded]: 'Top traded',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SortIconMapping: {
|
const SortIconMapping: {
|
||||||
@ -35,6 +37,7 @@ const SortIconMapping: {
|
|||||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||||
[Sort.New]: VegaIconNames.STAR,
|
[Sort.New]: VegaIconNames.STAR,
|
||||||
|
[Sort.TopTraded]: VegaIconNames.ARROW_UP,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SortDropdown = ({
|
export const SortDropdown = ({
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
import { calcCandleVolume, useMarketList } from '@vegaprotocol/markets';
|
import {
|
||||||
|
calcCandleVolume,
|
||||||
|
calcTradedFactor,
|
||||||
|
useMarketList,
|
||||||
|
} from '@vegaprotocol/markets';
|
||||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||||
import type { Filter } from '../../components/market-selector/market-selector';
|
import type { Filter } from '../../components/market-selector/market-selector';
|
||||||
import { Sort } from './sort-dropdown';
|
import { Sort } from './sort-dropdown';
|
||||||
@ -20,7 +24,7 @@ export const useMarketSelectorList = ({
|
|||||||
sort,
|
sort,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
}: Filter) => {
|
}: Filter) => {
|
||||||
const { data, loading, error } = useMarketList();
|
const { data, loading, error, reload } = useMarketList();
|
||||||
|
|
||||||
const markets = useMemo(() => {
|
const markets = useMemo(() => {
|
||||||
if (!data?.length) return [];
|
if (!data?.length) return [];
|
||||||
@ -94,10 +98,14 @@ export const useMarketSelectorList = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sort === Sort.TopTraded) {
|
||||||
|
return orderBy(markets, [(m) => calcTradedFactor(m)], ['desc']);
|
||||||
|
}
|
||||||
|
|
||||||
return markets;
|
return markets;
|
||||||
}, [data, product, searchTerm, assets, sort]);
|
}, [data, product, searchTerm, assets, sort]);
|
||||||
|
|
||||||
return { markets, data, loading, error };
|
return { markets, data, loading, error, reload };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isMarketActive = (state: MarketState) => {
|
export const isMarketActive = (state: MarketState) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
import { MarketSelector } from '../market-selector';
|
import { MarketSelector } from '../market-selector';
|
||||||
import { useMarket } from '@vegaprotocol/markets';
|
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||||
@ -14,6 +14,10 @@ export const NavHeader = () => {
|
|||||||
const { data } = useMarket(marketId);
|
const { data } = useMarket(marketId);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
// Ensure that markets are kept cached so opening the list
|
||||||
|
// shows all markets instantly
|
||||||
|
useMarketList();
|
||||||
|
|
||||||
if (!marketId) return null;
|
if (!marketId) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import type { Market } from './markets-provider';
|
import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider';
|
||||||
import { filterAndSortMarkets, totalFeesPercentage } from './market-utils';
|
import {
|
||||||
|
calcTradedFactor,
|
||||||
|
filterAndSortMarkets,
|
||||||
|
totalFeesPercentage,
|
||||||
|
} from './market-utils';
|
||||||
const { MarketState, MarketTradingMode } = Schema;
|
const { MarketState, MarketTradingMode } = Schema;
|
||||||
|
|
||||||
const MARKET_A: Partial<Market> = {
|
const MARKET_A: Partial<Market> = {
|
||||||
@ -77,3 +81,52 @@ describe('totalFees', () => {
|
|||||||
expect(totalFeesPercentage(i)).toEqual(o);
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
import { formatNumberPercentage, toBigNum } from '@vegaprotocol/utils';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import type { Market, Candle, MarketMaybeWithData } from '../';
|
import type {
|
||||||
|
Market,
|
||||||
|
Candle,
|
||||||
|
MarketMaybeWithData,
|
||||||
|
MarketMaybeWithDataAndCandles,
|
||||||
|
} from '../';
|
||||||
const { MarketState, MarketTradingMode } = Schema;
|
const { MarketState, MarketTradingMode } = Schema;
|
||||||
|
|
||||||
export const totalFees = (fees: Market['fees']['factors']) => {
|
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 =>
|
export const calcCandleVolume = (candles: Candle[]): string | undefined =>
|
||||||
candles &&
|
candles &&
|
||||||
candles.reduce((acc, c) => new BigNumber(acc).plus(c.volume).toString(), '0');
|
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();
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user