Feat/637 Market settlement expiry (#1094)
* feat: add market expiry field to the trade grid header * feat: add oracle spec linking to explorer page * fix: oracle id to tooltip link * fix: add dashed underlines to tooltip triggers * fix: lint * fix: format * chore: merge master * fix: readd type generation WIP * fix: wording Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * fix: refactor expiry to components Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
This commit is contained in:
parent
e9019c04f7
commit
eb2f4fd27c
@ -17,6 +17,7 @@ export const SubHeading = ({
|
|||||||
'uppercase',
|
'uppercase',
|
||||||
'mt-12',
|
'mt-12',
|
||||||
'mb-12',
|
'mb-12',
|
||||||
|
'truncate',
|
||||||
className
|
className
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { gql, useQuery } from '@apollo/client';
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import type { OracleSpecs as OracleSpecsQuery } from './__generated__/OracleSpecs';
|
import type { OracleSpecs as OracleSpecsQuery } from './__generated__/OracleSpecs';
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||||
import { RouteTitle } from '../../components/route-title';
|
import { RouteTitle } from '../../components/route-title';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
@ -33,7 +34,20 @@ const ORACLE_SPECS_QUERY = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Oracles = () => {
|
const Oracles = () => {
|
||||||
const { data } = useQuery<OracleSpecsQuery>(ORACLE_SPECS_QUERY);
|
const { hash } = useLocation();
|
||||||
|
const { data, loading } = useQuery<OracleSpecsQuery>(ORACLE_SPECS_QUERY);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data && !loading && hash) {
|
||||||
|
const element = document.getElementById(hash.substring(1));
|
||||||
|
if (element) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: element.offsetTop,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [hash, loading, !!data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@ -41,7 +55,7 @@ const Oracles = () => {
|
|||||||
{data?.oracleSpecs
|
{data?.oracleSpecs
|
||||||
? data.oracleSpecs.map((o) => (
|
? data.oracleSpecs.map((o) => (
|
||||||
<React.Fragment key={o.id}>
|
<React.Fragment key={o.id}>
|
||||||
<SubHeading>{o.id}</SubHeading>
|
<SubHeading id={o.id.toString()}>{o.id}</SubHeading>
|
||||||
<SyntaxHighlighter data={o} />
|
<SyntaxHighlighter data={o} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))
|
))
|
||||||
|
@ -48,6 +48,9 @@ const MARKET_QUERY = gql`
|
|||||||
}
|
}
|
||||||
product {
|
product {
|
||||||
... on Future {
|
... on Future {
|
||||||
|
oracleSpecForTradingTermination {
|
||||||
|
id
|
||||||
|
}
|
||||||
quoteName
|
quoteName
|
||||||
settlementAsset {
|
settlementAsset {
|
||||||
id
|
id
|
||||||
|
12
apps/trading/pages/markets/__generated__/Market.ts
generated
12
apps/trading/pages/markets/__generated__/Market.ts
generated
@ -81,6 +81,14 @@ export interface Market_market_tradableInstrument_instrument_metadata {
|
|||||||
tags: string[] | null;
|
tags: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Market_market_tradableInstrument_instrument_product_oracleSpecForTradingTermination {
|
||||||
|
__typename: "OracleSpec";
|
||||||
|
/**
|
||||||
|
* ID is a hash generated from the OracleSpec data.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Market_market_tradableInstrument_instrument_product_settlementAsset {
|
export interface Market_market_tradableInstrument_instrument_product_settlementAsset {
|
||||||
__typename: "Asset";
|
__typename: "Asset";
|
||||||
/**
|
/**
|
||||||
@ -99,6 +107,10 @@ export interface Market_market_tradableInstrument_instrument_product_settlementA
|
|||||||
|
|
||||||
export interface Market_market_tradableInstrument_instrument_product {
|
export interface Market_market_tradableInstrument_instrument_product {
|
||||||
__typename: "Future";
|
__typename: "Future";
|
||||||
|
/**
|
||||||
|
* The oracle spec describing the oracle data of interest for trading termination.
|
||||||
|
*/
|
||||||
|
oracleSpecForTradingTermination: Market_market_tradableInstrument_instrument_product_oracleSpecForTradingTermination;
|
||||||
/**
|
/**
|
||||||
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||||
*/
|
*/
|
||||||
|
@ -8,7 +8,11 @@ import { SelectMarketPopover } from '@vegaprotocol/market-list';
|
|||||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||||
import { FillsContainer } from '@vegaprotocol/fills';
|
import { FillsContainer } from '@vegaprotocol/fills';
|
||||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
getDateFormat,
|
||||||
|
t,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { TradesContainer } from '@vegaprotocol/trades';
|
import { TradesContainer } from '@vegaprotocol/trades';
|
||||||
import {
|
import {
|
||||||
AuctionTrigger,
|
AuctionTrigger,
|
||||||
@ -18,9 +22,8 @@ import {
|
|||||||
} from '@vegaprotocol/types';
|
} from '@vegaprotocol/types';
|
||||||
import { Allotment, LayoutPriority } from 'allotment';
|
import { Allotment, LayoutPriority } from 'allotment';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
import { useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { Market_market } from './__generated__/Market';
|
import type { Market_market } from './__generated__/Market';
|
||||||
import type { CandleClose } from '@vegaprotocol/types';
|
import type { CandleClose } from '@vegaprotocol/types';
|
||||||
@ -29,11 +32,13 @@ import { AccountsContainer } from '@vegaprotocol/accounts';
|
|||||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/market-list';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/market-list';
|
||||||
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import {
|
import {
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
PriceCellChange,
|
PriceCellChange,
|
||||||
Button,
|
Button,
|
||||||
|
Link,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
@ -54,6 +59,55 @@ const TradingViews = {
|
|||||||
|
|
||||||
type TradingView = keyof typeof TradingViews;
|
type TradingView = keyof typeof TradingViews;
|
||||||
|
|
||||||
|
type ExpiryLabelProps = {
|
||||||
|
market: Market_market;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
|
||||||
|
if (market.marketTimestamps.close === null) {
|
||||||
|
return <>{t('Not time-based')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeDate = new Date(market.marketTimestamps.close);
|
||||||
|
const isExpired = Date.now() - closeDate.valueOf() > 0;
|
||||||
|
const expiryDate = getDateFormat().format(closeDate);
|
||||||
|
|
||||||
|
return <>{`${isExpired ? `${t('Expired')} ` : ''} ${expiryDate}`}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExpiryTooltipContentProps = {
|
||||||
|
market: Market_market;
|
||||||
|
explorerUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExpiryTooltipContent = ({
|
||||||
|
market,
|
||||||
|
explorerUrl,
|
||||||
|
}: ExpiryTooltipContentProps) => {
|
||||||
|
if (market.marketTimestamps.close === null) {
|
||||||
|
const oracleId =
|
||||||
|
market.tradableInstrument.instrument.product
|
||||||
|
.oracleSpecForTradingTermination?.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
'This market expires when triggered by its oracle, not on a set date.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{explorerUrl && oracleId && (
|
||||||
|
<Link href={`${explorerUrl}/oracles#${oracleId}`} target="_blank">
|
||||||
|
{t('View oracle specification')}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
interface TradeMarketHeaderProps {
|
interface TradeMarketHeaderProps {
|
||||||
market: Market_market;
|
market: Market_market;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -63,6 +117,7 @@ export const TradeMarketHeader = ({
|
|||||||
market,
|
market,
|
||||||
className,
|
className,
|
||||||
}: TradeMarketHeaderProps) => {
|
}: TradeMarketHeaderProps) => {
|
||||||
|
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||||
useAssetDetailsDialogStore();
|
useAssetDetailsDialogStore();
|
||||||
const candlesClose: string[] = (market?.candles || [])
|
const candlesClose: string[] = (market?.candles || [])
|
||||||
@ -87,6 +142,8 @@ export const TradeMarketHeader = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasExpiry = market.marketTimestamps.close !== null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={headerClassName}>
|
<header className={headerClassName}>
|
||||||
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
|
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
|
||||||
@ -95,6 +152,27 @@ export const TradeMarketHeader = ({
|
|||||||
data-testid="market-summary"
|
data-testid="market-summary"
|
||||||
className="flex flex-auto items-start gap-64 overflow-x-auto whitespace-nowrap py-8 pr-8"
|
className="flex flex-auto items-start gap-64 overflow-x-auto whitespace-nowrap py-8 pr-8"
|
||||||
>
|
>
|
||||||
|
<Tooltip
|
||||||
|
align="start"
|
||||||
|
description={
|
||||||
|
<ExpiryTooltipContent
|
||||||
|
market={market}
|
||||||
|
explorerUrl={VEGA_EXPLORER_URL}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={headerItemClassName}>
|
||||||
|
<span className={itemClassName}>{t('Expiry')}</span>
|
||||||
|
<span
|
||||||
|
data-testid="trading-expiry"
|
||||||
|
className={classNames(itemValueClassName, {
|
||||||
|
'underline decoration-dashed': !hasExpiry,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ExpiryLabel market={market} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
<div className={headerItemClassName}>
|
<div className={headerItemClassName}>
|
||||||
<span className={itemClassName}>{t('Change (24h)')}</span>
|
<span className={itemClassName}>{t('Change (24h)')}</span>
|
||||||
<PriceCellChange
|
<PriceCellChange
|
||||||
@ -119,7 +197,13 @@ export const TradeMarketHeader = ({
|
|||||||
>
|
>
|
||||||
<div className={headerItemClassName}>
|
<div className={headerItemClassName}>
|
||||||
<span className={itemClassName}>{t('Trading mode')}</span>
|
<span className={itemClassName}>{t('Trading mode')}</span>
|
||||||
<span data-testid="trading-mode" className={itemValueClassName}>
|
<span
|
||||||
|
data-testid="trading-mode"
|
||||||
|
className={classNames(
|
||||||
|
itemValueClassName,
|
||||||
|
'underline decoration-dashed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{market.tradingMode ===
|
{market.tradingMode ===
|
||||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||||
market.data?.trigger &&
|
market.data?.trigger &&
|
||||||
|
Loading…
Reference in New Issue
Block a user