feat: 991 deal ticket should show estimated crossing price for market orders when market is in auction (#1561)

* feat: #991 move order validation back in deal ticket, add tooltip

* fix: align view full market list link styling

* feat: #991 move tooltip add for monitoring liq auction

* feat: #991 order validation messages, LP table sortable, padding sparkline, warning instead of errro

* feat: #991 check only in monitoring auction to add tooltips, re-use isMarketInAuction

* fix: #991 fix import for isMarketInAuction

* fix: #991 console-lite imports

* fix: formatting in console-lite deal-ticket-steps.tsx

* fix: add market depth

* fix: add market depth and fix generate orders

* fix: revert market list and generate orders

* fix: fix trading-e2e build

* feat: #991 in monitoring auction don't show a price input color update based on intent

* fix: #991 update docs links to docs.vega.xyz in tooltips
This commit is contained in:
m.ray 2022-10-03 09:41:34 +01:00 committed by GitHub
parent b10844147e
commit f743828cb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 503 additions and 269 deletions

View File

@ -1,8 +1,13 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form';
import { Stepper } from '../stepper';
import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket';
import {
getDefaultOrder,
useOrderValidation,
validateSize,
} from '@vegaprotocol/deal-ticket';
import { InputError } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from 'bignumber.js';
import { MarketSelector } from '@vegaprotocol/deal-ticket';
@ -15,14 +20,11 @@ import {
removeDecimal,
} from '@vegaprotocol/react-helpers';
import {
getDefaultOrder,
useOrderValidation,
useOrderSubmit,
getOrderDialogTitle,
getOrderDialogIntent,
getOrderDialogIcon,
OrderFeedback,
validateSize,
} from '@vegaprotocol/orders';
import { DealTicketSize } from './deal-ticket-size';
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';

View File

@ -28,6 +28,7 @@ export const generateDealTicketQuery = (
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
name: 'tBTC TEST',
decimals: 5,
},
},
},

View File

