From a08f63c7d89e1125785fd5a1df8bb578d42ac70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Tue, 13 Sep 2022 12:14:06 +0200 Subject: [PATCH] Feature/1243 market data providers refactoring (#1254) * feat(#1243): Market data providers * feat(#1243): Market data providers * feat(#1243): Market data providers * feat(#1243): refactor market lists * feat(#1243): refactor market lists * feat(#1243): refactor market lists * feat(#1243): refactor orderbook data providers * feat(#1243): refactor orderbook data providers * feat(#1243): refactor depth chart data layer * feat(#1243): fix market depth typing * fixed unit tests * feat(#1243): e2e test fixes * feat(#1243): code style fixes * feat(#1243): post merge fixes * feat(#1243): fix lint issues * feat(#1243): post merge fixes * feat(#1243): remove market name from market * feat(#1243): fix console lite e2e data depth mocks * feat(#1243): fix console lite e2e market trade test * feat(#1243): fix lint issues * feat(#1243): fix trading e2e home test Co-authored-by: asiaznik --- .../src/integration/market-selector.test.ts | 2 + .../src/integration/market-trade.test.ts | 6 +- .../support/mocks/generate-market-depth.ts | 28 +- .../src/support/mocks/generate-markets.ts | 40 +++ .../app/hooks/use-calculate-slippage.spec.tsx | 9 + .../src/app/hooks/use-calculate-slippage.ts | 19 +- apps/trading-e2e/src/integration/home.cy.ts | 11 +- .../trading-e2e/src/integration/markets.cy.ts | 16 +- .../support/mocks/generate-market-depth.ts | 17 - .../src/support/mocks/generate-markets.ts | 315 +++++++++++------ .../src/support/mocks/generate-order-book.ts | 20 -- apps/trading-e2e/src/support/trading.ts | 11 +- apps/trading/pages/index.page.tsx | 8 +- .../deal-ticket/deal-ticket-container.tsx | 1 + .../src/lib/__generated__/MarketDepth.ts | 82 ----- .../__generated__/MarketDepthSubscription.ts | 85 +---- libs/market-depth/src/lib/depth-chart.tsx | 145 ++++++-- libs/market-depth/src/lib/index.ts | 2 +- .../src/lib/market-depth-data-provider.ts | 138 -------- .../src/lib/market-depth-provider.ts | 111 ++++++ libs/market-depth/src/lib/orderbook-data.ts | 94 ++--- .../src/lib/orderbook-manager.tsx | 124 ++++--- .../src/lib/use-orderbook-data.ts | 4 +- libs/market-list/src/lib/MarketData.graphql | 75 ---- .../lib/__generated__/MarketCandlesQuery.ts | 78 +++++ .../src/lib/__generated__/MarketCandlesSub.ts | 46 +++ .../src/lib/__generated__/MarketDataFields.ts | 32 +- .../src/lib/__generated__/MarketDataQuery.ts | 95 ++++++ .../src/lib/__generated__/MarketDataSub.ts | 50 +-- .../src/lib/__generated__/MarketFields.ts | 154 +++++++++ .../{MarketList.ts => Markets.ts} | 131 ++----- .../lib/__generated__/MarketsCandlesQuery.ts | 81 +++++ .../src/lib/__generated__/MarketsDataQuery.ts | 91 +++++ .../src/lib/__generated__/index.ts | 9 + .../markets-container/market-list-table.tsx | 24 +- .../markets-container/markets-container.tsx | 78 +---- .../lib/components/select-market-columns.tsx | 79 ++--- .../src/lib/components/select-market.spec.tsx | 321 +++++++++--------- .../src/lib/components/select-market.tsx | 175 ++++++---- libs/market-list/src/lib/index.ts | 9 +- .../src/lib/market-candles-provider.ts | 83 +++++ .../src/lib/market-data-provider.ts | 81 +++++ libs/market-list/src/lib/market-provider.ts | 19 ++ .../src/lib/markets-candles-provider.ts | 53 +++ .../src/lib/markets-data-provider.ts | 156 ++------- libs/market-list/src/lib/markets-provider.ts | 144 ++++++++ .../src/lib/utils/market-utils.spec.tsx | 257 +++----------- .../market-list/src/lib/utils/market-utils.ts | 44 +-- libs/positions/src/index.ts | 1 + .../src/lib/positions-data-providers.ts | 4 +- .../src/hooks/use-data-provider.ts | 20 +- .../src/lib/generic-data-provider.ts | 65 ++-- libs/types/src/__generated__/globalTypes.ts | 4 +- 53 files changed, 2201 insertions(+), 1546 deletions(-) delete mode 100644 libs/market-depth/src/lib/market-depth-data-provider.ts create mode 100644 libs/market-depth/src/lib/market-depth-provider.ts delete mode 100644 libs/market-list/src/lib/MarketData.graphql create mode 100644 libs/market-list/src/lib/__generated__/MarketCandlesQuery.ts create mode 100644 libs/market-list/src/lib/__generated__/MarketCandlesSub.ts create mode 100644 libs/market-list/src/lib/__generated__/MarketDataQuery.ts create mode 100644 libs/market-list/src/lib/__generated__/MarketFields.ts rename libs/market-list/src/lib/__generated__/{MarketList.ts => Markets.ts} (55%) create mode 100644 libs/market-list/src/lib/__generated__/MarketsCandlesQuery.ts create mode 100644 libs/market-list/src/lib/__generated__/MarketsDataQuery.ts create mode 100644 libs/market-list/src/lib/__generated__/index.ts create mode 100644 libs/market-list/src/lib/market-candles-provider.ts create mode 100644 libs/market-list/src/lib/market-data-provider.ts create mode 100644 libs/market-list/src/lib/market-provider.ts create mode 100644 libs/market-list/src/lib/markets-candles-provider.ts create mode 100644 libs/market-list/src/lib/markets-provider.ts diff --git a/apps/console-lite-e2e/src/integration/market-selector.test.ts b/apps/console-lite-e2e/src/integration/market-selector.test.ts index be2502964..7b94eacb6 100644 --- a/apps/console-lite-e2e/src/integration/market-selector.test.ts +++ b/apps/console-lite-e2e/src/integration/market-selector.test.ts @@ -9,6 +9,7 @@ import { generatePartyBalance } from '../support/mocks/generate-party-balance'; import { generatePartyMarketData } from '../support/mocks/generate-party-market-data'; import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price'; import { generateMarketNames } from '../support/mocks/generate-market-names'; +import { generateMarketDepth } from '../support/mocks/generate-market-depth'; describe('market selector', () => { let markets; @@ -23,6 +24,7 @@ describe('market selector', () => { aliasQuery(req, 'PartyMarketData', generatePartyMarketData()); aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice()); aliasQuery(req, 'MarketNames', generateMarketNames()); + aliasQuery(req, 'MarketDepth', generateMarketDepth()); }); cy.visit('/markets'); diff --git a/apps/console-lite-e2e/src/integration/market-trade.test.ts b/apps/console-lite-e2e/src/integration/market-trade.test.ts index 6665940bd..0e7cf03a7 100644 --- a/apps/console-lite-e2e/src/integration/market-trade.test.ts +++ b/apps/console-lite-e2e/src/integration/market-trade.test.ts @@ -1,5 +1,8 @@ import { aliasQuery } from '@vegaprotocol/cypress'; -import { generateSimpleMarkets } from '../support/mocks/generate-markets'; +import { + generateSimpleMarkets, + generateMarkets, +} from '../support/mocks/generate-markets'; import { generateDealTicket } from '../support/mocks/generate-deal-ticket'; import { generateMarketTags } from '../support/mocks/generate-market-tags'; import { generateMarketPositions } from '../support/mocks/generate-market-positions'; @@ -14,6 +17,7 @@ describe('Market trade', () => { let markets; beforeEach(() => { cy.mockGQL((req) => { + aliasQuery(req, 'Markets', generateMarkets()); aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets()); aliasQuery(req, 'DealTicketQuery', generateDealTicket()); aliasQuery(req, 'MarketTags', generateMarketTags()); diff --git a/apps/console-lite-e2e/src/support/mocks/generate-market-depth.ts b/apps/console-lite-e2e/src/support/mocks/generate-market-depth.ts index f8c60fead..d0342a6ea 100644 --- a/apps/console-lite-e2e/src/support/mocks/generate-market-depth.ts +++ b/apps/console-lite-e2e/src/support/mocks/generate-market-depth.ts @@ -1,24 +1,15 @@ -export const generateMarketDepth = () => { - return { +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries +import type { MarketDepth } from '../../../../../libs/market-depth/src/lib/__generated__/MarketDepth'; + +export const generateMarketDepth = ( + override?: PartialDeep +): MarketDepth => { + const defaultResult: MarketDepth = { market: { id: 'a46bd7e5277087723b7ab835844dec3cef8b4445738101269624bf5537d5d423', - decimalPlaces: 5, - positionDecimalPlaces: 0, - data: { - staticMidPrice: '9893006', - marketTradingMode: 'TRADING_MODE_CONTINUOUS', - indicativeVolume: '0', - indicativePrice: '0', - bestStaticBidPrice: '9893006', - bestStaticOfferPrice: '9893006', - market: { - id: 'a46bd7e5277087723b7ab835844dec3cef8b4445738101269624bf5537d5d423', - __typename: 'Market', - }, - __typename: 'MarketData', - }, depth: { - lastTrade: { price: '9893006', __typename: 'Trade' }, sell: [ { price: '9893007', @@ -215,4 +206,5 @@ export const generateMarketDepth = () => { __typename: 'Market', }, }; + return merge(defaultResult, override); }; diff --git a/apps/console-lite-e2e/src/support/mocks/generate-markets.ts b/apps/console-lite-e2e/src/support/mocks/generate-markets.ts index 18a3cdc42..16d39a83a 100644 --- a/apps/console-lite-e2e/src/support/mocks/generate-markets.ts +++ b/apps/console-lite-e2e/src/support/mocks/generate-markets.ts @@ -1,3 +1,5 @@ +import merge from 'lodash/merge'; +import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import { protoMarket } from './commons'; export const generateSimpleMarkets = () => { @@ -880,3 +882,41 @@ export const generateLongListMarkets = (count: number) => { } return { markets }; }; + +export const generateMarkets = (override?) => { + const markets = [ + { + ...protoMarket, + decimalPlaces: 5, + positionDecimalPlaces: 0, + tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + state: MarketState.STATE_ACTIVE, + marketTimestamps: { + __typename: 'MarketTimestamps', + close: '', + open: '', + }, + fees: { + __typename: 'Fees', + factors: { + __typename: 'FeeFactors', + makerFee: '', + infrastructureFee: '', + liquidityFee: '', + }, + }, + }, + ]; + + const defaultResult = { + marketsConnection: { + __typename: 'MarketConnection', + edges: markets.map((node) => ({ + __typename: 'MarketEdge', + node, + })), + }, + }; + + return merge(defaultResult, override); +}; diff --git a/apps/console-lite/src/app/hooks/use-calculate-slippage.spec.tsx b/apps/console-lite/src/app/hooks/use-calculate-slippage.spec.tsx index 49b58dfd5..b5706cb88 100644 --- a/apps/console-lite/src/app/hooks/use-calculate-slippage.spec.tsx +++ b/apps/console-lite/src/app/hooks/use-calculate-slippage.spec.tsx @@ -64,6 +64,15 @@ jest.mock('@vegaprotocol/market-depth', () => ({ useOrderBookData: jest.fn(() => mockOrderBookData), })); +jest.mock('@vegaprotocol/react-helpers', () => ({ + ...jest.requireActual('@vegaprotocol/react-helpers'), + useDataProvider: jest.fn(() => ({ + data: { + marketsConnection: [], + }, + })), +})); + describe('useCalculateSlippage Hook', () => { describe('calculate proper result', () => { afterEach(() => { diff --git a/apps/console-lite/src/app/hooks/use-calculate-slippage.ts b/apps/console-lite/src/app/hooks/use-calculate-slippage.ts index 13520587a..ccf937a66 100644 --- a/apps/console-lite/src/app/hooks/use-calculate-slippage.ts +++ b/apps/console-lite/src/app/hooks/use-calculate-slippage.ts @@ -1,9 +1,15 @@ import { useMemo } from 'react'; import { Side } from '@vegaprotocol/types'; import { useOrderBookData } from '@vegaprotocol/market-depth'; +import { marketProvider } from '@vegaprotocol/market-list'; +import type { Market } from '@vegaprotocol/market-list'; import type { Order } from '@vegaprotocol/orders'; import { BigNumber } from 'bignumber.js'; -import { formatNumber, toBigNum } from '@vegaprotocol/react-helpers'; +import { + formatNumber, + toBigNum, + useDataProvider, +} from '@vegaprotocol/react-helpers'; interface Props { marketId: string; @@ -16,11 +22,16 @@ const useCalculateSlippage = ({ marketId, order }: Props) => { variables, throttleMilliseconds: 5000, }); + const { data: market } = useDataProvider({ + dataProvider: marketProvider, + noUpdate: true, + variables, + }); const volPriceArr = data?.depth[order.side === Side.SIDE_BUY ? 'sell' : 'buy'] || []; - if (volPriceArr.length) { - const decimals = data?.decimalPlaces ?? 0; - const positionDecimals = data?.positionDecimalPlaces ?? 0; + if (volPriceArr.length && market) { + const decimals = market.decimalPlaces ?? 0; + const positionDecimals = market.positionDecimalPlaces ?? 0; const bestPrice = toBigNum(volPriceArr[0].price, decimals); const { size } = order; let descSize = new BigNumber(size); diff --git a/apps/trading-e2e/src/integration/home.cy.ts b/apps/trading-e2e/src/integration/home.cy.ts index 996050767..509b020bf 100644 --- a/apps/trading-e2e/src/integration/home.cy.ts +++ b/apps/trading-e2e/src/integration/home.cy.ts @@ -55,11 +55,16 @@ describe('home', () => { it('redirects to a the market list page if no sensible default is found', () => { // Mock markets query that is triggered by home page to find default market cy.mockGQL((req) => { - aliasQuery(req, 'MarketList', { markets: [] }); + const data = { + marketsConnection: { + __typename: 'MarketConnection', + edges: [], + }, + }; + aliasQuery(req, 'Markets', data); }); - cy.visit('/'); - cy.wait('@MarketList'); + cy.wait('@Markets'); cy.url().should('eq', Cypress.config().baseUrl + '/markets'); }); }); diff --git a/apps/trading-e2e/src/integration/markets.cy.ts b/apps/trading-e2e/src/integration/markets.cy.ts index afc4ea06f..fada777dc 100644 --- a/apps/trading-e2e/src/integration/markets.cy.ts +++ b/apps/trading-e2e/src/integration/markets.cy.ts @@ -11,7 +11,9 @@ describe('markets table', () => { it('renders markets correctly', () => { cy.visit('/'); cy.wait('@Market'); - cy.wait('@MarketList'); + cy.wait('@Markets'); + cy.wait('@MarketsDataQuery'); + cy.wait('@MarketsCandlesQuery'); cy.get('[data-testid^="market-link-"]') .should('not.be.empty') .and('have.attr', 'href'); @@ -24,7 +26,9 @@ describe('markets table', () => { it('renders market list drop down', () => { cy.visit('/'); - cy.wait('@MarketList'); + cy.wait('@Markets'); + cy.wait('@MarketsDataQuery'); + cy.wait('@MarketsCandlesQuery'); openMarketDropDown(); cy.getByTestId('price').invoke('text').should('not.be.empty'); cy.getByTestId('trading-mode').should('not.be.empty'); @@ -35,7 +39,9 @@ describe('markets table', () => { it('Able to select market from dropdown', () => { cy.visit('/'); - cy.wait('@MarketList'); + cy.wait('@Markets'); + cy.wait('@MarketsDataQuery'); + cy.wait('@MarketsCandlesQuery'); openMarketDropDown(); cy.getByTestId('market-link-market-0').should('be.visible').click(); @@ -53,7 +59,9 @@ describe('markets table', () => { 'SOLUSD', ]; cy.visit('/'); - cy.wait('@MarketList'); + cy.wait('@Markets'); + cy.wait('@MarketsDataQuery'); + cy.wait('@MarketsCandlesQuery'); cy.getByTestId('link').should('have.attr', 'href', '/markets').click(); cy.url().should('eq', Cypress.config('baseUrl') + '/markets'); cy.contains('AAPL.MF21').should('be.visible'); diff --git a/apps/trading-e2e/src/support/mocks/generate-market-depth.ts b/apps/trading-e2e/src/support/mocks/generate-market-depth.ts index d7016fc1c..74012beb1 100644 --- a/apps/trading-e2e/src/support/mocks/generate-market-depth.ts +++ b/apps/trading-e2e/src/support/mocks/generate-market-depth.ts @@ -1,5 +1,4 @@ import merge from 'lodash/merge'; -import { MarketTradingMode } from '@vegaprotocol/types'; import type { PartialDeep } from 'type-fest'; // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import type { MarketDepth } from '../../../../../libs/market-depth/src/lib/__generated__/MarketDepth'; @@ -10,26 +9,10 @@ export const generateMarketDepth = ( const defaultResult: MarketDepth = { market: { id: 'market-0', - decimalPlaces: 5, - positionDecimalPlaces: 0, - data: { - marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, - staticMidPrice: '0', - indicativePrice: '0', - bestStaticBidPrice: '0', - bestStaticOfferPrice: '0', - indicativeVolume: '0', - market: { - id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', - __typename: 'Market', - }, - __typename: 'MarketData', - }, depth: { __typename: 'MarketDepth', buy: [], sell: [], - lastTrade: null, sequenceNumber: '', }, __typename: 'Market', diff --git a/apps/trading-e2e/src/support/mocks/generate-markets.ts b/apps/trading-e2e/src/support/mocks/generate-markets.ts index c4724aa7a..f40925d47 100644 --- a/apps/trading-e2e/src/support/mocks/generate-markets.ts +++ b/apps/trading-e2e/src/support/mocks/generate-markets.ts @@ -5,12 +5,17 @@ import { MarketTradingMode, } from '@vegaprotocol/types'; import type { PartialDeep } from 'type-fest'; -import type { MarketList, MarketList_markets } from '@vegaprotocol/market-list'; +import type { + Markets, + Markets_marketsConnection_edges_node, + MarketsCandlesQuery, + MarketsCandlesQuery_marketsConnection_edges_node, + MarketsDataQuery, + MarketsDataQuery_marketsConnection_edges_node, +} from '@vegaprotocol/market-list'; -export const generateMarkets = ( - override?: PartialDeep -): MarketList => { - const markets: MarketList_markets[] = [ +export const generateMarkets = (override?: PartialDeep): Markets => { + const markets: Markets_marketsConnection_edges_node[] = [ { id: 'market-0', decimalPlaces: 5, @@ -22,15 +27,6 @@ export const generateMarkets = ( close: '', open: '', }, - candles: [ - { - __typename: 'Candle', - open: '100', - close: '100', - high: '110', - low: '90', - }, - ], fees: { __typename: 'Fees', factors: { @@ -40,20 +36,6 @@ export const generateMarkets = ( liquidityFee: '', }, }, - data: { - market: { - id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', - state: MarketState.STATE_ACTIVE, - tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, - __typename: 'Market', - }, - indicativeVolume: '0', - bestBidPrice: '0', - bestOfferPrice: '0', - markPrice: '4612690058', - trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, - __typename: 'MarketData', - }, tradableInstrument: { instrument: { id: '', @@ -87,15 +69,6 @@ export const generateMarkets = ( close: '', open: '', }, - candles: [ - { - __typename: 'Candle', - open: '100', - close: '100', - high: '110', - low: '90', - }, - ], fees: { __typename: 'Fees', factors: { @@ -105,20 +78,6 @@ export const generateMarkets = ( liquidityFee: '', }, }, - data: { - market: { - id: '34d95e10faa00c21d19d382d6d7e6fc9722a96985369f0caec041b0f44b775ed', - state: MarketState.STATE_SUSPENDED, - tradingMode: MarketTradingMode.TRADING_MODE_NO_TRADING, - __typename: 'Market', - }, - bestBidPrice: '0', - bestOfferPrice: '0', - indicativeVolume: '0', - markPrice: '8441', - trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, - __typename: 'MarketData', - }, tradableInstrument: { instrument: { id: 'SOLUSD', @@ -152,15 +111,6 @@ export const generateMarkets = ( close: '2022-08-26T11:36:32.252490405Z', open: null, }, - candles: [ - { - __typename: 'Candle', - open: '100', - close: '100', - high: '110', - low: '90', - }, - ], fees: { __typename: 'Fees', factors: { @@ -170,20 +120,6 @@ export const generateMarkets = ( liquidityFee: '0.001', }, }, - data: { - market: { - id: 'a1c731af07570ca49b22a3cd253cc143dc14068edbec918e1087e69db934af5f', - state: MarketState.STATE_SUSPENDED, - tradingMode: MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, - __typename: 'Market', - }, - indicativeVolume: '0', - bestBidPrice: '0', - bestOfferPrice: '0', - markPrice: '4612690058', - trigger: AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY, - __typename: 'MarketData', - }, tradableInstrument: { instrument: { id: '', @@ -217,15 +153,6 @@ export const generateMarkets = ( close: '2022-08-26T11:36:32.252490405Z', open: null, }, - candles: [ - { - __typename: 'Candle', - open: '100', - close: '100', - high: '110', - low: '90', - }, - ], fees: { __typename: 'Fees', factors: { @@ -235,20 +162,6 @@ export const generateMarkets = ( liquidityFee: '0.001', }, }, - data: { - market: { - id: 'bebea8ec669b913a7d6a704a6d8cede164bc1376229e0d472bc6fdaa976629b2', - state: MarketState.STATE_ACTIVE, - tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, - __typename: 'Market', - }, - indicativeVolume: '0', - bestBidPrice: '0', - bestOfferPrice: '0', - markPrice: '4612690058', - trigger: AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY, - __typename: 'MarketData', - }, tradableInstrument: { instrument: { id: '', @@ -272,8 +185,212 @@ export const generateMarkets = ( __typename: 'Market', }, ]; - const defaultResult = { - markets, + + const defaultResult: Markets = { + marketsConnection: { + __typename: 'MarketConnection', + edges: markets.map((node) => ({ + __typename: 'MarketEdge', + node, + })), + }, + }; + + return merge(defaultResult, override); +}; + +export const generateMarketsData = ( + override?: PartialDeep +): MarketsDataQuery => { + const markets: MarketsDataQuery_marketsConnection_edges_node[] = [ + { + data: { + market: { + id: 'market-0', + __typename: 'Market', + }, + marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '4612690058', + trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, + __typename: 'MarketData', + }, + __typename: 'Market', + }, + { + data: { + market: { + id: 'market-1', + __typename: 'Market', + }, + marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '8441', + trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, + __typename: 'MarketData', + }, + __typename: 'Market', + }, + { + data: { + market: { + id: 'market-2', + __typename: 'Market', + }, + marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '4612690058', + trigger: AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY, + __typename: 'MarketData', + }, + __typename: 'Market', + }, + { + data: { + market: { + id: 'market-3', + __typename: 'Market', + }, + marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '4612690058', + trigger: AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY, + __typename: 'MarketData', + }, + __typename: 'Market', + }, + ]; + + const defaultResult: MarketsDataQuery = { + marketsConnection: { + __typename: 'MarketConnection', + edges: markets.map((node) => ({ + __typename: 'MarketEdge', + node, + })), + }, + }; + + return merge(defaultResult, override); +}; + +export const generateMarketsCandles = ( + override?: PartialDeep +): MarketsCandlesQuery => { + const markets: MarketsCandlesQuery_marketsConnection_edges_node[] = [ + { + __typename: 'Market', + id: 'market-0', + candlesConnection: { + __typename: 'CandleDataConnection', + edges: [ + { + __typename: 'CandleEdge', + node: { + __typename: 'CandleNode', + open: '100', + close: '100', + high: '110', + low: '90', + volume: '1', + }, + }, + ], + }, + }, + { + __typename: 'Market', + id: 'market-1', + candlesConnection: { + __typename: 'CandleDataConnection', + edges: [ + { + __typename: 'CandleEdge', + node: { + __typename: 'CandleNode', + open: '100', + close: '100', + high: '110', + low: '90', + volume: '1', + }, + }, + ], + }, + }, + { + __typename: 'Market', + id: 'market-2', + candlesConnection: { + __typename: 'CandleDataConnection', + edges: [ + { + __typename: 'CandleEdge', + node: { + __typename: 'CandleNode', + open: '100', + close: '100', + high: '110', + low: '90', + volume: '1', + }, + }, + ], + }, + }, + { + __typename: 'Market', + id: 'market-3', + candlesConnection: { + __typename: 'CandleDataConnection', + edges: [ + { + __typename: 'CandleEdge', + node: { + __typename: 'CandleNode', + open: '100', + close: '100', + high: '110', + low: '90', + volume: '1', + }, + }, + ], + }, + }, + ]; + const defaultResult: MarketsCandlesQuery = { + marketsConnection: { + __typename: 'MarketConnection', + edges: markets.map((node) => ({ + __typename: 'MarketEdge', + node, + })), + }, }; return merge(defaultResult, override); diff --git a/apps/trading-e2e/src/support/mocks/generate-order-book.ts b/apps/trading-e2e/src/support/mocks/generate-order-book.ts index 763072179..1529083e2 100644 --- a/apps/trading-e2e/src/support/mocks/generate-order-book.ts +++ b/apps/trading-e2e/src/support/mocks/generate-order-book.ts @@ -4,33 +4,13 @@ import type { MarketDepth, MarketDepth_market, } from '@vegaprotocol/market-depth'; -import { MarketTradingMode } from '@vegaprotocol/types'; export const generateOrderBook = ( override?: PartialDeep ): MarketDepth => { const marketDepth: MarketDepth_market = { id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e', - decimalPlaces: 5, - positionDecimalPlaces: 0, - data: { - staticMidPrice: '826337', - marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, - indicativeVolume: '0', - indicativePrice: '0', - bestStaticBidPrice: '826336', - bestStaticOfferPrice: '826338', - market: { - id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e', - __typename: 'Market', - }, - __typename: 'MarketData', - }, depth: { - lastTrade: { - price: '826338', - __typename: 'Trade', - }, sell: [ { price: '826338', diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts index 0b11bfeb1..a63074c0f 100644 --- a/apps/trading-e2e/src/support/trading.ts +++ b/apps/trading-e2e/src/support/trading.ts @@ -8,7 +8,11 @@ import { generateDealTicketQuery } from './mocks/generate-deal-ticket-query'; import { generateMarket } from './mocks/generate-market'; import { generateMarketDepth } from './mocks/generate-market-depth'; import { generateMarketInfoQuery } from './mocks/generate-market-info-query'; -import { generateMarkets } from './mocks/generate-markets'; +import { + generateMarkets, + generateMarketsData, + generateMarketsCandles, +} from './mocks/generate-markets'; import { generateOrders } from './mocks/generate-orders'; import { generatePositions } from './mocks/generate-positions'; import { generateTrades } from './mocks/generate-trades'; @@ -31,7 +35,10 @@ export const mockTradingPage = ( }, }) ); - aliasQuery(req, 'MarketList', generateMarkets()); + aliasQuery(req, 'Markets', generateMarkets()); + aliasQuery(req, 'MarketsDataQuery', generateMarketsData()); + aliasQuery(req, 'MarketsCandlesQuery', generateMarketsCandles()); + aliasQuery(req, 'MarketDepth', generateMarketDepth()); aliasQuery(req, 'Orders', generateOrders()); aliasQuery(req, 'Accounts', generateAccounts()); diff --git a/apps/trading/pages/index.page.tsx b/apps/trading/pages/index.page.tsx index 542344bc8..347e2b8ff 100644 --- a/apps/trading/pages/index.page.tsx +++ b/apps/trading/pages/index.page.tsx @@ -1,4 +1,5 @@ -import { useMarketList } from '@vegaprotocol/market-list'; +import { activeMarketsProvider } from '@vegaprotocol/market-list'; +import { useDataProvider } from '@vegaprotocol/react-helpers'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; @@ -8,7 +9,10 @@ export function Index() { const { replace } = useRouter(); // The default market selected in the platform behind the overlay // should be the oldest market that is currently trading in continuous mode(i.e. not in auction). - const { data, error, loading } = useMarketList(); + const { data, error, loading } = useDataProvider({ + dataProvider: activeMarketsProvider, + noUpdate: true, + }); const { riskNoticeDialog, update } = useGlobalStore((store) => ({ riskNoticeDialog: store.riskNoticeDialog, update: store.update, diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx index 62682822f..3282ac731 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx @@ -64,6 +64,7 @@ export const DealTicketContainer = ({ ) ) : ( + {JSON.stringify(data)}

{t('Could not load market')}

)} diff --git a/libs/market-depth/src/lib/__generated__/MarketDepth.ts b/libs/market-depth/src/lib/__generated__/MarketDepth.ts index 7b043223b..7de7f05b6 100644 --- a/libs/market-depth/src/lib/__generated__/MarketDepth.ts +++ b/libs/market-depth/src/lib/__generated__/MarketDepth.ts @@ -3,60 +3,10 @@ // @generated // This file was automatically generated and should not be edited. -import { MarketTradingMode } from "@vegaprotocol/types"; - // ==================================================== // GraphQL query operation: MarketDepth // ==================================================== -export interface MarketDepth_market_data_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; -} - -export interface MarketDepth_market_data { - __typename: "MarketData"; - /** - * the arithmetic average of the best static bid price and best static offer price - */ - staticMidPrice: string; - /** - * what state the market is in (auction, continuous, etc) - */ - marketTradingMode: MarketTradingMode; - /** - * 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 highest price level on an order book for buy orders not including pegged orders. - */ - bestStaticBidPrice: string; - /** - * the lowest price level on an order book for offer orders not including pegged orders. - */ - bestStaticOfferPrice: string; - /** - * market ID of the associated mark price - */ - market: MarketDepth_market_data_market; -} - -export interface MarketDepth_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 MarketDepth_market_depth_sell { __typename: "PriceLevel"; /** @@ -91,10 +41,6 @@ export interface MarketDepth_market_depth_buy { export interface MarketDepth_market_depth { __typename: "MarketDepth"; - /** - * Last trade for the given market (if available) - */ - lastTrade: MarketDepth_market_depth_lastTrade | null; /** * Sell side price levels (if available) */ @@ -115,34 +61,6 @@ export interface MarketDepth_market { * Market ID */ id: string; - /** - * decimalPlaces indicates 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; - /** - * positionDecimalPlaces indicates 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: MarketDepth_market_data | null; /** * Current depth on the order book for this market */ diff --git a/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts b/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts index 456b6cc8c..03de5c765 100644 --- a/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts +++ b/libs/market-depth/src/lib/__generated__/MarketDepthSubscription.ts @@ -3,72 +3,11 @@ // @generated // This file was automatically generated and should not be edited. -import { MarketTradingMode } from "@vegaprotocol/types"; - // ==================================================== // GraphQL subscription operation: MarketDepthSubscription // ==================================================== -export interface MarketDepthSubscription_marketDepthUpdate_market_data_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; -} - -export interface MarketDepthSubscription_marketDepthUpdate_market_data { - __typename: "MarketData"; - /** - * the arithmetic average of the best static bid price and best static offer price - */ - staticMidPrice: string; - /** - * what state the market is in (auction, continuous, etc) - */ - marketTradingMode: MarketTradingMode; - /** - * 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 highest price level on an order book for buy orders not including pegged orders. - */ - bestStaticBidPrice: string; - /** - * the lowest price level on an order book for offer orders not including pegged orders. - */ - bestStaticOfferPrice: string; - /** - * market ID of the associated mark price - */ - market: MarketDepthSubscription_marketDepthUpdate_market_data_market; -} - -export interface MarketDepthSubscription_marketDepthUpdate_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; - /** - * positionDecimalPlaces indicates 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: MarketDepthSubscription_marketDepthUpdate_market_data | null; -} - -export interface MarketDepthSubscription_marketDepthUpdate_sell { +export interface MarketDepthSubscription_marketsDepthUpdate_sell { __typename: "PriceLevel"; /** * The price of all the orders at this level (uint64) @@ -84,7 +23,7 @@ export interface MarketDepthSubscription_marketDepthUpdate_sell { numberOfOrders: string; } -export interface MarketDepthSubscription_marketDepthUpdate_buy { +export interface MarketDepthSubscription_marketsDepthUpdate_buy { __typename: "PriceLevel"; /** * The price of all the orders at this level (uint64) @@ -100,33 +39,37 @@ export interface MarketDepthSubscription_marketDepthUpdate_buy { numberOfOrders: string; } -export interface MarketDepthSubscription_marketDepthUpdate { - __typename: "MarketDepthUpdate"; +export interface MarketDepthSubscription_marketsDepthUpdate { + __typename: "ObservableMarketDepthUpdate"; /** - * Market + * Market ID */ - market: MarketDepthSubscription_marketDepthUpdate_market; + marketId: string; /** * Sell side price levels (if available) */ - sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null; + sell: MarketDepthSubscription_marketsDepthUpdate_sell[] | null; /** * Buy side price levels (if available) */ - buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null; + buy: MarketDepthSubscription_marketsDepthUpdate_buy[] | null; /** * Sequence number for the current snapshot of the market depth. It is always increasing but not monotonic. */ sequenceNumber: string; + /** + * Sequence number of the last update sent; useful for checking that no updates were missed. + */ + previousSequenceNumber: string; } export interface MarketDepthSubscription { /** * Subscribe to price level market depth updates */ - marketDepthUpdate: MarketDepthSubscription_marketDepthUpdate; + marketsDepthUpdate: MarketDepthSubscription_marketsDepthUpdate[]; } export interface MarketDepthSubscriptionVariables { - marketId: string; + id: string; } diff --git a/libs/market-depth/src/lib/depth-chart.tsx b/libs/market-depth/src/lib/depth-chart.tsx index 8b830b49b..87cd59550 100644 --- a/libs/market-depth/src/lib/depth-chart.tsx +++ b/libs/market-depth/src/lib/depth-chart.tsx @@ -7,7 +7,7 @@ import { ThemeContext, getNumberFormat, } from '@vegaprotocol/react-helpers'; -import dataProvider from './market-depth-data-provider'; +import dataProvider from './market-depth-provider'; import { useCallback, useEffect, @@ -16,7 +16,13 @@ import { useState, useContext, } from 'react'; -import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription'; +import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list'; +import type { MarketData } from '@vegaprotocol/market-list'; +import type { + MarketDepthSubscription_marketsDepthUpdate, + MarketDepthSubscription_marketsDepthUpdate_sell, + MarketDepthSubscription_marketsDepthUpdate_buy, +} from './__generated__/MarketDepthSubscription'; import type { DepthChartProps } from 'pennant'; import { parseLevel, updateLevels } from './depth-chart-utils'; @@ -33,46 +39,80 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => { const theme = useContext(ThemeContext); const variables = useMemo(() => ({ marketId }), [marketId]); const [depthData, setDepthData] = useState(null); - const [decimalPlaces, setDecimalPlaces] = useState(0); - const [positionDecimalPlaces, setPositionDecimalPlaces] = useState(0); const dataRef = useRef(null); - const setDepthDataThrottledRef = useRef(throttle(setDepthData, 1000)); + const marketDataRef = useRef(null); + const deltaRef = useRef<{ + sell: MarketDepthSubscription_marketsDepthUpdate_sell[]; + buy: MarketDepthSubscription_marketsDepthUpdate_buy[]; + }>({ + sell: [], + buy: [], + }); - // Apply updates to the table - const update = useCallback( - ({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => { - if (!dataRef.current) { - return false; + const updateDepthData = useRef( + throttle(() => { + if (!dataRef.current || !marketDataRef.current || !market) { + return; } dataRef.current = { ...dataRef.current, - midPrice: delta.market.data?.staticMidPrice - ? formatMidPrice(delta.market.data?.staticMidPrice, decimalPlaces) + midPrice: marketDataRef.current?.staticMidPrice + ? formatMidPrice( + marketDataRef.current?.staticMidPrice, + market.decimalPlaces + ) : undefined, data: { - buy: delta.buy + buy: deltaRef.current.buy ? updateLevels( dataRef.current.data.buy, - delta.buy, - decimalPlaces, - positionDecimalPlaces, + deltaRef.current.buy, + market.decimalPlaces, + market.positionDecimalPlaces, true ) : dataRef.current.data.buy, - sell: delta.sell + sell: deltaRef.current.sell ? updateLevels( dataRef.current.data.sell, - delta.sell, - decimalPlaces, - positionDecimalPlaces + deltaRef.current.sell, + market.decimalPlaces, + market.positionDecimalPlaces ) : dataRef.current.data.sell, }, }; - setDepthDataThrottledRef.current(dataRef.current); + deltaRef.current.buy = []; + deltaRef.current.sell = []; + setDepthData(dataRef.current); + }, 1000) + ); + + // Apply updates to the table + const update = useCallback( + ({ + delta: deltas, + }: { + delta: MarketDepthSubscription_marketsDepthUpdate[]; + }) => { + if (!dataRef.current) { + return false; + } + for (const delta of deltas) { + if (delta.marketId !== marketId) { + continue; + } + if (delta.sell) { + deltaRef.current.sell.push(...delta.sell); + } + if (delta.buy) { + deltaRef.current.buy.push(...delta.buy); + } + updateDepthData.current(); + } return true; }, - [decimalPlaces, positionDecimalPlaces] + [marketId, updateDepthData] ); const { data, error, loading } = useDataProvider({ @@ -81,53 +121,86 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => { variables, }); + const { + data: market, + error: marketError, + loading: marketLoading, + } = useDataProvider({ + dataProvider: marketProvider, + noUpdate: true, + variables, + }); + + const marketDataUpdate = useCallback(({ data }: { data: MarketData }) => { + marketDataRef.current = data; + return true; + }, []); + + const { + data: marketData, + error: marketDataError, + loading: marketDataLoading, + } = useDataProvider({ + dataProvider: marketDataProvider, + update: marketDataUpdate, + variables, + }); + + marketDataRef.current = marketData; + useEffect(() => { + if (!marketData || !market || !data) { + return; + } if (!data) { dataRef.current = null; setDepthData(dataRef.current); return; } dataRef.current = { - midPrice: data.data?.staticMidPrice - ? formatMidPrice(data.data?.staticMidPrice, data.decimalPlaces) + midPrice: marketData.staticMidPrice + ? formatMidPrice(marketData.staticMidPrice, market.decimalPlaces) : undefined, data: { buy: data.depth.buy?.map((priceLevel) => parseLevel( priceLevel, - data.decimalPlaces, - data.positionDecimalPlaces + market.decimalPlaces, + market.positionDecimalPlaces ) ) ?? [], sell: data.depth.sell?.map((priceLevel) => parseLevel( priceLevel, - data.decimalPlaces, - data.positionDecimalPlaces + market.decimalPlaces, + market.positionDecimalPlaces ) ) ?? [], }, }; setDepthData(dataRef.current); - setDecimalPlaces(data.decimalPlaces); - setPositionDecimalPlaces(data.positionDecimalPlaces); - }, [data]); + }, [data, marketData, market]); const volumeFormat = useCallback( (volume: number) => - getNumberFormat(data?.positionDecimalPlaces || 0).format(volume), - [data?.positionDecimalPlaces] + getNumberFormat(market?.positionDecimalPlaces || 0).format(volume), + [market?.positionDecimalPlaces] ); const priceFormat = useCallback( - (price: number) => getNumberFormat(data?.decimalPlaces || 0).format(price), - [data?.decimalPlaces] + (price: number) => + getNumberFormat(market?.decimalPlaces || 0).format(price), + [market?.decimalPlaces] ); return ( - + {depthData && ( = {}; - -const update: Update< - MarketDepth_market, - MarketDepthSubscription_marketDepthUpdate -> = (data, delta, reload) => { - if (delta.market.id !== data.id) { - return data; - } - const sequenceNumber = Number(delta.sequenceNumber); - if (sequenceNumber <= sequenceNumbers[delta.market.id]) { - return data; - } - /* - if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) { - sequenceNumbers[delta.market.id] = 0; - reload(); - return; - } - */ - sequenceNumbers[delta.market.id] = sequenceNumber; - const updatedData = { - ...data, - data: delta.market.data, - depth: { ...data.depth }, - }; - if (delta.buy) { - updatedData.depth.buy = updateLevels(data.depth.buy ?? [], delta.buy); - } - if (delta.sell) { - updatedData.depth.sell = updateLevels(data.depth.sell ?? [], delta.sell); - } - return updatedData; -}; - -const getData = (responseData: MarketDepth) => { - if (responseData.market?.id) { - sequenceNumbers[responseData.market.id] = Number( - responseData.market.depth.sequenceNumber - ); - } - return responseData.market; -}; -const getDelta = (subscriptionData: MarketDepthSubscription) => - subscriptionData.marketDepthUpdate; - -export const marketDepthDataProvider = makeDataProvider({ - query: MARKET_DEPTH_QUERY, - subscriptionQuery: MARKET_DEPTH_SUBSCRIPTION_QUERY, - update, - getData, - getDelta, -}); - -export default marketDepthDataProvider; diff --git a/libs/market-depth/src/lib/market-depth-provider.ts b/libs/market-depth/src/lib/market-depth-provider.ts new file mode 100644 index 000000000..2a5c1dcee --- /dev/null +++ b/libs/market-depth/src/lib/market-depth-provider.ts @@ -0,0 +1,111 @@ +import { gql } from '@apollo/client'; +import { makeDataProvider } from '@vegaprotocol/react-helpers'; +import { updateLevels } from './orderbook-data'; +import type { Update } from '@vegaprotocol/react-helpers'; +import type { + MarketDepth, + MarketDepth_market, +} from './__generated__/MarketDepth'; +import type { + MarketDepthSubscription, + MarketDepthSubscription_marketsDepthUpdate, +} from './__generated__/MarketDepthSubscription'; + +const MARKET_DEPTH_QUERY = gql` + query MarketDepth($marketId: ID!) { + market(id: $marketId) { + id + depth { + sell { + price + volume + numberOfOrders + } + buy { + price + volume + numberOfOrders + } + sequenceNumber + } + } + } +`; + +export const MARKET_DEPTH_SUBSCRIPTION_QUERY = gql` + subscription MarketDepthSubscription($marketId: ID!) { + marketsDepthUpdate(marketIds: [$marketId]) { + marketId + sell { + price + volume + numberOfOrders + } + buy { + price + volume + numberOfOrders + } + sequenceNumber + previousSequenceNumber + } + } +`; + +const sequenceNumbers: Record = {}; + +const update: Update< + MarketDepth_market, + MarketDepthSubscription_marketsDepthUpdate[] +> = (data, deltas, reload) => { + for (const delta of deltas) { + if (delta.marketId !== data.id) { + continue; + } + const sequenceNumber = Number(delta.sequenceNumber); + if (sequenceNumber <= sequenceNumbers[delta.marketId]) { + return data; + } + /* + if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) { + sequenceNumbers[delta.market.id] = 0; + reload(); + return; + } + */ + sequenceNumbers[delta.marketId] = sequenceNumber; + const updatedData = { + ...data, + depth: { ...data.depth }, + }; + if (delta.buy) { + updatedData.depth.buy = updateLevels(data.depth.buy ?? [], delta.buy); + } + if (delta.sell) { + updatedData.depth.sell = updateLevels(data.depth.sell ?? [], delta.sell); + } + return updatedData; + } + return data; +}; + +const getData = (responseData: MarketDepth) => { + if (responseData.market?.id) { + sequenceNumbers[responseData.market.id] = Number( + responseData.market.depth.sequenceNumber + ); + } + return responseData.market; +}; +const getDelta = (subscriptionData: MarketDepthSubscription) => + subscriptionData.marketsDepthUpdate; + +export const marketDepthProvider = makeDataProvider({ + query: MARKET_DEPTH_QUERY, + subscriptionQuery: MARKET_DEPTH_SUBSCRIPTION_QUERY, + update, + getData, + getDelta, +}); + +export default marketDepthProvider; diff --git a/libs/market-depth/src/lib/orderbook-data.ts b/libs/market-depth/src/lib/orderbook-data.ts index fa515a464..1bb1341e3 100644 --- a/libs/market-depth/src/lib/orderbook-data.ts +++ b/libs/market-depth/src/lib/orderbook-data.ts @@ -1,15 +1,14 @@ import groupBy from 'lodash/groupBy'; import { VolumeType } from '@vegaprotocol/react-helpers'; import { MarketTradingMode } from '@vegaprotocol/types'; +import type { MarketData } from '@vegaprotocol/market-list'; import type { MarketDepth_market_depth_sell, MarketDepth_market_depth_buy, - MarketDepth_market_data, } from './__generated__/MarketDepth'; import type { - MarketDepthSubscription_marketDepthUpdate_sell, - MarketDepthSubscription_marketDepthUpdate_buy, - MarketDepthSubscription_marketDepthUpdate_market_data, + MarketDepthSubscription_marketsDepthUpdate_sell, + MarketDepthSubscription_marketsDepthUpdate_buy, } from './__generated__/MarketDepthSubscription'; export interface CumulativeVol { @@ -33,7 +32,7 @@ export interface OrderbookRowData { type PartialOrderbookRowData = Pick; export type OrderbookData = Partial< - Omit + Omit > & { rows: OrderbookRowData[] | null }; export const getPriceLevel = (price: string | bigint, resolution: number) => { @@ -105,11 +104,13 @@ export const createRow = ( const mapRawData = (dataType: VolumeType.ask | VolumeType.bid) => ( - data: + data: Omit< | MarketDepth_market_depth_sell - | MarketDepthSubscription_marketDepthUpdate_sell + | MarketDepthSubscription_marketsDepthUpdate_sell | MarketDepth_market_depth_buy - | MarketDepthSubscription_marketDepthUpdate_buy + | MarketDepthSubscription_marketsDepthUpdate_buy, + '__typename' + > ): PartialOrderbookRowData => createPartialRow(data.price, Number(data.volume), dataType); @@ -118,16 +119,18 @@ const mapRawData = */ export const compactRows = ( sell: - | ( + | Omit< | MarketDepth_market_depth_sell - | MarketDepthSubscription_marketDepthUpdate_sell - )[] + | MarketDepthSubscription_marketsDepthUpdate_sell, + '__typename' + >[] | null, buy: - | ( + | Omit< | MarketDepth_market_depth_buy - | MarketDepthSubscription_marketDepthUpdate_buy - )[] + | MarketDepthSubscription_marketsDepthUpdate_buy, + '__typename' + >[] | null, resolution: number ) => { @@ -199,8 +202,8 @@ const partiallyUpdateCompactedRows = ( dataType: VolumeType, data: OrderbookRowData[], delta: - | MarketDepthSubscription_marketDepthUpdate_sell - | MarketDepthSubscription_marketDepthUpdate_buy, + | MarketDepthSubscription_marketsDepthUpdate_sell + | MarketDepthSubscription_marketsDepthUpdate_buy, resolution: number, modifiedIndex: number ): [number, OrderbookRowData[]] => { @@ -255,8 +258,8 @@ const partiallyUpdateCompactedRows = ( */ export const updateCompactedRows = ( rows: OrderbookRowData[], - sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null, - buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null, + sell: MarketDepthSubscription_marketsDepthUpdate_sell[] | null, + buy: MarketDepthSubscription_marketsDepthUpdate_buy[] | null, resolution: number ) => { let sellModifiedIndex = -1; @@ -320,10 +323,13 @@ export const updateCompactedRows = ( }; export const mapMarketData = ( - data: - | MarketDepth_market_data - | MarketDepthSubscription_marketDepthUpdate_market_data - | null, + data: Pick< + MarketData, + | 'staticMidPrice' + | 'bestStaticBidPrice' + | 'bestStaticOfferPrice' + | 'indicativePrice' + > | null, resolution: number ) => ({ staticMidPrice: @@ -347,8 +353,8 @@ export const mapMarketData = ( export const updateLevels = ( draft: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[], updates: ( - | MarketDepthSubscription_marketDepthUpdate_buy - | MarketDepthSubscription_marketDepthUpdate_sell + | MarketDepthSubscription_marketsDepthUpdate_buy + | MarketDepthSubscription_marketsDepthUpdate_sell )[] ) => { const levels = [...draft]; @@ -399,41 +405,37 @@ export const generateMockData = ({ }: MockDataGeneratorParams) => { let matrix = new Array(numberOfSellRows).fill(undefined); let price = midPrice + (numberOfSellRows - Math.ceil(overlap / 2) + 1); - const sell: MarketDepth_market_depth_sell[] = matrix.map((row, i) => ({ - __typename: 'PriceLevel', - price: (price -= 1).toString(), - volume: (numberOfSellRows - i + 1).toString(), - numberOfOrders: '', - })); + const sell: Omit[] = matrix.map( + (row, i) => ({ + price: (price -= 1).toString(), + volume: (numberOfSellRows - i + 1).toString(), + numberOfOrders: '', + }) + ); price += overlap; matrix = new Array(numberOfBuyRows).fill(undefined); - const buy: MarketDepth_market_depth_buy[] = matrix.map((row, i) => ({ - __typename: 'PriceLevel', - price: (price -= 1).toString(), - volume: (i + 2).toString(), - numberOfOrders: '', - })); + const buy: Omit[] = matrix.map( + (row, i) => ({ + price: (price -= 1).toString(), + volume: (i + 2).toString(), + numberOfOrders: '', + }) + ); const rows = compactRows(sell, buy, resolution); return { rows, resolution, indicativeVolume: indicativeVolume?.toString(), + marketTradingMode: + overlap > 0 + ? MarketTradingMode.TRADING_MODE_BATCH_AUCTION + : MarketTradingMode.TRADING_MODE_CONTINUOUS, ...mapMarketData( { - __typename: 'MarketData', staticMidPrice: '', - marketTradingMode: - overlap > 0 - ? MarketTradingMode.TRADING_MODE_BATCH_AUCTION - : MarketTradingMode.TRADING_MODE_CONTINUOUS, bestStaticBidPrice: bestStaticBidPrice.toString(), bestStaticOfferPrice: bestStaticOfferPrice.toString(), indicativePrice: indicativePrice?.toString() ?? '', - indicativeVolume: indicativeVolume?.toString() ?? '', - market: { - __typename: 'Market', - id: '', - }, }, resolution ), diff --git a/libs/market-depth/src/lib/orderbook-manager.tsx b/libs/market-depth/src/lib/orderbook-manager.tsx index 0772401cd..e43bd559c 100644 --- a/libs/market-depth/src/lib/orderbook-manager.tsx +++ b/libs/market-depth/src/lib/orderbook-manager.tsx @@ -2,9 +2,15 @@ import throttle from 'lodash/throttle'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { Orderbook } from './orderbook'; import { useDataProvider } from '@vegaprotocol/react-helpers'; -import dataProvider from './market-depth-data-provider'; +import marketDepthProvider from './market-depth-provider'; +import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list'; +import type { MarketData } from '@vegaprotocol/market-list'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription'; +import type { + MarketDepthSubscription_marketsDepthUpdate, + MarketDepthSubscription_marketsDepthUpdate_sell, + MarketDepthSubscription_marketsDepthUpdate_buy, +} from './__generated__/MarketDepthSubscription'; import { compactRows, updateCompactedRows, @@ -24,82 +30,116 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => { rows: null, }); const dataRef = useRef({ rows: null }); - const deltaRef = useRef(); + const marketDataRef = useRef(null); + const deltaRef = useRef<{ + sell: MarketDepthSubscription_marketsDepthUpdate_sell[]; + buy: MarketDepthSubscription_marketsDepthUpdate_buy[]; + }>({ + sell: [], + buy: [], + }); const updateOrderbookData = useRef( throttle(() => { - if (!deltaRef.current) { - return; - } dataRef.current = { - ...deltaRef.current.market.data, - ...mapMarketData(deltaRef.current.market.data, resolutionRef.current), - rows: updateCompactedRows( - dataRef.current.rows ?? [], - deltaRef.current.sell, - deltaRef.current.buy, - resolutionRef.current - ), + ...marketDataRef.current, + ...mapMarketData(marketDataRef.current, resolutionRef.current), + rows: + deltaRef.current.buy.length || deltaRef.current.sell.length + ? updateCompactedRows( + dataRef.current.rows ?? [], + deltaRef.current.sell, + deltaRef.current.buy, + resolutionRef.current + ) + : dataRef.current.rows, }; - deltaRef.current = undefined; + deltaRef.current.buy = []; + deltaRef.current.sell = []; setOrderbookData(dataRef.current); }, 1000) ); const update = useCallback( - ({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => { + ({ + delta: deltas, + }: { + delta: MarketDepthSubscription_marketsDepthUpdate[]; + }) => { if (!dataRef.current.rows) { return false; } - if (deltaRef.current) { - deltaRef.current.market = delta.market; + for (const delta of deltas) { + if (delta.marketId !== marketId) { + continue; + } if (delta.sell) { - if (deltaRef.current.sell) { - deltaRef.current.sell.push(...delta.sell); - } else { - deltaRef.current.sell = delta.sell; - } + deltaRef.current.sell.push(...delta.sell); } if (delta.buy) { - if (deltaRef.current.buy) { - deltaRef.current.buy.push(...delta.buy); - } else { - deltaRef.current.buy = delta.buy; - } + deltaRef.current.buy.push(...delta.buy); } - } else { - deltaRef.current = delta; + updateOrderbookData.current(); } - updateOrderbookData.current(); return true; }, - // using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change - [] + [marketId, updateOrderbookData] ); const { data, error, loading, flush } = useDataProvider({ - dataProvider, + dataProvider: marketDepthProvider, update, variables, }); + const { + data: market, + error: marketError, + loading: marketLoading, + } = useDataProvider({ + dataProvider: marketProvider, + noUpdate: true, + variables, + }); + + const marketDataUpdate = useCallback(({ data }: { data: MarketData }) => { + marketDataRef.current = data; + updateOrderbookData.current(); + return true; + }, []); + + const { + data: marketData, + error: marketDataError, + loading: marketDataLoading, + } = useDataProvider({ + dataProvider: marketDataProvider, + update: marketDataUpdate, + variables, + }); + + marketDataRef.current = marketData; + useEffect(() => { const throttleRunnner = updateOrderbookData.current; + if (!marketDataRef.current) { + return; + } if (!data) { dataRef.current = { rows: null }; setOrderbookData(dataRef.current); return; } dataRef.current = { - ...data.data, + ...marketDataRef.current, + ...mapMarketData(marketDataRef.current, resolution), rows: compactRows(data.depth.sell, data.depth.buy, resolution), - ...mapMarketData(data.data, resolution), }; setOrderbookData(dataRef.current); return () => { throttleRunnner.cancel(); }; - }, [data, resolution]); + }, [data, marketData, resolution]); useEffect(() => { resolutionRef.current = resolution; @@ -107,11 +147,15 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => { }, [resolution, flush]); return ( - + setResolution(resolution)} /> diff --git a/libs/market-depth/src/lib/use-orderbook-data.ts b/libs/market-depth/src/lib/use-orderbook-data.ts index ace259830..c4a007f59 100644 --- a/libs/market-depth/src/lib/use-orderbook-data.ts +++ b/libs/market-depth/src/lib/use-orderbook-data.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import throttle from 'lodash/throttle'; import { useDataProvider } from '@vegaprotocol/react-helpers'; -import dataProvider from './market-depth-data-provider'; -import type { MarketDepth_market } from './'; +import dataProvider from './market-depth-provider'; +import type { MarketDepth_market } from './__generated__/MarketDepth'; interface Props { variables: { marketId: string }; diff --git a/libs/market-list/src/lib/MarketData.graphql b/libs/market-list/src/lib/MarketData.graphql deleted file mode 100644 index 5bc5f9906..000000000 --- a/libs/market-list/src/lib/MarketData.graphql +++ /dev/null @@ -1,75 +0,0 @@ -fragment MarketDataFields on MarketData { - market { - id - state - tradingMode - } - bestBidPrice - bestOfferPrice - markPrice - trigger - indicativeVolume -} - -query MarketList($interval: Interval!, $since: String!) { - markets { - id - name - decimalPlaces - positionDecimalPlaces - state - tradingMode - fees { - factors { - makerFee - infrastructureFee - liquidityFee - } - } - data { - market { - id - state - tradingMode - } - bestBidPrice - bestOfferPrice - markPrice - trigger - indicativeVolume - } - tradableInstrument { - instrument { - id - name - code - metadata { - tags - } - product { - ... on Future { - settlementAsset { - symbol - } - } - } - } - } - marketTimestamps { - open - close - } - candles(interval: $interval, since: $since) { - open - close - high - low - } - } -} - -subscription MarketDataSub { - marketData { - ...MarketDataFields - } -} diff --git a/libs/market-list/src/lib/__generated__/MarketCandlesQuery.ts b/libs/market-list/src/lib/__generated__/MarketCandlesQuery.ts new file mode 100644 index 000000000..070d3e591 --- /dev/null +++ b/libs/market-list/src/lib/__generated__/MarketCandlesQuery.ts @@ -0,0 +1,78 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { Interval } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketCandlesQuery +// ==================================================== + +export interface MarketCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node { + __typename: "CandleNode"; + /** + * High price (uint64) + */ + high: string; + /** + * Low price (uint64) + */ + low: string; + /** + * Open price (uint64) + */ + open: string; + /** + * Close price (uint64) + */ + close: string; + /** + * Volume price (uint64) + */ + volume: string; +} + +export interface MarketCandlesQuery_marketsConnection_edges_node_candlesConnection_edges { + __typename: "CandleEdge"; + node: MarketCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node; +} + +export interface MarketCandlesQuery_marketsConnection_edges_node_candlesConnection { + __typename: "CandleDataConnection"; + /** + * The candles + */ + edges: (MarketCandlesQuery_marketsConnection_edges_node_candlesConnection_edges | null)[] | null; +} + +export interface MarketCandlesQuery_marketsConnection_edges_node { + __typename: "Market"; + /** + * Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters using cursor based pagination + */ + candlesConnection: MarketCandlesQuery_marketsConnection_edges_node_candlesConnection; +} + +export interface MarketCandlesQuery_marketsConnection_edges { + __typename: "MarketEdge"; + node: MarketCandlesQuery_marketsConnection_edges_node; +} + +export interface MarketCandlesQuery_marketsConnection { + __typename: "MarketConnection"; + /** + * The markets in this connection + */ + edges: MarketCandlesQuery_marketsConnection_edges[]; +} + +export interface MarketCandlesQuery { + marketsConnection: MarketCandlesQuery_marketsConnection; +} + +export interface MarketCandlesQueryVariables { + interval: Interval; + since: string; + marketId: string; +} diff --git a/libs/market-list/src/lib/__generated__/MarketCandlesSub.ts b/libs/market-list/src/lib/__generated__/MarketCandlesSub.ts new file mode 100644 index 000000000..c5a5a4738 --- /dev/null +++ b/libs/market-list/src/lib/__generated__/MarketCandlesSub.ts @@ -0,0 +1,46 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { Interval } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL subscription operation: MarketCandlesSub +// ==================================================== + +export interface MarketCandlesSub_candles { + __typename: "Candle"; + /** + * High price (uint64) + */ + high: string; + /** + * Low price (uint64) + */ + low: string; + /** + * Open price (uint64) + */ + open: string; + /** + * Close price (uint64) + */ + close: string; + /** + * Volume price (uint64) + */ + volume: string; +} + +export interface MarketCandlesSub { + /** + * Subscribe to the candles updates + */ + candles: MarketCandlesSub_candles; +} + +export interface MarketCandlesSubVariables { + marketId: string; + interval: Interval; +} diff --git a/libs/market-list/src/lib/__generated__/MarketDataFields.ts b/libs/market-list/src/lib/__generated__/MarketDataFields.ts index 44cdd10ac..d31d4b3c2 100644 --- a/libs/market-list/src/lib/__generated__/MarketDataFields.ts +++ b/libs/market-list/src/lib/__generated__/MarketDataFields.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { MarketState, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types"; +import { AuctionTrigger, MarketTradingMode } from "@vegaprotocol/types"; // ==================================================== // GraphQL fragment: MarketDataFields @@ -15,20 +15,12 @@ export interface MarketDataFields_market { * Market ID */ id: string; - /** - * Current state of the market - */ - state: MarketState; - /** - * Current mode of execution of the market - */ - tradingMode: MarketTradingMode; } export interface MarketDataFields { __typename: "MarketData"; /** - * market ID of the associated mark price + * market of the associated mark price */ market: MarketDataFields_market; /** @@ -47,8 +39,28 @@ export interface MarketDataFields { * what triggered an auction (if an auction was started) */ trigger: AuctionTrigger; + /** + * the arithmetic average of the best static bid price and best static offer price + */ + staticMidPrice: string; + /** + * what state the market is in (auction, continuous, etc) + */ + marketTradingMode: MarketTradingMode; /** * 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 highest price level on an order book for buy orders not including pegged orders. + */ + bestStaticBidPrice: string; + /** + * the lowest price level on an order book for offer orders not including pegged orders. + */ + bestStaticOfferPrice: string; } diff --git a/libs/market-list/src/lib/__generated__/MarketDataQuery.ts b/libs/market-list/src/lib/__generated__/MarketDataQuery.ts new file mode 100644 index 000000000..67b13d022 --- /dev/null +++ b/libs/market-list/src/lib/__generated__/MarketDataQuery.ts @@ -0,0 +1,95 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { AuctionTrigger, MarketTradingMode } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketDataQuery +// ==================================================== + +export interface MarketDataQuery_marketsConnection_edges_node_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface MarketDataQuery_marketsConnection_edges_node_data { + __typename: "MarketData"; + /** + * market of the associated mark price + */ + market: MarketDataQuery_marketsConnection_edges_node_data_market; + /** + * the highest price level on an order book for buy orders. + */ + bestBidPrice: string; + /** + * the lowest price level on an order book for offer orders. + */ + bestOfferPrice: string; + /** + * the mark price (an unsigned integer) + */ + markPrice: string; + /** + * what triggered an auction (if an auction was started) + */ + trigger: AuctionTrigger; + /** + * the arithmetic average of the best static bid price and best static offer price + */ + staticMidPrice: string; + /** + * what state the market is in (auction, continuous, etc) + */ + marketTradingMode: MarketTradingMode; + /** + * 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 highest price level on an order book for buy orders not including pegged orders. + */ + bestStaticBidPrice: string; + /** + * the lowest price level on an order book for offer orders not including pegged orders. + */ + bestStaticOfferPrice: string; +} + +export interface MarketDataQuery_marketsConnection_edges_node { + __typename: "Market"; + /** + * marketData for the given market + */ + data: MarketDataQuery_marketsConnection_edges_node_data | null; +} + +export interface MarketDataQuery_marketsConnection_edges { + __typename: "MarketEdge"; + node: MarketDataQuery_marketsConnection_edges_node; +} + +export interface MarketDataQuery_marketsConnection { + __typename: "MarketConnection"; + /** + * The markets in this connection + */ + edges: MarketDataQuery_marketsConnection_edges[]; +} + +export interface MarketDataQuery { + marketsConnection: MarketDataQuery_marketsConnection; +} + +export interface MarketDataQueryVariables { + id: string; +} diff --git a/libs/market-list/src/lib/__generated__/MarketDataSub.ts b/libs/market-list/src/lib/__generated__/MarketDataSub.ts index c77181ea3..bb78f4ae2 100644 --- a/libs/market-list/src/lib/__generated__/MarketDataSub.ts +++ b/libs/market-list/src/lib/__generated__/MarketDataSub.ts @@ -3,34 +3,18 @@ // @generated // This file was automatically generated and should not be edited. -import { MarketState, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types"; +import { AuctionTrigger, MarketTradingMode } from "@vegaprotocol/types"; // ==================================================== // GraphQL subscription operation: MarketDataSub // ==================================================== -export interface MarketDataSub_marketData_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; - /** - * Current state of the market - */ - state: MarketState; - /** - * Current mode of execution of the market - */ - tradingMode: MarketTradingMode; -} - -export interface MarketDataSub_marketData { - __typename: "MarketData"; +export interface MarketDataSub_marketsData { + __typename: "ObservableMarketData"; /** * market ID of the associated mark price */ - market: MarketDataSub_marketData_market; + marketId: string; /** * the highest price level on an order book for buy orders. */ @@ -47,15 +31,39 @@ export interface MarketDataSub_marketData { * what triggered an auction (if an auction was started) */ trigger: AuctionTrigger; + /** + * the arithmetic average of the best static bid price and best static offer price + */ + staticMidPrice: string; + /** + * what state the market is in (auction, continuous etc) + */ + marketTradingMode: MarketTradingMode; /** * 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 highest price level on an order book for buy orders not including pegged orders. + */ + bestStaticBidPrice: string; + /** + * the lowest price level on an order book for offer orders not including pegged orders + */ + bestStaticOfferPrice: string; } export interface MarketDataSub { /** * Subscribe to the mark price changes */ - marketData: MarketDataSub_marketData; + marketsData: MarketDataSub_marketsData[]; +} + +export interface MarketDataSubVariables { + id: string; } diff --git a/libs/market-list/src/lib/__generated__/MarketFields.ts b/libs/market-list/src/lib/__generated__/MarketFields.ts new file mode 100644 index 000000000..29c9cca7e --- /dev/null +++ b/libs/market-list/src/lib/__generated__/MarketFields.ts @@ -0,0 +1,154 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketState, MarketTradingMode } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL fragment: MarketFields +// ==================================================== + +export interface MarketFields_fees_factors { + __typename: "FeeFactors"; + /** + * The factor applied to calculate MakerFees, a non-negative float + */ + makerFee: string; + /** + * The factor applied to calculate InfrastructureFees, a non-negative float + */ + infrastructureFee: string; + /** + * The factor applied to calculate LiquidityFees, a non-negative float + */ + liquidityFee: string; +} + +export interface MarketFields_fees { + __typename: "Fees"; + /** + * The factors used to calculate the different fees + */ + factors: MarketFields_fees_factors; +} + +export interface MarketFields_tradableInstrument_instrument_metadata { + __typename: "InstrumentMetadata"; + /** + * An arbitrary list of tags to associated to associate to the Instrument (string list) + */ + tags: string[] | null; +} + +export interface MarketFields_tradableInstrument_instrument_product_settlementAsset { + __typename: "Asset"; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; +} + +export interface MarketFields_tradableInstrument_instrument_product { + __typename: "Future"; + /** + * The name of the asset (string) + */ + settlementAsset: MarketFields_tradableInstrument_instrument_product_settlementAsset; +} + +export interface MarketFields_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: MarketFields_tradableInstrument_instrument_metadata; + /** + * A reference to or instance of a fully specified product, including all required product parameters for that product (Product union) + */ + product: MarketFields_tradableInstrument_instrument_product; +} + +export interface MarketFields_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of, or reference to, a fully specified instrument. + */ + instrument: MarketFields_tradableInstrument_instrument; +} + +export interface MarketFields_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 MarketFields { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * decimalPlaces indicates 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; + /** + * positionDecimalPlaces indicates 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; + /** + * Current state of the market + */ + state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; + /** + * Fees related data + */ + fees: MarketFields_fees; + /** + * An instance of, or reference to, a tradable instrument. + */ + tradableInstrument: MarketFields_tradableInstrument; + /** + * timestamps for state changes in the market + */ + marketTimestamps: MarketFields_marketTimestamps; +} diff --git a/libs/market-list/src/lib/__generated__/MarketList.ts b/libs/market-list/src/lib/__generated__/Markets.ts similarity index 55% rename from libs/market-list/src/lib/__generated__/MarketList.ts rename to libs/market-list/src/lib/__generated__/Markets.ts index f147b3498..c155703fa 100644 --- a/libs/market-list/src/lib/__generated__/MarketList.ts +++ b/libs/market-list/src/lib/__generated__/Markets.ts @@ -3,13 +3,13 @@ // @generated // This file was automatically generated and should not be edited. -import { Interval, MarketState, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types"; +import { MarketState, MarketTradingMode } from "@vegaprotocol/types"; // ==================================================== -// GraphQL query operation: MarketList +// GraphQL query operation: Markets // ==================================================== -export interface MarketList_markets_fees_factors { +export interface Markets_marketsConnection_edges_node_fees_factors { __typename: "FeeFactors"; /** * The factor applied to calculate MakerFees, a non-negative float @@ -25,59 +25,15 @@ export interface MarketList_markets_fees_factors { liquidityFee: string; } -export interface MarketList_markets_fees { +export interface Markets_marketsConnection_edges_node_fees { __typename: "Fees"; /** * The factors used to calculate the different fees */ - factors: MarketList_markets_fees_factors; + factors: Markets_marketsConnection_edges_node_fees_factors; } -export interface MarketList_markets_data_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; - /** - * Current state of the market - */ - state: MarketState; - /** - * Current mode of execution of the market - */ - tradingMode: MarketTradingMode; -} - -export interface MarketList_markets_data { - __typename: "MarketData"; - /** - * market ID of the associated mark price - */ - market: MarketList_markets_data_market; - /** - * the highest price level on an order book for buy orders. - */ - bestBidPrice: string; - /** - * the lowest price level on an order book for offer orders. - */ - bestOfferPrice: string; - /** - * the mark price (an unsigned integer) - */ - markPrice: string; - /** - * what triggered an auction (if an auction was started) - */ - trigger: AuctionTrigger; - /** - * indicative volume if the auction ended now, 0 if not in auction mode - */ - indicativeVolume: string; -} - -export interface MarketList_markets_tradableInstrument_instrument_metadata { +export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument_metadata { __typename: "InstrumentMetadata"; /** * An arbitrary list of tags to associated to associate to the Instrument (string list) @@ -85,7 +41,7 @@ export interface MarketList_markets_tradableInstrument_instrument_metadata { tags: string[] | null; } -export interface MarketList_markets_tradableInstrument_instrument_product_settlementAsset { +export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument_product_settlementAsset { __typename: "Asset"; /** * The symbol of the asset (e.g: GBP) @@ -93,15 +49,15 @@ export interface MarketList_markets_tradableInstrument_instrument_product_settle symbol: string; } -export interface MarketList_markets_tradableInstrument_instrument_product { +export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument_product { __typename: "Future"; /** * The name of the asset (string) */ - settlementAsset: MarketList_markets_tradableInstrument_instrument_product_settlementAsset; + settlementAsset: Markets_marketsConnection_edges_node_tradableInstrument_instrument_product_settlementAsset; } -export interface MarketList_markets_tradableInstrument_instrument { +export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument { __typename: "Instrument"; /** * Uniquely identify an instrument across all instruments available on Vega (string) @@ -118,22 +74,22 @@ export interface MarketList_markets_tradableInstrument_instrument { /** * Metadata for this instrument */ - metadata: MarketList_markets_tradableInstrument_instrument_metadata; + metadata: Markets_marketsConnection_edges_node_tradableInstrument_instrument_metadata; /** * A reference to or instance of a fully specified product, including all required product parameters for that product (Product union) */ - product: MarketList_markets_tradableInstrument_instrument_product; + product: Markets_marketsConnection_edges_node_tradableInstrument_instrument_product; } -export interface MarketList_markets_tradableInstrument { +export interface Markets_marketsConnection_edges_node_tradableInstrument { __typename: "TradableInstrument"; /** * An instance of, or reference to, a fully specified instrument. */ - instrument: MarketList_markets_tradableInstrument_instrument; + instrument: Markets_marketsConnection_edges_node_tradableInstrument_instrument; } -export interface MarketList_markets_marketTimestamps { +export interface Markets_marketsConnection_edges_node_marketTimestamps { __typename: "MarketTimestamps"; /** * Time when the market is open and ready to accept trades @@ -145,27 +101,7 @@ export interface MarketList_markets_marketTimestamps { close: string | null; } -export interface MarketList_markets_candles { - __typename: "Candle"; - /** - * Open price (uint64) - */ - open: string; - /** - * Close price (uint64) - */ - close: string; - /** - * High price (uint64) - */ - high: string; - /** - * Low price (uint64) - */ - low: string; -} - -export interface MarketList_markets { +export interface Markets_marketsConnection_edges_node { __typename: "Market"; /** * Market ID @@ -206,33 +142,30 @@ export interface MarketList_markets { /** * Fees related data */ - fees: MarketList_markets_fees; - /** - * marketData for the given market - */ - data: MarketList_markets_data | null; + fees: Markets_marketsConnection_edges_node_fees; /** * An instance of, or reference to, a tradable instrument. */ - tradableInstrument: MarketList_markets_tradableInstrument; + tradableInstrument: Markets_marketsConnection_edges_node_tradableInstrument; /** * timestamps for state changes in the market */ - marketTimestamps: MarketList_markets_marketTimestamps; - /** - * Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters - */ - candles: (MarketList_markets_candles | null)[] | null; + marketTimestamps: Markets_marketsConnection_edges_node_marketTimestamps; } -export interface MarketList { - /** - * One or more instruments that are trading on the VEGA network - */ - markets: MarketList_markets[] | null; +export interface Markets_marketsConnection_edges { + __typename: "MarketEdge"; + node: Markets_marketsConnection_edges_node; } -export interface MarketListVariables { - interval: Interval; - since: string; +export interface Markets_marketsConnection { + __typename: "MarketConnection"; + /** + * The markets in this connection + */ + edges: Markets_marketsConnection_edges[]; +} + +export interface Markets { + marketsConnection: Markets_marketsConnection; } diff --git a/libs/market-list/src/lib/__generated__/MarketsCandlesQuery.ts b/libs/market-list/src/lib/__generated__/MarketsCandlesQuery.ts new file mode 100644 index 000000000..03eb6cc86 --- /dev/null +++ b/libs/market-list/src/lib/__generated__/MarketsCandlesQuery.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { Interval } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketsCandlesQuery +// ==================================================== + +export interface MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node { + __typename: "CandleNode"; + /** + * High price (uint64) + */ + high: string; + /** + * Low price (uint64) + */ + low: string; + /** + * Open price (uint64) + */ + open: string; + /** + * Close price (uint64) + */ + close: string; + /** + * Volume price (uint64) + */ + volume: string; +} + +export interface MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection_edges { + __typename: "CandleEdge"; + node: MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node; +} + +export interface MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection { + __typename: "CandleDataConnection"; + /** + * The candles + */ + edges: (MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection_edges | null)[] | null; +} + +export interface MarketsCandlesQuery_marketsConnection_edges_node { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters using cursor based pagination + */ + candlesConnection: MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection; +} + +export interface MarketsCandlesQuery_marketsConnection_edges { + __typename: "MarketEdge"; + node: MarketsCandlesQuery_marketsConnection_edges_node; +} + +export interface MarketsCandlesQuery_marketsConnection { + __typename: "MarketConnection"; + /** + * The markets in this connection + */ + edges: MarketsCandlesQuery_marketsConnection_edges[]; +} + +export interface MarketsCandlesQuery { + marketsConnection: MarketsCandlesQuery_marketsConnection; +} + +export interface MarketsCandlesQueryVariables { + interval: Interval; + since: string; +} diff --git a/libs/market-list/src/lib/__generated__/MarketsDataQuery.ts b/libs/market-list/src/lib/__generated__/MarketsDataQuery.ts new file mode 100644 index 000000000..8a69a3b44 --- /dev/null +++ b/libs/market-list/src/lib/__generated__/MarketsDataQuery.ts @@ -0,0 +1,91 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { AuctionTrigger, MarketTradingMode } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketsDataQuery +// ==================================================== + +export interface MarketsDataQuery_marketsConnection_edges_node_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface MarketsDataQuery_marketsConnection_edges_node_data { + __typename: "MarketData"; + /** + * market of the associated mark price + */ + market: MarketsDataQuery_marketsConnection_edges_node_data_market; + /** + * the highest price level on an order book for buy orders. + */ + bestBidPrice: string; + /** + * the lowest price level on an order book for offer orders. + */ + bestOfferPrice: string; + /** + * the mark price (an unsigned integer) + */ + markPrice: string; + /** + * what triggered an auction (if an auction was started) + */ + trigger: AuctionTrigger; + /** + * the arithmetic average of the best static bid price and best static offer price + */ + staticMidPrice: string; + /** + * what state the market is in (auction, continuous, etc) + */ + marketTradingMode: MarketTradingMode; + /** + * 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 highest price level on an order book for buy orders not including pegged orders. + */ + bestStaticBidPrice: string; + /** + * the lowest price level on an order book for offer orders not including pegged orders. + */ + bestStaticOfferPrice: string; +} + +export interface MarketsDataQuery_marketsConnection_edges_node { + __typename: "Market"; + /** + * marketData for the given market + */ + data: MarketsDataQuery_marketsConnection_edges_node_data | null; +} + +export interface MarketsDataQuery_marketsConnection_edges { + __typename: "MarketEdge"; + node: MarketsDataQuery_marketsConnection_edges_node; +} + +export interface MarketsDataQuery_marketsConnection { + __typename: "MarketConnection"; + /** + * The markets in this connection + */ + edges: MarketsDataQuery_marketsConnection_edges[]; +} + +export interface MarketsDataQuery { + marketsConnection: MarketsDataQuery_marketsConnection; +} diff --git a/libs/market-list/src/lib/__generated__/index.ts b/libs/market-list/src/lib/__generated__/index.ts new file mode 100644 index 000000000..c60e2b138 --- /dev/null +++ b/libs/market-list/src/lib/__generated__/index.ts @@ -0,0 +1,9 @@ +export * from './MarketCandlesQuery'; +export * from './MarketCandlesSub'; +export * from './MarketDataFields'; +export * from './MarketDataQuery'; +export * from './MarketDataSub'; +export * from './MarketFields'; +export * from './Markets'; +export * from './MarketsCandlesQuery'; +export * from './MarketsDataQuery'; diff --git a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx index e19f2dfdd..9c44b0c92 100644 --- a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx +++ b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx @@ -22,7 +22,7 @@ import { MarketTradingModeMapping, AuctionTriggerMapping, } from '@vegaprotocol/types'; -import type { MarketList_markets } from '../../'; +import type { MarketWithData } from '../../'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; type Props = AgGridReactProps | AgReactUiProps; @@ -31,7 +31,7 @@ type MarketListTableValueFormatterParams = Omit< ValueFormatterParams, 'data' | 'value' > & { - data: MarketList_markets; + data: MarketWithData; }; export const getRowId = ({ data }: { data: { id: string } }) => data.id; @@ -83,17 +83,17 @@ export const MarketListTable = forwardRef((props, ref) => { headerName={t('Trading mode')} field="data" minWidth={170} - valueGetter={({ data }: { data?: MarketList_markets }) => { + valueGetter={({ data }: { data?: MarketWithData }) => { if (!data?.data) return undefined; - const { market, trigger } = data.data; - return market && - market.tradingMode === - MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && + const { trigger } = data.data; + const { tradingMode } = data; + return tradingMode === + MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && trigger && trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED - ? `${MarketTradingModeMapping[market.tradingMode]} + ? `${MarketTradingModeMapping[tradingMode]} - ${AuctionTriggerMapping[trigger]}` - : MarketTradingModeMapping[market.tradingMode]; + : MarketTradingModeMapping[tradingMode]; }} /> ((props, ref) => { type="rightAligned" cellRenderer="PriceFlashCell" filter="agNumberColumnFilter" - valueGetter={({ data }: { data?: MarketList_markets }) => { + valueGetter={({ data }: { data?: MarketWithData }) => { return data?.data?.bestBidPrice === undefined ? undefined : toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber(); @@ -122,7 +122,7 @@ export const MarketListTable = forwardRef((props, ref) => { type="rightAligned" cellRenderer="PriceFlashCell" filter="agNumberColumnFilter" - valueGetter={({ data }: { data?: MarketList_markets }) => { + valueGetter={({ data }: { data?: MarketWithData }) => { return data?.data?.bestOfferPrice === undefined ? undefined : toBigNum( @@ -145,7 +145,7 @@ export const MarketListTable = forwardRef((props, ref) => { type="rightAligned" cellRenderer="PriceFlashCell" filter="agNumberColumnFilter" - valueGetter={({ data }: { data?: MarketList_markets }) => { + valueGetter={({ data }: { data?: MarketWithData }) => { return data?.data?.markPrice === undefined ? undefined : toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber(); diff --git a/libs/market-list/src/lib/components/markets-container/markets-container.tsx b/libs/market-list/src/lib/components/markets-container/markets-container.tsx index 98c8e3508..bcbbdc9ab 100644 --- a/libs/market-list/src/lib/components/markets-container/markets-container.tsx +++ b/libs/market-list/src/lib/components/markets-container/markets-container.tsx @@ -1,89 +1,29 @@ -import { useRef, useCallback, useMemo } from 'react'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; -import { MarketListTable, getRowId } from './market-list-table'; +import { MarketListTable } from './market-list-table'; import { useDataProvider } from '@vegaprotocol/react-helpers'; -import type { AgGridReact } from 'ag-grid-react'; import type { RowClickedEvent } from 'ag-grid-community'; -import { produce } from 'immer'; -import merge from 'lodash/merge'; -import type { - MarketList_markets, - MarketList_markets_data, - MarketDataSub_marketData, -} from '../../'; -import { marketsDataProvider as dataProvider } from '../../markets-data-provider'; -import { Interval, MarketState } from '@vegaprotocol/types'; - +import { marketsWithDataProvider as dataProvider } from '../../markets-provider'; +import type { MarketWithData } from '../../markets-provider'; interface MarketsContainerProps { onSelect: (marketId: string) => void; } export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => { - const gridRef = useRef(null); - - const yTimestamp = useMemo(() => { - const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600; - return new Date(yesterday * 1000).toISOString(); - }, []); - const variables = useMemo( - () => ({ interval: Interval.INTERVAL_I1H, since: yTimestamp }), - [yTimestamp] - ); - - const update = useCallback( - ({ delta }: { delta: MarketDataSub_marketData }) => { - const update: MarketList_markets[] = []; - const add: MarketList_markets[] = []; - const remove: MarketList_markets[] = []; - if (!gridRef.current?.api) { - return false; - } - const rowNode = gridRef.current.api.getRowNode( - getRowId({ data: delta.market }) - ); - if (rowNode) { - const updatedData = produce( - rowNode.data.data, - (draft: MarketList_markets) => merge(draft, delta) - ); - if (updatedData !== rowNode.data.data) { - update.push({ ...rowNode.data, data: updatedData }); - } - } - // @TODO - else add new market - if (update.length || add.length || remove.length) { - gridRef.current.api.applyTransactionAsync({ - update, - add, - addIndex: 0, - }); - } - return true; - }, - [gridRef] - ); - - const { data, error, loading } = useDataProvider< - MarketList_markets[], - MarketList_markets_data - >({ dataProvider, update, variables }); + const { data, error, loading } = useDataProvider({ + dataProvider, + noUpdate: true, + }); return ( m.data?.market.state !== MarketState.STATE_REJECTED - ) - } - ref={gridRef} + rowData={data} onRowClicked={(rowEvent: RowClickedEvent) => { const { data, event } = rowEvent; // filters out clicks on the symbol column because it should display asset details if ((event?.target as HTMLElement).tagName.toUpperCase() === 'BUTTON') return; - onSelect((data as MarketList_markets).id); + onSelect((data as MarketWithData).id); }} /> diff --git a/libs/market-list/src/lib/components/select-market-columns.tsx b/libs/market-list/src/lib/components/select-market-columns.tsx index 1db863775..130c0f2bb 100644 --- a/libs/market-list/src/lib/components/select-market-columns.tsx +++ b/libs/market-list/src/lib/components/select-market-columns.tsx @@ -3,6 +3,7 @@ import { addDecimalsFormatNumber, formatNumberPercentage, PriceCell, + signedNumberCssClass, t, } from '@vegaprotocol/react-helpers'; import { @@ -18,10 +19,7 @@ import Link from 'next/link'; import { calcCandleHigh, calcCandleLow, totalFees } from '../utils'; import type { CandleClose } from '@vegaprotocol/types'; -import type { - MarketList_markets, - MarketList_markets_fees_factors, -} from '../__generated__/MarketList'; +import type { Market, MarketData, Candle } from '../'; import isNil from 'lodash/isNil'; export const cellClassNames = 'py-1 first:text-left text-right'; @@ -171,10 +169,12 @@ export const columnHeaders: Column[] = [ ]; export const columns = ( - market: MarketList_markets, + market: Market, + marketData: MarketData | undefined, + candles: Candle[] | undefined, onSelect: (id: string) => void ) => { - const candlesClose = market.candles + const candlesClose = candles ?.map((candle) => candle?.close) .filter((c: string | undefined): c is CandleClose => !isNil(c)); const handleKeyPress = ( @@ -185,8 +185,8 @@ export const columns = ( return onSelect(id); } }; - const candleLow = calcCandleLow(market); - const candleHigh = calcCandleHigh(market); + const candleLow = candles && calcCandleLow(candles); + const candleHigh = candles && calcCandleHigh(candles); const selectMarketColumns: Column[] = [ { value: ( @@ -207,11 +207,11 @@ export const columns = ( onlyOnDetailed: false, }, { - value: market.data?.markPrice ? ( + value: marketData?.markPrice ? (