chore: market header live update (#1672)

* chore: market header live update

* chore: market header live update

* chore: market header live update - adjust some mocks

* chore: market header live update - add single market query

* chore: market header live update - small fixes

* chore: market header live update - fix int tests

* chore: market header live update - fix int tests

* chore: market header live update - remove unnecessary props from query

* chore: market header live update - change concept - split for small comps

* chore: market header live update - small fix for mocks

* chore: market header live update - fix updates throttling

* chore: market header live update - improve update methods of data providers

* chore: market header live update - improve update methods of data providers

* chore: market header live update - improve update methods store for get rid of blinking

* chore: market header live update - fix title component

Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
macqbat 2022-10-11 14:30:07 +02:00 committed by GitHub
parent 64c85b2b4a
commit 0bb2e95091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 752 additions and 865 deletions

View File

@ -3,6 +3,7 @@ import {
generateSimpleMarkets, generateSimpleMarkets,
generateMarketsCandles, generateMarketsCandles,
generateMarketsData, generateMarketsData,
generateMarket,
} from '../support/mocks/generate-markets'; } from '../support/mocks/generate-markets';
import { generateDealTicket } from '../support/mocks/generate-deal-ticket'; import { generateDealTicket } from '../support/mocks/generate-deal-ticket';
import { generateMarketTags } from '../support/mocks/generate-market-tags'; import { generateMarketTags } from '../support/mocks/generate-market-tags';
@ -31,6 +32,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
aliasQuery(req, 'PartyMarketData', generatePartyMarketData()); aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice()); aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
aliasQuery(req, 'MarketDepth', generateMarketDepth()); aliasQuery(req, 'MarketDepth', generateMarketDepth());
aliasQuery(req, 'Market', generateMarket());
}); });
cy.visit('/markets'); cy.visit('/markets');
cy.wait('@Markets').then((response) => { cy.wait('@Markets').then((response) => {

View File

@ -1,5 +1,6 @@
import type { Market } from '@vegaprotocol/market-list'; import type { Market } from '@vegaprotocol/market-list';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
export const protoCandles = [ export const protoCandles = [
{ open: '9556163', close: '9587028', __typename: 'Candle' }, { open: '9556163', close: '9587028', __typename: 'Candle' },
@ -123,3 +124,24 @@ export const protoMarket: Market = {
}, },
__typename: 'Market', __typename: 'Market',
}; };
export const singleMarket: SingleMarketFieldsFragment = {
...protoMarket,
tradableInstrument: {
...protoMarket.tradableInstrument,
instrument: {
...protoMarket.tradableInstrument.instrument,
product: {
...protoMarket.tradableInstrument.instrument.product,
settlementAsset: {
...protoMarket.tradableInstrument.instrument.product.settlementAsset,
id: 'dai-id',
name: 'DAI Name',
},
oracleSpecForTradingTermination: {
id: 'oid',
},
},
},
},
};

View File

@ -12,8 +12,9 @@ import type {
MarketsDataQuery, MarketsDataQuery,
MarketDataFieldsFragment, MarketDataFieldsFragment,
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import { protoMarket, protoCandles } from './commons'; import { protoMarket, protoCandles, singleMarket } from './commons';
import type { PartialDeep } from 'type-fest'; import type { PartialDeep } from 'type-fest';
import type { MarketQuery } from '@vegaprotocol/market-list';
export const generateSimpleMarkets = (): MarketsQuery => { export const generateSimpleMarkets = (): MarketsQuery => {
const markets: Market[] = [ const markets: Market[] = [
@ -1377,3 +1378,11 @@ export const generatePositionsMarkets = () => {
}, },
}; };
}; };
export const generateMarket = (): MarketQuery => {
return {
market: {
...singleMarket,
},
};
};

View File

@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { Side } from '@vegaprotocol/types'; import { Side } from '@vegaprotocol/types';
import { useOrderBookData } from '@vegaprotocol/market-depth'; import { useOrderBookData } from '@vegaprotocol/market-depth';
import { marketProvider } from '@vegaprotocol/market-list'; import { marketProvider } from '@vegaprotocol/market-list';
import type { Market } from '@vegaprotocol/market-list'; import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { BigNumber } from 'bignumber.js'; import { BigNumber } from 'bignumber.js';
import { import {
@ -22,7 +22,7 @@ const useCalculateSlippage = ({ marketId, order }: Props) => {
variables, variables,
throttleMilliseconds: 5000, throttleMilliseconds: 5000,
}); });
const { data: market } = useDataProvider<Market, never>({ const { data: market } = useDataProvider<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider, dataProvider: marketProvider,
noUpdate: true, noUpdate: true,
variables, variables,

View File

@ -1,40 +1,19 @@
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
AuctionTrigger,
MarketState,
MarketTradingMode,
} from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest'; import type { PartialDeep } from 'type-fest';
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import type { Market } from '../../../../trading/pages/markets/__generated__/Market'; import type { MarketQuery } from '@vegaprotocol/market-list';
export const generateMarket = (override?: PartialDeep<Market>): Market => { export const generateMarket = (
const defaultResult: Market = { override?: PartialDeep<MarketQuery>
): MarketQuery => {
const defaultResult: MarketQuery = {
market: { market: {
id: 'market-0', id: 'market-0',
tradingMode: MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, tradingMode: MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
state: MarketState.STATE_ACTIVE, state: MarketState.STATE_ACTIVE,
decimalPlaces: 5, decimalPlaces: 5,
positionDecimalPlaces: 0, positionDecimalPlaces: 0,
data: {
market: {
id: 'market-0',
__typename: 'Market',
},
auctionStart: '2022-08-12T11:13:47.611014117Z',
auctionEnd: '2022-08-16T09:08:23.611014117Z',
markPrice: '13739109',
indicativeVolume: '2316',
indicativePrice: '88470230',
suppliedStake: '79481836527',
targetStake: '97284519014',
bestBidVolume: '244',
bestOfferVolume: '100',
bestStaticBidVolume: '482',
bestStaticOfferVolume: '2188',
trigger: AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY,
__typename: 'MarketData',
},
tradableInstrument: { tradableInstrument: {
instrument: { instrument: {
id: 'BTCUSD.MF21', id: 'BTCUSD.MF21',
@ -75,36 +54,15 @@ export const generateMarket = (override?: PartialDeep<Market>): Market => {
close: null, close: null,
__typename: 'MarketTimestamps', __typename: 'MarketTimestamps',
}, },
depth: { fees: {
__typename: 'MarketDepth', __typename: 'Fees',
lastTrade: { factors: {
__typename: 'Trade', __typename: 'FeeFactors',
price: '88470230', makerFee: '',
infrastructureFee: '',
liquidityFee: '',
}, },
}, },
candlesConnection: {
__typename: 'CandleDataConnection',
edges: [
{
__typename: 'CandleEdge',
node: {
open: '2095312844',
close: '2090090607',
volume: '4847',
__typename: 'Candle',
},
},
{
__typename: 'CandleEdge',
node: {
open: '2090090000',
close: '2090090607',
volume: '4847',
__typename: 'Candle',
},
},
],
},
__typename: 'Market', __typename: 'Market',
}, },
}; };

View File

@ -0,0 +1 @@
export const DEBOUNCE_UPDATE_TIME = 500;

View File

@ -0,0 +1 @@
export * from './last-24h-price-change';

View File

@ -0,0 +1,85 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import throttle from 'lodash/throttle';
import { t, useDataProvider, useYesterday } from '@vegaprotocol/react-helpers';
import { PriceCellChange } from '@vegaprotocol/ui-toolkit';
import { Interval } from '@vegaprotocol/types';
import type { CandleClose } from '@vegaprotocol/types';
import type {
SingleMarketFieldsFragment,
Candle,
} from '@vegaprotocol/market-list';
import {
marketCandlesProvider,
marketProvider,
} from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import * as constants from '../constants';
export const Last24hPriceChange = ({ marketId }: { marketId: string }) => {
const [candlesClose, setCandlesClose] = useState<string[]>([]);
const yesterday = useYesterday();
// Cache timestamp for yesterday to prevent full unmount of market page when
// a rerender occurs
const yTimestamp = useMemo(() => {
return new Date(yesterday).toISOString();
}, [yesterday]);
const marketVariables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const variables = useMemo(
() => ({
marketId: marketId,
interval: Interval.INTERVAL_I1H,
since: yTimestamp,
}),
[marketId, yTimestamp]
);
const { data, error } = useDataProvider<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider,
variables: marketVariables,
skip: !marketId,
});
const throttledSetCandles = useRef(
throttle((data: Candle[]) => {
const candlesClose: string[] = data
.map((candle) => candle?.close)
.filter((c): c is CandleClose => c !== null);
setCandlesClose(candlesClose);
}, constants.DEBOUNCE_UPDATE_TIME)
).current;
const update = useCallback(
({ data }: { data: Candle[] }) => {
throttledSetCandles(data);
return true;
},
[throttledSetCandles]
);
useDataProvider<Candle[], Candle>({
dataProvider: marketCandlesProvider,
update,
variables,
skip: !marketId || !data,
updateOnInit: true,
});
return (
<HeaderStat heading={t('Change (24h)')}>
{!error && data?.decimalPlaces ? (
<PriceCellChange
candles={candlesClose}
decimalPlaces={data.decimalPlaces}
/>
) : (
'-'
)}
</HeaderStat>
);
};

View File

@ -0,0 +1 @@
export * from './market-mark-price';

View File

@ -0,0 +1,60 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import throttle from 'lodash/throttle';
import {
addDecimalsFormatNumber,
t,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
SingleMarketFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import * as constants from '../constants';
export const MarketMarkPrice = ({ marketId }: { marketId: string }) => {
const [marketPrice, setMarketPrice] = useState<string | null>(null);
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const { data } = useDataProvider<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const throttledSetMarketPrice = useRef(
throttle((price: string) => {
setMarketPrice(price);
}, constants.DEBOUNCE_UPDATE_TIME)
).current;
const update = useCallback(
({ data: marketData }: { data: MarketData }) => {
throttledSetMarketPrice(
marketData.markPrice && data?.decimalPlaces
? addDecimalsFormatNumber(marketData.markPrice, data.decimalPlaces)
: '-'
);
return true;
},
[data?.decimalPlaces, throttledSetMarketPrice]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
skip: !marketId || !data,
updateOnInit: true,
});
return (
<HeaderStat heading={t('Price')}>
<div data-testid="mark-price">{marketPrice}</div>
</HeaderStat>
);
};

View File

@ -0,0 +1 @@
export * from './market-trading-mode';

View File

@ -0,0 +1,89 @@
import { useCallback, useMemo, useState } from 'react';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import { compileGridData, TradingModeTooltip } from '@vegaprotocol/deal-ticket';
import type { Schema as Types } from '@vegaprotocol/types';
import {
AuctionTrigger,
AuctionTriggerMapping,
MarketTradingModeMapping,
MarketTradingMode,
} from '@vegaprotocol/types';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
SingleMarketFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
interface Props {
marketId: string;
onSelect: (marketId: string) => void;
}
type TradingModeMarket = Omit<DealTicketMarketFragment, 'depth'>;
export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => {
const [tradingMode, setTradingMode] =
useState<Types.MarketTradingMode | null>(null);
const [trigger, setTrigger] = useState<Types.AuctionTrigger | null>(null);
const [market, setMarket] = useState<TradingModeMarket | null>(null);
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const { data } = useDataProvider<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const update = useCallback(
({ data: marketData }: { data: MarketData }) => {
setTradingMode(marketData.marketTradingMode);
setTrigger(marketData.trigger);
setMarket({
...data,
data: marketData,
} as TradingModeMarket);
return true;
},
[data]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
skip: !marketId || !data,
updateOnInit: true,
});
return (
<HeaderStat
heading={t('Trading mode')}
description={
market && (
<TradingModeTooltip
tradingMode={tradingMode}
trigger={trigger}
compiledGrid={compileGridData(market, onSelect)}
/>
)
}
>
<div data-testid="trading-mode">
{tradingMode === MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${MarketTradingModeMapping[tradingMode]}
- ${AuctionTriggerMapping[trigger]}`
: MarketTradingModeMapping[tradingMode as Types.MarketTradingMode]}
</div>
</HeaderStat>
);
};

View File

@ -0,0 +1 @@
export * from './market-volume';

View File

@ -0,0 +1,64 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import throttle from 'lodash/throttle';
import {
addDecimalsFormatNumber,
t,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
SingleMarketFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import * as constants from '../constants';
export const MarketVolume = ({ marketId }: { marketId: string }) => {
const [marketVolume, setMarketVolume] = useState<string>('-');
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const { data } = useDataProvider<SingleMarketFieldsFragment, never>({
dataProvider: marketProvider,
variables,
skip: !marketId,
});
const throttledSetMarketVolume = useRef(
throttle((volume: string) => {
setMarketVolume(volume);
}, constants.DEBOUNCE_UPDATE_TIME)
).current;
const update = useCallback(
({ data: marketData }: { data: MarketData }) => {
throttledSetMarketVolume(
marketData.indicativeVolume && data?.positionDecimalPlaces !== undefined
? addDecimalsFormatNumber(
marketData.indicativeVolume,
data.positionDecimalPlaces
)
: '-'
);
return true;
},
[data?.positionDecimalPlaces, throttledSetMarketVolume]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
dataProvider: marketDataProvider,
update,
variables,
skip: !marketId || !data,
updateOnInit: true,
});
return (
<HeaderStat heading={t('Volume')}>
<div data-testid="trading-volume">{marketVolume}</div>
</HeaderStat>
);
};

View File

@ -22,14 +22,10 @@ import { useMemo } from 'react';
const DEFAULT_TITLE = t('Welcome to Vega trading!'); const DEFAULT_TITLE = t('Welcome to Vega trading!');
function AppBody({ Component, pageProps }: AppProps) { const Title = () => {
const { connectDialog, pageTitle, update } = useGlobalStore((store) => ({ const { pageTitle } = useGlobalStore((store) => ({
connectDialog: store.connectDialog,
pageTitle: store.pageTitle, pageTitle: store.pageTitle,
update: store.update,
})); }));
const { isOpen, symbol, trigger, setOpen } = useAssetDetailsDialogStore();
const [theme, toggleTheme] = useThemeSwitcher();
const { VEGA_ENV } = useEnvironment(); const { VEGA_ENV } = useEnvironment();
const networkName = envTriggerMapping[VEGA_ENV]; const networkName = envTriggerMapping[VEGA_ENV];
@ -39,11 +35,21 @@ function AppBody({ Component, pageProps }: AppProps) {
if (networkName) return `${pageTitle} [${networkName}]`; if (networkName) return `${pageTitle} [${networkName}]`;
return pageTitle; return pageTitle;
}, [pageTitle, networkName]); }, [pageTitle, networkName]);
return <title>{title}</title>;
};
function AppBody({ Component, pageProps }: AppProps) {
const { connectDialog, update } = useGlobalStore((store) => ({
connectDialog: store.connectDialog,
update: store.update,
}));
const { isOpen, symbol, trigger, setOpen } = useAssetDetailsDialogStore();
const [theme, toggleTheme] = useThemeSwitcher();
return ( return (
<ThemeContext.Provider value={theme}> <ThemeContext.Provider value={theme}>
<Head> <Head>
<title>{title}</title> <Title />
</Head> </Head>
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]"> <div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">
<AppLoader> <AppLoader>

View File

@ -1,68 +0,0 @@
query Market($marketId: ID!, $interval: Interval!, $since: String!) {
market(id: $marketId) {
id
tradingMode
state
decimalPlaces
positionDecimalPlaces
data {
market {
id
}
auctionStart
auctionEnd
markPrice
indicativeVolume
indicativePrice
suppliedStake
targetStake
bestBidVolume
bestOfferVolume
bestStaticBidVolume
bestStaticOfferVolume
trigger
}
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
product {
... on Future {
oracleSpecForTradingTermination {
id
}
quoteName
settlementAsset {
id
symbol
name
decimals
}
}
}
}
}
marketTimestamps {
open
close
}
depth {
lastTrade {
price
}
}
candlesConnection(interval: $interval, since: $since) {
edges {
node {
open
close
volume
}
}
}
}
}

View File

@ -1,158 +1,114 @@
import { gql, useQuery } from '@apollo/client'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import debounce from 'lodash/debounce';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
t, t,
titlefy, titlefy,
useYesterday, useDataProvider,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { Interval } from '@vegaprotocol/types';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import debounce from 'lodash/debounce';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect, useMemo, useState } from 'react'; import type {
SingleMarketFieldsFragment,
MarketData,
Candle,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
import { useGlobalStore } from '../../stores'; import { useGlobalStore } from '../../stores';
import { TradeGrid, TradePanels } from './trade-grid'; import { TradeGrid, TradePanels } from './trade-grid';
import type { Market, MarketVariables } from './__generated__/Market';
import { ColumnKind, SelectMarketDialog } from '../../components/select-market'; import { ColumnKind, SelectMarketDialog } from '../../components/select-market';
// Top level page query const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
const MARKET_QUERY = gql` return markPrice && decimalPlaces
query Market($marketId: ID!, $interval: Interval!, $since: String!) { ? addDecimalsFormatNumber(markPrice, decimalPlaces)
market(id: $marketId) { : '-';
id };
tradingMode
state
decimalPlaces
positionDecimalPlaces
data {
market {
id
}
auctionStart
auctionEnd
markPrice
indicativeVolume
indicativePrice
suppliedStake
targetStake
bestBidVolume
bestOfferVolume
bestStaticBidVolume
bestStaticOfferVolume
trigger
}
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
product {
... on Future {
oracleSpecForTradingTermination {
id
}
quoteName
settlementAsset {
id
symbol
name
decimals
}
}
}
}
}
marketTimestamps {
open
close
}
depth {
lastTrade {
price
}
}
candlesConnection(interval: $interval, since: $since) {
edges {
node {
open
close
volume
}
}
}
}
}
`;
const MarketPage = ({ id }: { id?: string }) => { export interface SingleMarketData extends SingleMarketFieldsFragment {
candles: Candle[];
data: MarketData;
}
const MarketPage = ({
id,
marketId: mid,
}: {
id?: string;
marketId?: string;
}) => {
const { query, push } = useRouter(); const { query, push } = useRouter();
const { w } = useWindowSize(); const { w } = useWindowSize();
const { landingDialog, riskNoticeDialog, update } = useGlobalStore( const {
(store) => ({ landingDialog,
landingDialog: store.landingDialog, riskNoticeDialog,
riskNoticeDialog: store.riskNoticeDialog, update,
update: store.update, updateTitle,
}) updateMarketId,
); } = useGlobalStore((store) => ({
const { update: updateStore } = useGlobalStore((store) => ({ landingDialog: store.landingDialog,
riskNoticeDialog: store.riskNoticeDialog,
update: store.update, update: store.update,
updateTitle: store.updateTitle,
updateMarketId: store.updateMarketId,
})); }));
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
// Default to first marketId query item if found // Default to first marketId query item if found
const marketId = const marketId =
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId); id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
const onSelect = (id: string) => { const onSelect = useCallback(
if (id && id !== marketId) { (id: string) => {
updateStore({ marketId: id }); if (id && id !== marketId) {
push(`/markets/${id}`); updateMarketId(id);
} push(`/markets/${id}`);
}; }
},
const yesterday = useYesterday(); [marketId, updateMarketId, push]
// Cache timestamp for yesterday to prevent full unmount of market page when );
// a rerender occurs
const yTimestamp = useMemo(() => {
return new Date(yesterday).toISOString();
}, [yesterday]);
const variables = useMemo( const variables = useMemo(
() => ({ () => ({
marketId: marketId || '', marketId: marketId || '',
interval: Interval.INTERVAL_I1H,
since: yTimestamp,
}), }),
[marketId, yTimestamp] [marketId]
); );
const { data, error, loading } = useQuery<Market, MarketVariables>( const { data, error, loading } = useDataProvider<
MARKET_QUERY, SingleMarketFieldsFragment,
{ never
variables, >({
fetchPolicy: 'network-only', dataProvider: marketProvider,
errorPolicy: 'ignore', variables,
} skip: !marketId,
});
const updateProvider = useCallback(
({ data: marketData }: { data: MarketData }) => {
const marketName = data?.tradableInstrument.instrument.name;
const marketPrice = calculatePrice(
marketData.markPrice,
data?.decimalPlaces
);
if (marketName) {
const pageTitle = titlefy([marketName, marketPrice]);
updateTitle(pageTitle);
}
return true;
},
[updateTitle, data?.tradableInstrument.instrument.name, data?.decimalPlaces]
); );
useEffect(() => { useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
const marketName = data?.market?.tradableInstrument.instrument.name; dataProvider: marketDataProvider,
const marketPrice = update: updateProvider,
data?.market && data?.market?.data variables,
? addDecimalsFormatNumber( skip: !marketId || !data,
data.market.data.markPrice, updateOnInit: true,
data.market.decimalPlaces });
)
: null;
if (marketName) {
const pageTitle = titlefy([marketName, marketPrice]);
update({ pageTitle });
}
}, [data, update]);
if (!marketId) { if (!marketId) {
return ( return (
@ -163,20 +119,20 @@ const MarketPage = ({ id }: { id?: string }) => {
} }
return ( return (
<AsyncRenderer<Market> <AsyncRenderer<SingleMarketFieldsFragment>
loading={loading} loading={loading}
error={error} error={error}
data={data} data={data || undefined}
render={({ market }) => { render={(data) => {
if (!market) { if (!data) {
return <Splash>{t('Market not found')}</Splash>; return <Splash>{t('Market not found')}</Splash>;
} }
return ( return (
<> <>
{w > 960 ? ( {w > 960 ? (
<TradeGrid market={market} onSelect={onSelect} /> <TradeGrid market={data} onSelect={onSelect} />
) : ( ) : (
<TradePanels market={market} onSelect={onSelect} /> <TradePanels market={data} onSelect={onSelect} />
)} )}
<SelectMarketDialog <SelectMarketDialog
dialogOpen={landingDialog && !riskNoticeDialog} dialogOpen={landingDialog && !riskNoticeDialog}

View File

@ -1,291 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { Interval, MarketTradingMode, MarketState, AuctionTrigger } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Market
// ====================================================
export interface Market_market_data_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
}
export interface Market_market_data {
__typename: "MarketData";
/**
* Market of the associated mark price
*/
market: Market_market_data_market;
/**
* RFC3339Nano time at which the next auction will start (null if none is scheduled)
*/
auctionStart: string | null;
/**
* RFC3339Nano time at which the auction will stop (null if not in auction mode)
*/
auctionEnd: string | null;
/**
* The mark price (an unsigned integer)
*/
markPrice: string;
/**
* Indicative volume if the auction ended now, 0 if not in auction mode
*/
indicativeVolume: string;
/**
* Indicative price if the auction ended now, 0 if not in auction mode
*/
indicativePrice: string;
/**
* The supplied stake for the market
*/
suppliedStake: string | null;
/**
* The amount of stake targeted for this market
*/
targetStake: string | null;
/**
* The aggregated volume being bid at the best bid price.
*/
bestBidVolume: string;
/**
* The aggregated volume being offered at the best offer price.
*/
bestOfferVolume: string;
/**
* The aggregated volume being offered at the best static bid price, excluding pegged orders
*/
bestStaticBidVolume: string;
/**
* The aggregated volume being offered at the best static offer price, excluding pegged orders.
*/
bestStaticOfferVolume: string;
/**
* What triggered an auction (if an auction was started)
*/
trigger: AuctionTrigger;
}
export interface Market_market_tradableInstrument_instrument_metadata {
__typename: "InstrumentMetadata";
/**
* An arbitrary list of tags to associated to associate to the Instrument (string list)
*/
tags: string[] | null;
}
export interface Market_market_tradableInstrument_instrument_product_oracleSpecForTradingTermination {
__typename: "OracleSpec";
/**
* ID is a hash generated from the OracleSpec data.
*/
id: string;
}
export interface Market_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The ID of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
/**
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
}
export interface Market_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* The oracle spec describing the oracle data of interest for trading termination.
*/
oracleSpecForTradingTermination: Market_market_tradableInstrument_instrument_product_oracleSpecForTradingTermination;
/**
* String representing the quote (e.g. BTCUSD -> USD is quote)
*/
quoteName: string;
/**
* The name of the asset (string)
*/
settlementAsset: Market_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface Market_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* Uniquely identify an instrument across all instruments available on Vega (string)
*/
id: string;
/**
* Full and fairly descriptive name for the instrument
*/
name: string;
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
/**
* Metadata for this instrument
*/
metadata: Market_market_tradableInstrument_instrument_metadata;
/**
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
*/
product: Market_market_tradableInstrument_instrument_product;
}
export interface Market_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of, or reference to, a fully specified instrument.
*/
instrument: Market_market_tradableInstrument_instrument;
}
export interface Market_market_marketTimestamps {
__typename: "MarketTimestamps";
/**
* Time when the market is open and ready to accept trades
*/
open: string | null;
/**
* Time when the market is closed
*/
close: string | null;
}
export interface Market_market_depth_lastTrade {
__typename: "Trade";
/**
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
*/
price: string;
}
export interface Market_market_depth {
__typename: "MarketDepth";
/**
* Last trade for the given market (if available)
*/
lastTrade: Market_market_depth_lastTrade | null;
}
export interface Market_market_candlesConnection_edges_node {
__typename: "Candle";
/**
* Open price (uint64)
*/
open: string;
/**
* Close price (uint64)
*/
close: string;
/**
* Volume price (uint64)
*/
volume: string;
}
export interface Market_market_candlesConnection_edges {
__typename: "CandleEdge";
/**
* The candle
*/
node: Market_market_candlesConnection_edges_node;
}
export interface Market_market_candlesConnection {
__typename: "CandleDataConnection";
/**
* The candles
*/
edges: (Market_market_candlesConnection_edges | null)[] | null;
}
export interface Market_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
/**
* Current state of the market
*/
state: MarketState;
/**
* The number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* The number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
* This sets how big the smallest order / position on the market can be.
*/
positionDecimalPlaces: number;
/**
* marketData for the given market
*/
data: Market_market_data | null;
/**
* An instance of, or reference to, a tradable instrument.
*/
tradableInstrument: Market_market_tradableInstrument;
/**
* Timestamps for state changes in the market
*/
marketTimestamps: Market_market_marketTimestamps;
/**
* Current depth on the order book for this market
*/
depth: Market_market_depth;
/**
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters using cursor based pagination
*/
candlesConnection: Market_market_candlesConnection | null;
}
export interface Market {
/**
* An instrument that is trading on the Vega network
*/
market: Market_market | null;
}
export interface MarketVariables {
marketId: string;
interval: Interval;
since: string;
}

View File

@ -1,115 +0,0 @@
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type MarketQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
interval: Types.Interval;
since: Types.Scalars['String'];
}>;
export type MarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradingMode: Types.MarketTradingMode, state: Types.MarketState, decimalPlaces: number, positionDecimalPlaces: number, data?: { __typename?: 'MarketData', auctionStart?: string | null, auctionEnd?: string | null, markPrice: string, indicativeVolume: string, indicativePrice: string, suppliedStake?: string | null, targetStake?: string | null, bestBidVolume: string, bestOfferVolume: string, bestStaticBidVolume: string, bestStaticOfferVolume: string, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, oracleSpecForTradingTermination: { __typename?: 'OracleSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', open: string, close: string, volume: string } } | null> | null } | null } | null };
export const MarketDocument = gql`
query Market($marketId: ID!, $interval: Interval!, $since: String!) {
market(id: $marketId) {
id
tradingMode
state
decimalPlaces
positionDecimalPlaces
data {
market {
id
}
auctionStart
auctionEnd
markPrice
indicativeVolume
indicativePrice
suppliedStake
targetStake
bestBidVolume
bestOfferVolume
bestStaticBidVolume
bestStaticOfferVolume
trigger
}
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
product {
... on Future {
oracleSpecForTradingTermination {
id
}
quoteName
settlementAsset {
id
symbol
name
decimals
}
}
}
}
}
marketTimestamps {
open
close
}
depth {
lastTrade {
price
}
}
candlesConnection(interval: $interval, since: $since) {
edges {
node {
open
close
volume
}
}
}
}
}
`;
/**
* __useMarketQuery__
*
* To run a query within a React component, call `useMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMarketQuery({
* variables: {
* marketId: // value for 'marketId'
* interval: // value for 'interval'
* since: // value for 'since'
* },
* });
*/
export function useMarketQuery(baseOptions: Apollo.QueryHookOptions<MarketQuery, MarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MarketQuery, MarketQueryVariables>(MarketDocument, options);
}
export function useMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketQuery, MarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MarketQuery, MarketQueryVariables>(MarketDocument, options);
}
export type MarketQueryHookResult = ReturnType<typeof useMarketQuery>;
export type MarketLazyQueryHookResult = ReturnType<typeof useMarketLazyQuery>;
export type MarketQueryResult = Apollo.QueryResult<MarketQuery, MarketQueryVariables>;

View File

@ -1,8 +1,4 @@
import { import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
compileGridData,
DealTicketContainer,
TradingModeTooltip,
} from '@vegaprotocol/deal-ticket';
import { MarketInfoContainer } from '@vegaprotocol/market-info'; import { MarketInfoContainer } from '@vegaprotocol/market-info';
import { OrderbookContainer } from '@vegaprotocol/market-depth'; import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { OrderListContainer } from '@vegaprotocol/orders'; import { OrderListContainer } from '@vegaprotocol/orders';
@ -12,9 +8,8 @@ import { TradesContainer } from '@vegaprotocol/trades';
import { LayoutPriority } from 'allotment'; import { LayoutPriority } from 'allotment';
import classNames from 'classnames'; import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { useState } from 'react'; import { memo, useState } from 'react';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { Market_market } from './__generated__/Market';
import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { import {
@ -23,23 +18,11 @@ import {
ResizableGrid, ResizableGrid,
ResizableGridPanel, ResizableGridPanel,
ButtonLink, ButtonLink,
PriceCellChange,
Link, Link,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { import { getDateFormat, t } from '@vegaprotocol/react-helpers';
addDecimalsFormatNumber,
getDateFormat,
t,
} from '@vegaprotocol/react-helpers';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import type { CandleClose } from '@vegaprotocol/types';
import {
AuctionTrigger,
AuctionTriggerMapping,
MarketTradingMode,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { Header, HeaderStat } from '../../components/header'; import { Header, HeaderStat } from '../../components/header';
import { AccountsContainer } from '../portfolio/accounts-container'; import { AccountsContainer } from '../portfolio/accounts-container';
import { import {
@ -47,6 +30,11 @@ import {
SelectMarketPopover, SelectMarketPopover,
} from '../../components/select-market'; } from '../../components/select-market';
import type { OnCellClickHandler } from '../../components/select-market'; import type { OnCellClickHandler } from '../../components/select-market';
import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
import { Last24hPriceChange } from '../../components/last-24h-price-change';
import { MarketMarkPrice } from '../../components/market-mark-price';
import { MarketVolume } from '../../components/market-volume';
import { MarketTradingModeComponent } from '../../components/market-trading-mode';
const TradingViews = { const TradingViews = {
Candles: CandlesChartContainer, Candles: CandlesChartContainer,
@ -64,7 +52,7 @@ const TradingViews = {
type TradingView = keyof typeof TradingViews; type TradingView = keyof typeof TradingViews;
type ExpiryLabelProps = { type ExpiryLabelProps = {
market: Market_market; market: SingleMarketFieldsFragment;
}; };
const ExpiryLabel = ({ market }: ExpiryLabelProps) => { const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
@ -72,7 +60,7 @@ const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
if (market.marketTimestamps.close === null) { if (market.marketTimestamps.close === null) {
content = t('Not time-based'); content = t('Not time-based');
} else { } else {
const closeDate = new Date(market.marketTimestamps.close); const closeDate = new Date(market.marketTimestamps.close as string);
const isExpired = Date.now() - closeDate.valueOf() > 0; const isExpired = Date.now() - closeDate.valueOf() > 0;
const expiryDate = getDateFormat().format(closeDate); const expiryDate = getDateFormat().format(closeDate);
content = `${isExpired ? `${t('Expired')} ` : ''} ${expiryDate}`; content = `${isExpired ? `${t('Expired')} ` : ''} ${expiryDate}`;
@ -81,7 +69,7 @@ const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
}; };
type ExpiryTooltipContentProps = { type ExpiryTooltipContentProps = {
market: Market_market; market: SingleMarketFieldsFragment;
explorerUrl?: string; explorerUrl?: string;
}; };
@ -114,7 +102,7 @@ const ExpiryTooltipContent = ({
}; };
interface TradeMarketHeaderProps { interface TradeMarketHeaderProps {
market: Market_market; market: SingleMarketFieldsFragment;
onSelect: (marketId: string) => void; onSelect: (marketId: string) => void;
} }
@ -125,9 +113,6 @@ export const TradeMarketHeader = ({
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL } = useEnvironment();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const candlesClose: string[] = (market?.candlesConnection?.edges || [])
.map((candle) => candle?.node.close)
.filter((c): c is CandleClose => c !== null);
const symbol = const symbol =
market.tradableInstrument.instrument.product?.settlementAsset?.symbol; market.tradableInstrument.instrument.product?.settlementAsset?.symbol;
@ -158,51 +143,10 @@ export const TradeMarketHeader = ({
> >
<ExpiryLabel market={market} /> <ExpiryLabel market={market} />
</HeaderStat> </HeaderStat>
<HeaderStat heading={t('Price')}> <MarketMarkPrice marketId={market.id} />
<div data-testid="mark-price"> <Last24hPriceChange marketId={market.id} />
{market.data && market.data.markPrice !== '0' <MarketVolume marketId={market.id} />
? addDecimalsFormatNumber( <MarketTradingModeComponent marketId={market.id} onSelect={onSelect} />
market.data.markPrice,
market.decimalPlaces
)
: '-'}
</div>
</HeaderStat>
<HeaderStat heading={t('Change (24h)')}>
<PriceCellChange
candles={candlesClose}
decimalPlaces={market.decimalPlaces}
/>
</HeaderStat>
<HeaderStat heading={t('Volume')}>
<div data-testid="trading-volume">
{market.data && market.data.indicativeVolume !== '0'
? addDecimalsFormatNumber(
market.data.indicativeVolume,
market.positionDecimalPlaces
)
: '-'}
</div>
</HeaderStat>
<HeaderStat
heading={t('Trading mode')}
description={
<TradingModeTooltip
market={market}
compiledGrid={compileGridData(market, onSelect)}
/>
}
>
<div data-testid="trading-mode">
{market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger &&
market.data.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${MarketTradingModeMapping[market.tradingMode]}
- ${AuctionTriggerMapping[market.data.trigger]}`
: MarketTradingModeMapping[market.tradingMode]}
</div>
</HeaderStat>
{symbol ? ( {symbol ? (
<HeaderStat heading={t('Settlement asset')}> <HeaderStat heading={t('Settlement asset')}>
<div data-testid="trading-mode"> <div data-testid="trading-mode">
@ -221,95 +165,106 @@ export const TradeMarketHeader = ({
}; };
interface TradeGridProps { interface TradeGridProps {
market: Market_market; market: SingleMarketFieldsFragment;
onSelect: (marketId: string) => void; onSelect: (marketId: string) => void;
} }
export const TradeGrid = ({ market, onSelect }: TradeGridProps) => { const MainGrid = ({
return ( marketId,
<div className="h-full grid grid-rows-[min-content_1fr]"> onSelect,
<TradeMarketHeader market={market} onSelect={onSelect} /> }: {
<ResizableGrid vertical={true}> marketId: string;
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}> onSelect: (marketId: string) => void;
<ResizableGrid proportionalLayout={false} minSize={200}> }) => (
<ResizableGridPanel <ResizableGrid vertical>
priority={LayoutPriority.High} <ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
minSize={200} <ResizableGrid proportionalLayout={false} minSize={200}>
preferredSize="50%"
>
<TradeGridChild>
<Tabs>
<Tab id="candles" name={t('Candles')}>
<TradingViews.Candles marketId={market.id} />
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={market.id} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={330}
minSize={300}
>
<TradeGridChild>
<Tabs>
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket marketId={market.id} />
</Tab>
<Tab id="info" name={t('Info')}>
<TradingViews.Info
marketId={market.id}
onSelect={(id: string) => {
onSelect(id);
}}
/>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={430}
minSize={200}
>
<TradeGridChild>
<Tabs>
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={market.id} />
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={market.id} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel <ResizableGridPanel
priority={LayoutPriority.Low} priority={LayoutPriority.High}
preferredSize="25%" minSize={200}
minSize={50} preferredSize="50%"
> >
<TradeGridChild> <TradeGridChild>
<Tabs> <Tabs>
<Tab id="positions" name={t('Positions')}> <Tab id="candles" name={t('Candles')}>
<TradingViews.Positions /> <TradingViews.Candles marketId={marketId} />
</Tab> </Tab>
<Tab id="orders" name={t('Orders')}> <Tab id="depth" name={t('Depth')}>
<TradingViews.Orders /> <TradingViews.Depth marketId={marketId} />
</Tab> </Tab>
<Tab id="fills" name={t('Fills')}> </Tabs>
<TradingViews.Fills /> </TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={330}
minSize={300}
>
<TradeGridChild>
<Tabs>
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket marketId={marketId} />
</Tab> </Tab>
<Tab id="accounts" name={t('Collateral')}> <Tab id="info" name={t('Info')}>
<TradingViews.Collateral /> <TradingViews.Info
marketId={marketId}
onSelect={(id: string) => {
onSelect(id);
}}
/>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={430}
minSize={200}
>
<TradeGridChild>
<Tabs>
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={marketId} />
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={marketId} />
</Tab> </Tab>
</Tabs> </Tabs>
</TradeGridChild> </TradeGridChild>
</ResizableGridPanel> </ResizableGridPanel>
</ResizableGrid> </ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="25%"
minSize={50}
>
<TradeGridChild>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<TradingViews.Positions />
</Tab>
<Tab id="orders" name={t('Orders')}>
<TradingViews.Orders />
</Tab>
<Tab id="fills" name={t('Fills')}>
<TradingViews.Fills />
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<TradingViews.Collateral />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
);
const MainGridWrapped = memo(MainGrid);
export const TradeGrid = ({ market, onSelect }: TradeGridProps) => {
return (
<div className="h-full grid grid-rows-[min-content_1fr]">
<TradeMarketHeader market={market} onSelect={onSelect} />
<MainGridWrapped marketId={market.id} onSelect={onSelect} />
</div> </div>
); );
}; };
@ -329,7 +284,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
}; };
interface TradePanelsProps { interface TradePanelsProps {
market: Market_market; market: SingleMarketFieldsFragment;
onSelect: (marketId: string) => void; onSelect: (marketId: string) => void;
} }
@ -337,20 +292,16 @@ export const TradePanels = ({ market, onSelect }: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('Candles'); const [view, setView] = useState<TradingView>('Candles');
const renderView = () => { const renderView = () => {
const Component = TradingViews[view]; const Component = memo<{
marketId: string;
onSelect: (marketId: string) => void;
}>(TradingViews[view]);
if (!Component) { if (!Component) {
throw new Error(`No component for view: ${view}`); throw new Error(`No component for view: ${view}`);
} }
return ( return <Component marketId={market.id} onSelect={onSelect} />;
<Component
marketId={market.id}
onSelect={(id: string) => {
onSelect(id);
}}
/>
);
}; };
return ( return (

View File

@ -9,6 +9,8 @@ interface GlobalStore {
marketId: string | null; marketId: string | null;
pageTitle: string | null; pageTitle: string | null;
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void; update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
updateTitle: (title: string) => void;
updateMarketId: (marketId: string) => void;
} }
export const useGlobalStore = create<GlobalStore>((set) => ({ export const useGlobalStore = create<GlobalStore>((set) => ({
@ -24,4 +26,9 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
LocalStorage.setItem('marketId', state.marketId); LocalStorage.setItem('marketId', state.marketId);
} }
}, },
updateTitle: (title: string) => set({ pageTitle: title }),
updateMarketId: (marketId: string) => {
set({ marketId });
LocalStorage.setItem('marketId', marketId);
},
})); }));

View File

@ -11,7 +11,7 @@ import type { MarketDataGridProps } from './market-data-grid';
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket'; import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
export const compileGridData = ( export const compileGridData = (
market: DealTicketMarketFragment, market: Omit<DealTicketMarketFragment, 'depth'>,
onSelect?: (id: string) => void onSelect?: (id: string) => void
): { label: ReactNode; value?: ReactNode }[] => { ): { label: ReactNode; value?: ReactNode }[] => {
const grid: MarketDataGridProps['grid'] = []; const grid: MarketDataGridProps['grid'] = [];

View File

@ -5,18 +5,17 @@ import type { ReactNode } from 'react';
import { MarketDataGrid } from './market-data-grid'; import { MarketDataGrid } from './market-data-grid';
type TradingModeTooltipProps = { type TradingModeTooltipProps = {
market: { tradingMode: MarketTradingMode | null;
tradingMode: MarketTradingMode; trigger: AuctionTrigger | null;
data: { trigger: AuctionTrigger | null } | null;
};
compiledGrid: { label: ReactNode; value?: ReactNode }[]; compiledGrid: { label: ReactNode; value?: ReactNode }[];
}; };
export const TradingModeTooltip = ({ export const TradingModeTooltip = ({
market, tradingMode,
trigger,
compiledGrid, compiledGrid,
}: TradingModeTooltipProps) => { }: TradingModeTooltipProps) => {
switch (market.tradingMode) { switch (tradingMode) {
case MarketTradingMode.TRADING_MODE_CONTINUOUS: { case MarketTradingMode.TRADING_MODE_CONTINUOUS: {
return ( return (
<> <>
@ -44,7 +43,7 @@ export const TradingModeTooltip = ({
); );
} }
case MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: { case MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: {
switch (market.data?.trigger) { switch (trigger) {
case AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: { case AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
return ( return (
<> <>

View File

@ -39,7 +39,7 @@ export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => {
delta, delta,
}: { }: {
data: (TradeEdge | null)[] | null; data: (TradeEdge | null)[] | null;
delta: Trade[]; delta?: Trade[];
}) => { }) => {
if (!gridRef.current?.api) { if (!gridRef.current?.api) {
return false; return false;
@ -48,7 +48,7 @@ export const useFillsList = ({ partyId, gridRef, scrolledToTop }: Props) => {
if (!scrolledToTop.current) { if (!scrolledToTop.current) {
const createdAt = dataRef.current?.[0]?.node.createdAt; const createdAt = dataRef.current?.[0]?.node.createdAt;
if (createdAt) { if (createdAt) {
newRows.current += delta.filter( newRows.current += (delta || []).filter(
(trade) => trade.createdAt > createdAt (trade) => trade.createdAt > createdAt
).length; ).length;
} }

View File

@ -93,12 +93,12 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
({ ({
delta: deltas, delta: deltas,
}: { }: {
delta: MarketDepthSubscription_marketsDepthUpdate[]; delta?: MarketDepthSubscription_marketsDepthUpdate[];
}) => { }) => {
if (!dataRef.current) { if (!dataRef.current) {
return false; return false;
} }
for (const delta of deltas) { for (const delta of deltas || []) {
if (delta.marketId !== marketId) { if (delta.marketId !== marketId) {
continue; continue;
} }

View File

@ -63,12 +63,12 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
({ ({
delta: deltas, delta: deltas,
}: { }: {
delta: MarketDepthSubscription_marketsDepthUpdate[]; delta?: MarketDepthSubscription_marketsDepthUpdate[];
}) => { }) => {
if (!dataRef.current.rows) { if (!dataRef.current.rows) {
return false; return false;
} }
for (const delta of deltas) { for (const delta of deltas || []) {
if (delta.marketId !== marketId) { if (delta.marketId !== marketId) {
continue; continue;
} }

View File

@ -0,0 +1,93 @@
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type SingleMarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, oracleSpecForTradingTermination: { __typename?: 'OracleSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null } };
export type MarketQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type MarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, oracleSpecForTradingTermination: { __typename?: 'OracleSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null } } | null };
export const SingleMarketFieldsFragmentDoc = gql`
fragment SingleMarketFields on Market {
id
decimalPlaces
positionDecimalPlaces
state
tradingMode
fees {
factors {
makerFee
infrastructureFee
liquidityFee
}
}
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
product {
... on Future {
oracleSpecForTradingTermination {
id
}
settlementAsset {
id
symbol
name
decimals
}
quoteName
}
}
}
}
marketTimestamps {
open
close
}
}
`;
export const MarketDocument = gql`
query Market($marketId: ID!) {
market(id: $marketId) {
...SingleMarketFields
}
}
${SingleMarketFieldsFragmentDoc}`;
/**
* __useMarketQuery__
*
* To run a query within a React component, call `useMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useMarketQuery({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useMarketQuery(baseOptions: Apollo.QueryHookOptions<MarketQuery, MarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MarketQuery, MarketQueryVariables>(MarketDocument, options);
}
export function useMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketQuery, MarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MarketQuery, MarketQueryVariables>(MarketDocument, options);
}
export type MarketQueryHookResult = ReturnType<typeof useMarketQuery>;
export type MarketLazyQueryHookResult = ReturnType<typeof useMarketLazyQuery>;
export type MarketQueryResult = Apollo.QueryResult<MarketQuery, MarketQueryVariables>;

View File

@ -8,6 +8,7 @@ export * from './markets-data-provider';
export * from './markets-provider'; export * from './markets-provider';
export * from './__generated___/market-candles'; export * from './__generated___/market-candles';
export * from './__generated___/market-data'; export * from './__generated___/market-data';
export * from './__generated___/market';
export * from './__generated___/markets'; export * from './__generated___/markets';
export * from './__generated___/markets-candles'; export * from './__generated___/markets-candles';
export * from './__generated___/markets-data'; export * from './__generated___/markets-data';

View File

@ -11,9 +11,8 @@ import {
export type Candle = MarketCandlesFieldsFragment; export type Candle = MarketCandlesFieldsFragment;
const update = (data: Candle[], delta: Candle) => { const update = (data: Candle[], delta: Candle) =>
return data && delta ? [...data, delta] : data; data && delta ? [...data, delta] : data;
};
const getData = (responseData: MarketCandlesQuery): Candle[] | null => const getData = (responseData: MarketCandlesQuery): Candle[] | null =>
responseData?.marketsConnection?.edges[0]?.node.candlesConnection?.edges responseData?.marketsConnection?.edges[0]?.node.candlesConnection?.edges

View File

@ -1,19 +1,20 @@
import { makeDerivedDataProvider } from '@vegaprotocol/react-helpers'; import { makeDataProvider } from '@vegaprotocol/react-helpers';
import { MarketDocument } from './__generated___/market';
import type {
MarketQuery,
SingleMarketFieldsFragment,
} from './__generated___/market';
import type { Market } from './markets-provider'; const getData = (
import { marketsProvider } from './markets-provider'; responseData: MarketQuery
): SingleMarketFieldsFragment | null => responseData?.market || null;
export const marketProvider = makeDerivedDataProvider<Market, never>( export const marketProvider = makeDataProvider<
[(callback, client) => marketsProvider(callback, client)], // omit variables param MarketQuery,
([markets], variables) => { SingleMarketFieldsFragment,
if (markets) { never,
const market = (markets as Market[]).find( never
(market) => market.id === variables?.['marketId'] >({
); query: MarketDocument,
if (market) { getData,
return market; });
}
}
return null;
}
);

View File

@ -0,0 +1,48 @@
fragment SingleMarketFields on Market {
id
decimalPlaces
positionDecimalPlaces
state
tradingMode
fees {
factors {
makerFee
infrastructureFee
liquidityFee
}
}
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
product {
... on Future {
oracleSpecForTradingTermination {
id
}
settlementAsset {
id
symbol
name
decimals
}
quoteName
}
}
}
}
marketTimestamps {
open
close
}
}
query Market($marketId: ID!) {
market(id: $marketId) {
...SingleMarketFields
}
}

View File

@ -40,7 +40,7 @@ export const useOrderListData = ({
}, [gridRef]); }, [gridRef]);
const update = useCallback( const update = useCallback(
({ data, delta }: { data: (OrderEdge | null)[]; delta: Order[] }) => { ({ data, delta }: { data: (OrderEdge | null)[]; delta?: Order[] }) => {
if (!gridRef.current?.api) { if (!gridRef.current?.api) {
return false; return false;
} }
@ -48,7 +48,7 @@ export const useOrderListData = ({
if (!scrolledToTop.current) { if (!scrolledToTop.current) {
const createdAt = dataRef.current?.[0]?.node.createdAt; const createdAt = dataRef.current?.[0]?.node.createdAt;
if (createdAt) { if (createdAt) {
newRows.current += delta.filter( newRows.current += (delta || []).filter(
(trade) => trade.createdAt > createdAt (trade) => trade.createdAt > createdAt
).length; ).length;
} }

View File

@ -26,11 +26,12 @@ export function useDataProvider<Data, Delta>({
update, update,
insert, insert,
variables, variables,
updateOnInit,
noUpdate, noUpdate,
skip, skip,
}: { }: {
dataProvider: Subscribe<Data, Delta>; dataProvider: Subscribe<Data, Delta>;
update?: ({ delta, data }: { delta: Delta; data: Data }) => boolean; update?: ({ delta, data }: { delta?: Delta; data: Data }) => boolean;
insert?: ({ insert?: ({
insertionData, insertionData,
data, data,
@ -41,6 +42,7 @@ export function useDataProvider<Data, Delta>({
totalCount?: number; totalCount?: number;
}) => boolean; }) => boolean;
variables?: OperationVariables; variables?: OperationVariables;
updateOnInit?: boolean;
noUpdate?: boolean; noUpdate?: boolean;
skip?: boolean; skip?: boolean;
}) { }) {
@ -103,12 +105,15 @@ export function useDataProvider<Data, Delta>({
return; return;
} }
} }
initialized.current = true;
setTotalCount(totalCount); setTotalCount(totalCount);
setData(data); setData(data);
if (updateOnInit && !initialized.current && update && data) {
update({ data });
}
initialized.current = true;
} }
}, },
[update, insert, noUpdate] [update, insert, noUpdate, updateOnInit]
); );
useEffect(() => { useEffect(() => {
if (skip) { if (skip) {
@ -122,6 +127,7 @@ export function useDataProvider<Data, Delta>({
flushRef.current = flush; flushRef.current = flush;
reloadRef.current = reload; reloadRef.current = reload;
loadRef.current = load; loadRef.current = load;
initialized.current = false;
return unsubscribe; return unsubscribe;
}, [client, initialized, dataProvider, callback, variables, skip]); }, [client, initialized, dataProvider, callback, variables, skip]);
return { data, loading, error, flush, reload, load, totalCount }; return { data, loading, error, flush, reload, load, totalCount };

View File

@ -47,7 +47,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
delta, delta,
}: { }: {
data: (TradeEdge | null)[] | null; data: (TradeEdge | null)[] | null;
delta: Trade[]; delta?: Trade[];
}) => { }) => {
if (!gridRef.current?.api) { if (!gridRef.current?.api) {
return false; return false;
@ -56,7 +56,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
if (!scrolledToTop.current) { if (!scrolledToTop.current) {
const createdAt = dataRef.current?.[0]?.node.createdAt; const createdAt = dataRef.current?.[0]?.node.createdAt;
if (createdAt) { if (createdAt) {
newRows.current += delta.filter( newRows.current += (delta || []).filter(
(trade) => trade.createdAt > createdAt (trade) => trade.createdAt > createdAt
).length; ).length;
} }