feat(trading,market-list): closed markets datagrid (#3429)
This commit is contained in:
parent
8d42481130
commit
351a20abad
2
.github/workflows/ci-cd-trigger.yml
vendored
2
.github/workflows/ci-cd-trigger.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
lint-test-build:
|
lint-test-build:
|
||||||
timeout-minutes: 35
|
timeout-minutes: 60
|
||||||
needs: node-modules
|
needs: node-modules
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
name: '(CI) lint + unit test + build'
|
name: '(CI) lint + unit test + build'
|
||||||
|
375
apps/trading-e2e/src/integration/closed-markets.cy.ts
Normal file
375
apps/trading-e2e/src/integration/closed-markets.cy.ts
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||||
|
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
|
import {
|
||||||
|
chainIdQuery,
|
||||||
|
statisticsQuery,
|
||||||
|
createDataConnection,
|
||||||
|
oracleSpecDataConnectionQuery,
|
||||||
|
createMarketFragment,
|
||||||
|
marketsQuery,
|
||||||
|
marketsDataQuery,
|
||||||
|
createMarketsDataFragment,
|
||||||
|
assetQuery,
|
||||||
|
networkParamsQuery,
|
||||||
|
} from '@vegaprotocol/mock';
|
||||||
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
getDateTimeFormat,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
|
||||||
|
describe('Closed markets', { tags: '@smoke' }, () => {
|
||||||
|
const rowSelector =
|
||||||
|
'[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row';
|
||||||
|
|
||||||
|
const assetsResult = assetQuery();
|
||||||
|
// @ts-ignore asset definitely exists
|
||||||
|
const settlementAsset = assetsResult.assetsConnection.edges[0].node;
|
||||||
|
|
||||||
|
const settledMarket = createMarketFragment({
|
||||||
|
id: '0',
|
||||||
|
state: MarketState.STATE_SETTLED,
|
||||||
|
marketTimestamps: {
|
||||||
|
open: subDays(new Date(), 10).toISOString(),
|
||||||
|
close: subDays(new Date(), 4).toISOString(),
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
product: {
|
||||||
|
dataSourceSpecForTradingTermination: {
|
||||||
|
id: 'market-1-trading-termination-oracle-id',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
id: 'market-1-settlement-data-oracle-id',
|
||||||
|
},
|
||||||
|
settlementAsset,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const terminatedMarket = createMarketFragment({
|
||||||
|
id: '1',
|
||||||
|
state: MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
marketTimestamps: {
|
||||||
|
open: subDays(new Date(), 10).toISOString(),
|
||||||
|
close: null, // market
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
metadata: {
|
||||||
|
tags: [
|
||||||
|
`settlement-expiry-date:${addDays(new Date(), 4).toISOString()}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayedSettledMarket = createMarketFragment({
|
||||||
|
id: '2',
|
||||||
|
state: MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
marketTimestamps: {
|
||||||
|
open: subDays(new Date(), 10).toISOString(),
|
||||||
|
close: null, // market
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
metadata: {
|
||||||
|
tags: [
|
||||||
|
`settlement-expiry-date:${subDays(new Date(), 2).toISOString()}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const unknownMarket = createMarketFragment({
|
||||||
|
id: '3',
|
||||||
|
state: MarketState.STATE_SETTLED,
|
||||||
|
});
|
||||||
|
|
||||||
|
const closedMarketsResult = [
|
||||||
|
{
|
||||||
|
node: settledMarket,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: terminatedMarket,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: delayedSettledMarket,
|
||||||
|
},
|
||||||
|
{ node: unknownMarket },
|
||||||
|
{
|
||||||
|
node: createMarketFragment({ id: '4', state: MarketState.STATE_PENDING }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: createMarketFragment({ id: '5', state: MarketState.STATE_ACTIVE }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const settledMarketData = createMarketsDataFragment({
|
||||||
|
market: {
|
||||||
|
id: settledMarket.id,
|
||||||
|
},
|
||||||
|
bestBidPrice: '1000',
|
||||||
|
bestOfferPrice: '2000',
|
||||||
|
markPrice: '1500',
|
||||||
|
});
|
||||||
|
|
||||||
|
const closedMarketsDataResult = [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
data: settledMarketData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
market: {
|
||||||
|
id: terminatedMarket.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
market: {
|
||||||
|
id: delayedSettledMarket.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
data: createMarketsDataFragment({
|
||||||
|
market: {
|
||||||
|
id: unknownMarket.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const specDataConnection = createDataConnection();
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.mockGQL((req) => {
|
||||||
|
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||||
|
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||||
|
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||||
|
aliasGQLQuery(
|
||||||
|
req,
|
||||||
|
'Markets',
|
||||||
|
marketsQuery({
|
||||||
|
marketsConnection: {
|
||||||
|
edges: closedMarketsResult,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
aliasGQLQuery(
|
||||||
|
req,
|
||||||
|
'MarketsData',
|
||||||
|
marketsDataQuery({
|
||||||
|
marketsConnection: {
|
||||||
|
edges: closedMarketsDataResult,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
aliasGQLQuery(
|
||||||
|
req,
|
||||||
|
'OracleSpecDataConnection',
|
||||||
|
oracleSpecDataConnectionQuery()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.mockSubscription();
|
||||||
|
|
||||||
|
cy.visit('/#/markets/all');
|
||||||
|
cy.get('[data-testid="Closed markets"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a settled market', () => {
|
||||||
|
const expectedMarkets = closedMarketsResult.filter((edge) => {
|
||||||
|
return [
|
||||||
|
MarketState.STATE_SETTLED,
|
||||||
|
MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
].includes(edge.node.state);
|
||||||
|
});
|
||||||
|
const product = settledMarket.tradableInstrument.instrument.product;
|
||||||
|
|
||||||
|
// rows should be filtered to only include settled/terminated markets
|
||||||
|
cy.get(rowSelector).should('have.length', expectedMarkets.length);
|
||||||
|
|
||||||
|
// check each column in the first row renders correctly
|
||||||
|
// 6001-MARK-001
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="code"]')
|
||||||
|
.should('have.text', settledMarket.tradableInstrument.instrument.code);
|
||||||
|
|
||||||
|
// 6001-MARK-002
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="name"]')
|
||||||
|
.should('have.text', settledMarket.tradableInstrument.instrument.name);
|
||||||
|
|
||||||
|
// 6001-MARK-003
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="state"]')
|
||||||
|
.should('have.text', MarketStateMapping[settledMarket.state]);
|
||||||
|
|
||||||
|
// 6001-MARK-004
|
||||||
|
// 6001-MARK-005
|
||||||
|
// 6001-MARK-009
|
||||||
|
// 6001-MARK-008
|
||||||
|
// 6001-MARK-010
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="settlementDate"]')
|
||||||
|
.find('[data-testid="link"]')
|
||||||
|
.should(($el) => {
|
||||||
|
const href = $el.attr('href');
|
||||||
|
expect(href).to.match(
|
||||||
|
new RegExp(
|
||||||
|
`/oracles/${product.dataSourceSpecForTradingTermination.id}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.should('have.text', '4 days ago')
|
||||||
|
.should(
|
||||||
|
'have.attr',
|
||||||
|
'title',
|
||||||
|
getDateTimeFormat().format(
|
||||||
|
new Date(settledMarket.marketTimestamps.close)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6001-MARK-011
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="bestBidPrice"]')
|
||||||
|
.should(
|
||||||
|
'have.text',
|
||||||
|
addDecimalsFormatNumber(
|
||||||
|
settledMarketData.bestBidPrice,
|
||||||
|
settledMarket.decimalPlaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6001-MARK-012
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="bestOfferPrice"]')
|
||||||
|
.should(
|
||||||
|
'have.text',
|
||||||
|
addDecimalsFormatNumber(
|
||||||
|
settledMarketData.bestOfferPrice,
|
||||||
|
settledMarket.decimalPlaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6001-MARK-013
|
||||||
|
cy.get(rowSelector).first().find('[col-id="markPrice"]').should(
|
||||||
|
'have.text',
|
||||||
|
|
||||||
|
addDecimalsFormatNumber(
|
||||||
|
settledMarketData.markPrice,
|
||||||
|
settledMarket.decimalPlaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6001-MARK-014
|
||||||
|
// 6001-MARK-015
|
||||||
|
// 6001-MARK-016
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="settlementDataOracleId"]')
|
||||||
|
.find('[data-testid="link"]')
|
||||||
|
.should(($el) => {
|
||||||
|
const href = $el.attr('href');
|
||||||
|
expect(href).to.match(
|
||||||
|
new RegExp(`/oracles/${product.dataSourceSpecForSettlementData.id}`)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.should(
|
||||||
|
'have.text',
|
||||||
|
addDecimalsFormatNumber(
|
||||||
|
// @ts-ignore cannot deep un-partial
|
||||||
|
specDataConnection.externalData.data.data[0].value,
|
||||||
|
settledMarket.decimalPlaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6001-MARK-017
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="realisedPNL"]')
|
||||||
|
.should('have.text', '-');
|
||||||
|
|
||||||
|
// 6001-MARK-018
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="settlementAsset"]')
|
||||||
|
.should('have.text', product.settlementAsset.symbol);
|
||||||
|
|
||||||
|
// 6001-MARK-020
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="id"]')
|
||||||
|
.should('have.text', settledMarket.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// test market list for market in terminated state
|
||||||
|
it('renders a terminated market', () => {
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.eq(1)
|
||||||
|
.find('[col-id="state"]')
|
||||||
|
.should('have.text', MarketStateMapping[terminatedMarket.state]);
|
||||||
|
|
||||||
|
// 6001-MARK-006
|
||||||
|
// 6001-MARK-007
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.eq(1)
|
||||||
|
.find('[col-id="settlementDate"]')
|
||||||
|
.find('[data-testid="link"]')
|
||||||
|
.should('have.text', 'Expected in 4 days');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a terminated market which was expected to have settled', () => {
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.eq(2)
|
||||||
|
.find('[col-id="settlementDate"]')
|
||||||
|
.should('have.class', 'text-danger')
|
||||||
|
.find('[data-testid="link"]')
|
||||||
|
.should('have.text', 'Expected 2 days ago');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders terminated market which doesnt have settlement date metadata', () => {
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.eq(3)
|
||||||
|
.find('[col-id="settlementDate"]')
|
||||||
|
.find('[data-testid="link"]')
|
||||||
|
.should('have.text', 'Unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can open asset detail dialog', () => {
|
||||||
|
cy.mockGQL((req) => {
|
||||||
|
aliasGQLQuery(req, 'Asset', assetsResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get(rowSelector)
|
||||||
|
.first()
|
||||||
|
.find('[col-id="settlementAsset"]')
|
||||||
|
.find('button')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// 6001-MARK-019
|
||||||
|
cy.get('[data-testid="dialog-title"]').should(
|
||||||
|
'have.text',
|
||||||
|
`Asset details - ${settlementAsset.symbol}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -101,7 +101,7 @@ describe('deposit actions', { tags: '@smoke' }, () => {
|
|||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.setVegaWallet();
|
cy.setVegaWallet();
|
||||||
cy.visit('/');
|
cy.visit('/#/markets/market-1');
|
||||||
cy.wait('@MarketsCandles');
|
cy.wait('@MarketsCandles');
|
||||||
cy.getByTestId('dialog-close').click();
|
cy.getByTestId('dialog-close').click();
|
||||||
});
|
});
|
||||||
@ -112,7 +112,6 @@ describe('deposit actions', { tags: '@smoke' }, () => {
|
|||||||
'be.visible'
|
'be.visible'
|
||||||
);
|
);
|
||||||
cy.contains('[data-testid="deposit"]', 'Deposit to trade').click();
|
cy.contains('[data-testid="deposit"]', 'Deposit to trade').click();
|
||||||
connectEthereumWallet('MetaMask');
|
|
||||||
cy.getByTestId('deposit-submit').should('be.visible');
|
cy.getByTestId('deposit-submit').should('be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -153,7 +153,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
// the choose market overlay is no longer showing
|
// the choose market overlay is no longer showing
|
||||||
cy.contains('Select a market to get started').should('not.exist');
|
cy.contains('Select a market to get started').should('not.exist');
|
||||||
cy.contains('Loading...').should('not.exist');
|
cy.contains('Loading...').should('not.exist');
|
||||||
cy.url().should('eq', Cypress.config().baseUrl + '/#/markets/market-1');
|
cy.url().should('eq', Cypress.config().baseUrl + '/#/markets/market-0');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -172,6 +172,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
// @ts-ignore partial deep check failing
|
||||||
const data = marketsDataQuery(override);
|
const data = marketsDataQuery(override);
|
||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
aliasGQLQuery(req, 'MarketsData', data);
|
aliasGQLQuery(req, 'MarketsData', data);
|
||||||
|
@ -222,6 +222,7 @@ describe('markets table', { tags: '@smoke' }, () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
// @ts-ignore partial deep check failing
|
||||||
const market = marketsQuery(override);
|
const market = marketsQuery(override);
|
||||||
aliasGQLQuery(req, 'Market', market);
|
aliasGQLQuery(req, 'Market', market);
|
||||||
aliasGQLQuery(req, 'ProposalOfMarket', {
|
aliasGQLQuery(req, 'ProposalOfMarket', {
|
||||||
|
@ -26,6 +26,7 @@ export function updateOrder(
|
|||||||
override?: PartialDeep<OrderUpdateFieldsFragment>
|
override?: PartialDeep<OrderUpdateFieldsFragment>
|
||||||
): void {
|
): void {
|
||||||
const update: OrdersUpdateSubscription = orderUpdateSubscription({
|
const update: OrdersUpdateSubscription = orderUpdateSubscription({
|
||||||
|
// @ts-ignore partial deep check failing
|
||||||
orders: [override],
|
orders: [override],
|
||||||
});
|
});
|
||||||
if (!sendOrderUpdate) {
|
if (!sendOrderUpdate) {
|
||||||
|
@ -46,7 +46,9 @@ const marketDataOverride = (
|
|||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
data: {
|
data: {
|
||||||
|
// @ts-ignore conflict between incoming and outgoing types
|
||||||
trigger: data.trigger,
|
trigger: data.trigger,
|
||||||
|
// @ts-ignore same as above
|
||||||
marketTradingMode: data.tradingMode,
|
marketTradingMode: data.tradingMode,
|
||||||
marketState: data.state,
|
marketState: data.state,
|
||||||
},
|
},
|
||||||
@ -63,6 +65,7 @@ const marketsDataOverride = (
|
|||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
|
// @ts-ignore conflict between incoming and outgoing types
|
||||||
tradingMode: data.tradingMode,
|
tradingMode: data.tradingMode,
|
||||||
state: data.state,
|
state: data.state,
|
||||||
},
|
},
|
||||||
|
335
apps/trading/client-pages/markets/closed.spec.tsx
Normal file
335
apps/trading/client-pages/markets/closed.spec.tsx
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
import { act, render, screen, within } from '@testing-library/react';
|
||||||
|
import { Closed } from './closed';
|
||||||
|
import { MarketStateMapping } from '@vegaprotocol/types';
|
||||||
|
import { PositionStatus } from '@vegaprotocol/types';
|
||||||
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
|
import { subDays } from 'date-fns';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/oracles';
|
||||||
|
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/oracles';
|
||||||
|
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||||
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
|
import type {
|
||||||
|
PositionsQuery,
|
||||||
|
PositionFieldsFragment,
|
||||||
|
} from '@vegaprotocol/positions';
|
||||||
|
import { PositionsDocument } from '@vegaprotocol/positions';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
import type { MarketsDataQuery, MarketsQuery } from '@vegaprotocol/market-list';
|
||||||
|
import { MarketsDataDocument } from '@vegaprotocol/market-list';
|
||||||
|
import { MarketsDocument } from '@vegaprotocol/market-list';
|
||||||
|
import {
|
||||||
|
createMarketFragment,
|
||||||
|
marketsQuery,
|
||||||
|
marketsDataQuery,
|
||||||
|
createMarketsDataFragment,
|
||||||
|
} from '@vegaprotocol/mock';
|
||||||
|
|
||||||
|
describe('Closed', () => {
|
||||||
|
let originalNow: typeof Date.now;
|
||||||
|
const mockNowTimestamp = 1672531200000;
|
||||||
|
const settlementDateMetaDate = subDays(
|
||||||
|
new Date(mockNowTimestamp),
|
||||||
|
3
|
||||||
|
).toISOString();
|
||||||
|
const settlementDateTag = `settlement-expiry-date:${settlementDateMetaDate}`;
|
||||||
|
const pubKey = 'pubKey';
|
||||||
|
const marketId = 'market-0';
|
||||||
|
const settlementDataProperty = 'spec-binding';
|
||||||
|
const settlementDataId = 'settlement-data-oracle-id';
|
||||||
|
|
||||||
|
const market = createMarketFragment({
|
||||||
|
id: marketId,
|
||||||
|
state: MarketState.STATE_SETTLED,
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
metadata: {
|
||||||
|
tags: [settlementDateTag],
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
id: settlementDataId,
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
settlementDataProperty,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const marketsMock: MockedResponse<MarketsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketsDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: marketsQuery({
|
||||||
|
marketsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: market,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const marketsData = createMarketsDataFragment({
|
||||||
|
__typename: 'MarketData',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: marketId,
|
||||||
|
},
|
||||||
|
bestBidPrice: '1000',
|
||||||
|
bestOfferPrice: '2000',
|
||||||
|
markPrice: '1500',
|
||||||
|
});
|
||||||
|
const marketsDataMock: MockedResponse<MarketsDataQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketsDataDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: marketsDataQuery({
|
||||||
|
marketsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
data: marketsData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create mock oracle data
|
||||||
|
const property = {
|
||||||
|
__typename: 'Property' as const,
|
||||||
|
name: settlementDataProperty,
|
||||||
|
value: '12345',
|
||||||
|
};
|
||||||
|
const oracleDataMock: MockedResponse<OracleSpecDataConnectionQuery> = {
|
||||||
|
request: {
|
||||||
|
query: OracleSpecDataConnectionDocument,
|
||||||
|
variables: {
|
||||||
|
oracleSpecId: settlementDataId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
oracleSpec: {
|
||||||
|
dataConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
externalData: {
|
||||||
|
data: {
|
||||||
|
data: [property],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create mock position
|
||||||
|
const createPosition = (): PositionFieldsFragment => {
|
||||||
|
return {
|
||||||
|
__typename: 'Position' as const,
|
||||||
|
realisedPNL: '1000',
|
||||||
|
unrealisedPNL: '2000',
|
||||||
|
openVolume: '3000',
|
||||||
|
averageEntryPrice: '100',
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||||
|
lossSocializationAmount: '1000',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: marketId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const position = createPosition();
|
||||||
|
const positionsMock: MockedResponse<PositionsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: PositionsDocument,
|
||||||
|
variables: {
|
||||||
|
partyId: pubKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
party: {
|
||||||
|
__typename: 'Party',
|
||||||
|
id: pubKey,
|
||||||
|
positionsConnection: {
|
||||||
|
__typename: 'PositionConnection',
|
||||||
|
edges: [{ __typename: 'PositionEdge', node: position }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
originalNow = Date.now;
|
||||||
|
Date.now = jest.fn().mockReturnValue(mockNowTimestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
Date.now = originalNow;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correctly formatted and filtered rows', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MockedProvider
|
||||||
|
mocks={[marketsMock, marketsDataMock, positionsMock, oracleDataMock]}
|
||||||
|
>
|
||||||
|
<VegaWalletContext.Provider
|
||||||
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
|
>
|
||||||
|
<Closed />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// screen.debug(document, Infinity);
|
||||||
|
|
||||||
|
const headers = screen.getAllByRole('columnheader');
|
||||||
|
const expectedHeaders = [
|
||||||
|
'Market',
|
||||||
|
'Description',
|
||||||
|
'Status',
|
||||||
|
'Settlement date',
|
||||||
|
'Best bid',
|
||||||
|
'Best offer',
|
||||||
|
'Mark price',
|
||||||
|
'Settlement price',
|
||||||
|
'Realised PNL',
|
||||||
|
'Settlement asset',
|
||||||
|
'Market ID',
|
||||||
|
];
|
||||||
|
expect(headers).toHaveLength(expectedHeaders.length);
|
||||||
|
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||||
|
|
||||||
|
const cells = screen.getAllByRole('gridcell');
|
||||||
|
const expectedValues = [
|
||||||
|
market.tradableInstrument.instrument.code,
|
||||||
|
market.tradableInstrument.instrument.name,
|
||||||
|
MarketStateMapping[market.state],
|
||||||
|
'3 days ago',
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
addDecimalsFormatNumber(marketsData.bestBidPrice, market.decimalPlaces),
|
||||||
|
addDecimalsFormatNumber(
|
||||||
|
marketsData!.bestOfferPrice,
|
||||||
|
market.decimalPlaces
|
||||||
|
),
|
||||||
|
addDecimalsFormatNumber(marketsData!.markPrice, market.decimalPlaces),
|
||||||
|
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||||
|
addDecimalsFormatNumber(property.value, market.decimalPlaces),
|
||||||
|
addDecimalsFormatNumber(position.realisedPNL, market.decimalPlaces),
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||||
|
market.id,
|
||||||
|
];
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only renders settled and terminated markets', async () => {
|
||||||
|
const mixedMarkets = [
|
||||||
|
{
|
||||||
|
// inlclude as settled
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: createMarketFragment({
|
||||||
|
id: 'include-0',
|
||||||
|
state: MarketState.STATE_SETTLED,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// omit this market
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: createMarketFragment({
|
||||||
|
id: 'discard-0',
|
||||||
|
state: MarketState.STATE_SUSPENDED,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// include as terminated
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: createMarketFragment({
|
||||||
|
id: 'include-1',
|
||||||
|
state: MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// omit this market
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: createMarketFragment({
|
||||||
|
id: 'discard-1',
|
||||||
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketsDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
marketsConnection: {
|
||||||
|
__typename: 'MarketConnection',
|
||||||
|
edges: mixedMarkets,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MockedProvider
|
||||||
|
mocks={[
|
||||||
|
mixedMarketsMock,
|
||||||
|
marketsDataMock,
|
||||||
|
positionsMock,
|
||||||
|
oracleDataMock,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<VegaWalletContext.Provider
|
||||||
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
|
>
|
||||||
|
<Closed />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// check that the number of rows in datagrid is 2
|
||||||
|
const container = within(
|
||||||
|
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||||
|
);
|
||||||
|
const expectedRows = mixedMarkets.filter((m) => {
|
||||||
|
return [
|
||||||
|
MarketState.STATE_SETTLED,
|
||||||
|
MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
].includes(m.node.state);
|
||||||
|
});
|
||||||
|
|
||||||
|
// check rows length is correct
|
||||||
|
const rows = container.getAllByRole('row');
|
||||||
|
expect(rows).toHaveLength(expectedRows.length);
|
||||||
|
|
||||||
|
// check that only included ids are shown
|
||||||
|
const cells = screen
|
||||||
|
.getAllByRole('gridcell')
|
||||||
|
.filter((cell) => cell.getAttribute('col-id') === 'id')
|
||||||
|
.map((cell) => cell.textContent?.trim());
|
||||||
|
expect(cells).toEqual(expectedRows.map((m) => m.node.id));
|
||||||
|
});
|
||||||
|
});
|
285
apps/trading/client-pages/markets/closed.tsx
Normal file
285
apps/trading/client-pages/markets/closed.tsx
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
import compact from 'lodash/compact';
|
||||||
|
import { isAfter } from 'date-fns';
|
||||||
|
import type {
|
||||||
|
VegaICellRendererParams,
|
||||||
|
VegaValueFormatterParams,
|
||||||
|
} from '@vegaprotocol/datagrid';
|
||||||
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
getMarketExpiryDate,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
import { usePositionsQuery } from '@vegaprotocol/positions';
|
||||||
|
import type { MarketMaybeWithData } from '@vegaprotocol/market-list';
|
||||||
|
import { closedMarketsWithDataProvider } from '@vegaprotocol/market-list';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
|
import type { ColDef } from 'ag-grid-community';
|
||||||
|
import { SettlementDateCell } from './settlement-date-cell';
|
||||||
|
import { SettlementPriceCell } from './settlement-price-cell';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
type SettlementAsset =
|
||||||
|
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||||
|
|
||||||
|
interface Row {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
decimalPlaces: number;
|
||||||
|
state: MarketState;
|
||||||
|
metadata: string[];
|
||||||
|
closeTimestamp: string | null;
|
||||||
|
bestBidPrice: string | undefined;
|
||||||
|
bestOfferPrice: string | undefined;
|
||||||
|
markPrice: string | undefined;
|
||||||
|
settlementDataOracleId: string;
|
||||||
|
settlementDataSpecBinding: string;
|
||||||
|
tradingTerminationOracleId: string;
|
||||||
|
settlementAsset: SettlementAsset;
|
||||||
|
realisedPNL: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Closed = () => {
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
const {
|
||||||
|
data: marketData,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
reload,
|
||||||
|
} = useDataProvider({
|
||||||
|
dataProvider: closedMarketsWithDataProvider,
|
||||||
|
variables: undefined,
|
||||||
|
});
|
||||||
|
const { data: positionData } = usePositionsQuery({
|
||||||
|
variables: {
|
||||||
|
partyId: pubKey || '',
|
||||||
|
},
|
||||||
|
skip: !pubKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
// find a position for each market and add the realised pnl to
|
||||||
|
// a normalized object
|
||||||
|
const rowData = compact(marketData).map((market) => {
|
||||||
|
const position = positionData?.party?.positionsConnection?.edges?.find(
|
||||||
|
(edge) => {
|
||||||
|
return edge.node.market.id === market.id;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const row: Row = {
|
||||||
|
id: market.id,
|
||||||
|
code: market.tradableInstrument.instrument.code,
|
||||||
|
name: market.tradableInstrument.instrument.name,
|
||||||
|
decimalPlaces: market.decimalPlaces,
|
||||||
|
state: market.state,
|
||||||
|
metadata: market.tradableInstrument.instrument.metadata.tags ?? [],
|
||||||
|
closeTimestamp: market.marketTimestamps.close,
|
||||||
|
bestBidPrice: market.data?.bestBidPrice,
|
||||||
|
bestOfferPrice: market.data?.bestOfferPrice,
|
||||||
|
markPrice: market.data?.markPrice,
|
||||||
|
settlementDataOracleId:
|
||||||
|
market.tradableInstrument.instrument.product
|
||||||
|
.dataSourceSpecForSettlementData.id,
|
||||||
|
settlementDataSpecBinding:
|
||||||
|
market.tradableInstrument.instrument.product.dataSourceSpecBinding
|
||||||
|
.settlementDataProperty,
|
||||||
|
tradingTerminationOracleId:
|
||||||
|
market.tradableInstrument.instrument.product
|
||||||
|
.dataSourceSpecForTradingTermination.id,
|
||||||
|
settlementAsset:
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset,
|
||||||
|
realisedPNL: position?.node.realisedPNL,
|
||||||
|
};
|
||||||
|
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="h-full relative">
|
||||||
|
<ClosedMarketsDataGrid rowData={rowData} />
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<AsyncRenderer
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
data={marketData}
|
||||||
|
noDataMessage={t('No markets')}
|
||||||
|
reload={reload}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
|
||||||
|
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||||
|
const colDefs = useMemo(() => {
|
||||||
|
const cols: ColDef[] = [
|
||||||
|
{
|
||||||
|
headerName: t('Market'),
|
||||||
|
field: 'code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Description'),
|
||||||
|
field: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Status'),
|
||||||
|
field: 'state',
|
||||||
|
valueFormatter: ({ value }: VegaValueFormatterParams<Row, 'state'>) => {
|
||||||
|
if (!value) return '-';
|
||||||
|
return MarketStateMapping[value];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Settlement date'),
|
||||||
|
colId: 'settlementDate', // colId needed if no field property provided otherwise column order is ruined in tests
|
||||||
|
valueGetter: ({ data }: { data: Row }) => {
|
||||||
|
return getMarketExpiryDate(data.metadata);
|
||||||
|
},
|
||||||
|
cellRenderer: ({ value, data }: { value: Date | null; data: Row }) => {
|
||||||
|
return (
|
||||||
|
<SettlementDateCell
|
||||||
|
oracleSpecId={data.tradingTerminationOracleId}
|
||||||
|
metaDate={value}
|
||||||
|
marketState={data.state}
|
||||||
|
closeTimestamp={data.closeTimestamp}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cellClassRules: {
|
||||||
|
'text-danger': ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
value: Date | null;
|
||||||
|
data: Row;
|
||||||
|
}) => {
|
||||||
|
const date = data.closeTimestamp
|
||||||
|
? new Date(data.closeTimestamp)
|
||||||
|
: value;
|
||||||
|
|
||||||
|
if (!date) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
// expiry has passed and market is not yet settled
|
||||||
|
isAfter(new Date(), date) &&
|
||||||
|
data.state !== MarketState.STATE_SETTLED
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Best bid'),
|
||||||
|
field: 'bestBidPrice',
|
||||||
|
type: 'numericColumn',
|
||||||
|
cellClass: 'font-mono ag-right-aligned-cell',
|
||||||
|
valueFormatter: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Row, 'bestBidPrice'>) => {
|
||||||
|
if (!value || !data) return '-';
|
||||||
|
return addDecimalsFormatNumber(value, data.decimalPlaces);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Best offer'),
|
||||||
|
field: 'bestOfferPrice',
|
||||||
|
cellClass: 'font-mono ag-right-aligned-cell',
|
||||||
|
type: 'numericColumn',
|
||||||
|
valueFormatter: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Row, 'bestOfferPrice'>) => {
|
||||||
|
if (!value || !data) return '-';
|
||||||
|
return addDecimalsFormatNumber(value, data.decimalPlaces);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Mark price'),
|
||||||
|
field: 'markPrice',
|
||||||
|
cellClass: 'font-mono ag-right-aligned-cell',
|
||||||
|
type: 'numericColumn',
|
||||||
|
valueFormatter: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Row, 'markPrice'>) => {
|
||||||
|
if (!value || !data) return '-';
|
||||||
|
return addDecimalsFormatNumber(value, data.decimalPlaces);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Settlement price'),
|
||||||
|
type: 'numericColumn',
|
||||||
|
field: 'settlementDataOracleId',
|
||||||
|
// 'tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id',
|
||||||
|
cellRenderer: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaICellRendererParams<Row, 'settlementDataOracleId'>) => (
|
||||||
|
<SettlementPriceCell
|
||||||
|
oracleSpecId={value}
|
||||||
|
decimalPlaces={data?.decimalPlaces ?? 0}
|
||||||
|
settlementDataSpecBinding={data?.settlementDataSpecBinding}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Realised PNL'),
|
||||||
|
field: 'realisedPNL',
|
||||||
|
cellClass: 'font-mono ag-right-aligned-cell',
|
||||||
|
type: 'numericColumn',
|
||||||
|
valueFormatter: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Row, 'realisedPNL'>) => {
|
||||||
|
if (!value || !data) return '-';
|
||||||
|
return addDecimalsFormatNumber(value, data.decimalPlaces);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Settlement asset'),
|
||||||
|
field: 'settlementAsset',
|
||||||
|
cellRenderer: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<Row, 'settlementAsset'>) => (
|
||||||
|
<button
|
||||||
|
className="underline"
|
||||||
|
onClick={() => {
|
||||||
|
if (!value) return;
|
||||||
|
openAssetDialog(value.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value ? value.symbol : '-'}
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Market ID'),
|
||||||
|
field: 'id',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return cols;
|
||||||
|
}, [openAssetDialog]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AgGrid
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
rowData={rowData}
|
||||||
|
columnDefs={colDefs}
|
||||||
|
getRowId={({ data }) => data.id}
|
||||||
|
defaultColDef={{
|
||||||
|
flex: 1,
|
||||||
|
resizable: true,
|
||||||
|
}}
|
||||||
|
overlayNoRowsTemplate="No data"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { Markets } from './markets';
|
import { Markets } from './markets';
|
||||||
import { Proposed } from './proposed';
|
import { Proposed } from './proposed';
|
||||||
import { usePageTitleStore } from '../../stores';
|
import { usePageTitleStore } from '../../stores';
|
||||||
|
import { Closed } from './closed';
|
||||||
|
|
||||||
export const MarketsPage = () => {
|
export const MarketsPage = () => {
|
||||||
const { updateTitle } = usePageTitleStore((store) => ({
|
const { updateTitle } = usePageTitleStore((store) => ({
|
||||||
@ -21,6 +22,9 @@ export const MarketsPage = () => {
|
|||||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
||||||
<Proposed />
|
<Proposed />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||||
|
<Closed />
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
105
apps/trading/client-pages/markets/settlement-date-cell.spec.tsx
Normal file
105
apps/trading/client-pages/markets/settlement-date-cell.spec.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
|
import type { SettlementDataCellProps } from './settlement-date-cell';
|
||||||
|
import { SettlementDateCell } from './settlement-date-cell';
|
||||||
|
|
||||||
|
describe('SettlementDateCell', () => {
|
||||||
|
let originalNow: typeof Date.now;
|
||||||
|
const mockNowTimestamp = 1672531200000;
|
||||||
|
|
||||||
|
const createProps = (): SettlementDataCellProps => {
|
||||||
|
return {
|
||||||
|
oracleSpecId: 'oracle-spec-id',
|
||||||
|
metaDate: null,
|
||||||
|
closeTimestamp: null,
|
||||||
|
marketState: MarketState.STATE_SETTLED,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
originalNow = Date.now;
|
||||||
|
Date.now = jest.fn().mockReturnValue(mockNowTimestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
Date.now = originalNow;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders unknown if date cannot be determined', () => {
|
||||||
|
const props = createProps();
|
||||||
|
render(<SettlementDateCell {...props} />);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toHaveTextContent('Unknown');
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders using close timestamp if provided', () => {
|
||||||
|
const daysAgo = 3;
|
||||||
|
const props = createProps();
|
||||||
|
props.closeTimestamp = subDays(
|
||||||
|
new Date(mockNowTimestamp),
|
||||||
|
daysAgo
|
||||||
|
).toISOString();
|
||||||
|
|
||||||
|
render(<SettlementDateCell {...props} />);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toHaveTextContent(`${daysAgo} days ago`);
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders using meta tag date if no close timestamp provided', () => {
|
||||||
|
const daysAgo = 4;
|
||||||
|
const props = createProps();
|
||||||
|
props.metaDate = subDays(new Date(mockNowTimestamp), daysAgo);
|
||||||
|
|
||||||
|
render(<SettlementDateCell {...props} />);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toHaveTextContent(`${daysAgo} days ago`);
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders past expected settlement date', () => {
|
||||||
|
const daysAgo = 3;
|
||||||
|
const props = createProps();
|
||||||
|
props.metaDate = subDays(new Date(mockNowTimestamp), daysAgo);
|
||||||
|
props.marketState = MarketState.STATE_TRADING_TERMINATED;
|
||||||
|
|
||||||
|
render(<SettlementDateCell {...props} />);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toHaveTextContent(`Expected ${daysAgo} days ago`);
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders future expected settlement date', () => {
|
||||||
|
const daysAgo = 3;
|
||||||
|
const props = createProps();
|
||||||
|
props.metaDate = addDays(new Date(mockNowTimestamp), daysAgo);
|
||||||
|
props.marketState = MarketState.STATE_TRADING_TERMINATED;
|
||||||
|
|
||||||
|
render(<SettlementDateCell {...props} />);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
expect(link).toHaveTextContent(`Expected in ${daysAgo} days`);
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
55
apps/trading/client-pages/markets/settlement-date-cell.tsx
Normal file
55
apps/trading/client-pages/markets/settlement-date-cell.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
|
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||||
|
import { formatDistanceToNowStrict, isAfter } from 'date-fns';
|
||||||
|
|
||||||
|
export interface SettlementDataCellProps {
|
||||||
|
oracleSpecId: string;
|
||||||
|
metaDate: Date | null;
|
||||||
|
closeTimestamp: string | null;
|
||||||
|
marketState: MarketState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SettlementDateCell = ({
|
||||||
|
oracleSpecId,
|
||||||
|
metaDate,
|
||||||
|
closeTimestamp,
|
||||||
|
marketState,
|
||||||
|
}: SettlementDataCellProps) => {
|
||||||
|
const linkCreator = useLinks(DApp.Explorer);
|
||||||
|
const date = closeTimestamp ? new Date(closeTimestamp) : metaDate;
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
if (!date) {
|
||||||
|
text = t('Unknown');
|
||||||
|
} else {
|
||||||
|
// pass Date.now() to date constructor for easier mocking
|
||||||
|
const expiryHasPassed = isAfter(new Date(Date.now()), date);
|
||||||
|
const distance = formatDistanceToNowStrict(date); // X days/mins ago
|
||||||
|
|
||||||
|
if (expiryHasPassed) {
|
||||||
|
if (marketState !== MarketState.STATE_SETTLED) {
|
||||||
|
text = t('Expected %s ago', distance);
|
||||||
|
} else {
|
||||||
|
text = t('%s ago', distance);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = t('Expected in %s', distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={linkCreator(EXPLORER_ORACLE.replace(':id', oracleSpecId))}
|
||||||
|
className="underline"
|
||||||
|
target="_blank"
|
||||||
|
title={
|
||||||
|
date ? getDateTimeFormat().format(date) : t('Unknown settlement date')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
113
apps/trading/client-pages/markets/settlement-price-cell.spec.tsx
Normal file
113
apps/trading/client-pages/markets/settlement-price-cell.spec.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import type { Property } from '@vegaprotocol/types';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { OracleSpecDataConnectionQuery } from '@vegaprotocol/oracles';
|
||||||
|
import { OracleSpecDataConnectionDocument } from '@vegaprotocol/oracles';
|
||||||
|
import type { SettlementPriceCellProps } from './settlement-price-cell';
|
||||||
|
import { SettlementPriceCell } from './settlement-price-cell';
|
||||||
|
|
||||||
|
describe('SettlementPriceCell', () => {
|
||||||
|
const createMock = (
|
||||||
|
id: string,
|
||||||
|
property: Property
|
||||||
|
): MockedResponse<OracleSpecDataConnectionQuery> => {
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
query: OracleSpecDataConnectionDocument,
|
||||||
|
variables: {
|
||||||
|
oracleSpecId: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
oracleSpec: {
|
||||||
|
dataConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
externalData: {
|
||||||
|
data: {
|
||||||
|
data: [property],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const createProps = (): SettlementPriceCellProps => {
|
||||||
|
return {
|
||||||
|
oracleSpecId: 'oracle-spec-id',
|
||||||
|
decimalPlaces: 2,
|
||||||
|
settlementDataSpecBinding: 'settlement-data-spec-binding',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
it('renders fetches and renders the settlment data value', async () => {
|
||||||
|
const props = createProps();
|
||||||
|
const property = {
|
||||||
|
__typename: 'Property' as const,
|
||||||
|
name: props.settlementDataSpecBinding as string,
|
||||||
|
value: '1234',
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const mock = createMock(props.oracleSpecId!, property);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={[mock]}>
|
||||||
|
<SettlementPriceCell {...props} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('-')).toBeInTheDocument();
|
||||||
|
const link = await screen.findByRole('link');
|
||||||
|
expect(link).toHaveTextContent('12.34');
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders "-" if no spec value is found', async () => {
|
||||||
|
const props = createProps();
|
||||||
|
const property = {
|
||||||
|
__typename: 'Property' as const,
|
||||||
|
name: 'no matching spec binding',
|
||||||
|
value: '1234',
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const mock = createMock(props.oracleSpecId!, property);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={[mock]}>
|
||||||
|
<SettlementPriceCell {...props} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('-')).toBeInTheDocument();
|
||||||
|
const link = await screen.findByRole('link');
|
||||||
|
expect(link).toHaveTextContent('-');
|
||||||
|
expect(link).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
expect.stringContaining(`/oracles/${props.oracleSpecId}`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders "-" with no link if oracle spec id is not provided', () => {
|
||||||
|
const props = createProps();
|
||||||
|
props.oracleSpecId = undefined;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={[]}>
|
||||||
|
<SettlementPriceCell {...props} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('-')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
39
apps/trading/client-pages/markets/settlement-price-cell.tsx
Normal file
39
apps/trading/client-pages/markets/settlement-price-cell.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { DApp, EXPLORER_ORACLE, useLinks } from '@vegaprotocol/environment';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { useOracleSpecBindingData } from '@vegaprotocol/oracles';
|
||||||
|
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
|
export interface SettlementPriceCellProps {
|
||||||
|
oracleSpecId: string | undefined;
|
||||||
|
decimalPlaces: number;
|
||||||
|
settlementDataSpecBinding: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SettlementPriceCell = ({
|
||||||
|
oracleSpecId,
|
||||||
|
decimalPlaces,
|
||||||
|
settlementDataSpecBinding,
|
||||||
|
}: SettlementPriceCellProps) => {
|
||||||
|
const linkCreator = useLinks(DApp.Explorer);
|
||||||
|
const { property, loading } = useOracleSpecBindingData(
|
||||||
|
oracleSpecId,
|
||||||
|
settlementDataSpecBinding
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!oracleSpecId || loading) {
|
||||||
|
return <span>-</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={linkCreator(EXPLORER_ORACLE.replace(':id', oracleSpecId))}
|
||||||
|
className="underline font-mono"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{property
|
||||||
|
? addDecimalsFormatNumber(property.value, decimalPlaces)
|
||||||
|
: t('-')}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
@ -39,7 +39,17 @@ const MARKET_A: PartialMarket = {
|
|||||||
symbol: 'ABC',
|
symbol: 'ABC',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -115,7 +125,17 @@ const MARKET_B: PartialMarket = {
|
|||||||
symbol: 'XYZ',
|
symbol: 'XYZ',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
|
@ -41,7 +41,17 @@ const MARKET_A: PartialMarket = {
|
|||||||
symbol: 'ABC',
|
symbol: 'ABC',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -117,7 +127,17 @@ const MARKET_B: PartialMarket = {
|
|||||||
symbol: 'XYZ',
|
symbol: 'XYZ',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
|
@ -17,6 +17,7 @@ export * from '../market-list/src/lib/market-data.mock';
|
|||||||
export * from '../market-list/src/lib/markets-candles.mock';
|
export * from '../market-list/src/lib/markets-candles.mock';
|
||||||
export * from '../market-list/src/lib/markets-data.mock';
|
export * from '../market-list/src/lib/markets-data.mock';
|
||||||
export * from '../market-list/src/lib/markets.mock';
|
export * from '../market-list/src/lib/markets.mock';
|
||||||
|
export * from '../oracles/src/lib/oracle-spec-data-connection.mock';
|
||||||
export * from '../orders/src/lib/components/order-data-provider/orders.mock';
|
export * from '../orders/src/lib/components/order-data-provider/orders.mock';
|
||||||
export * from '../positions/src/lib/positions.mock';
|
export * from '../positions/src/lib/positions.mock';
|
||||||
export * from '../react-helpers/src/hooks/network-params.mock';
|
export * from '../react-helpers/src/hooks/network-params.mock';
|
||||||
|
@ -35,7 +35,17 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
|
|||||||
__typename: 'Asset',
|
__typename: 'Asset',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
quoteName: 'BTC',
|
quoteName: 'BTC',
|
||||||
__typename: 'Future',
|
__typename: 'Future',
|
||||||
|
@ -100,6 +100,7 @@ export const TOKEN_VALIDATOR = '/validators/:id';
|
|||||||
|
|
||||||
// Explorer pages
|
// Explorer pages
|
||||||
export const EXPLORER_TX = '/txs/:hash';
|
export const EXPLORER_TX = '/txs/:hash';
|
||||||
|
export const EXPLORER_ORACLE = '/oracles/:id';
|
||||||
|
|
||||||
// Etherscan pages
|
// Etherscan pages
|
||||||
export const ETHERSCAN_ADDRESS = '/address/:hash';
|
export const ETHERSCAN_ADDRESS = '/address/:hash';
|
||||||
|
@ -78,7 +78,17 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
|
|||||||
},
|
},
|
||||||
quoteName: '',
|
quoteName: '',
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
11
libs/market-list/src/lib/__generated__/markets.ts
generated
11
libs/market-list/src/lib/__generated__/markets.ts
generated
@ -3,12 +3,12 @@ import * as Types from '@vegaprotocol/types';
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
const defaultOptions = {} as const;
|
const defaultOptions = {} as const;
|
||||||
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
|
export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
|
||||||
|
|
||||||
export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
|
export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
|
||||||
|
|
||||||
export const MarketFieldsFragmentDoc = gql`
|
export const MarketFieldsFragmentDoc = gql`
|
||||||
fragment MarketFields on Market {
|
fragment MarketFields on Market {
|
||||||
@ -44,6 +44,13 @@ export const MarketFieldsFragmentDoc = gql`
|
|||||||
dataSourceSpecForTradingTermination {
|
dataSourceSpecForTradingTermination {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
dataSourceSpecForSettlementData {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
dataSourceSpecBinding {
|
||||||
|
settlementDataProperty
|
||||||
|
tradingTerminationProperty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,10 @@ export const marketsDataQuery = (
|
|||||||
return merge(defaultResult, override);
|
return merge(defaultResult, override);
|
||||||
};
|
};
|
||||||
|
|
||||||
const marketsDataFieldsFragments: MarketsDataFieldsFragment[] = [
|
export const createMarketsDataFragment = (
|
||||||
{
|
override?: PartialDeep<MarketsDataFieldsFragment>
|
||||||
|
): MarketsDataFieldsFragment => {
|
||||||
|
const defaultResult = {
|
||||||
market: {
|
market: {
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
@ -42,56 +44,28 @@ const marketsDataFieldsFragments: MarketsDataFieldsFragment[] = [
|
|||||||
markPrice: '4612690058',
|
markPrice: '4612690058',
|
||||||
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
||||||
__typename: 'MarketData',
|
__typename: 'MarketData',
|
||||||
},
|
};
|
||||||
{
|
return merge(defaultResult, override);
|
||||||
|
};
|
||||||
|
|
||||||
|
const marketsDataFieldsFragments: MarketsDataFieldsFragment[] = [
|
||||||
|
createMarketsDataFragment(),
|
||||||
|
createMarketsDataFragment({
|
||||||
market: {
|
market: {
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
__typename: 'Market',
|
|
||||||
},
|
},
|
||||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
|
||||||
staticMidPrice: '0',
|
|
||||||
indicativePrice: '0',
|
|
||||||
bestStaticBidPrice: '0',
|
|
||||||
bestStaticOfferPrice: '0',
|
|
||||||
indicativeVolume: '0',
|
|
||||||
bestBidPrice: '0',
|
|
||||||
bestOfferPrice: '0',
|
|
||||||
markPrice: '8441',
|
markPrice: '8441',
|
||||||
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
}),
|
||||||
__typename: 'MarketData',
|
createMarketsDataFragment({
|
||||||
},
|
|
||||||
{
|
|
||||||
market: {
|
market: {
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
__typename: 'Market',
|
|
||||||
},
|
},
|
||||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
|
||||||
staticMidPrice: '0',
|
|
||||||
indicativePrice: '0',
|
|
||||||
bestStaticBidPrice: '0',
|
|
||||||
bestStaticOfferPrice: '0',
|
|
||||||
indicativeVolume: '0',
|
|
||||||
bestBidPrice: '0',
|
|
||||||
bestOfferPrice: '0',
|
|
||||||
markPrice: '4612690058',
|
|
||||||
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET,
|
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET,
|
||||||
__typename: 'MarketData',
|
}),
|
||||||
},
|
createMarketsDataFragment({
|
||||||
{
|
|
||||||
market: {
|
market: {
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
__typename: 'Market',
|
|
||||||
},
|
},
|
||||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
|
||||||
staticMidPrice: '0',
|
|
||||||
indicativePrice: '0',
|
|
||||||
bestStaticBidPrice: '0',
|
|
||||||
bestStaticOfferPrice: '0',
|
|
||||||
indicativeVolume: '0',
|
|
||||||
bestBidPrice: '0',
|
|
||||||
bestOfferPrice: '0',
|
|
||||||
markPrice: '4612690058',
|
|
||||||
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET,
|
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET,
|
||||||
__typename: 'MarketData',
|
}),
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
@ -13,7 +13,7 @@ import type { MarketData } from './market-data-provider';
|
|||||||
import type { MarketCandles } from './markets-candles-provider';
|
import type { MarketCandles } from './markets-candles-provider';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { filterAndSortMarkets } from './utils';
|
import { filterAndSortClosedMarkets, filterAndSortMarkets } from './utils';
|
||||||
import { MarketsDocument } from './__generated__/markets';
|
import { MarketsDocument } from './__generated__/markets';
|
||||||
|
|
||||||
import type { Candle } from './market-candles-provider';
|
import type { Candle } from './market-candles-provider';
|
||||||
@ -72,6 +72,11 @@ export const activeMarketsProvider = makeDerivedDataProvider<Market[], never>(
|
|||||||
([markets]) => filterAndSortMarkets(markets)
|
([markets]) => filterAndSortMarkets(markets)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const closedMarketsProvider = makeDerivedDataProvider<Market[], never>(
|
||||||
|
[marketsProvider],
|
||||||
|
([markets]) => filterAndSortClosedMarkets(markets)
|
||||||
|
);
|
||||||
|
|
||||||
export type MarketMaybeWithCandles = Market & { candles?: Candle[] };
|
export type MarketMaybeWithCandles = Market & { candles?: Candle[] };
|
||||||
|
|
||||||
const addCandles = <T extends Market>(
|
const addCandles = <T extends Market>(
|
||||||
@ -111,6 +116,13 @@ export const marketsWithDataProvider = makeDerivedDataProvider<
|
|||||||
addData(parts[0] as Market[], parts[1] as MarketData[])
|
addData(parts[0] as Market[], parts[1] as MarketData[])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const closedMarketsWithDataProvider = makeDerivedDataProvider<
|
||||||
|
MarketMaybeWithData[],
|
||||||
|
never
|
||||||
|
>([closedMarketsProvider, marketsDataProvider], (parts) =>
|
||||||
|
addData(parts[0] as Market[], parts[1] as MarketData[])
|
||||||
|
);
|
||||||
|
|
||||||
export type MarketMaybeWithDataAndCandles = MarketMaybeWithData &
|
export type MarketMaybeWithDataAndCandles = MarketMaybeWithData &
|
||||||
MarketMaybeWithCandles;
|
MarketMaybeWithCandles;
|
||||||
|
|
||||||
|
@ -31,6 +31,13 @@ fragment MarketFields on Market {
|
|||||||
dataSourceSpecForTradingTermination {
|
dataSourceSpecForTradingTermination {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
dataSourceSpecForSettlementData {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
dataSourceSpecBinding {
|
||||||
|
settlementDataProperty
|
||||||
|
tradingTerminationProperty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,10 @@ export const marketsQuery = (
|
|||||||
return merge(defaultResult, override);
|
return merge(defaultResult, override);
|
||||||
};
|
};
|
||||||
|
|
||||||
const marketFieldsFragments: MarketFieldsFragment[] = [
|
export const createMarketFragment = (
|
||||||
{
|
override?: PartialDeep<MarketFieldsFragment>
|
||||||
|
): MarketFieldsFragment => {
|
||||||
|
const defaultFragment = {
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
decimalPlaces: 5,
|
decimalPlaces: 5,
|
||||||
positionDecimalPlaces: 0,
|
positionDecimalPlaces: 0,
|
||||||
@ -61,8 +63,18 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
__typename: 'Asset',
|
__typename: 'Asset',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
id: 'oracleId',
|
id: 'oracleId',
|
||||||
},
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
|
},
|
||||||
quoteName: 'DAI',
|
quoteName: 'DAI',
|
||||||
__typename: 'Future',
|
__typename: 'Future',
|
||||||
},
|
},
|
||||||
@ -71,36 +83,20 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
__typename: 'TradableInstrument',
|
__typename: 'TradableInstrument',
|
||||||
},
|
},
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
},
|
};
|
||||||
{
|
|
||||||
|
return merge(defaultFragment, override);
|
||||||
|
};
|
||||||
|
|
||||||
|
const marketFieldsFragments: MarketFieldsFragment[] = [
|
||||||
|
createMarketFragment({ id: 'market-0' }),
|
||||||
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
decimalPlaces: 2,
|
decimalPlaces: 2,
|
||||||
positionDecimalPlaces: 0,
|
|
||||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
|
||||||
state: Schema.MarketState.STATE_ACTIVE,
|
|
||||||
marketTimestamps: {
|
|
||||||
__typename: 'MarketTimestamps',
|
|
||||||
close: '',
|
|
||||||
open: '',
|
|
||||||
},
|
|
||||||
fees: {
|
|
||||||
__typename: 'Fees',
|
|
||||||
factors: {
|
|
||||||
__typename: 'FeeFactors',
|
|
||||||
makerFee: '',
|
|
||||||
infrastructureFee: '',
|
|
||||||
liquidityFee: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
id: 'SOLUSD',
|
|
||||||
name: 'SUSPENDED MARKET',
|
name: 'SUSPENDED MARKET',
|
||||||
code: 'SOLUSD',
|
code: 'SOLUSD',
|
||||||
metadata: {
|
|
||||||
__typename: 'InstrumentMetadata',
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
product: {
|
product: {
|
||||||
settlementAsset: {
|
settlementAsset: {
|
||||||
id: 'asset-1',
|
id: 'asset-1',
|
||||||
@ -109,33 +105,19 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
decimals: 5,
|
decimals: 5,
|
||||||
__typename: 'Asset',
|
__typename: 'Asset',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
|
||||||
id: 'oracleId',
|
|
||||||
},
|
},
|
||||||
quoteName: 'USD',
|
|
||||||
__typename: 'Future',
|
|
||||||
},
|
},
|
||||||
__typename: 'Instrument',
|
|
||||||
},
|
},
|
||||||
__typename: 'TradableInstrument',
|
}),
|
||||||
},
|
createMarketFragment({
|
||||||
__typename: 'Market',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
decimalPlaces: 5,
|
|
||||||
positionDecimalPlaces: 0,
|
|
||||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
tradingMode: Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||||
state: Schema.MarketState.STATE_SUSPENDED,
|
state: Schema.MarketState.STATE_SUSPENDED,
|
||||||
marketTimestamps: {
|
marketTimestamps: {
|
||||||
__typename: 'MarketTimestamps',
|
|
||||||
close: '2022-08-26T11:36:32.252490405Z',
|
close: '2022-08-26T11:36:32.252490405Z',
|
||||||
open: null,
|
|
||||||
},
|
},
|
||||||
fees: {
|
fees: {
|
||||||
__typename: 'Fees',
|
|
||||||
factors: {
|
factors: {
|
||||||
__typename: 'FeeFactors',
|
|
||||||
makerFee: '0.0002',
|
makerFee: '0.0002',
|
||||||
infrastructureFee: '0.0005',
|
infrastructureFee: '0.0005',
|
||||||
liquidityFee: '0.001',
|
liquidityFee: '0.001',
|
||||||
@ -143,13 +125,8 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
},
|
},
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
id: '',
|
|
||||||
code: 'AAPL.MF21',
|
code: 'AAPL.MF21',
|
||||||
name: 'Apple Monthly (30 Jun 2022)',
|
name: 'Apple Monthly (30 Jun 2022)',
|
||||||
metadata: {
|
|
||||||
__typename: 'InstrumentMetadata',
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
product: {
|
product: {
|
||||||
settlementAsset: {
|
settlementAsset: {
|
||||||
id: 'asset-2',
|
id: 'asset-2',
|
||||||
@ -158,33 +135,18 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
decimals: 5,
|
decimals: 5,
|
||||||
__typename: 'Asset',
|
__typename: 'Asset',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
|
||||||
id: 'oracleId',
|
|
||||||
},
|
|
||||||
quoteName: 'USDC',
|
quoteName: 'USDC',
|
||||||
__typename: 'Future',
|
|
||||||
},
|
},
|
||||||
__typename: 'Instrument',
|
|
||||||
},
|
},
|
||||||
__typename: 'TradableInstrument',
|
|
||||||
},
|
},
|
||||||
__typename: 'Market',
|
}),
|
||||||
},
|
createMarketFragment({
|
||||||
{
|
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
decimalPlaces: 5,
|
|
||||||
positionDecimalPlaces: 0,
|
|
||||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
|
||||||
state: Schema.MarketState.STATE_ACTIVE,
|
|
||||||
marketTimestamps: {
|
marketTimestamps: {
|
||||||
__typename: 'MarketTimestamps',
|
|
||||||
close: '2022-08-26T11:36:32.252490405Z',
|
close: '2022-08-26T11:36:32.252490405Z',
|
||||||
open: null,
|
|
||||||
},
|
},
|
||||||
fees: {
|
fees: {
|
||||||
__typename: 'Fees',
|
|
||||||
factors: {
|
factors: {
|
||||||
__typename: 'FeeFactors',
|
|
||||||
makerFee: '0.0002',
|
makerFee: '0.0002',
|
||||||
infrastructureFee: '0.0005',
|
infrastructureFee: '0.0005',
|
||||||
liquidityFee: '0.001',
|
liquidityFee: '0.001',
|
||||||
@ -192,13 +154,8 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
},
|
},
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
id: '',
|
|
||||||
code: 'ETHBTC.QM21',
|
code: 'ETHBTC.QM21',
|
||||||
name: 'ETHBTC Quarterly (30 Jun 2022)',
|
name: 'ETHBTC Quarterly (30 Jun 2022)',
|
||||||
metadata: {
|
|
||||||
__typename: 'InstrumentMetadata',
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
product: {
|
product: {
|
||||||
settlementAsset: {
|
settlementAsset: {
|
||||||
id: 'asset-3',
|
id: 'asset-3',
|
||||||
@ -207,16 +164,9 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
|||||||
decimals: 5,
|
decimals: 5,
|
||||||
__typename: 'Asset',
|
__typename: 'Asset',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
|
||||||
id: 'oracleId',
|
|
||||||
},
|
|
||||||
quoteName: 'BTC',
|
quoteName: 'BTC',
|
||||||
__typename: 'Future',
|
|
||||||
},
|
},
|
||||||
__typename: 'Instrument',
|
|
||||||
},
|
},
|
||||||
__typename: 'TradableInstrument',
|
|
||||||
},
|
|
||||||
__typename: 'Market',
|
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
@ -43,6 +43,15 @@ export const filterAndSortMarkets = (markets: Market[]) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const filterAndSortClosedMarkets = (markets: Market[]) => {
|
||||||
|
return markets.filter((m) => {
|
||||||
|
return [
|
||||||
|
MarketState.STATE_SETTLED,
|
||||||
|
MarketState.STATE_TRADING_TERMINATED,
|
||||||
|
].includes(m.state);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const calcCandleLow = (candles: Candle[]): string | undefined => {
|
export const calcCandleLow = (candles: Candle[]): string | undefined => {
|
||||||
return candles
|
return candles
|
||||||
?.reduce((acc: BigNumber, c) => {
|
?.reduce((acc: BigNumber, c) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||||
"ignorePatterns": ["!**/*"],
|
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
export * from './lib/oracle-schema';
|
export * from './lib/oracle-schema';
|
||||||
export * from './lib/use-oracle-proofs';
|
export * from './lib/use-oracle-proofs';
|
||||||
|
export * from './lib/use-oracle-spec-binding-data';
|
||||||
|
export * from './lib/__generated__/OracleSpecDataConnection';
|
||||||
|
18
libs/oracles/src/lib/OracleSpecDataConnection.graphql
Normal file
18
libs/oracles/src/lib/OracleSpecDataConnection.graphql
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
query OracleSpecDataConnection($oracleSpecId: ID!) {
|
||||||
|
oracleSpec(oracleSpecId: $oracleSpecId) {
|
||||||
|
dataConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
externalData {
|
||||||
|
data {
|
||||||
|
data {
|
||||||
|
name
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
libs/oracles/src/lib/__generated__/OracleSpecDataConnection.ts
generated
Normal file
61
libs/oracles/src/lib/__generated__/OracleSpecDataConnection.ts
generated
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type OracleSpecDataConnectionQueryVariables = Types.Exact<{
|
||||||
|
oracleSpecId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type OracleSpecDataConnectionQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
|
||||||
|
|
||||||
|
|
||||||
|
export const OracleSpecDataConnectionDocument = gql`
|
||||||
|
query OracleSpecDataConnection($oracleSpecId: ID!) {
|
||||||
|
oracleSpec(oracleSpecId: $oracleSpecId) {
|
||||||
|
dataConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
externalData {
|
||||||
|
data {
|
||||||
|
data {
|
||||||
|
name
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useOracleSpecDataConnectionQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useOracleSpecDataConnectionQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useOracleSpecDataConnectionQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useOracleSpecDataConnectionQuery({
|
||||||
|
* variables: {
|
||||||
|
* oracleSpecId: // value for 'oracleSpecId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useOracleSpecDataConnectionQuery(baseOptions: Apollo.QueryHookOptions<OracleSpecDataConnectionQuery, OracleSpecDataConnectionQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<OracleSpecDataConnectionQuery, OracleSpecDataConnectionQueryVariables>(OracleSpecDataConnectionDocument, options);
|
||||||
|
}
|
||||||
|
export function useOracleSpecDataConnectionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<OracleSpecDataConnectionQuery, OracleSpecDataConnectionQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<OracleSpecDataConnectionQuery, OracleSpecDataConnectionQueryVariables>(OracleSpecDataConnectionDocument, options);
|
||||||
|
}
|
||||||
|
export type OracleSpecDataConnectionQueryHookResult = ReturnType<typeof useOracleSpecDataConnectionQuery>;
|
||||||
|
export type OracleSpecDataConnectionLazyQueryHookResult = ReturnType<typeof useOracleSpecDataConnectionLazyQuery>;
|
||||||
|
export type OracleSpecDataConnectionQueryResult = Apollo.QueryResult<OracleSpecDataConnectionQuery, OracleSpecDataConnectionQueryVariables>;
|
38
libs/oracles/src/lib/oracle-spec-data-connection.mock.ts
Normal file
38
libs/oracles/src/lib/oracle-spec-data-connection.mock.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import merge from 'lodash/merge';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
import type { OracleSpecDataConnectionQuery } from './__generated__/OracleSpecDataConnection';
|
||||||
|
import type { OracleData } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export function createDataConnection(
|
||||||
|
override?: PartialDeep<OracleData>
|
||||||
|
): OracleData {
|
||||||
|
const defaultDataConnection = {
|
||||||
|
externalData: {
|
||||||
|
data: {
|
||||||
|
broadcastAt: new Date().toISOString(),
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'settlement-data-property',
|
||||||
|
value: '100',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultDataConnection, override);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function oracleSpecDataConnectionQuery(
|
||||||
|
override?: PartialDeep<OracleSpecDataConnectionQuery>
|
||||||
|
): OracleSpecDataConnectionQuery {
|
||||||
|
const defaultResult = {
|
||||||
|
oracleSpec: {
|
||||||
|
dataConnection: {
|
||||||
|
edges: [{ node: createDataConnection() }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultResult, override);
|
||||||
|
}
|
105
libs/oracles/src/lib/use-oracle-spec-binding-data.spec.tsx
Normal file
105
libs/oracles/src/lib/use-oracle-spec-binding-data.spec.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { useOracleSpecBindingData } from './use-oracle-spec-binding-data';
|
||||||
|
import type { Property } from '@vegaprotocol/types';
|
||||||
|
import type { OracleSpecDataConnectionQuery } from './__generated__/OracleSpecDataConnection';
|
||||||
|
import { OracleSpecDataConnectionDocument } from './__generated__/OracleSpecDataConnection';
|
||||||
|
|
||||||
|
describe('useSettlementPrice', () => {
|
||||||
|
const setup = (
|
||||||
|
oracleSpecId: string,
|
||||||
|
specBinding: string,
|
||||||
|
mocks: MockedResponse<OracleSpecDataConnectionQuery>[]
|
||||||
|
) => {
|
||||||
|
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||||
|
<MockedProvider mocks={mocks}>{children}</MockedProvider>
|
||||||
|
);
|
||||||
|
return renderHook(
|
||||||
|
() => useOracleSpecBindingData(oracleSpecId, specBinding),
|
||||||
|
{
|
||||||
|
wrapper,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const createMock = (
|
||||||
|
id: string,
|
||||||
|
property: Property
|
||||||
|
): MockedResponse<OracleSpecDataConnectionQuery> => {
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
query: OracleSpecDataConnectionDocument,
|
||||||
|
variables: {
|
||||||
|
oracleSpecId: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
oracleSpec: {
|
||||||
|
dataConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
externalData: {
|
||||||
|
data: {
|
||||||
|
data: [property],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should returns the matching value for the spec binding', async () => {
|
||||||
|
const oracleSpecId = 'oracle-spec-id';
|
||||||
|
const specBinding = 'spec-binding';
|
||||||
|
const value = '123456';
|
||||||
|
const property = {
|
||||||
|
__typename: 'Property' as const,
|
||||||
|
name: specBinding,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
const mock = createMock(oracleSpecId, property);
|
||||||
|
|
||||||
|
const { result } = setup(oracleSpecId, specBinding, [mock]);
|
||||||
|
|
||||||
|
expect(result.current.property).toEqual(undefined);
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.property).toEqual(property);
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns nothing if matching poperty not found', async () => {
|
||||||
|
const oracleSpecId = 'oracle-spec-id';
|
||||||
|
const specBinding = 'spec-binding';
|
||||||
|
const value = '123456';
|
||||||
|
const property = {
|
||||||
|
__typename: 'Property' as const,
|
||||||
|
name: 'does not match',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
const mock = createMock(oracleSpecId, property);
|
||||||
|
|
||||||
|
const { result } = setup(oracleSpecId, specBinding, [mock]);
|
||||||
|
expect(result.current.property).toEqual(undefined);
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.property).toEqual(undefined);
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
26
libs/oracles/src/lib/use-oracle-spec-binding-data.ts
Normal file
26
libs/oracles/src/lib/use-oracle-spec-binding-data.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useOracleSpecDataConnectionQuery } from './__generated__/OracleSpecDataConnection';
|
||||||
|
|
||||||
|
export const useOracleSpecBindingData = (
|
||||||
|
oracleSpecId: string | undefined,
|
||||||
|
specBinding: string | undefined
|
||||||
|
) => {
|
||||||
|
const { data, loading, error } = useOracleSpecDataConnectionQuery({
|
||||||
|
variables: {
|
||||||
|
oracleSpecId: oracleSpecId || '',
|
||||||
|
},
|
||||||
|
skip: !oracleSpecId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataConnectionEdges = data?.oracleSpec?.dataConnection.edges;
|
||||||
|
const firstDataConnection = dataConnectionEdges?.[0]?.node;
|
||||||
|
const property = firstDataConnection?.externalData.data.data?.find(
|
||||||
|
(d) => d.name === specBinding
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
property,
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
@ -15,6 +15,7 @@
|
|||||||
"**/*.spec.js",
|
"**/*.spec.js",
|
||||||
"**/*.test.jsx",
|
"**/*.test.jsx",
|
||||||
"**/*.spec.jsx",
|
"**/*.spec.jsx",
|
||||||
"**/*.d.ts"
|
"**/*.d.ts",
|
||||||
|
"src/lib/use-oracle-spec-binding-data.spec.tsx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,17 @@ export const generateOrder = (partialOrder?: PartialDeep<Order>) => {
|
|||||||
name: 'XYZ',
|
name: 'XYZ',
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -148,7 +148,17 @@ describe('WithdrawFormContainer', () => {
|
|||||||
decimals: 5,
|
decimals: 5,
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
id: '',
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
},
|
},
|
||||||
quoteName: 'USD',
|
quoteName: 'USD',
|
||||||
},
|
},
|
||||||
|
@ -195,7 +195,7 @@
|
|||||||
"ts-jest": "27.1.4",
|
"ts-jest": "27.1.4",
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"type-fest": "^2.12.2",
|
"type-fest": "^3.8.0",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"url-loader": "^3.0.0"
|
"url-loader": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
@ -23637,10 +23637,10 @@ type-fest@^0.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||||
|
|
||||||
type-fest@^2.12.2:
|
type-fest@^3.8.0:
|
||||||
version "2.19.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.8.0.tgz#ce80d1ca7c7d11c5540560999cbd410cb5b3a385"
|
||||||
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
|
integrity sha512-FVNSzGQz9Th+/9R6Lvv7WIAkstylfHN2/JYxkyhhmKFYh9At2DST8t6L6Lref9eYO8PXFTfG9Sg1Agg0K3vq3Q==
|
||||||
|
|
||||||
type-is@~1.6.18:
|
type-is@~1.6.18:
|
||||||
version "1.6.18"
|
version "1.6.18"
|
||||||
|
Loading…
Reference in New Issue
Block a user