@ -75,6 +75,13 @@ export const generateMarket = (override?: PartialDeep<Market>): Market => {
close: null,
__typename: 'MarketTimestamps',
},
depth: {
__typename: 'MarketDepth',
lastTrade: {
__typename: 'Trade',
price: '88470230',
},
},
candlesConnection: {
__typename: 'CandleDataConnection',
edges: [

View File

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

View File

@ -1,207 +0,0 @@
import type { ReactNode } from 'react';
import {
t,
getDateTimeFormat,
addDecimalsFormatNumber,
} from '@vegaprotocol/react-helpers';
import { ExternalLink, Link as UiToolkitLink } from '@vegaprotocol/ui-toolkit';
import Link from 'next/link';
import { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types';
import type { Market_market } from '../../pages/markets/__generated__/Market';
type MarketDataGridProps = {
grid: {
label: string | ReactNode;
value?: ReactNode;
}[];
};
const MarketDataGrid = ({ grid }: MarketDataGridProps) => {
return (
<>
{grid.map(
({ label, value }, index) =>
value && (
<div key={index} className="grid grid-cols-2">
<span data-testid="tooltip-label">{label}</span>
<span data-testid="tooltip-value" className="text-right">
{value}
</span>
</div>
)
)}
</>
);
};
const formatStake = (value: string, market: Market_market) => {
const formattedValue = addDecimalsFormatNumber(
value,
market.tradableInstrument.instrument.product.settlementAsset.decimals
);
const asset =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
return `${formattedValue} ${asset}`;
};
const compileGridData = (
market: Market_market,
onSelect?: (id: string) => void
) => {
const grid: MarketDataGridProps['grid'] = [];
const isLiquidityMonitoringAuction =
market.tradingMode === MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger === AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
if (!market.data) return grid;
if (market.data?.auctionStart) {
grid.push({
label: t('Auction start'),
value: getDateTimeFormat().format(new Date(market.data.auctionStart)),
});
}
if (market.data?.auctionEnd) {
const endDate = getDateTimeFormat().format(
new Date(market.data.auctionEnd)
);
grid.push({
label: isLiquidityMonitoringAuction
? t('Est auction end')
: t('Auction end'),
value: isLiquidityMonitoringAuction ? `~${endDate}` : endDate,
});
}
if (isLiquidityMonitoringAuction && market.data?.targetStake) {
grid.push({
label: t('Target liquidity'),
value: formatStake(market.data.targetStake, market),
});
}
if (isLiquidityMonitoringAuction && market.data?.suppliedStake) {
grid.push({
label: (
<Link href={`/liquidity/${market.id}`} passHref={true}>
<UiToolkitLink onClick={() => onSelect && onSelect(market.id)}>
{t('Current liquidity')}
</UiToolkitLink>
</Link>
),
value: formatStake(market.data.suppliedStake, market),
});
}
if (market.data?.indicativePrice) {
grid.push({
label: t('Est uncrossing price'),
value:
'~' +
addDecimalsFormatNumber(
market.data.indicativePrice,
market.positionDecimalPlaces
),
});
}
if (market.data?.indicativeVolume) {
grid.push({
label: t('Est uncrossing vol'),
value:
'~' +
addDecimalsFormatNumber(
market.data.indicativeVolume,
market.positionDecimalPlaces
),
});
}
return grid;
};
type TradingModeTooltipProps = {
market: Market_market;
onSelect?: (marketId: string) => void;
};
export const TradingModeTooltip = ({
market,
onSelect,
}: TradingModeTooltipProps) => {
switch (market.tradingMode) {
case MarketTradingMode.TRADING_MODE_CONTINUOUS: {
return (
<>
{t(
'This is the standard trading mode where trades are executed whenever orders are received.'
)}
</>
);
}
case MarketTradingMode.TRADING_MODE_OPENING_AUCTION: {
return (
<>
<p className="mb-4">
<span>
{t(
'This new market is in an opening auction to determine a fair mid-price before starting continuous trading.'
)}
</span>{' '}
<ExternalLink href="https://docs.fairground.vega.xyz/docs/trading-questions/#auctions-what-happens-in-an-opening-auction">
{t('Find out more')}
</ExternalLink>
</p>
<MarketDataGrid grid={compileGridData(market)} />
</>
);
}
case MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: {
switch (market.data?.trigger) {
case AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
return (
<>
<p data-testid="tooltip-market-info" className="mb-4">
<span>
{t(
'This market is in auction until it reaches sufficient liquidity.'
)}
</span>{' '}
<ExternalLink href="https://docs.fairground.vega.xyz/docs/trading-questions/#auctions-what-is-a-liquidity-monitoring-auction">
{t('Find out more')}
</ExternalLink>
</p>
<MarketDataGrid grid={compileGridData(market, onSelect)} />
</>
);
}
case AuctionTrigger.AUCTION_TRIGGER_PRICE: {
return (
<>
<p className="mb-4">
<span>
{t('This market is in auction due to high price volatility.')}
</span>{' '}
<ExternalLink href="https://docs.fairground.vega.xyz/docs/trading-questions/#auctions-what-is-a-price-monitoring-auction">
{t('Find out more')}
</ExternalLink>
</p>
<MarketDataGrid grid={compileGridData(market)} />
</>
);
}
default: {
return null;
}
}
}
case MarketTradingMode.TRADING_MODE_NO_TRADING: {
return <>{t('No trading enabled for this market.')}</>;
}
case MarketTradingMode.TRADING_MODE_BATCH_AUCTION:
default: {
return null;
}
}
};

View File

@ -50,6 +50,11 @@ query Market($marketId: ID!, $interval: Interval!, $since: String!) {
open
close
}
depth {
lastTrade {
price
}
}
candlesConnection(interval: $interval, since: $since) {
edges {
node {

View File

@ -69,6 +69,11 @@ const MARKET_QUERY = gql`
open
close
}
depth {
lastTrade {
price
}
}
candlesConnection(interval: $interval, since: $since) {
edges {
node {

View File

@ -169,6 +169,22 @@ export interface Market_market_marketTimestamps {
close: string | null;
}
export interface Market_market_depth_lastTrade {
__typename: "Trade";
/**
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
*/
price: string;
}
export interface Market_market_depth {
__typename: "MarketDepth";
/**
* Last trade for the given market (if available)
*/
lastTrade: Market_market_depth_lastTrade | null;
}
export interface Market_market_candlesConnection_edges_node {
__typename: "Candle";
/**
@ -251,6 +267,10 @@ export interface Market_market {
* Timestamps for state changes in the market
*/
marketTimestamps: Market_market_marketTimestamps;
/**
* Current depth on the order book for this market
*/
depth: Market_market_depth;
/**
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters using cursor based pagination
*/

View File

@ -10,7 +10,7 @@ export type MarketQueryVariables = Types.Exact<{
}>;
export type MarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradingMode: Types.MarketTradingMode, state: Types.MarketState, decimalPlaces: number, positionDecimalPlaces: number, data?: { __typename?: 'MarketData', auctionStart?: string | null, auctionEnd?: string | null, markPrice: string, indicativeVolume: string, indicativePrice: string, suppliedStake?: string | null, targetStake?: string | null, bestBidVolume: string, bestOfferVolume: string, bestStaticBidVolume: string, bestStaticOfferVolume: string, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, oracleSpecForTradingTermination: { __typename?: 'OracleSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', open: string, close: string, volume: string } } | null> | null } | null } | null };
export type MarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, tradingMode: Types.MarketTradingMode, state: Types.MarketState, decimalPlaces: number, positionDecimalPlaces: number, data?: { __typename?: 'MarketData', auctionStart?: string | null, auctionEnd?: string | null, markPrice: string, indicativeVolume: string, indicativePrice: string, suppliedStake?: string | null, targetStake?: string | null, bestBidVolume: string, bestOfferVolume: string, bestStaticBidVolume: string, bestStaticOfferVolume: string, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, oracleSpecForTradingTermination: { __typename?: 'OracleSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', open: string, close: string, volume: string } } | null> | null } | null } | null };
export const MarketDocument = gql`
@ -66,6 +66,11 @@ export const MarketDocument = gql`
open
close
}
depth {
lastTrade {
price
}
}
candlesConnection(interval: $interval, since: $since) {
edges {
node {

View File

@ -1,4 +1,8 @@
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
import {
compileGridData,
DealTicketContainer,
TradingModeTooltip,
} from '@vegaprotocol/deal-ticket';
import { MarketInfoContainer } from '@vegaprotocol/market-info';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { ColumnKind, SelectMarketPopover } from '@vegaprotocol/market-list';
@ -38,7 +42,6 @@ import {
MarketTradingMode,
MarketTradingModeMapping,
} from '@vegaprotocol/types';
import { TradingModeTooltip } from '../../components/trading-mode-tooltip';
import { Header, HeaderStat } from '../../components/header';
import { AccountsContainer } from '../portfolio/accounts-container';
@ -173,9 +176,7 @@ export const TradeMarketHeader = ({
description={
<TradingModeTooltip
market={market}
onSelect={(marketId: string) => {
onSelect(marketId);
}}
compiledGrid={compileGridData(market, onSelect)}
/>
}
>

View File

@ -1,5 +1,4 @@
import { toDecimal } from '@vegaprotocol/react-helpers';
import type { Order } from '../order-hooks';
import { OrderTimeInForce, OrderType, Side } from '@vegaprotocol/types';
export const getDefaultOrder = (market: {
@ -12,3 +11,13 @@ export const getDefaultOrder = (market: {
timeInForce: OrderTimeInForce.TIME_IN_FORCE_IOC,
size: String(toDecimal(market.positionDecimalPlaces)),
});
export interface Order {
marketId: string;
type: OrderType;
size: string;
side: Side;
timeInForce: OrderTimeInForce;
price?: string;
expiresAt?: Date;
}

View File

@ -1,2 +1,3 @@
export * from './get-default-order';
export * from './use-order-validation';
export * from './validate-size';

View File

@ -14,12 +14,11 @@ import {
import type { ValidationProps } from './use-order-validation';
import { marketTranslations } from './use-order-validation';
import { useOrderValidation } from './use-order-validation';
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
import { ERROR_SIZE_DECIMAL } from './validate-size';
jest.mock('@vegaprotocol/wallet');
const market = {
__typename: 'Market',
id: 'market-id',
decimalPlaces: 2,
positionDecimalPlaces: 1,
@ -74,7 +73,7 @@ const ERROR = {
MARKET_CONTINUOUS_LIMIT:
'Only limit orders are permitted when market is in auction',
MARKET_CONTINUOUS_TIF:
'Only GTT, GTC and GFA are permitted when market is in auction',
'Until the auction ends, you can only place GFA, GTT, or GTC limit orders',
FIELD_SIZE_REQ: 'You need to provide an amount',
FIELD_SIZE_MIN: `The amount cannot be lower than "${defaultOrder.step}"`,
FIELD_PRICE_REQ: 'You need to provide a price',
@ -165,10 +164,8 @@ describe('useOrderValidation', () => {
market: { ...defaultOrder.market, tradingMode },
orderType: OrderType.TYPE_MARKET,
});
expect(result.current).toStrictEqual({
isDisabled: true,
message: errorMessage,
});
expect(result.current.isDisabled).toBeTruthy();
expect(result.current.message).toBe(errorMessage);
}
);

View File

@ -3,22 +3,31 @@ import { useMemo } from 'react';
import { t, toDecimal } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
AuctionTrigger,
MarketState,
MarketStateMapping,
MarketTradingMode,
OrderTimeInForce,
OrderType,
} from '@vegaprotocol/types';
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
import type { Order } from './use-order-submit';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import type { Order } from './get-default-order';
import { ERROR_SIZE_DECIMAL } from './validate-size';
import { MarketDataGrid } from '../trading-mode-tooltip';
import { compileGridData } from '../trading-mode-tooltip/compile-grid-data';
import type { DealTicketMarketFragment } from '../deal-ticket/__generated__/DealTicket';
export const isMarketInAuction = (market: DealTicketMarketFragment) => {
return [
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(market.tradingMode);
};
export type ValidationProps = {
step?: number;
market: {
state: MarketState;
tradingMode: MarketTradingMode;
positionDecimalPlaces: number;
};
market: DealTicketMarketFragment;
orderType: OrderType;
orderTimeInForce: OrderTimeInForce;
fieldErrors?: FieldErrors<Order>;
@ -38,7 +47,10 @@ export const useOrderValidation = ({
fieldErrors = {},
orderType,
orderTimeInForce,
}: ValidationProps) => {
}: ValidationProps): {
message: React.ReactNode | string;
isDisabled: boolean;
} => {
const { keypair } = useVegaWallet();
const minSize = toDecimal(market.positionDecimalPlaces);
@ -88,14 +100,54 @@ export const useOrderValidation = ({
};
}
if (
[
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(market.tradingMode)
) {
if (orderType !== OrderType.TYPE_LIMIT) {
if (isMarketInAuction(market)) {
if (orderType === OrderType.TYPE_MARKET) {
if (
market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger === AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction until it reaches')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('sufficient liquidity')}</span>
</Tooltip>
{'. '}
{t('Only limit orders are permitted when market is in auction')}
</span>
),
};
}
if (
market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger === AuctionTrigger.AUCTION_TRIGGER_PRICE
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction due to')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('high price volatility')}</span>
</Tooltip>
{'. '}
{t('Only limit orders are permitted when market is in auction')}
</span>
),
};
}
return {
isDisabled: true,
message: t(
@ -103,18 +155,68 @@ export const useOrderValidation = ({
),
};
}
if (
orderType === OrderType.TYPE_LIMIT &&
[
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
OrderTimeInForce.TIME_IN_FORCE_GFN,
].includes(orderTimeInForce)
) {
if (
market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger === AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction until it reaches')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('sufficient liquidity')}</span>
</Tooltip>
{'. '}
{t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
)}
</span>
),
};
}
if (
market.tradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger === AuctionTrigger.AUCTION_TRIGGER_PRICE
) {
return {
isDisabled: true,
message: (
<span>
{t('This market is in auction due to')}{' '}
<Tooltip
description={
<MarketDataGrid grid={compileGridData(market)} />
}
>
<span>{t('high price volatility')}</span>
</Tooltip>
{'. '}
{t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
)}
</span>
),
};
}
return {
isDisabled: true,
message: t(
'Only GTT, GTC and GFA are permitted when market is in auction'
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
),
};
}

View File

@ -4,6 +4,18 @@ fragment DealTicketMarket on Market {
positionDecimalPlaces
state
tradingMode
data {
market {
id
}
indicativePrice
indicativeVolume
targetStake
suppliedStake
auctionStart
auctionEnd
trigger
}
tradableInstrument {
instrument {
id
@ -14,6 +26,7 @@ fragment DealTicketMarket on Market {
settlementAsset {
id
symbol
decimals
name
}
}

View File

@ -3,14 +3,14 @@ import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type DealTicketMarketFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } };
export type DealTicketMarketFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } };
export type DealTicketQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type DealTicketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } } | null };
export type DealTicketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } } | null };
export const DealTicketMarketFragmentDoc = gql`
fragment DealTicketMarket on Market {
@ -19,6 +19,18 @@ export const DealTicketMarketFragmentDoc = gql`
positionDecimalPlaces
state
tradingMode
data {
market {
id
}
indicativePrice
indicativeVolume
targetStake
suppliedStake
auctionStart
auctionEnd
trigger
}
tradableInstrument {
instrument {
id
@ -29,6 +41,7 @@ export const DealTicketMarketFragmentDoc = gql`
settlementAsset {
id
symbol
decimals
name
}
}

View File

@ -1,7 +1,7 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { t, toDecimal } from '@vegaprotocol/react-helpers';
import { validateSize } from '@vegaprotocol/orders';
import type { DealTicketAmountProps } from './deal-ticket-amount';
import { validateSize } from '../deal-ticket-validation';
export type DealTicketLimitAmountProps = Omit<
DealTicketAmountProps,

View File

@ -1,7 +1,8 @@
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import { FormGroup, Input, Tooltip } from '@vegaprotocol/ui-toolkit';
import { t, toDecimal } from '@vegaprotocol/react-helpers';
import { validateSize } from '@vegaprotocol/orders';
import type { DealTicketAmountProps } from './deal-ticket-amount';
import { validateSize } from '../deal-ticket-validation/validate-size';
import { isMarketInAuction } from '../deal-ticket-validation/use-order-validation';
export type DealTicketMarketAmountProps = Omit<
DealTicketAmountProps,
@ -37,13 +38,26 @@ export const DealTicketMarketAmount = ({
</div>
<div>@</div>
<div className="flex-1" data-testid="last-price">
{price && quoteName ? (
<>
~{price} {quoteName}
</>
) : (
'-'
{isMarketInAuction(market) && (
<Tooltip
description={t(
'This market is in auction. The uncrossing price is an indication of what the price is expected to be when the auction ends.'
)}
>
<span className={'block mb-2 text-sm text-left'}>
{t(`Estimated uncrossing price`)}
</span>
</Tooltip>
)}
<span className="text-sm">
{price && quoteName ? (
<>
~{price} {quoteName}
</>
) : (
'-'
)}
</span>
</div>
</div>
);

View File

@ -27,6 +27,7 @@ const market: DealTicketMarketFragment = {
id: 'asset-id',
name: 'asset-name',
symbol: 'asset-symbol',
decimals: 2,
},
},
},

View File

@ -12,9 +12,11 @@ import { DealTicketAmount } from './deal-ticket-amount';
import { TimeInForceSelector } from './time-in-force-selector';
import type { DealTicketMarketFragment } from './__generated__/DealTicket';
import { ExpirySelector } from './expiry-selector';
import type { Order } from '@vegaprotocol/orders';
import { getDefaultOrder, useOrderValidation } from '@vegaprotocol/orders';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import type { Order } from '../deal-ticket-validation';
import { getDefaultOrder } from '../deal-ticket-validation';
import { useOrderValidation } from '../deal-ticket-validation/use-order-validation';
import { MarketTradingMode } from '@vegaprotocol/types';
export type TransactionStatus = 'default' | 'pending';
@ -70,6 +72,22 @@ export const DealTicket = ({
[isDisabled, submit, market.decimalPlaces, market.positionDecimalPlaces]
);
const getPrice = () => {
if (
market.tradingMode === MarketTradingMode.TRADING_MODE_OPENING_AUCTION ||
market.tradingMode === MarketTradingMode.TRADING_MODE_BATCH_AUCTION
) {
return market.data?.indicativePrice;
}
if (
market.tradingMode === MarketTradingMode.TRADING_MODE_MONITORING_AUCTION
) {
return null;
}
return market.depth.lastTrade?.price;
};
const price = getPrice();
return (
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
<Controller
@ -101,11 +119,8 @@ export const DealTicket = ({
market={market}
register={register}
price={
market.depth.lastTrade
? addDecimalsFormatNumber(
market.depth.lastTrade.price,
market.decimalPlaces
)
price
? addDecimalsFormatNumber(price, market.decimalPlaces)
: undefined
}
quoteName={market.tradableInstrument.instrument.product.quoteName}

View File

@ -1 +1,3 @@
export * from './deal-ticket';
export * from './deal-ticket-validation';
export * from './trading-mode-tooltip';

View File

@ -0,0 +1,98 @@
import {
t,
getDateTimeFormat,
addDecimalsFormatNumber,
} from '@vegaprotocol/react-helpers';
import { Link as UiToolkitLink } from '@vegaprotocol/ui-toolkit';
import Link from 'next/link';
import { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import type { MarketDataGridProps } from './market-data-grid';
import type { DealTicketMarketFragment } from '../deal-ticket/__generated__/DealTicket';
export const compileGridData = (
market: DealTicketMarketFragment,
onSelect?: (id: string) => void
): { label: ReactNode; value?: ReactNode }[] => {
const grid: MarketDataGridProps['grid'] = [];
const isLiquidityMonitoringAuction =
market.tradingMode === MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.data?.trigger === AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
const formatStake = (value: string) => {
const formattedValue = addDecimalsFormatNumber(
value,
market.tradableInstrument.instrument.product.settlementAsset.decimals
);
const asset =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
return `${formattedValue} ${asset}`;
};
if (!market.data) return grid;
if (market.data?.auctionStart) {
grid.push({
label: t('Auction start'),
value: getDateTimeFormat().format(new Date(market.data.auctionStart)),
});
}
if (market.data?.auctionEnd) {
const endDate = getDateTimeFormat().format(
new Date(market.data.auctionEnd)
);
grid.push({
label: isLiquidityMonitoringAuction
? t('Est auction end')
: t('Auction end'),
value: isLiquidityMonitoringAuction ? `~${endDate}` : endDate,
});
}
if (isLiquidityMonitoringAuction && market.data?.targetStake) {
grid.push({
label: t('Target liquidity'),
value: formatStake(market.data.targetStake),
});
}
if (isLiquidityMonitoringAuction && market.data?.suppliedStake) {
grid.push({
label: (
<Link href={`/liquidity/${market.id}`} passHref={true}>
<UiToolkitLink onClick={() => onSelect && onSelect(market.id)}>
{t('Current liquidity')}
</UiToolkitLink>
</Link>
),
value: formatStake(market.data.suppliedStake),
});
}
if (market.data?.indicativePrice) {
grid.push({
label: t('Est uncrossing price'),
value:
'~' +
addDecimalsFormatNumber(
market.data.indicativePrice,
market.positionDecimalPlaces
),
});
}
if (market.data?.indicativeVolume) {
grid.push({
label: t('Est uncrossing vol'),
value:
'~' +
addDecimalsFormatNumber(
market.data.indicativeVolume,
market.positionDecimalPlaces
),
});
}
return grid;
};

View File

@ -0,0 +1,3 @@
export * from './market-data-grid';
export * from './trading-mode-tooltip';
export * from './compile-grid-data';

View File

@ -0,0 +1,26 @@
import type { ReactNode } from 'react';
export type MarketDataGridProps = {
grid: {
label: string | ReactNode;
value?: ReactNode;
}[];
};
export const MarketDataGrid = ({ grid }: MarketDataGridProps) => {
return (
<>
{grid.map(
({ label, value }, index) =>
value && (
<div key={index} className="grid grid-cols-2">
<span data-testid="tooltip-label">{label}</span>
<span data-testid="tooltip-value" className="text-right">
{value}
</span>
</div>
)
)}
</>
);
};

View File

@ -0,0 +1,93 @@
import { t } from '@vegaprotocol/react-helpers';
import { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { MarketDataGrid } from './market-data-grid';
type TradingModeTooltipProps = {
market: {
tradingMode: MarketTradingMode;
data: { trigger: AuctionTrigger | null } | null;
};
compiledGrid: { label: ReactNode; value?: ReactNode }[];
};
export const TradingModeTooltip = ({
market,
compiledGrid,
}: TradingModeTooltipProps) => {
switch (market.tradingMode) {
case MarketTradingMode.TRADING_MODE_CONTINUOUS: {
return (
<>
{t(
'This is the standard trading mode where trades are executed whenever orders are received.'
)}
</>
);
}
case MarketTradingMode.TRADING_MODE_OPENING_AUCTION: {
return (
<>
<p className="mb-4">
<span>
{t(
'This new market is in an opening auction to determine a fair mid-price before starting continuous trading.'
)}
</span>{' '}
<ExternalLink href="https://docs.vega.xyz/docs/testnet/concepts/trading-on-vega/trading-modes#auction-type-opening">
{t('Find out more')}
</ExternalLink>
</p>
<MarketDataGrid grid={compiledGrid} />
</>
);
}
case MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: {
switch (market.data?.trigger) {
case AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
return (
<>
<p data-testid="tooltip-market-info" className="mb-4">
<span>
{t(
'This market is in auction until it reaches sufficient liquidity.'
)}
</span>{' '}
<ExternalLink href="https://docs.vega.xyz/docs/testnet/concepts/trading-on-vega/trading-modes#auction-type-liquidity-monitoring">
{t('Find out more')}
</ExternalLink>
</p>
<MarketDataGrid grid={compiledGrid} />
</>
);
}
case AuctionTrigger.AUCTION_TRIGGER_PRICE: {
return (
<>
<p className="mb-4">
<span>
{t('This market is in auction due to high price volatility.')}
</span>{' '}
<ExternalLink href="https://docs.vega.xyz/docs/testnet/concepts/trading-on-vega/trading-modes#auction-type-price-monitoring">
{t('Find out more')}
</ExternalLink>
</p>
<MarketDataGrid grid={compiledGrid} />
</>
);
}
default: {
return null;
}
}
}
case MarketTradingMode.TRADING_MODE_NO_TRADING: {
return <>{t('No trading enabled for this market.')}</>;
}
case MarketTradingMode.TRADING_MODE_BATCH_AUCTION:
default: {
return null;
}
}
};

View File

@ -39,7 +39,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
({ data, symbol = '', assetDecimalPlaces }, ref) => {
const assetDecimalsFormatter = ({ value }: ValueFormatterParams) => {
if (!value) return '-';
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0)}`;
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0, 5)}`;
};
return (
<AgGrid
@ -54,6 +54,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
resizable: true,
minWidth: 100,
tooltipComponent: TooltipCellComponent,
sortable: true,
}}
rowData={data}
>

View File

@ -77,7 +77,9 @@ export const SelectMarketLandingTable = ({
</tbody>
</table>
</div>
<Link href="/markets">{'Or view full market list'}</Link>
<div className="mt-4 text-md">
<Link href="/markets">{'Or view full market list'}</Link>
</div>
</>
);
};

View File

@ -1,3 +1,2 @@
export * from './components';
export * from './order-hooks';
export * from './utils';

View File

@ -2,5 +2,4 @@ export * from './__generated__/OrderEvent';
export * from './order-event-query';
export * from './use-order-cancel';
export * from './use-order-submit';
export * from './use-order-validation';
export * from './use-order-edit';

View File

@ -3625,7 +3625,7 @@ export type QuerypartiesConnectionArgs = {
/** Queries allow a caller to read data and filter data via GraphQL. */
export type QuerypartyArgs = {
id: Scalars['ID'];
id?: InputMaybe<Scalars['ID']>;
};

View File

@ -14,11 +14,15 @@ export const InputError = ({
...props
}: InputErrorProps) => {
const effectiveClassName = classNames(
'text-sm text-vega-red flex items-center',
'text-sm flex items-center',
'mt-2',
{
'border-danger': intent === 'danger',
'border-warning': intent === 'warning',
},
{
'text-warning': intent === 'warning',
'text-danger': intent === 'danger',
}
);
return (

View File

@ -92,7 +92,10 @@ export const SparklineView = ({
return (
<svg
data-testid="sparkline-svg"
className={classNames('pt-px pr-0 w-full overflow-visible', className)}
className={classNames(
'pt-px pr-0 w-full overflow-visible p-2',
className
)}
width={width}
height={height}
viewBox="0 0 100 100"

View File

@ -8,6 +8,7 @@
"test": "nx test",
"postinstall": "husky install && yarn tsc -b tools/executors/next && yarn tsc -b tools/executors/webpack",
"test:all": "nx run-many --all --target=test",
"build:all": "nx run-many --all --target=build",
"vegacapsule": "vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl"
},
"engines": {