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 <artur@vegaprotocol.io>
This commit is contained in:
Bartłomiej Głownia 2022-09-13 12:14:06 +02:00 committed by GitHub
parent 3ff5bbb5a7
commit a08f63c7d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 2201 additions and 1546 deletions

View File

@ -9,6 +9,7 @@ import { generatePartyBalance } from '../support/mocks/generate-party-balance';
import { generatePartyMarketData } from '../support/mocks/generate-party-market-data'; import { generatePartyMarketData } from '../support/mocks/generate-party-market-data';
import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price'; import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price';
import { generateMarketNames } from '../support/mocks/generate-market-names'; import { generateMarketNames } from '../support/mocks/generate-market-names';
import { generateMarketDepth } from '../support/mocks/generate-market-depth';
describe('market selector', () => { describe('market selector', () => {
let markets; let markets;
@ -23,6 +24,7 @@ describe('market selector', () => {
aliasQuery(req, 'PartyMarketData', generatePartyMarketData()); aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice()); aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
aliasQuery(req, 'MarketNames', generateMarketNames()); aliasQuery(req, 'MarketNames', generateMarketNames());
aliasQuery(req, 'MarketDepth', generateMarketDepth());
}); });
cy.visit('/markets'); cy.visit('/markets');

View File

@ -1,5 +1,8 @@
import { aliasQuery } from '@vegaprotocol/cypress'; 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 { generateDealTicket } from '../support/mocks/generate-deal-ticket';
import { generateMarketTags } from '../support/mocks/generate-market-tags'; import { generateMarketTags } from '../support/mocks/generate-market-tags';
import { generateMarketPositions } from '../support/mocks/generate-market-positions'; import { generateMarketPositions } from '../support/mocks/generate-market-positions';
@ -14,6 +17,7 @@ describe('Market trade', () => {
let markets; let markets;
beforeEach(() => { beforeEach(() => {
cy.mockGQL((req) => { cy.mockGQL((req) => {
aliasQuery(req, 'Markets', generateMarkets());
aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets()); aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
aliasQuery(req, 'DealTicketQuery', generateDealTicket()); aliasQuery(req, 'DealTicketQuery', generateDealTicket());
aliasQuery(req, 'MarketTags', generateMarketTags()); aliasQuery(req, 'MarketTags', generateMarketTags());

View File

@ -1,24 +1,15 @@
export const generateMarketDepth = () => { import merge from 'lodash/merge';
return { 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>
): MarketDepth => {
const defaultResult: MarketDepth = {
market: { market: {
id: 'a46bd7e5277087723b7ab835844dec3cef8b4445738101269624bf5537d5d423', 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: { depth: {
lastTrade: { price: '9893006', __typename: 'Trade' },
sell: [ sell: [
{ {
price: '9893007', price: '9893007',
@ -215,4 +206,5 @@ export const generateMarketDepth = () => {
__typename: 'Market', __typename: 'Market',
}, },
}; };
return merge(defaultResult, override);
}; };

View File

@ -1,3 +1,5 @@
import merge from 'lodash/merge';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import { protoMarket } from './commons'; import { protoMarket } from './commons';
export const generateSimpleMarkets = () => { export const generateSimpleMarkets = () => {
@ -880,3 +882,41 @@ export const generateLongListMarkets = (count: number) => {
} }
return { markets }; 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);
};

View File

@ -64,6 +64,15 @@ jest.mock('@vegaprotocol/market-depth', () => ({
useOrderBookData: jest.fn(() => mockOrderBookData), useOrderBookData: jest.fn(() => mockOrderBookData),
})); }));
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useDataProvider: jest.fn(() => ({
data: {
marketsConnection: [],
},
})),
}));
describe('useCalculateSlippage Hook', () => { describe('useCalculateSlippage Hook', () => {
describe('calculate proper result', () => { describe('calculate proper result', () => {
afterEach(() => { afterEach(() => {

View File

@ -1,9 +1,15 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Side } from '@vegaprotocol/types'; import { Side } from '@vegaprotocol/types';
import { useOrderBookData } from '@vegaprotocol/market-depth'; import { useOrderBookData } from '@vegaprotocol/market-depth';
import { marketProvider } from '@vegaprotocol/market-list';
import type { Market } from '@vegaprotocol/market-list';
import type { Order } from '@vegaprotocol/orders'; import type { Order } from '@vegaprotocol/orders';
import { BigNumber } from 'bignumber.js'; import { BigNumber } from 'bignumber.js';
import { formatNumber, toBigNum } from '@vegaprotocol/react-helpers'; import {
formatNumber,
toBigNum,
useDataProvider,
} from '@vegaprotocol/react-helpers';
interface Props { interface Props {
marketId: string; marketId: string;
@ -16,11 +22,16 @@ const useCalculateSlippage = ({ marketId, order }: Props) => {
variables, variables,
throttleMilliseconds: 5000, throttleMilliseconds: 5000,
}); });
const { data: market } = useDataProvider<Market, never>({
dataProvider: marketProvider,
noUpdate: true,
variables,
});
const volPriceArr = const volPriceArr =
data?.depth[order.side === Side.SIDE_BUY ? 'sell' : 'buy'] || []; data?.depth[order.side === Side.SIDE_BUY ? 'sell' : 'buy'] || [];
if (volPriceArr.length) { if (volPriceArr.length && market) {
const decimals = data?.decimalPlaces ?? 0; const decimals = market.decimalPlaces ?? 0;
const positionDecimals = data?.positionDecimalPlaces ?? 0; const positionDecimals = market.positionDecimalPlaces ?? 0;
const bestPrice = toBigNum(volPriceArr[0].price, decimals); const bestPrice = toBigNum(volPriceArr[0].price, decimals);
const { size } = order; const { size } = order;
let descSize = new BigNumber(size); let descSize = new BigNumber(size);

View File

@ -55,11 +55,16 @@ describe('home', () => {
it('redirects to a the market list page if no sensible default is found', () => { 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 // Mock markets query that is triggered by home page to find default market
cy.mockGQL((req) => { cy.mockGQL((req) => {
aliasQuery(req, 'MarketList', { markets: [] }); const data = {
marketsConnection: {
__typename: 'MarketConnection',
edges: [],
},
};
aliasQuery(req, 'Markets', data);
}); });
cy.visit('/'); cy.visit('/');
cy.wait('@MarketList'); cy.wait('@Markets');
cy.url().should('eq', Cypress.config().baseUrl + '/markets'); cy.url().should('eq', Cypress.config().baseUrl + '/markets');
}); });
}); });

View File

@ -11,7 +11,9 @@ describe('markets table', () => {
it('renders markets correctly', () => { it('renders markets correctly', () => {
cy.visit('/'); cy.visit('/');
cy.wait('@Market'); cy.wait('@Market');
cy.wait('@MarketList'); cy.wait('@Markets');
cy.wait('@MarketsDataQuery');
cy.wait('@MarketsCandlesQuery');
cy.get('[data-testid^="market-link-"]') cy.get('[data-testid^="market-link-"]')
.should('not.be.empty') .should('not.be.empty')
.and('have.attr', 'href'); .and('have.attr', 'href');
@ -24,7 +26,9 @@ describe('markets table', () => {
it('renders market list drop down', () => { it('renders market list drop down', () => {
cy.visit('/'); cy.visit('/');
cy.wait('@MarketList'); cy.wait('@Markets');
cy.wait('@MarketsDataQuery');
cy.wait('@MarketsCandlesQuery');
openMarketDropDown(); openMarketDropDown();
cy.getByTestId('price').invoke('text').should('not.be.empty'); cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('trading-mode').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', () => { it('Able to select market from dropdown', () => {
cy.visit('/'); cy.visit('/');
cy.wait('@MarketList'); cy.wait('@Markets');
cy.wait('@MarketsDataQuery');
cy.wait('@MarketsCandlesQuery');
openMarketDropDown(); openMarketDropDown();
cy.getByTestId('market-link-market-0').should('be.visible').click(); cy.getByTestId('market-link-market-0').should('be.visible').click();
@ -53,7 +59,9 @@ describe('markets table', () => {
'SOLUSD', 'SOLUSD',
]; ];
cy.visit('/'); cy.visit('/');
cy.wait('@MarketList'); cy.wait('@Markets');
cy.wait('@MarketsDataQuery');
cy.wait('@MarketsCandlesQuery');
cy.getByTestId('link').should('have.attr', 'href', '/markets').click(); cy.getByTestId('link').should('have.attr', 'href', '/markets').click();
cy.url().should('eq', Cypress.config('baseUrl') + '/markets'); cy.url().should('eq', Cypress.config('baseUrl') + '/markets');
cy.contains('AAPL.MF21').should('be.visible'); cy.contains('AAPL.MF21').should('be.visible');

View File

@ -1,5 +1,4 @@
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { MarketTradingMode } from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest'; import type { PartialDeep } from 'type-fest';
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import type { MarketDepth } from '../../../../../libs/market-depth/src/lib/__generated__/MarketDepth'; import type { MarketDepth } from '../../../../../libs/market-depth/src/lib/__generated__/MarketDepth';
@ -10,26 +9,10 @@ export const generateMarketDepth = (
const defaultResult: MarketDepth = { const defaultResult: MarketDepth = {
market: { market: {
id: 'market-0', 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: { depth: {
__typename: 'MarketDepth', __typename: 'MarketDepth',
buy: [], buy: [],
sell: [], sell: [],
lastTrade: null,
sequenceNumber: '', sequenceNumber: '',
}, },
__typename: 'Market', __typename: 'Market',

View File

@ -5,12 +5,17 @@ import {
MarketTradingMode, MarketTradingMode,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest'; 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 = ( export const generateMarkets = (override?: PartialDeep<Markets>): Markets => {
override?: PartialDeep<MarketList> const markets: Markets_marketsConnection_edges_node[] = [
): MarketList => {
const markets: MarketList_markets[] = [
{ {
id: 'market-0', id: 'market-0',
decimalPlaces: 5, decimalPlaces: 5,
@ -22,15 +27,6 @@ export const generateMarkets = (
close: '', close: '',
open: '', open: '',
}, },
candles: [
{
__typename: 'Candle',
open: '100',
close: '100',
high: '110',
low: '90',
},
],
fees: { fees: {
__typename: 'Fees', __typename: 'Fees',
factors: { factors: {
@ -40,20 +36,6 @@ export const generateMarkets = (
liquidityFee: '', 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: { tradableInstrument: {
instrument: { instrument: {
id: '', id: '',
@ -87,15 +69,6 @@ export const generateMarkets = (
close: '', close: '',
open: '', open: '',
}, },
candles: [
{
__typename: 'Candle',
open: '100',
close: '100',
high: '110',
low: '90',
},
],
fees: { fees: {
__typename: 'Fees', __typename: 'Fees',
factors: { factors: {
@ -105,20 +78,6 @@ export const generateMarkets = (
liquidityFee: '', 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: { tradableInstrument: {
instrument: { instrument: {
id: 'SOLUSD', id: 'SOLUSD',
@ -152,15 +111,6 @@ export const generateMarkets = (
close: '2022-08-26T11:36:32.252490405Z', close: '2022-08-26T11:36:32.252490405Z',
open: null, open: null,
}, },
candles: [
{
__typename: 'Candle',
open: '100',
close: '100',
high: '110',
low: '90',
},
],
fees: { fees: {
__typename: 'Fees', __typename: 'Fees',
factors: { factors: {
@ -170,20 +120,6 @@ export const generateMarkets = (
liquidityFee: '0.001', 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: { tradableInstrument: {
instrument: { instrument: {
id: '', id: '',
@ -217,15 +153,6 @@ export const generateMarkets = (
close: '2022-08-26T11:36:32.252490405Z', close: '2022-08-26T11:36:32.252490405Z',
open: null, open: null,
}, },
candles: [
{
__typename: 'Candle',
open: '100',
close: '100',
high: '110',
low: '90',
},
],
fees: { fees: {
__typename: 'Fees', __typename: 'Fees',
factors: { factors: {
@ -235,20 +162,6 @@ export const generateMarkets = (
liquidityFee: '0.001', 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: { tradableInstrument: {
instrument: { instrument: {
id: '', id: '',
@ -272,8 +185,212 @@ export const generateMarkets = (
__typename: 'Market', __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>
): 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>
): 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); return merge(defaultResult, override);

View File

@ -4,33 +4,13 @@ import type {
MarketDepth, MarketDepth,
MarketDepth_market, MarketDepth_market,
} from '@vegaprotocol/market-depth'; } from '@vegaprotocol/market-depth';
import { MarketTradingMode } from '@vegaprotocol/types';
export const generateOrderBook = ( export const generateOrderBook = (
override?: PartialDeep<MarketDepth> override?: PartialDeep<MarketDepth>
): MarketDepth => { ): MarketDepth => {
const marketDepth: MarketDepth_market = { const marketDepth: MarketDepth_market = {
id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e', 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: { depth: {
lastTrade: {
price: '826338',
__typename: 'Trade',
},
sell: [ sell: [
{ {
price: '826338', price: '826338',

View File

@ -8,7 +8,11 @@ import { generateDealTicketQuery } from './mocks/generate-deal-ticket-query';
import { generateMarket } from './mocks/generate-market'; import { generateMarket } from './mocks/generate-market';
import { generateMarketDepth } from './mocks/generate-market-depth'; import { generateMarketDepth } from './mocks/generate-market-depth';
import { generateMarketInfoQuery } from './mocks/generate-market-info-query'; 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 { generateOrders } from './mocks/generate-orders';
import { generatePositions } from './mocks/generate-positions'; import { generatePositions } from './mocks/generate-positions';
import { generateTrades } from './mocks/generate-trades'; 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, 'MarketDepth', generateMarketDepth());
aliasQuery(req, 'Orders', generateOrders()); aliasQuery(req, 'Orders', generateOrders());
aliasQuery(req, 'Accounts', generateAccounts()); aliasQuery(req, 'Accounts', generateAccounts());

View File

@ -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 { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect } from 'react'; import { useEffect } from 'react';
@ -8,7 +9,10 @@ export function Index() {
const { replace } = useRouter(); const { replace } = useRouter();
// The default market selected in the platform behind the overlay // 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). // 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) => ({ const { riskNoticeDialog, update } = useGlobalStore((store) => ({
riskNoticeDialog: store.riskNoticeDialog, riskNoticeDialog: store.riskNoticeDialog,
update: store.update, update: store.update,

View File

@ -64,6 +64,7 @@ export const DealTicketContainer = ({
) )
) : ( ) : (
<Splash> <Splash>
{JSON.stringify(data)}
<p>{t('Could not load market')}</p> <p>{t('Could not load market')}</p>
</Splash> </Splash>
)} )}

View File

@ -3,60 +3,10 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MarketTradingMode } from "@vegaprotocol/types";
// ==================================================== // ====================================================
// GraphQL query operation: MarketDepth // 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 { export interface MarketDepth_market_depth_sell {
__typename: "PriceLevel"; __typename: "PriceLevel";
/** /**
@ -91,10 +41,6 @@ export interface MarketDepth_market_depth_buy {
export interface MarketDepth_market_depth { export interface MarketDepth_market_depth {
__typename: "MarketDepth"; __typename: "MarketDepth";
/**
* Last trade for the given market (if available)
*/
lastTrade: MarketDepth_market_depth_lastTrade | null;
/** /**
* Sell side price levels (if available) * Sell side price levels (if available)
*/ */
@ -115,34 +61,6 @@ export interface MarketDepth_market {
* Market ID * Market ID
*/ */
id: string; 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 * Current depth on the order book for this market
*/ */

View File

@ -3,72 +3,11 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MarketTradingMode } from "@vegaprotocol/types";
// ==================================================== // ====================================================
// GraphQL subscription operation: MarketDepthSubscription // GraphQL subscription operation: MarketDepthSubscription
// ==================================================== // ====================================================
export interface MarketDepthSubscription_marketDepthUpdate_market_data_market { export interface MarketDepthSubscription_marketsDepthUpdate_sell {
__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 {
__typename: "PriceLevel"; __typename: "PriceLevel";
/** /**
* The price of all the orders at this level (uint64) * The price of all the orders at this level (uint64)
@ -84,7 +23,7 @@ export interface MarketDepthSubscription_marketDepthUpdate_sell {
numberOfOrders: string; numberOfOrders: string;
} }
export interface MarketDepthSubscription_marketDepthUpdate_buy { export interface MarketDepthSubscription_marketsDepthUpdate_buy {
__typename: "PriceLevel"; __typename: "PriceLevel";
/** /**
* The price of all the orders at this level (uint64) * The price of all the orders at this level (uint64)
@ -100,33 +39,37 @@ export interface MarketDepthSubscription_marketDepthUpdate_buy {
numberOfOrders: string; numberOfOrders: string;
} }
export interface MarketDepthSubscription_marketDepthUpdate { export interface MarketDepthSubscription_marketsDepthUpdate {
__typename: "MarketDepthUpdate"; __typename: "ObservableMarketDepthUpdate";
/** /**
* Market * Market ID
*/ */
market: MarketDepthSubscription_marketDepthUpdate_market; marketId: string;
/** /**
* Sell side price levels (if available) * Sell side price levels (if available)
*/ */
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null; sell: MarketDepthSubscription_marketsDepthUpdate_sell[] | null;
/** /**
* Buy side price levels (if available) * 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. * Sequence number for the current snapshot of the market depth. It is always increasing but not monotonic.
*/ */
sequenceNumber: string; sequenceNumber: string;
/**
* Sequence number of the last update sent; useful for checking that no updates were missed.
*/
previousSequenceNumber: string;
} }
export interface MarketDepthSubscription { export interface MarketDepthSubscription {
/** /**
* Subscribe to price level market depth updates * Subscribe to price level market depth updates
*/ */
marketDepthUpdate: MarketDepthSubscription_marketDepthUpdate; marketsDepthUpdate: MarketDepthSubscription_marketsDepthUpdate[];
} }
export interface MarketDepthSubscriptionVariables { export interface MarketDepthSubscriptionVariables {
marketId: string; id: string;
} }

View File

@ -7,7 +7,7 @@ import {
ThemeContext, ThemeContext,
getNumberFormat, getNumberFormat,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import dataProvider from './market-depth-data-provider'; import dataProvider from './market-depth-provider';
import { import {
useCallback, useCallback,
useEffect, useEffect,
@ -16,7 +16,13 @@ import {
useState, useState,
useContext, useContext,
} from 'react'; } 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 type { DepthChartProps } from 'pennant';
import { parseLevel, updateLevels } from './depth-chart-utils'; import { parseLevel, updateLevels } from './depth-chart-utils';
@ -33,46 +39,80 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
const theme = useContext(ThemeContext); const theme = useContext(ThemeContext);
const variables = useMemo(() => ({ marketId }), [marketId]); const variables = useMemo(() => ({ marketId }), [marketId]);
const [depthData, setDepthData] = useState<DepthData | null>(null); const [depthData, setDepthData] = useState<DepthData | null>(null);
const [decimalPlaces, setDecimalPlaces] = useState<number>(0);
const [positionDecimalPlaces, setPositionDecimalPlaces] = useState<number>(0);
const dataRef = useRef<DepthData | null>(null); const dataRef = useRef<DepthData | null>(null);
const setDepthDataThrottledRef = useRef(throttle(setDepthData, 1000)); const marketDataRef = useRef<MarketData | null>(null);
const deltaRef = useRef<{
sell: MarketDepthSubscription_marketsDepthUpdate_sell[];
buy: MarketDepthSubscription_marketsDepthUpdate_buy[];
}>({
sell: [],
buy: [],
});
// Apply updates to the table const updateDepthData = useRef(
const update = useCallback( throttle(() => {
({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => { if (!dataRef.current || !marketDataRef.current || !market) {
if (!dataRef.current) { return;
return false;
} }
dataRef.current = { dataRef.current = {
...dataRef.current, ...dataRef.current,
midPrice: delta.market.data?.staticMidPrice midPrice: marketDataRef.current?.staticMidPrice
? formatMidPrice(delta.market.data?.staticMidPrice, decimalPlaces) ? formatMidPrice(
marketDataRef.current?.staticMidPrice,
market.decimalPlaces
)
: undefined, : undefined,
data: { data: {
buy: delta.buy buy: deltaRef.current.buy
? updateLevels( ? updateLevels(
dataRef.current.data.buy, dataRef.current.data.buy,
delta.buy, deltaRef.current.buy,
decimalPlaces, market.decimalPlaces,
positionDecimalPlaces, market.positionDecimalPlaces,
true true
) )
: dataRef.current.data.buy, : dataRef.current.data.buy,
sell: delta.sell sell: deltaRef.current.sell
? updateLevels( ? updateLevels(
dataRef.current.data.sell, dataRef.current.data.sell,
delta.sell, deltaRef.current.sell,
decimalPlaces, market.decimalPlaces,
positionDecimalPlaces market.positionDecimalPlaces
) )
: dataRef.current.data.sell, : 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; return true;
}, },
[decimalPlaces, positionDecimalPlaces] [marketId, updateDepthData]
); );
const { data, error, loading } = useDataProvider({ const { data, error, loading } = useDataProvider({
@ -81,53 +121,86 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
variables, 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(() => { useEffect(() => {
if (!marketData || !market || !data) {
return;
}
if (!data) { if (!data) {
dataRef.current = null; dataRef.current = null;
setDepthData(dataRef.current); setDepthData(dataRef.current);
return; return;
} }
dataRef.current = { dataRef.current = {
midPrice: data.data?.staticMidPrice midPrice: marketData.staticMidPrice
? formatMidPrice(data.data?.staticMidPrice, data.decimalPlaces) ? formatMidPrice(marketData.staticMidPrice, market.decimalPlaces)
: undefined, : undefined,
data: { data: {
buy: buy:
data.depth.buy?.map((priceLevel) => data.depth.buy?.map((priceLevel) =>
parseLevel( parseLevel(
priceLevel, priceLevel,
data.decimalPlaces, market.decimalPlaces,
data.positionDecimalPlaces market.positionDecimalPlaces
) )
) ?? [], ) ?? [],
sell: sell:
data.depth.sell?.map((priceLevel) => data.depth.sell?.map((priceLevel) =>
parseLevel( parseLevel(
priceLevel, priceLevel,
data.decimalPlaces, market.decimalPlaces,
data.positionDecimalPlaces market.positionDecimalPlaces
) )
) ?? [], ) ?? [],
}, },
}; };
setDepthData(dataRef.current); setDepthData(dataRef.current);
setDecimalPlaces(data.decimalPlaces); }, [data, marketData, market]);
setPositionDecimalPlaces(data.positionDecimalPlaces);
}, [data]);
const volumeFormat = useCallback( const volumeFormat = useCallback(
(volume: number) => (volume: number) =>
getNumberFormat(data?.positionDecimalPlaces || 0).format(volume), getNumberFormat(market?.positionDecimalPlaces || 0).format(volume),
[data?.positionDecimalPlaces] [market?.positionDecimalPlaces]
); );
const priceFormat = useCallback( const priceFormat = useCallback(
(price: number) => getNumberFormat(data?.decimalPlaces || 0).format(price), (price: number) =>
[data?.decimalPlaces] getNumberFormat(market?.decimalPlaces || 0).format(price),
[market?.decimalPlaces]
); );
return ( return (
<AsyncRenderer loading={loading} error={error} data={data}> <AsyncRenderer
loading={loading || marketLoading || marketDataLoading}
error={error || marketError || marketDataError}
data={data}
>
{depthData && ( {depthData && (
<DepthChart <DepthChart
{...depthData} {...depthData}

View File

@ -1,7 +1,7 @@
export * from './__generated__/MarketDepth'; export * from './__generated__/MarketDepth';
export * from './__generated__/MarketDepthSubscription'; export * from './__generated__/MarketDepthSubscription';
export * from './depth-chart'; export * from './depth-chart';
export * from './market-depth-data-provider'; export * from './market-depth-provider';
export * from './orderbook-container'; export * from './orderbook-container';
export * from './orderbook-data'; export * from './orderbook-data';
export * from './orderbook-manager'; export * from './orderbook-manager';

View File

@ -1,138 +0,0 @@
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_marketDepthUpdate,
} from './__generated__/MarketDepthSubscription';
const MARKET_DEPTH_QUERY = gql`
query MarketDepth($marketId: ID!) {
market(id: $marketId) {
id
decimalPlaces
positionDecimalPlaces
data {
staticMidPrice
marketTradingMode
indicativeVolume
indicativePrice
bestStaticBidPrice
bestStaticOfferPrice
market {
id
}
}
depth {
lastTrade {
price
}
sell {
price
volume
numberOfOrders
}
buy {
price
volume
numberOfOrders
}
sequenceNumber
}
}
}
`;
export const MARKET_DEPTH_SUBSCRIPTION_QUERY = gql`
subscription MarketDepthSubscription($marketId: ID!) {
marketDepthUpdate(marketId: $marketId) {
market {
id
positionDecimalPlaces
data {
staticMidPrice
marketTradingMode
indicativeVolume
indicativePrice
bestStaticBidPrice
bestStaticOfferPrice
market {
id
}
}
}
sell {
price
volume
numberOfOrders
}
buy {
price
volume
numberOfOrders
}
sequenceNumber
}
}
`;
const sequenceNumbers: Record<string, number> = {};
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;

View File

@ -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<string, number> = {};
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;

View File

@ -1,15 +1,14 @@
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy';
import { VolumeType } from '@vegaprotocol/react-helpers'; import { VolumeType } from '@vegaprotocol/react-helpers';
import { MarketTradingMode } from '@vegaprotocol/types'; import { MarketTradingMode } from '@vegaprotocol/types';
import type { MarketData } from '@vegaprotocol/market-list';
import type { import type {
MarketDepth_market_depth_sell, MarketDepth_market_depth_sell,
MarketDepth_market_depth_buy, MarketDepth_market_depth_buy,
MarketDepth_market_data,
} from './__generated__/MarketDepth'; } from './__generated__/MarketDepth';
import type { import type {
MarketDepthSubscription_marketDepthUpdate_sell, MarketDepthSubscription_marketsDepthUpdate_sell,
MarketDepthSubscription_marketDepthUpdate_buy, MarketDepthSubscription_marketsDepthUpdate_buy,
MarketDepthSubscription_marketDepthUpdate_market_data,
} from './__generated__/MarketDepthSubscription'; } from './__generated__/MarketDepthSubscription';
export interface CumulativeVol { export interface CumulativeVol {
@ -33,7 +32,7 @@ export interface OrderbookRowData {
type PartialOrderbookRowData = Pick<OrderbookRowData, 'price' | 'ask' | 'bid'>; type PartialOrderbookRowData = Pick<OrderbookRowData, 'price' | 'ask' | 'bid'>;
export type OrderbookData = Partial< export type OrderbookData = Partial<
Omit<MarketDepth_market_data, '__typename' | 'market'> Omit<MarketData, '__typename' | 'market'>
> & { rows: OrderbookRowData[] | null }; > & { rows: OrderbookRowData[] | null };
export const getPriceLevel = (price: string | bigint, resolution: number) => { export const getPriceLevel = (price: string | bigint, resolution: number) => {
@ -105,11 +104,13 @@ export const createRow = (
const mapRawData = const mapRawData =
(dataType: VolumeType.ask | VolumeType.bid) => (dataType: VolumeType.ask | VolumeType.bid) =>
( (
data: data: Omit<
| MarketDepth_market_depth_sell | MarketDepth_market_depth_sell
| MarketDepthSubscription_marketDepthUpdate_sell | MarketDepthSubscription_marketsDepthUpdate_sell
| MarketDepth_market_depth_buy | MarketDepth_market_depth_buy
| MarketDepthSubscription_marketDepthUpdate_buy | MarketDepthSubscription_marketsDepthUpdate_buy,
'__typename'
>
): PartialOrderbookRowData => ): PartialOrderbookRowData =>
createPartialRow(data.price, Number(data.volume), dataType); createPartialRow(data.price, Number(data.volume), dataType);
@ -118,16 +119,18 @@ const mapRawData =
*/ */
export const compactRows = ( export const compactRows = (
sell: sell:
| ( | Omit<
| MarketDepth_market_depth_sell | MarketDepth_market_depth_sell
| MarketDepthSubscription_marketDepthUpdate_sell | MarketDepthSubscription_marketsDepthUpdate_sell,
)[] '__typename'
>[]
| null, | null,
buy: buy:
| ( | Omit<
| MarketDepth_market_depth_buy | MarketDepth_market_depth_buy
| MarketDepthSubscription_marketDepthUpdate_buy | MarketDepthSubscription_marketsDepthUpdate_buy,
)[] '__typename'
>[]
| null, | null,
resolution: number resolution: number
) => { ) => {
@ -199,8 +202,8 @@ const partiallyUpdateCompactedRows = (
dataType: VolumeType, dataType: VolumeType,
data: OrderbookRowData[], data: OrderbookRowData[],
delta: delta:
| MarketDepthSubscription_marketDepthUpdate_sell | MarketDepthSubscription_marketsDepthUpdate_sell
| MarketDepthSubscription_marketDepthUpdate_buy, | MarketDepthSubscription_marketsDepthUpdate_buy,
resolution: number, resolution: number,
modifiedIndex: number modifiedIndex: number
): [number, OrderbookRowData[]] => { ): [number, OrderbookRowData[]] => {
@ -255,8 +258,8 @@ const partiallyUpdateCompactedRows = (
*/ */
export const updateCompactedRows = ( export const updateCompactedRows = (
rows: OrderbookRowData[], rows: OrderbookRowData[],
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null, sell: MarketDepthSubscription_marketsDepthUpdate_sell[] | null,
buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null, buy: MarketDepthSubscription_marketsDepthUpdate_buy[] | null,
resolution: number resolution: number
) => { ) => {
let sellModifiedIndex = -1; let sellModifiedIndex = -1;
@ -320,10 +323,13 @@ export const updateCompactedRows = (
}; };
export const mapMarketData = ( export const mapMarketData = (
data: data: Pick<
| MarketDepth_market_data MarketData,
| MarketDepthSubscription_marketDepthUpdate_market_data | 'staticMidPrice'
| null, | 'bestStaticBidPrice'
| 'bestStaticOfferPrice'
| 'indicativePrice'
> | null,
resolution: number resolution: number
) => ({ ) => ({
staticMidPrice: staticMidPrice:
@ -347,8 +353,8 @@ export const mapMarketData = (
export const updateLevels = ( export const updateLevels = (
draft: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[], draft: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[],
updates: ( updates: (
| MarketDepthSubscription_marketDepthUpdate_buy | MarketDepthSubscription_marketsDepthUpdate_buy
| MarketDepthSubscription_marketDepthUpdate_sell | MarketDepthSubscription_marketsDepthUpdate_sell
)[] )[]
) => { ) => {
const levels = [...draft]; const levels = [...draft];
@ -399,41 +405,37 @@ export const generateMockData = ({
}: MockDataGeneratorParams) => { }: MockDataGeneratorParams) => {
let matrix = new Array(numberOfSellRows).fill(undefined); let matrix = new Array(numberOfSellRows).fill(undefined);
let price = midPrice + (numberOfSellRows - Math.ceil(overlap / 2) + 1); let price = midPrice + (numberOfSellRows - Math.ceil(overlap / 2) + 1);
const sell: MarketDepth_market_depth_sell[] = matrix.map((row, i) => ({ const sell: Omit<MarketDepth_market_depth_sell, '__typename'>[] = matrix.map(
__typename: 'PriceLevel', (row, i) => ({
price: (price -= 1).toString(), price: (price -= 1).toString(),
volume: (numberOfSellRows - i + 1).toString(), volume: (numberOfSellRows - i + 1).toString(),
numberOfOrders: '', numberOfOrders: '',
})); })
);
price += overlap; price += overlap;
matrix = new Array(numberOfBuyRows).fill(undefined); matrix = new Array(numberOfBuyRows).fill(undefined);
const buy: MarketDepth_market_depth_buy[] = matrix.map((row, i) => ({ const buy: Omit<MarketDepth_market_depth_buy, '__typename'>[] = matrix.map(
__typename: 'PriceLevel', (row, i) => ({
price: (price -= 1).toString(), price: (price -= 1).toString(),
volume: (i + 2).toString(), volume: (i + 2).toString(),
numberOfOrders: '', numberOfOrders: '',
})); })
);
const rows = compactRows(sell, buy, resolution); const rows = compactRows(sell, buy, resolution);
return { return {
rows, rows,
resolution, resolution,
indicativeVolume: indicativeVolume?.toString(), indicativeVolume: indicativeVolume?.toString(),
...mapMarketData(
{
__typename: 'MarketData',
staticMidPrice: '',
marketTradingMode: marketTradingMode:
overlap > 0 overlap > 0
? MarketTradingMode.TRADING_MODE_BATCH_AUCTION ? MarketTradingMode.TRADING_MODE_BATCH_AUCTION
: MarketTradingMode.TRADING_MODE_CONTINUOUS, : MarketTradingMode.TRADING_MODE_CONTINUOUS,
...mapMarketData(
{
staticMidPrice: '',
bestStaticBidPrice: bestStaticBidPrice.toString(), bestStaticBidPrice: bestStaticBidPrice.toString(),
bestStaticOfferPrice: bestStaticOfferPrice.toString(), bestStaticOfferPrice: bestStaticOfferPrice.toString(),
indicativePrice: indicativePrice?.toString() ?? '', indicativePrice: indicativePrice?.toString() ?? '',
indicativeVolume: indicativeVolume?.toString() ?? '',
market: {
__typename: 'Market',
id: '',
},
}, },
resolution resolution
), ),

View File

@ -2,9 +2,15 @@ import throttle from 'lodash/throttle';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Orderbook } from './orderbook'; import { Orderbook } from './orderbook';
import { useDataProvider } from '@vegaprotocol/react-helpers'; 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 { 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 { import {
compactRows, compactRows,
updateCompactedRows, updateCompactedRows,
@ -24,82 +30,116 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
rows: null, rows: null,
}); });
const dataRef = useRef<OrderbookData>({ rows: null }); const dataRef = useRef<OrderbookData>({ rows: null });
const deltaRef = useRef<MarketDepthSubscription_marketDepthUpdate>(); const marketDataRef = useRef<MarketData | null>(null);
const deltaRef = useRef<{
sell: MarketDepthSubscription_marketsDepthUpdate_sell[];
buy: MarketDepthSubscription_marketsDepthUpdate_buy[];
}>({
sell: [],
buy: [],
});
const updateOrderbookData = useRef( const updateOrderbookData = useRef(
throttle(() => { throttle(() => {
if (!deltaRef.current) {
return;
}
dataRef.current = { dataRef.current = {
...deltaRef.current.market.data, ...marketDataRef.current,
...mapMarketData(deltaRef.current.market.data, resolutionRef.current), ...mapMarketData(marketDataRef.current, resolutionRef.current),
rows: updateCompactedRows( rows:
deltaRef.current.buy.length || deltaRef.current.sell.length
? updateCompactedRows(
dataRef.current.rows ?? [], dataRef.current.rows ?? [],
deltaRef.current.sell, deltaRef.current.sell,
deltaRef.current.buy, deltaRef.current.buy,
resolutionRef.current resolutionRef.current
), )
: dataRef.current.rows,
}; };
deltaRef.current = undefined; deltaRef.current.buy = [];
deltaRef.current.sell = [];
setOrderbookData(dataRef.current); setOrderbookData(dataRef.current);
}, 1000) }, 1000)
); );
const update = useCallback( const update = useCallback(
({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => { ({
delta: deltas,
}: {
delta: MarketDepthSubscription_marketsDepthUpdate[];
}) => {
if (!dataRef.current.rows) { if (!dataRef.current.rows) {
return false; return false;
} }
if (deltaRef.current) { for (const delta of deltas) {
deltaRef.current.market = delta.market; if (delta.marketId !== marketId) {
if (delta.sell) { continue;
if (deltaRef.current.sell) {
deltaRef.current.sell.push(...delta.sell);
} else {
deltaRef.current.sell = delta.sell;
} }
if (delta.sell) {
deltaRef.current.sell.push(...delta.sell);
} }
if (delta.buy) { if (delta.buy) {
if (deltaRef.current.buy) {
deltaRef.current.buy.push(...delta.buy); deltaRef.current.buy.push(...delta.buy);
} else {
deltaRef.current.buy = delta.buy;
}
}
} else {
deltaRef.current = delta;
} }
updateOrderbookData.current(); updateOrderbookData.current();
}
return true; 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({ const { data, error, loading, flush } = useDataProvider({
dataProvider, dataProvider: marketDepthProvider,
update, update,
variables, 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(() => { useEffect(() => {
const throttleRunnner = updateOrderbookData.current; const throttleRunnner = updateOrderbookData.current;
if (!marketDataRef.current) {
return;
}
if (!data) { if (!data) {
dataRef.current = { rows: null }; dataRef.current = { rows: null };
setOrderbookData(dataRef.current); setOrderbookData(dataRef.current);
return; return;
} }
dataRef.current = { dataRef.current = {
...data.data, ...marketDataRef.current,
...mapMarketData(marketDataRef.current, resolution),
rows: compactRows(data.depth.sell, data.depth.buy, resolution), rows: compactRows(data.depth.sell, data.depth.buy, resolution),
...mapMarketData(data.data, resolution),
}; };
setOrderbookData(dataRef.current); setOrderbookData(dataRef.current);
return () => { return () => {
throttleRunnner.cancel(); throttleRunnner.cancel();
}; };
}, [data, resolution]); }, [data, marketData, resolution]);
useEffect(() => { useEffect(() => {
resolutionRef.current = resolution; resolutionRef.current = resolution;
@ -107,11 +147,15 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
}, [resolution, flush]); }, [resolution, flush]);
return ( return (
<AsyncRenderer loading={loading} error={error} data={data}> <AsyncRenderer
loading={loading || marketDataLoading || marketLoading}
error={error || marketDataError || marketError}
data={data}
>
<Orderbook <Orderbook
{...orderbookData} {...orderbookData}
decimalPlaces={data?.decimalPlaces ?? 0} decimalPlaces={market?.decimalPlaces ?? 0}
positionDecimalPlaces={data?.positionDecimalPlaces ?? 0} positionDecimalPlaces={market?.positionDecimalPlaces ?? 0}
resolution={resolution} resolution={resolution}
onResolutionChange={(resolution: number) => setResolution(resolution)} onResolutionChange={(resolution: number) => setResolution(resolution)}
/> />

View File

@ -1,8 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { useDataProvider } from '@vegaprotocol/react-helpers'; import { useDataProvider } from '@vegaprotocol/react-helpers';
import dataProvider from './market-depth-data-provider'; import dataProvider from './market-depth-provider';
import type { MarketDepth_market } from './'; import type { MarketDepth_market } from './__generated__/MarketDepth';
interface Props { interface Props {
variables: { marketId: string }; variables: { marketId: string };

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -3,7 +3,7 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // 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 // GraphQL fragment: MarketDataFields
@ -15,20 +15,12 @@ export interface MarketDataFields_market {
* Market ID * Market ID
*/ */
id: string; id: string;
/**
* Current state of the market
*/
state: MarketState;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
} }
export interface MarketDataFields { export interface MarketDataFields {
__typename: "MarketData"; __typename: "MarketData";
/** /**
* market ID of the associated mark price * market of the associated mark price
*/ */
market: MarketDataFields_market; market: MarketDataFields_market;
/** /**
@ -47,8 +39,28 @@ export interface MarketDataFields {
* what triggered an auction (if an auction was started) * what triggered an auction (if an auction was started)
*/ */
trigger: AuctionTrigger; 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 * indicative volume if the auction ended now, 0 if not in auction mode
*/ */
indicativeVolume: string; 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;
} }

View File

@ -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;
}

View File

@ -3,34 +3,18 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // 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 // GraphQL subscription operation: MarketDataSub
// ==================================================== // ====================================================
export interface MarketDataSub_marketData_market { export interface MarketDataSub_marketsData {
__typename: "Market"; __typename: "ObservableMarketData";
/**
* 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";
/** /**
* market ID of the associated mark price * market ID of the associated mark price
*/ */
market: MarketDataSub_marketData_market; marketId: string;
/** /**
* the highest price level on an order book for buy orders. * 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) * what triggered an auction (if an auction was started)
*/ */
trigger: AuctionTrigger; 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 * indicative volume if the auction ended now, 0 if not in auction mode
*/ */
indicativeVolume: string; 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 { export interface MarketDataSub {
/** /**
* Subscribe to the mark price changes * Subscribe to the mark price changes
*/ */
marketData: MarketDataSub_marketData; marketsData: MarketDataSub_marketsData[];
}
export interface MarketDataSubVariables {
id: string;
} }

View File

@ -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;
}

View File

@ -3,13 +3,13 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // 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"; __typename: "FeeFactors";
/** /**
* The factor applied to calculate MakerFees, a non-negative float * The factor applied to calculate MakerFees, a non-negative float
@ -25,59 +25,15 @@ export interface MarketList_markets_fees_factors {
liquidityFee: string; liquidityFee: string;
} }
export interface MarketList_markets_fees { export interface Markets_marketsConnection_edges_node_fees {
__typename: "Fees"; __typename: "Fees";
/** /**
* The factors used to calculate the different 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 { export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument_metadata {
__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 {
__typename: "InstrumentMetadata"; __typename: "InstrumentMetadata";
/** /**
* An arbitrary list of tags to associated to associate to the Instrument (string list) * 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; tags: string[] | null;
} }
export interface MarketList_markets_tradableInstrument_instrument_product_settlementAsset { export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset"; __typename: "Asset";
/** /**
* The symbol of the asset (e.g: GBP) * The symbol of the asset (e.g: GBP)
@ -93,15 +49,15 @@ export interface MarketList_markets_tradableInstrument_instrument_product_settle
symbol: string; symbol: string;
} }
export interface MarketList_markets_tradableInstrument_instrument_product { export interface Markets_marketsConnection_edges_node_tradableInstrument_instrument_product {
__typename: "Future"; __typename: "Future";
/** /**
* The name of the asset (string) * 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"; __typename: "Instrument";
/** /**
* Uniquely identify an instrument across all instruments available on Vega (string) * 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 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) * 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"; __typename: "TradableInstrument";
/** /**
* An instance of, or reference to, a fully specified instrument. * 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"; __typename: "MarketTimestamps";
/** /**
* Time when the market is open and ready to accept trades * Time when the market is open and ready to accept trades
@ -145,27 +101,7 @@ export interface MarketList_markets_marketTimestamps {
close: string | null; close: string | null;
} }
export interface MarketList_markets_candles { export interface Markets_marketsConnection_edges_node {
__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 {
__typename: "Market"; __typename: "Market";
/** /**
* Market ID * Market ID
@ -206,33 +142,30 @@ export interface MarketList_markets {
/** /**
* Fees related data * Fees related data
*/ */
fees: MarketList_markets_fees; fees: Markets_marketsConnection_edges_node_fees;
/**
* marketData for the given market
*/
data: MarketList_markets_data | null;
/** /**
* An instance of, or reference to, a tradable instrument. * 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 * timestamps for state changes in the market
*/ */
marketTimestamps: MarketList_markets_marketTimestamps; marketTimestamps: Markets_marketsConnection_edges_node_marketTimestamps;
/**
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters
*/
candles: (MarketList_markets_candles | null)[] | null;
} }
export interface MarketList { export interface Markets_marketsConnection_edges {
/** __typename: "MarketEdge";
* One or more instruments that are trading on the VEGA network node: Markets_marketsConnection_edges_node;
*/
markets: MarketList_markets[] | null;
} }
export interface MarketListVariables { export interface Markets_marketsConnection {
interval: Interval; __typename: "MarketConnection";
since: string; /**
* The markets in this connection
*/
edges: Markets_marketsConnection_edges[];
}
export interface Markets {
marketsConnection: Markets_marketsConnection;
} }

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';

View File

@ -22,7 +22,7 @@ import {
MarketTradingModeMapping, MarketTradingModeMapping,
AuctionTriggerMapping, AuctionTriggerMapping,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import type { MarketList_markets } from '../../'; import type { MarketWithData } from '../../';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
type Props = AgGridReactProps | AgReactUiProps; type Props = AgGridReactProps | AgReactUiProps;
@ -31,7 +31,7 @@ type MarketListTableValueFormatterParams = Omit<
ValueFormatterParams, ValueFormatterParams,
'data' | 'value' 'data' | 'value'
> & { > & {
data: MarketList_markets; data: MarketWithData;
}; };
export const getRowId = ({ data }: { data: { id: string } }) => data.id; export const getRowId = ({ data }: { data: { id: string } }) => data.id;
@ -83,17 +83,17 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
headerName={t('Trading mode')} headerName={t('Trading mode')}
field="data" field="data"
minWidth={170} minWidth={170}
valueGetter={({ data }: { data?: MarketList_markets }) => { valueGetter={({ data }: { data?: MarketWithData }) => {
if (!data?.data) return undefined; if (!data?.data) return undefined;
const { market, trigger } = data.data; const { trigger } = data.data;
return market && const { tradingMode } = data;
market.tradingMode === return tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger && trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${MarketTradingModeMapping[market.tradingMode]} ? `${MarketTradingModeMapping[tradingMode]}
- ${AuctionTriggerMapping[trigger]}` - ${AuctionTriggerMapping[trigger]}`
: MarketTradingModeMapping[market.tradingMode]; : MarketTradingModeMapping[tradingMode];
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -102,7 +102,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
type="rightAligned" type="rightAligned"
cellRenderer="PriceFlashCell" cellRenderer="PriceFlashCell"
filter="agNumberColumnFilter" filter="agNumberColumnFilter"
valueGetter={({ data }: { data?: MarketList_markets }) => { valueGetter={({ data }: { data?: MarketWithData }) => {
return data?.data?.bestBidPrice === undefined return data?.data?.bestBidPrice === undefined
? undefined ? undefined
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber(); : toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
@ -122,7 +122,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
type="rightAligned" type="rightAligned"
cellRenderer="PriceFlashCell" cellRenderer="PriceFlashCell"
filter="agNumberColumnFilter" filter="agNumberColumnFilter"
valueGetter={({ data }: { data?: MarketList_markets }) => { valueGetter={({ data }: { data?: MarketWithData }) => {
return data?.data?.bestOfferPrice === undefined return data?.data?.bestOfferPrice === undefined
? undefined ? undefined
: toBigNum( : toBigNum(
@ -145,7 +145,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
type="rightAligned" type="rightAligned"
cellRenderer="PriceFlashCell" cellRenderer="PriceFlashCell"
filter="agNumberColumnFilter" filter="agNumberColumnFilter"
valueGetter={({ data }: { data?: MarketList_markets }) => { valueGetter={({ data }: { data?: MarketWithData }) => {
return data?.data?.markPrice === undefined return data?.data?.markPrice === undefined
? undefined ? undefined
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber(); : toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();

View File

@ -1,89 +1,29 @@
import { useRef, useCallback, useMemo } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; 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 { useDataProvider } from '@vegaprotocol/react-helpers';
import type { AgGridReact } from 'ag-grid-react';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent } from 'ag-grid-community';
import { produce } from 'immer'; import { marketsWithDataProvider as dataProvider } from '../../markets-provider';
import merge from 'lodash/merge'; import type { MarketWithData } from '../../markets-provider';
import type {
MarketList_markets,
MarketList_markets_data,
MarketDataSub_marketData,
} from '../../';
import { marketsDataProvider as dataProvider } from '../../markets-data-provider';
import { Interval, MarketState } from '@vegaprotocol/types';
interface MarketsContainerProps { interface MarketsContainerProps {
onSelect: (marketId: string) => void; onSelect: (marketId: string) => void;
} }
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => { export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const { data, error, loading } = useDataProvider<MarketWithData[], never>({
dataProvider,
const yTimestamp = useMemo(() => { noUpdate: true,
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<MarketList_markets>(
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 });
return ( return (
<AsyncRenderer loading={loading} error={error} data={data}> <AsyncRenderer loading={loading} error={error} data={data}>
<MarketListTable <MarketListTable
rowData={ rowData={data}
data &&
data.filter(
(m) => m.data?.market.state !== MarketState.STATE_REJECTED
)
}
ref={gridRef}
onRowClicked={(rowEvent: RowClickedEvent) => { onRowClicked={(rowEvent: RowClickedEvent) => {
const { data, event } = rowEvent; const { data, event } = rowEvent;
// filters out clicks on the symbol column because it should display asset details // filters out clicks on the symbol column because it should display asset details
if ((event?.target as HTMLElement).tagName.toUpperCase() === 'BUTTON') if ((event?.target as HTMLElement).tagName.toUpperCase() === 'BUTTON')
return; return;
onSelect((data as MarketList_markets).id); onSelect((data as MarketWithData).id);
}} }}
/> />
</AsyncRenderer> </AsyncRenderer>

View File

@ -3,6 +3,7 @@ import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
formatNumberPercentage, formatNumberPercentage,
PriceCell, PriceCell,
signedNumberCssClass,
t, t,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { import {
@ -18,10 +19,7 @@ import Link from 'next/link';
import { calcCandleHigh, calcCandleLow, totalFees } from '../utils'; import { calcCandleHigh, calcCandleLow, totalFees } from '../utils';
import type { CandleClose } from '@vegaprotocol/types'; import type { CandleClose } from '@vegaprotocol/types';
import type { import type { Market, MarketData, Candle } from '../';
MarketList_markets,
MarketList_markets_fees_factors,
} from '../__generated__/MarketList';
import isNil from 'lodash/isNil'; import isNil from 'lodash/isNil';
export const cellClassNames = 'py-1 first:text-left text-right'; export const cellClassNames = 'py-1 first:text-left text-right';
@ -171,10 +169,12 @@ export const columnHeaders: Column[] = [
]; ];
export const columns = ( export const columns = (
market: MarketList_markets, market: Market,
marketData: MarketData | undefined,
candles: Candle[] | undefined,
onSelect: (id: string) => void onSelect: (id: string) => void
) => { ) => {
const candlesClose = market.candles const candlesClose = candles
?.map((candle) => candle?.close) ?.map((candle) => candle?.close)
.filter((c: string | undefined): c is CandleClose => !isNil(c)); .filter((c: string | undefined): c is CandleClose => !isNil(c));
const handleKeyPress = ( const handleKeyPress = (
@ -185,8 +185,8 @@ export const columns = (
return onSelect(id); return onSelect(id);
} }
}; };
const candleLow = calcCandleLow(market); const candleLow = candles && calcCandleLow(candles);
const candleHigh = calcCandleHigh(market); const candleHigh = candles && calcCandleHigh(candles);
const selectMarketColumns: Column[] = [ const selectMarketColumns: Column[] = [
{ {
value: ( value: (
@ -207,11 +207,11 @@ export const columns = (
onlyOnDetailed: false, onlyOnDetailed: false,
}, },
{ {
value: market.data?.markPrice ? ( value: marketData?.markPrice ? (
<PriceCell <PriceCell
value={Number(market.data?.markPrice)} value={Number(marketData?.markPrice)}
valueFormatted={addDecimalsFormatNumber( valueFormatted={addDecimalsFormatNumber(
market.data?.markPrice.toString(), marketData?.markPrice.toString(),
market.decimalPlaces, market.decimalPlaces,
2 2
)} )}
@ -240,7 +240,7 @@ export const columns = (
onlyOnDetailed: false, onlyOnDetailed: false,
}, },
{ {
value: market.candles && ( value: candles && (
<Sparkline <Sparkline
width={100} width={100}
height={20} height={20}
@ -287,10 +287,10 @@ export const columns = (
value: value:
market.tradingMode === market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger && marketData?.trigger &&
market.data.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED marketData.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${MarketTradingModeMapping[market.tradingMode]} ? `${MarketTradingModeMapping[market.tradingMode]}
- ${AuctionTriggerMapping[market.data.trigger]}` - ${AuctionTriggerMapping[marketData.trigger]}`
: MarketTradingModeMapping[market.tradingMode], : MarketTradingModeMapping[market.tradingMode],
className: `${cellClassNames} hidden lg:table-cell`, className: `${cellClassNames} hidden lg:table-cell`,
onlyOnDetailed: true, onlyOnDetailed: true,
@ -298,9 +298,9 @@ export const columns = (
}, },
{ {
value: value:
market.data?.indicativeVolume && market.data.indicativeVolume !== '0' marketData?.indicativeVolume && marketData.indicativeVolume !== '0'
? addDecimalsFormatNumber( ? addDecimalsFormatNumber(
market.data.indicativeVolume, marketData.indicativeVolume,
market.positionDecimalPlaces market.positionDecimalPlaces
) )
: '-', : '-',
@ -325,14 +325,17 @@ export const columns = (
}; };
export const columnsPositionMarkets = ( export const columnsPositionMarkets = (
market: MarketList_markets & { openVolume: string }, market: Market,
onSelect: (id: string) => void marketData: MarketData | undefined,
candles: Candle[] | undefined,
onSelect: (id: string) => void,
openVolume?: string
) => { ) => {
const candlesClose = market.candles const candlesClose = candles
?.map((candle) => candle?.close) ?.map((candle) => candle?.close)
.filter((c: string | undefined): c is CandleClose => !isNil(c)); .filter((c: string | undefined): c is CandleClose => !isNil(c));
const candleLow = calcCandleLow(market); const candleLow = candles && calcCandleLow(candles);
const candleHigh = calcCandleHigh(market); const candleHigh = candles && calcCandleHigh(candles);
const handleKeyPress = ( const handleKeyPress = (
event: React.KeyboardEvent<HTMLAnchorElement>, event: React.KeyboardEvent<HTMLAnchorElement>,
id: string id: string
@ -361,11 +364,11 @@ export const columnsPositionMarkets = (
onlyOnDetailed: false, onlyOnDetailed: false,
}, },
{ {
value: market.data?.markPrice ? ( value: marketData?.markPrice ? (
<PriceCell <PriceCell
value={Number(market.data.markPrice)} value={Number(marketData.markPrice)}
valueFormatted={addDecimalsFormatNumber( valueFormatted={addDecimalsFormatNumber(
market.data.markPrice.toString(), marketData.markPrice.toString(),
market.decimalPlaces, market.decimalPlaces,
2 2
)} )}
@ -440,10 +443,10 @@ export const columnsPositionMarkets = (
value: value:
market.tradingMode === market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger && marketData?.trigger &&
market.data.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED marketData.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${MarketTradingModeMapping[market.tradingMode]} ? `${MarketTradingModeMapping[market.tradingMode]}
- ${AuctionTriggerMapping[market.data.trigger]}` - ${AuctionTriggerMapping[marketData.trigger]}`
: MarketTradingModeMapping[market.tradingMode], : MarketTradingModeMapping[market.tradingMode],
className: `${cellClassNames} hidden lg:table-cell`, className: `${cellClassNames} hidden lg:table-cell`,
onlyOnDetailed: true, onlyOnDetailed: true,
@ -451,9 +454,9 @@ export const columnsPositionMarkets = (
}, },
{ {
value: value:
market.data && market.data.indicativeVolume !== '0' marketData && marketData.indicativeVolume !== '0'
? addDecimalsFormatNumber( ? addDecimalsFormatNumber(
market.data.indicativeVolume, marketData.indicativeVolume,
market.positionDecimalPlaces market.positionDecimalPlaces
) )
: '-', : '-',
@ -467,17 +470,7 @@ export const columnsPositionMarkets = (
}, },
{ {
value: ( value: (
<p <p className={signedNumberCssClass(openVolume || '')}>{openVolume}</p>
className={
market.openVolume.includes('+')
? 'text-vega-green-dark dark:text-vega-green'
: market.openVolume.includes('-')
? 'text-vega-red-dark dark:text-vega-red'
: ''
}
>
{market.openVolume}
</p>
), ),
className: `${cellClassNames} hidden xxl:table-cell font-mono`, className: `${cellClassNames} hidden xxl:table-cell font-mono`,
onlyOnDetailed: true, onlyOnDetailed: true,
@ -489,7 +482,7 @@ export const columnsPositionMarkets = (
const FeesCell = ({ const FeesCell = ({
feeFactors, feeFactors,
}: { }: {
feeFactors: MarketList_markets_fees_factors; feeFactors: Market['fees']['factors'];
}) => ( }) => (
<Tooltip description={<FeesBreakdown feeFactors={feeFactors} />}> <Tooltip description={<FeesBreakdown feeFactors={feeFactors} />}>
<span>{totalFees(feeFactors) ?? '-'}</span> <span>{totalFees(feeFactors) ?? '-'}</span>
@ -499,7 +492,7 @@ const FeesCell = ({
export const FeesBreakdown = ({ export const FeesBreakdown = ({
feeFactors, feeFactors,
}: { }: {
feeFactors?: MarketList_markets_fees_factors; feeFactors?: Market['fees']['factors'];
}) => { }) => {
if (!feeFactors) return null; if (!feeFactors) return null;
return ( return (

View File

@ -1,8 +1,10 @@
import { fireEvent, render, screen } from '@testing-library/react'; import { fireEvent, render, screen } from '@testing-library/react';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { MarketList_markets } from '../__generated__/MarketList'; import type { MarketData } from '../market-data-provider';
import type { MarketCandles } from '../markets-candles-provider';
import type { Market } from '../markets-provider';
import { import {
SelectAllMarketsTableBody, SelectAllMarketsTableBody,
@ -16,186 +18,173 @@ jest.mock(
children children
); );
const MARKET_A: Partial<Market> = {
__typename: 'Market',
id: '1',
decimalPlaces: 2,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: '1',
code: 'ABCDEF',
name: 'ABCDEF 1-Day',
product: {
__typename: 'Future',
settlementAsset: {
__typename: 'Asset',
symbol: 'ABC',
},
},
metadata: {
__typename: 'InstrumentMetadata',
tags: ['ABC', 'DEF'],
},
},
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
infrastructureFee: '0.01',
liquidityFee: '0.01',
makerFee: '0.01',
},
},
};
const MARKET_B: Partial<Market> = {
__typename: 'Market',
id: '2',
decimalPlaces: 2,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
id: '2',
code: 'XYZ',
name: 'XYZ 1-Day',
product: {
__typename: 'Future',
settlementAsset: {
__typename: 'Asset',
symbol: 'XYZ',
},
},
metadata: {
__typename: 'InstrumentMetadata',
tags: ['XYZ'],
},
},
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
infrastructureFee: '0.01',
liquidityFee: '0.01',
makerFee: '0.01',
},
},
};
const MARKET_DATA_A: Partial<MarketData> = {
__typename: 'MarketData',
market: {
__typename: 'Market',
id: '1',
},
markPrice: '90',
trigger: AuctionTrigger.AUCTION_TRIGGER_OPENING,
indicativeVolume: '1000',
};
const MARKET_DATA_B: Partial<MarketData> = {
__typename: 'MarketData',
market: {
__typename: 'Market',
id: '2',
},
markPrice: '123.123',
trigger: AuctionTrigger.AUCTION_TRIGGER_OPENING,
indicativeVolume: '2000',
};
const MARKET_CANDLES_A: Partial<MarketCandles> = {
marketId: '1',
candles: [
{
__typename: 'CandleNode',
high: '100',
low: '10',
open: '10',
close: '80',
volume: '1000',
},
{
__typename: 'CandleNode',
high: '10',
low: '1',
open: '1',
close: '100',
volume: '1000',
},
],
};
const MARKET_CANDLES_B: Partial<MarketCandles> = {
marketId: '2',
candles: [
{
__typename: 'CandleNode',
high: '100',
low: '10',
open: '10',
close: '80',
volume: '1000',
},
],
};
describe('SelectMarket', () => { describe('SelectMarket', () => {
it('should render the SelectAllMarketsTableBody', () => { it('should render the SelectAllMarketsTableBody', () => {
const onSelect = jest.fn(); const onSelect = jest.fn();
const expectedMarket = mockData.data.markets[0];
const { container } = render( const { container } = render(
<SelectAllMarketsTableBody <SelectAllMarketsTableBody
data={mockData.data.markets} markets={[MARKET_A as Market, MARKET_B as Market]}
marketsData={[MARKET_DATA_A as MarketData, MARKET_DATA_B as MarketData]}
marketsCandles={[
MARKET_CANDLES_A as MarketCandles,
MARKET_CANDLES_B as MarketCandles,
]}
onSelect={onSelect} onSelect={onSelect}
/> />
); );
expect(screen.getByText('AAPL.MF21')).toBeTruthy(); expect(screen.getByText('ABCDEF')).toBeTruthy(); // name
expect(screen.getByText('-3.14%')).toBeTruthy(); expect(screen.getByText('25.00%')).toBeTruthy(); // price change
expect(container).toHaveTextContent(/141\.75/); expect(container).toHaveTextContent(/1,000/); // volume
fireEvent.click(screen.getByTestId(`market-link-${expectedMarket.id}`)); fireEvent.click(screen.getByTestId(`market-link-1`));
expect(onSelect).toHaveBeenCalledWith(expectedMarket.id); expect(onSelect).toHaveBeenCalledWith('1');
}); });
it('should call onSelect callback on SelectMarketLandingTable', () => { it('should call onSelect callback on SelectMarketLandingTable', () => {
const onSelect = jest.fn(); const onSelect = jest.fn();
const expectedMarket = mockData.data.markets[0];
render( render(
<SelectMarketLandingTable <SelectMarketLandingTable
data={mockData.data.markets} markets={[MARKET_A as Market, MARKET_B as Market]}
marketsData={[MARKET_DATA_A as MarketData, MARKET_DATA_B as MarketData]}
marketsCandles={[
MARKET_CANDLES_A as MarketCandles,
MARKET_CANDLES_B as MarketCandles,
]}
onSelect={onSelect} onSelect={onSelect}
/> />
); );
fireEvent.click(screen.getByTestId(`market-link-${expectedMarket.id}`)); fireEvent.click(screen.getByTestId(`market-link-1`));
expect(onSelect).toHaveBeenCalledWith(expectedMarket.id); expect(onSelect).toHaveBeenCalledWith('1');
fireEvent.click(screen.getByTestId(`market-link-2`));
expect(onSelect).toHaveBeenCalledWith('2');
}); });
}); });
const mockData = {
data: {
markets: [
{
__typename: 'Market',
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
decimalPlaces: 2,
name: '',
positionDecimalPlaces: 4,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
data: {
markPrice: '14175',
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'APEUSD (May 2022)',
code: 'APEUSD',
product: {
__typename: 'Future',
settlementAsset: {
__typename: 'Asset',
symbol: 'USD',
},
},
},
},
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:08:27.693537312Z',
close: null,
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
infrastructureFee: '0.01',
makerFee: '0.01',
liquidityFee: '0.01',
},
},
candles: [
{
__typename: 'Candle',
open: '822',
close: '798',
},
{
__typename: 'Candle',
open: '793',
close: '792',
},
{
__typename: 'Candle',
open: '794',
close: '776',
},
{
__typename: 'Candle',
open: '785',
close: '786',
},
{
__typename: 'Candle',
open: '803',
close: '770',
},
{
__typename: 'Candle',
open: '785',
close: '774',
},
],
} as MarketList_markets,
{
__typename: 'Market',
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
decimalPlaces: 5,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'Apple Monthly (30 Jun 2022)',
code: 'AAPL.MF21',
product: {
__typename: 'Future',
settlementAsset: {
__typename: 'Asset',
symbol: 'USD',
},
},
},
},
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
infrastructureFee: '0.01',
makerFee: '0.01',
liquidityFee: '0.01',
},
},
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:00:39.328347732Z',
close: null,
},
candles: [
{
__typename: 'Candle',
open: '14707175',
close: '14633864',
},
{
__typename: 'Candle',
open: '14658400',
close: '14550193',
},
{
__typename: 'Candle',
open: '14550193',
close: '14373526',
},
{
__typename: 'Candle',
open: '14307141',
close: '14339846',
},
{
__typename: 'Candle',
open: '14357485',
close: '14179971',
},
{
__typename: 'Candle',
open: '14179972',
close: '14174855',
},
],
name: '',
positionDecimalPlaces: 4,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
data: {
markPrice: '14175',
},
} as MarketList_markets,
],
},
};

View File

@ -1,5 +1,4 @@
import { useQuery } from '@apollo/client'; import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { t, volumePrefix } from '@vegaprotocol/react-helpers';
import { import {
Dialog, Dialog,
Icon, Icon,
@ -8,6 +7,7 @@ import {
Link, Link,
Popover, Popover,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import type { Column } from './select-market-columns'; import type { Column } from './select-market-columns';
@ -17,21 +17,29 @@ import {
} from './select-market-columns'; } from './select-market-columns';
import { columnHeaders } from './select-market-columns'; import { columnHeaders } from './select-market-columns';
import { columns } from './select-market-columns'; import { columns } from './select-market-columns';
import type { MarketList_markets } from '../'; import type { Market, MarketData, MarketCandles, Candle } from '../';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import type { Positions } from '@vegaprotocol/positions'; import type {
import { POSITIONS_QUERY } from '@vegaprotocol/positions'; Positions_party,
PositionsSubscription_positions,
Positions_party_positionsConnection_edges_node,
} from '@vegaprotocol/positions';
import { positionsDataProvider } from '@vegaprotocol/positions';
import { import {
SelectMarketTableHeader, SelectMarketTableHeader,
SelectMarketTableRow, SelectMarketTableRow,
} from './select-market-table'; } from './select-market-table';
import { useMarketList } from '../markets-data-provider'; import { useMarketList } from '../markets-provider';
export const SelectMarketLandingTable = ({ export const SelectMarketLandingTable = ({
data, markets,
marketsData,
marketsCandles,
onSelect, onSelect,
}: { }: {
data: MarketList_markets[] | undefined; markets: Market[] | undefined;
marketsData: MarketData[] | undefined;
marketsCandles: MarketCandles[] | undefined;
onSelect: (id: string) => void; onSelect: (id: string) => void;
}) => { }) => {
return ( return (
@ -45,11 +53,20 @@ export const SelectMarketLandingTable = ({
<SelectMarketTableHeader /> <SelectMarketTableHeader />
</thead> </thead>
<tbody> <tbody>
{data?.map((market, i) => ( {markets?.map((market, i) => (
<SelectMarketTableRow <SelectMarketTableRow
key={i} key={i}
detailed={false} detailed={false}
columns={columns(market, onSelect)} columns={columns(
market,
marketsData?.find(
(marketData) => marketData.market.id === market.id
),
marketsCandles?.find(
(marketCandles) => marketCandles.marketId === market.id
)?.candles,
onSelect
)}
/> />
))} ))}
</tbody> </tbody>
@ -61,31 +78,52 @@ export const SelectMarketLandingTable = ({
}; };
export const SelectAllMarketsTableBody = ({ export const SelectAllMarketsTableBody = ({
data, markets,
marketsData,
marketsCandles,
positions,
onSelect, onSelect,
headers = columnHeaders, headers = columnHeaders,
tableColumns = (market) => columns(market, onSelect), tableColumns = (market, marketData, candles) =>
columns(market, marketData, candles, onSelect),
}: { }: {
data?: MarketList_markets[]; markets: Market[] | undefined;
marketsData: MarketData[] | undefined;
marketsCandles: MarketCandles[] | undefined;
positions?: Positions_party_positionsConnection_edges_node[];
title?: string; title?: string;
onSelect: (id: string) => void; onSelect: (id: string) => void;
headers?: Column[]; headers?: Column[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any tableColumns?: (
tableColumns?: (market: any) => Column[]; market: Market,
marketData: MarketData | undefined,
candles: Candle[] | undefined,
openVolume?: string
) => Column[];
}) => { }) => {
if (!data) return null; if (!markets) return null;
return ( return (
<> <>
<thead className="bg-neutral-100 dark:bg-neutral-800"> <thead className="bg-neutral-100 dark:bg-neutral-800">
<SelectMarketTableHeader detailed={true} headers={headers} /> <SelectMarketTableHeader detailed={true} headers={headers} />
</thead> </thead>
{/* Border styles required to create space between tbody elements margin/padding dont work */} {/* Border styles required to create space between tbody elements margin/padding don't work */}
<tbody className="border-b-[10px] border-transparent"> <tbody className="border-b-[10px] border-transparent">
{data?.map((market, i) => ( {markets?.map((market, i) => (
<SelectMarketTableRow <SelectMarketTableRow
key={i} key={i}
detailed={true} detailed={true}
columns={tableColumns(market)} columns={tableColumns(
market,
marketsData?.find(
(marketData) => marketData.market.id === market.id
),
marketsCandles?.find(
(marketCandles) => marketCandles.marketId === market.id
)?.candles,
positions &&
positions.find((p) => p.market.id === market.id)?.openVolume
)}
/> />
))} ))}
</tbody> </tbody>
@ -106,42 +144,31 @@ export const SelectMarketPopover = ({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { data, loading: marketsLoading } = useMarketList(); const { data, loading: marketsLoading } = useMarketList();
const variables = useMemo(() => ({ partyId: keypair?.pub }), [keypair?.pub]); const variables = useMemo(() => ({ partyId: keypair?.pub }), [keypair?.pub]);
const { data: marketDataPositions, loading: positionsLoading } = const { data: party, loading: positionsLoading } = useDataProvider<
useQuery<Positions>(POSITIONS_QUERY, { Positions_party,
PositionsSubscription_positions
>({
dataProvider: positionsDataProvider,
update: () => false,
variables, variables,
skip: !keypair?.pub, skip: !keypair,
}); });
const positionMarkets = useMemo(
() => ({
markets:
data
?.filter((market) =>
marketDataPositions?.party?.positionsConnection.edges?.find(
(edge) => edge.node.market.id === market.id
)
)
.map((market) => {
const position =
marketDataPositions?.party?.positionsConnection.edges?.find(
(edge) => edge.node.market.id === market.id
)?.node;
return {
...market,
openVolume:
position?.openVolume && volumePrefix(position.openVolume),
};
}) || null,
}),
[data, marketDataPositions]
);
const onSelectMarket = (marketId: string) => { const onSelectMarket = (marketId: string) => {
onSelect(marketId); onSelect(marketId);
setOpen(false); setOpen(false);
}; };
const iconClass = open ? 'rotate-180' : ''; const iconClass = open ? 'rotate-180' : '';
const markets = useMemo(
() =>
data?.markets?.filter((market) =>
party?.positionsConnection.edges?.find(
(edge) => edge.node.market.id === market.id
)
),
[data, party]
);
return ( return (
<Popover <Popover
@ -165,25 +192,36 @@ export const SelectMarketPopover = ({
</div> </div>
) : ( ) : (
<> <>
{keypair && {keypair && (party?.positionsConnection.edges?.length ?? 0) > 0 ? (
positionMarkets?.markets &&
positionMarkets.markets.length > 0 && (
<table className="relative text-sm w-full whitespace-nowrap"> <table className="relative text-sm w-full whitespace-nowrap">
<TableTitle>{t('My markets')}</TableTitle> <TableTitle>{t('My markets')}</TableTitle>
<SelectAllMarketsTableBody <SelectAllMarketsTableBody
data={positionMarkets.markets} markets={markets}
marketsData={data?.marketsData}
marketsCandles={data?.marketsCandles}
positions={party?.positionsConnection.edges
?.filter((edge) => edge.node)
.map((edge) => edge.node)}
onSelect={onSelectMarket} onSelect={onSelectMarket}
headers={columnHeadersPositionMarkets} headers={columnHeadersPositionMarkets}
tableColumns={(market) => tableColumns={(market, marketData, candles, openVolume) =>
columnsPositionMarkets(market, onSelectMarket) columnsPositionMarkets(
market,
marketData,
candles,
onSelectMarket,
openVolume
)
} }
/> />
</table> </table>
)} ) : null}
<table className="relative text-sm w-full whitespace-nowrap"> <table className="relative text-sm w-full whitespace-nowrap">
<TableTitle>{t('All markets')}</TableTitle> <TableTitle>{t('All markets')}</TableTitle>
<SelectAllMarketsTableBody <SelectAllMarketsTableBody
data={data} markets={data?.markets}
marketsData={data?.marketsData}
marketsCandles={data?.marketsCandles}
onSelect={onSelectMarket} onSelect={onSelectMarket}
/> />
</table> </table>
@ -256,5 +294,12 @@ const LandingDialogContainer = ({ onSelect }: LandingDialogContainerProps) => {
); );
} }
return <SelectMarketLandingTable data={data} onSelect={onSelect} />; return (
<SelectMarketLandingTable
markets={data?.markets}
marketsData={data?.marketsData}
marketsCandles={data?.marketsCandles}
onSelect={onSelect}
/>
);
}; };

View File

@ -1,6 +1,9 @@
export * from './__generated__/MarketDataFields'; export * from './__generated__';
export * from './__generated__/MarketList';
export * from './__generated__/MarketDataSub';
export * from './components'; export * from './components';
export * from './utils'; export * from './utils';
export * from './market-candles-provider';
export * from './market-data-provider';
export * from './market-provider';
export * from './markets-candles-provider';
export * from './markets-data-provider'; export * from './markets-data-provider';
export * from './markets-provider';

View File

@ -0,0 +1,83 @@
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type {
MarketCandlesQuery,
MarketCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node,
MarketCandlesSub,
MarketCandlesSub_candles,
} from './__generated__';
export const MARKET_CANDLES_QUERY = gql`
query MarketCandlesQuery(
$interval: Interval!
$since: String!
$marketId: ID!
) {
marketsConnection(id: $marketId) {
edges {
node {
candlesConnection(interval: $interval, since: $since) {
edges {
node {
high
low
open
close
volume
}
}
}
}
}
}
}
`;
const MARKET_CANDLES_SUB = gql`
subscription MarketCandlesSub($marketId: ID!, $interval: Interval!) {
candles(interval: $interval, marketId: $marketId) {
high
low
open
close
volume
}
}
`;
export type Candle =
MarketCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node;
const update = (data: Candle[], delta: MarketCandlesSub_candles) => {
return data && delta
? [
...data,
{
...delta,
__typename: 'CandleNode',
} as Candle,
]
: data;
};
const getData = (responseData: MarketCandlesQuery): Candle[] | null =>
responseData.marketsConnection.edges[0].node.candlesConnection.edges
?.filter((edge) => edge?.node)
.map((edge) => edge?.node as Candle) || null;
const getDelta = (
subscriptionData: MarketCandlesSub
): MarketCandlesSub_candles => subscriptionData.candles;
export const marketCandlesProvider = makeDataProvider<
MarketCandlesQuery,
Candle[],
MarketCandlesSub,
MarketCandlesSub_candles
>({
query: MARKET_CANDLES_QUERY,
subscriptionQuery: MARKET_CANDLES_SUB,
update,
getData,
getDelta,
});

View File

@ -0,0 +1,81 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type {
MarketDataSub,
MarketDataSub_marketsData,
MarketDataQuery,
MarketDataQuery_marketsConnection_edges_node_data,
} from './__generated__';
export const MARKET_DATA_QUERY = gql`
query MarketDataQuery($marketId: ID!) {
marketsConnection(id: $marketId) {
edges {
node {
data {
market {
id
}
bestBidPrice
bestOfferPrice
markPrice
trigger
staticMidPrice
marketTradingMode
indicativeVolume
indicativePrice
bestStaticBidPrice
bestStaticOfferPrice
}
}
}
}
}
`;
const MARKET_DATA_SUB = gql`
subscription MarketDataSub($marketId: ID!) {
marketsData(marketIds: [$marketId]) {
marketId
bestBidPrice
bestOfferPrice
markPrice
trigger
staticMidPrice
marketTradingMode
indicativeVolume
indicativePrice
bestStaticBidPrice
bestStaticOfferPrice
}
}
`;
export type MarketData = MarketDataQuery_marketsConnection_edges_node_data;
const update = (data: MarketData, delta: MarketDataSub_marketsData) => {
return produce(data, (draft) => {
const { marketId, __typename, ...marketData } = delta;
Object.assign(draft, marketData);
});
};
const getData = (responseData: MarketDataQuery): MarketData | null =>
responseData.marketsConnection.edges[0].node.data || null;
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketsData =>
subscriptionData.marketsData[0];
export const marketDataProvider = makeDataProvider<
MarketDataQuery,
MarketData,
MarketDataSub,
MarketDataSub_marketsData
>({
query: MARKET_DATA_QUERY,
subscriptionQuery: MARKET_DATA_SUB,
update,
getData,
getDelta,
});

View File

@ -0,0 +1,19 @@
import { makeDerivedDataProvider } from '@vegaprotocol/react-helpers';
import type { Market } from './markets-provider';
import { marketsProvider } from './markets-provider';
export const marketProvider = makeDerivedDataProvider<Market>(
[(callback, client) => marketsProvider(callback, client)], // omit variables param
([markets], variables) => {
if (markets) {
const market = (markets as Market[]).find(
(market) => market.id === variables?.marketId
);
if (market) {
return market;
}
}
return null;
}
);

View File

@ -0,0 +1,53 @@
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type {
MarketsCandlesQuery,
MarketsCandlesQuery_marketsConnection_edges_node as Market,
MarketsCandlesQuery_marketsConnection_edges_node_candlesConnection_edges_node as Candle,
} from './__generated__';
export const MARKETS_CANDLES_QUERY = gql`
query MarketsCandlesQuery($interval: Interval!, $since: String!) {
marketsConnection {
edges {
node {
id
candlesConnection(interval: $interval, since: $since) {
edges {
node {
high
low
open
close
volume
}
}
}
}
}
}
}
`;
export interface MarketCandles {
marketId: Market['id'];
candles: Candle[] | undefined;
}
const getData = (responseData: MarketsCandlesQuery): MarketCandles[] | null =>
responseData.marketsConnection.edges.map((edge) => ({
marketId: edge.node.id,
candles: edge.node.candlesConnection.edges
?.filter((edge) => edge?.node)
.map((edge) => edge?.node as Candle),
})) || null;
export const marketsCandlesProvider = makeDataProvider<
MarketsCandlesQuery,
MarketCandles[],
never,
never
>({
query: MARKETS_CANDLES_QUERY,
getData,
});

View File

@ -1,141 +1,45 @@
import produce from 'immer'; import { gql } from '@apollo/client';
import { gql, useQuery } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers'; import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type { import type { MarketsDataQuery } from './__generated__/MarketsDataQuery';
MarketDataSub, import type { MarketData } from './market-data-provider';
MarketDataSub_marketData,
MarketList,
MarketList_markets,
} from './';
import { useMemo } from 'react';
import { Interval } from '@vegaprotocol/types';
import { mapDataToMarketList } from './utils';
export const useMarketList = () => { export const MARKETS_DATA_QUERY = gql`
const since = useMemo(() => { query MarketsDataQuery {
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600; marketsConnection {
return new Date(yesterday * 1000).toISOString(); edges {
}, []); node {
const { data, loading, error } = useQuery<MarketList>(MARKET_LIST_QUERY, {
variables: { interval: Interval.INTERVAL_I1H, since },
});
return {
data: useMemo(() => data && mapDataToMarketList(data), [data]),
loading,
error,
};
};
const MARKET_DATA_FRAGMENT = gql`
fragment MarketDataFields on MarketData {
market {
id
state
tradingMode
}
bestBidPrice
bestOfferPrice
markPrice
trigger
indicativeVolume
}
`;
export const MARKET_LIST_QUERY = gql`
query MarketList($interval: Interval!, $since: String!) {
markets {
id
decimalPlaces
positionDecimalPlaces
state
tradingMode
fees {
factors {
makerFee
infrastructureFee
liquidityFee
}
}
data { data {
market { market {
id id
state
tradingMode
} }
bestBidPrice bestBidPrice
bestOfferPrice bestOfferPrice
markPrice markPrice
trigger trigger
staticMidPrice
marketTradingMode
indicativeVolume indicativeVolume
} indicativePrice
tradableInstrument { bestStaticBidPrice
instrument { bestStaticOfferPrice
id
name
code
metadata {
tags
}
product {
... on Future {
settlementAsset {
symbol
} }
} }
} }
} }
} }
marketTimestamps {
open
close
}
candles(interval: $interval, since: $since) {
open
close
high
low
}
}
}
`; `;
const MARKET_DATA_SUB = gql` const getData = (responseData: MarketsDataQuery): MarketData[] | null =>
${MARKET_DATA_FRAGMENT} responseData.marketsConnection.edges
subscription MarketDataSub { .filter((edge) => edge.node.data)
marketData { .map((edge) => edge.node.data as MarketData) || null;
...MarketDataFields
}
}
`;
const update = (
data: MarketList_markets[],
delta: MarketDataSub_marketData
) => {
return produce(data, (draft) => {
const index = draft.findIndex((m) => m.id === delta.market.id);
if (index !== -1) {
draft[index].data = delta;
}
// @TODO - else push new market to draft
});
};
const getData = (responseData: MarketList): MarketList_markets[] | null =>
responseData.markets;
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
subscriptionData.marketData;
export const marketsDataProvider = makeDataProvider< export const marketsDataProvider = makeDataProvider<
MarketList, MarketsDataQuery,
MarketList_markets[], MarketData[],
MarketDataSub, never,
MarketDataSub_marketData never
>({ >({
query: MARKET_LIST_QUERY, query: MARKETS_DATA_QUERY,
subscriptionQuery: MARKET_DATA_SUB,
update,
getData, getData,
getDelta,
}); });

View File

@ -0,0 +1,144 @@
import { gql } from '@apollo/client';
import {
makeDataProvider,
makeDerivedDataProvider,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import type {
Markets,
Markets_marketsConnection_edges_node,
} from './__generated__';
import { marketsDataProvider } from './markets-data-provider';
import { marketsCandlesProvider } from './markets-candles-provider';
import type { MarketData } from './market-data-provider';
import type { MarketCandles } from './markets-candles-provider';
import { useMemo } from 'react';
import { Interval } from '@vegaprotocol/types';
import { mapDataToMarketList } from './utils';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export type Market = Markets_marketsConnection_edges_node;
const MARKET_DATA_FRAGMENT = gql`
fragment MarketFields on Market {
id
decimalPlaces
positionDecimalPlaces
state
tradingMode
fees {
factors {
makerFee
infrastructureFee
liquidityFee
}
}
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
product {
... on Future {
settlementAsset {
symbol
}
}
}
}
}
marketTimestamps {
open
close
}
}
`;
export const MARKET_LIST_QUERY = gql`
${MARKET_DATA_FRAGMENT}
query Markets {
marketsConnection {
edges {
node {
...MarketFields
}
}
}
}
`;
const getData = (responseData: Markets): Market[] | null =>
responseData.marketsConnection.edges.map((edge) => edge.node);
export const marketsProvider = makeDataProvider<
Markets,
Market[],
never,
never
>({
query: MARKET_LIST_QUERY,
getData,
});
export const activeMarketsProvider = makeDerivedDataProvider<Market[]>(
[marketsProvider],
([markets]) => mapDataToMarketList(markets)
);
interface MarketsListData {
markets: Market[];
marketsData: MarketData[];
marketsCandles: MarketCandles[];
}
export const marketListProvider = makeDerivedDataProvider<MarketsListData>(
[
(callback, client) => activeMarketsProvider(callback, client),
(callback, client) => marketsDataProvider(callback, client),
marketsCandlesProvider,
],
(parts) => {
return {
markets: parts[0] as Market[],
marketsData: parts[1] as MarketData[],
marketsCandles: parts[2] as MarketCandles[],
};
}
);
export type MarketWithData = Market & { data?: MarketData };
export const marketsWithDataProvider = makeDerivedDataProvider<
MarketWithData[]
>([activeMarketsProvider, marketsDataProvider], (parts) =>
(parts[0] as Market[]).map((market) => ({
...market,
data: (parts[1] as MarketData[]).find(
(data) => data.market.id === market.id
),
}))
);
export const useMarketList = () => {
const variables = useMemo(() => {
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
return {
since: new Date(yesterday * 1000).toISOString(),
interval: Interval.INTERVAL_I1H,
};
}, []);
const { data, loading, error } = useDataProvider<MarketsListData, never>({
dataProvider: marketListProvider,
variables,
noUpdate: true,
});
return {
data,
loading,
error,
};
};

View File

@ -1,210 +1,59 @@
import type { MarketList } from '../__generated__/MarketList'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import type { Market } from '../markets-provider';
import { mapDataToMarketList } from './market-utils'; import { mapDataToMarketList } from './market-utils';
const MARKET_A: Partial<Market> = {
id: '1',
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:08:27.693537312Z',
close: null,
},
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
};
const MARKET_B: Partial<Market> = {
id: '2',
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:00:39.328347732Z',
close: null,
},
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
};
const MARKET_C: Partial<Market> = {
id: '3',
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-17T13:00:39.328347732Z',
close: null,
},
state: MarketState.STATE_REJECTED,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
};
const MARKET_D: Partial<Market> = {
id: '4',
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-16T13:00:39.328347732Z',
close: null,
},
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_NO_TRADING,
};
describe('mapDataToMarketList', () => { describe('mapDataToMarketList', () => {
it('should map queried data to market list format', () => { it('should map queried data to market list format', () => {
const result = mapDataToMarketList(mockData.data as unknown as MarketList); const result = mapDataToMarketList([
expect(result).toEqual(mockList); MARKET_A,
MARKET_B,
MARKET_C,
MARKET_D,
] as unknown as Market[]);
expect(result).toEqual([MARKET_B, MARKET_A]);
}); });
}); });
const mockList = [
{
__typename: 'Market',
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
decimalPlaces: 5,
candles: [
{
open: '16141155',
close: '16293551',
high: '16320190',
low: '16023805',
__typename: 'Candle',
},
{
open: '16293548',
close: '16322118',
high: '16365861',
low: '16192970',
__typename: 'Candle',
},
],
fees: {
factors: {
makerFee: 0.0002,
infrastructureFee: 0.0005,
liquidityFee: 0.001,
},
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'Apple Monthly (30 Jun 2022)',
code: 'AAPL.MF21',
product: {
settlementAsset: { symbol: 'AAPL.MF21', __typename: 'Asset' },
__typename: 'Future',
},
},
},
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:00:39.328347732Z',
close: null,
},
open: 1652878839328,
close: null,
},
{
__typename: 'Market',
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
decimalPlaces: 2,
fees: {
factors: {
makerFee: 0.0002,
infrastructureFee: 0.0005,
liquidityFee: 0.001,
},
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'APEUSD (May 2022)',
code: 'APEUSD',
product: {
settlementAsset: { symbol: 'APEUSD', __typename: 'Asset' },
__typename: 'Future',
},
},
},
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:08:27.693537312Z',
close: null,
},
candles: [
{
open: '16141155',
close: '16293551',
high: '16320190',
low: '16023805',
__typename: 'Candle',
},
{
open: '16293548',
close: '16322118',
high: '16365861',
low: '16192970',
__typename: 'Candle',
},
],
open: 1652879307693,
close: null,
},
];
const mockData = {
data: {
markets: [
{
__typename: 'Market',
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
decimalPlaces: 2,
fees: {
factors: {
makerFee: 0.0002,
infrastructureFee: 0.0005,
liquidityFee: 0.001,
},
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'APEUSD (May 2022)',
code: 'APEUSD',
product: {
settlementAsset: {
symbol: 'APEUSD',
__typename: 'Asset',
},
__typename: 'Future',
},
},
},
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:08:27.693537312Z',
close: null,
},
candles: [
{
open: '16141155',
close: '16293551',
high: '16320190',
low: '16023805',
__typename: 'Candle',
},
{
open: '16293548',
close: '16322118',
high: '16365861',
low: '16192970',
__typename: 'Candle',
},
],
},
{
__typename: 'Market',
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
decimalPlaces: 5,
candles: [
{
open: '16141155',
close: '16293551',
high: '16320190',
low: '16023805',
__typename: 'Candle',
},
{
open: '16293548',
close: '16322118',
high: '16365861',
low: '16192970',
__typename: 'Candle',
},
],
fees: {
factors: {
makerFee: 0.0002,
infrastructureFee: 0.0005,
liquidityFee: 0.001,
},
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'Apple Monthly (30 Jun 2022)',
code: 'AAPL.MF21',
product: {
settlementAsset: {
symbol: 'AAPL.MF21',
__typename: 'Asset',
},
__typename: 'Future',
},
},
},
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-05-18T13:00:39.328347732Z',
close: null,
},
},
],
},
};

View File

@ -2,13 +2,9 @@ import { formatNumberPercentage } from '@vegaprotocol/react-helpers';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import type { import type { Market, Candle } from '../';
MarketList,
MarketList_markets,
MarketList_markets_fees_factors,
} from '../__generated__/MarketList';
export const totalFees = (fees: MarketList_markets_fees_factors) => { export const totalFees = (fees: Market['fees']['factors']) => {
if (!fees) { if (!fees) {
return undefined; return undefined;
} }
@ -20,7 +16,7 @@ export const totalFees = (fees: MarketList_markets_fees_factors) => {
); );
}; };
export const mapDataToMarketList = ({ markets }: MarketList) => { export const mapDataToMarketList = (markets: Market[]) => {
const tradingModesOrdering = [ const tradingModesOrdering = [
MarketTradingMode.TRADING_MODE_CONTINUOUS, MarketTradingMode.TRADING_MODE_CONTINUOUS,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
@ -29,24 +25,12 @@ export const mapDataToMarketList = ({ markets }: MarketList) => {
MarketTradingMode.TRADING_MODE_NO_TRADING, MarketTradingMode.TRADING_MODE_NO_TRADING,
]; ];
const orderedMarkets = orderBy( const orderedMarkets = orderBy(
markets markets?.filter(
?.filter(
(m) => (m) =>
m.state !== MarketState.STATE_REJECTED && m.state !== MarketState.STATE_REJECTED &&
m.tradingMode !== MarketTradingMode.TRADING_MODE_NO_TRADING m.tradingMode !== MarketTradingMode.TRADING_MODE_NO_TRADING
) ) || [],
.map((m) => { ['marketTimestamps.open', 'id'],
return {
...m,
open: m.marketTimestamps.open
? new Date(m.marketTimestamps.open).getTime()
: null,
close: m.marketTimestamps.close
? new Date(m.marketTimestamps.close).getTime()
: null,
};
}) || [],
['open', 'id'],
['asc', 'asc'] ['asc', 'asc']
); );
return orderedMarkets.sort( return orderedMarkets.sort(
@ -56,8 +40,8 @@ export const mapDataToMarketList = ({ markets }: MarketList) => {
); );
}; };
export const calcCandleLow = (m: MarketList_markets): string | undefined => { export const calcCandleLow = (candles: Candle[]): string | undefined => {
return m.candles return candles
?.reduce((acc: BigNumber, c) => { ?.reduce((acc: BigNumber, c) => {
if (c?.low) { if (c?.low) {
if (acc.isLessThan(new BigNumber(c.low))) { if (acc.isLessThan(new BigNumber(c.low))) {
@ -66,12 +50,12 @@ export const calcCandleLow = (m: MarketList_markets): string | undefined => {
return new BigNumber(c.low); return new BigNumber(c.low);
} }
return acc; return acc;
}, new BigNumber(m.candles?.[0]?.high ?? 0)) }, new BigNumber(candles?.[0]?.high ?? 0))
.toString(); .toString();
}; };
export const calcCandleHigh = (m: MarketList_markets): string | undefined => { export const calcCandleHigh = (candles: Candle[]): string | undefined => {
return m.candles return candles
?.reduce((acc: BigNumber, c) => { ?.reduce((acc: BigNumber, c) => {
if (c?.high) { if (c?.high) {
if (acc.isGreaterThan(new BigNumber(c.high))) { if (acc.isGreaterThan(new BigNumber(c.high))) {

View File

@ -1,4 +1,5 @@
export * from './lib/__generated__/Positions'; export * from './lib/__generated__/Positions';
export * from './lib/__generated__/PositionsSubscription';
export * from './lib/positions-container'; export * from './lib/positions-container';
export * from './lib/positions-data-providers'; export * from './lib/positions-data-providers';
export * from './lib/positions-table'; export * from './lib/positions-table';

View File

@ -252,7 +252,7 @@ export const update = (
}); });
}; };
export const positionDataProvider = makeDataProvider< export const positionsDataProvider = makeDataProvider<
Positions, Positions,
Positions_party, Positions_party,
PositionsSubscription, PositionsSubscription,
@ -267,7 +267,7 @@ export const positionDataProvider = makeDataProvider<
}); });
export const positionsMetricsDataProvider = makeDerivedDataProvider<Position[]>( export const positionsMetricsDataProvider = makeDerivedDataProvider<Position[]>(
[positionDataProvider, accountsDataProvider], [positionsDataProvider, accountsDataProvider],
([positions, accounts]) => { ([positions, accounts]) => {
return sortBy( return sortBy(
getMetrics( getMetrics(

View File

@ -19,6 +19,8 @@ export function useDataProvider<Data, Delta>({
update, update,
insert, insert,
variables, variables,
noUpdate,
skip,
}: { }: {
dataProvider: Subscribe<Data, Delta>; dataProvider: Subscribe<Data, Delta>;
update?: ({ delta, data }: { delta: Delta; data: Data }) => boolean; update?: ({ delta, data }: { delta: Delta; data: Data }) => boolean;
@ -32,11 +34,13 @@ export function useDataProvider<Data, Delta>({
totalCount?: number; totalCount?: number;
}) => boolean; }) => boolean;
variables?: OperationVariables; variables?: OperationVariables;
noUpdate?: boolean;
skip?: boolean;
}) { }) {
const client = useApolloClient(); const client = useApolloClient();
const [data, setData] = useState<Data | null>(null); const [data, setData] = useState<Data | null>(null);
const [totalCount, setTotalCount] = useState<number>(); const [totalCount, setTotalCount] = useState<number>();
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(!skip);
const [error, setError] = useState<Error | undefined>(undefined); const [error, setError] = useState<Error | undefined>(undefined);
const flushRef = useRef<(() => void) | undefined>(undefined); const flushRef = useRef<(() => void) | undefined>(undefined);
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined); const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
@ -75,7 +79,12 @@ export function useDataProvider<Data, Delta>({
// if update or insert function returns true it means that component handles updates // if update or insert function returns true it means that component handles updates
// component can use flush() which will call callback without delta and cause data state update // component can use flush() which will call callback without delta and cause data state update
if (initialized.current && data) { if (initialized.current && data) {
if (isUpdate && update && (!delta || update({ delta, data }))) { if (
isUpdate &&
!noUpdate &&
update &&
(!delta || update({ delta, data }))
) {
return; return;
} }
if ( if (
@ -91,9 +100,12 @@ export function useDataProvider<Data, Delta>({
setData(data); setData(data);
} }
}, },
[update, insert] [update, insert, noUpdate]
); );
useEffect(() => { useEffect(() => {
if (skip) {
return;
}
const { unsubscribe, flush, reload, load } = dataProvider( const { unsubscribe, flush, reload, load } = dataProvider(
callback, callback,
client, client,
@ -103,6 +115,6 @@ export function useDataProvider<Data, Delta>({
reloadRef.current = reload; reloadRef.current = reload;
loadRef.current = load; loadRef.current = load;
return unsubscribe; return unsubscribe;
}, [client, initialized, dataProvider, callback, variables]); }, [client, initialized, dataProvider, callback, variables, skip]);
return { data, loading, error, flush, reload, load, totalCount }; return { data, loading, error, flush, reload, load, totalCount };
} }

View File

@ -133,10 +133,10 @@ export function defaultAppend<Data>(
interface DataProviderParams<QueryData, Data, SubscriptionData, Delta> { interface DataProviderParams<QueryData, Data, SubscriptionData, Delta> {
query: Query<QueryData>; query: Query<QueryData>;
subscriptionQuery: Query<SubscriptionData>; subscriptionQuery?: Query<SubscriptionData>;
update: Update<Data, Delta>; update?: Update<Data, Delta>;
getData: GetData<QueryData, Data>; getData: GetData<QueryData, Data>;
getDelta: GetDelta<SubscriptionData, Delta>; getDelta?: GetDelta<SubscriptionData, Delta>;
pagination?: { pagination?: {
getPageInfo: GetPageInfo<QueryData>; getPageInfo: GetPageInfo<QueryData>;
getTotalCount?: GetTotalCount<QueryData>; getTotalCount?: GetTotalCount<QueryData>;
@ -270,6 +270,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
? { ...variables, pagination: { first: pagination.first } } ? { ...variables, pagination: { first: pagination.first } }
: variables, : variables,
fetchPolicy, fetchPolicy,
errorPolicy: 'ignore',
}); });
data = getData(res.data); data = getData(res.data);
if (data && pagination) { if (data && pagination) {
@ -291,7 +292,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
} }
} }
// if there was some updates received from subscription during initial query loading apply them on just received data // if there was some updates received from subscription during initial query loading apply them on just received data
if (data && updateQueue && updateQueue.length > 0) { if (update && data && updateQueue && updateQueue.length > 0) {
while (updateQueue.length) { while (updateQueue.length) {
const delta = updateQueue.shift(); const delta = updateQueue.shift();
if (delta) { if (delta) {
@ -339,6 +340,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
if (!client) { if (!client) {
return; return;
} }
if (subscriptionQuery && getDelta && update) {
subscription = client subscription = client
.subscribe<SubscriptionData>({ .subscribe<SubscriptionData>({
query: subscriptionQuery, query: subscriptionQuery,
@ -364,6 +366,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>({
}, },
() => reload() () => reload()
); );
}
await initialFetch(); await initialFetch();
}; };
@ -470,7 +473,8 @@ export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
type DependencySubscribe = Subscribe<any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any type DependencySubscribe = Subscribe<any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
type DependencyUpdateCallback = Parameters<DependencySubscribe>['0']; type DependencyUpdateCallback = Parameters<DependencySubscribe>['0'];
export type CombineDerivedData<Data> = ( export type CombineDerivedData<Data> = (
parts: Parameters<DependencyUpdateCallback>['0']['data'][] parts: Parameters<DependencyUpdateCallback>['0']['data'][],
variables?: OperationVariables
) => Data | null; ) => Data | null;
function makeDerivedDataProviderInternal<Data>( function makeDerivedDataProviderInternal<Data>(
@ -521,7 +525,10 @@ function makeDerivedDataProviderInternal<Data>(
}); });
const newData = newLoaded const newData = newLoaded
? combineData(parts.map((part) => part.data)) ? combineData(
parts.map((part) => part.data),
variables
)
: data; : data;
if ( if (
newLoading !== loading || newLoading !== loading ||