feat(trading): quote unit and settlement asset relation (#2953)

This commit is contained in:
m.ray 2023-02-22 12:05:31 -05:00 committed by GitHub
parent efeccc7972
commit 3aefa10a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 182 additions and 108 deletions

View File

@ -22,8 +22,7 @@ export const MarketDetails = ({
}: {
market: MarketInfoNoCandlesQuery['market'];
}) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol;
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
@ -39,6 +38,10 @@ export const MarketDetails = ({
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const panels = [
{
title: t('Key details'),
@ -179,13 +182,21 @@ export const MarketDetails = ({
{
title: t('Liquidity price range'),
content: (
<>
<p className="text-xs mb-4">
{`For liquidity orders count towards a commitment they have to be
within either the liquidity or price monitoring bounds (whichever is
tighter).`}
</p>
<p className="text-xs mb-4">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<MarketInfoTable
noBorder={false}
data={{
liquidityPriceRange: formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
),
LPVolumeMin:
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
@ -193,8 +204,8 @@ export const MarketDetails = ({
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
LPVolumeMax:
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
@ -202,9 +213,10 @@ export const MarketDetails = ({
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
)} ${quoteUnit}`,
}}
></MarketInfoTable>
</>
),
},
{

View File

@ -29,6 +29,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(0, 'Mark Price', '0.05749');
validateMarketDataRow(1, 'Best Bid Price', '6.81765 ');
validateMarketDataRow(2, 'Best Offer Price', '6.81769 ');
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
it('market volume displayed', () => {
@ -168,9 +169,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('liquidity price range displayed', () => {
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
validateMarketDataRow(0, 'Liquidity Price Range', '2.00%');
validateMarketDataRow(1, 'LP Volume Min', '0.05634 tBTC');
validateMarketDataRow(2, 'LP Volume Max', '0.05864 tBTC');
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
validateMarketDataRow(1, 'Lowest Price', '0.05634 BTC');
validateMarketDataRow(2, 'Highest Price', '0.05864 BTC');
});
it('oracle displayed', () => {

View File

@ -37,7 +37,7 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
cy.getByTestId(form).find('#wallet').click().type('invalid name');
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId('form-error').should('have.text', 'Invalid credentials');
cy.getByTestId('form-error').should('have.text', 'No wallet detected');
});
it('doesnt connect with invalid fields', () => {

View File

@ -123,7 +123,7 @@ export const MarketLiquiditySupplied = ({
{showMessage && (
<p className="mt-4">
{t(
'The market is in an auction because there are no priced limit orders, which are required to deploy liquidity commitment pegged orders. This means the order book is empty on one or both sides.'
'The market has sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.'
)}
</p>
)}

View File

@ -58,6 +58,7 @@ export const AssetDetailsDialog = ({
const { data } = useAssetsDataProvider();
const asset = data?.find((a) => a.id === assetId);
const assetSymbol = asset?.symbol || '';
const content = asset ? (
<div className="my-2">
@ -97,6 +98,12 @@ export const AssetDetailsDialog = ({
}}
>
{content}
<p className="text-sm mb-4">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit.',
[assetSymbol]
)}
</p>
<div className="w-1/4">
<Button
data-testid="close-asset-details-dialog"

View File

@ -48,7 +48,7 @@ export const DealTicketEstimates = ({
<DataTitle quoteName={quoteName}>{t('Est. Position Size')}</DataTitle>
<ValueTooltipRow
value={notionalSize}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
@ -66,7 +66,7 @@ export const DealTicketEstimates = ({
<DataTitle quoteName={quoteName}>{t('Est. Margin')}</DataTitle>
<ValueTooltipRow
value={estMargin}
description={constants.EST_MARGIN_TOOLTIP_TEXT}
description={constants.EST_MARGIN_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
@ -75,7 +75,7 @@ export const DealTicketEstimates = ({
<DataTitle quoteName={quoteName}>{t('Est. Close out')}</DataTitle>
<ValueTooltipRow
value={estCloseOut}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}

View File

@ -9,6 +9,7 @@ import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/react-helpers';
import { compileGridData } from './compile-grid-data';
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
import BigNumber from 'bignumber.js';
type TradingModeTooltipProps = {
marketId?: string;
@ -115,13 +116,21 @@ export const TradingModeTooltip = ({
case Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: {
switch (trigger) {
case Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
const notEnoughLiquidity = new BigNumber(
marketData.suppliedStake || 0
).isLessThan(marketData.targetStake || 0);
return (
<section data-testid="trading-mode-tooltip">
<p className={classNames({ 'mb-4': Boolean(compiledGrid) })}>
<span>
{t(
<span className="mb-2">
{notEnoughLiquidity &&
t(
'This market is in auction until it reaches sufficient liquidity.'
)}
{!notEnoughLiquidity &&
t(
'This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.'
)}
</span>{' '}
{VEGA_DOCS_URL && (
<ExternalLink

View File

@ -1,17 +1,27 @@
import { t } from '@vegaprotocol/react-helpers';
export const EST_MARGIN_TOOLTIP_TEXT = t(
'When opening a position on a futures market, you must post margin to cover any potential losses that you may incur. The margin is typically a fraction of the notional position size. For example, for a notional position size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.'
);
export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
`A fraction of the notional position size, in the market's settlement asset %s, to cover any potential losses that you may incur.
For example, for a notional size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.`,
[settlementAsset]
);
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
);
export const EST_CLOSEOUT_TOOLTIP_TEXT = t(
'Because you only need to post a fraction of your position size as margin when trading futures, it is possible to obtain leverage meaning your notional position size exceeds your account balance. In this scenario, if the market moves against your position, it will sometimes be necessary to force close your position due to insufficient funds. The estimated close out tells you the price at which that would happen based on current position and account balance.'
);
export const NOTIONAL_SIZE_TOOLTIP_TEXT = t(
'The notional size represents the position size in the settlement asset of the futures contract. The notional size is calculated by multiplying the number of contracts by the price of the contract. For example, ten contracts traded at a price of $50 has a notional size of $500.'
);
export const EST_CLOSEOUT_TOOLTIP_TEXT = (quote: string) =>
t(
`If the price drops below this number, measured in the market price quote unit %s, you will be closed out, based on your current position and account balance.`,
[quote]
);
export const NOTIONAL_SIZE_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
`The notional size represents the position size in the settlement asset %s of the futures contract. This is calculated by multiplying the number of contracts by the prices of the contract.
For example 10 contracts traded at a price of $50 has a notional size of $500.`,
[settlementAsset]
);
export const EST_FEES_TOOLTIP_TEXT = t(
'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.'
);

View File

@ -72,13 +72,13 @@ export const useFeeDealTicketDetails = (
return null;
}, [derivedPrice, order.size, market.decimalPlaces]);
const symbol =
const assetSymbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
return useMemo(() => {
return {
market,
symbol,
assetSymbol,
notionalSize,
estMargin,
estCloseOut,
@ -87,7 +87,7 @@ export const useFeeDealTicketDetails = (
};
}, [
market,
symbol,
assetSymbol,
notionalSize,
estMargin,
estCloseOut,
@ -98,7 +98,7 @@ export const useFeeDealTicketDetails = (
export interface FeeDetails {
market: Market;
symbol: string;
assetSymbol: string;
notionalSize: string | null;
estMargin: OrderMargin | null;
estCloseOut: string | null;
@ -106,7 +106,7 @@ export interface FeeDetails {
}
export const getFeeDetailsValues = ({
symbol,
assetSymbol,
notionalSize,
estMargin,
estCloseOut,
@ -114,6 +114,7 @@ export const getFeeDetailsValues = ({
}: FeeDetails) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const quoteName = market.tradableInstrument.instrument.product.quoteName;
const formatValueWithMarketDp = (
value: string | number | null | undefined
): string => {
@ -132,8 +133,8 @@ export const getFeeDetailsValues = ({
{
label: t('Notional'),
value: formatValueWithMarketDp(notionalSize),
symbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT,
symbol: assetSymbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
},
{
label: t('Fees'),
@ -144,31 +145,31 @@ export const getFeeDetailsValues = ({
<>
<span>
{t(
'The most you would be expected to pay in fees, the actual amount may vary.'
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
)}
</span>
<FeesBreakdown
fees={estMargin?.fees}
feeFactors={market.fees.factors}
symbol={symbol}
symbol={assetSymbol}
decimals={assetDecimals}
/>
</>
),
symbol,
symbol: assetSymbol,
},
{
label: t('Margin'),
value:
estMargin?.margin && `~${formatValueWithAssetDp(estMargin?.margin)}`,
symbol,
labelDescription: EST_MARGIN_TOOLTIP_TEXT,
symbol: assetSymbol,
labelDescription: EST_MARGIN_TOOLTIP_TEXT(assetSymbol),
},
{
label: t('Liquidation'),
value: estCloseOut && `~${formatValueWithMarketDp(estCloseOut)}`,
symbol: market.tradableInstrument.instrument.product.quoteName,
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT,
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT(quoteName),
},
];
};

View File

@ -93,7 +93,9 @@ export const Info = ({ market, onSelect }: InfoProps) => {
const headerClassName = 'uppercase text-lg';
const dayVolume = calcCandleVolume(market);
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol;
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
@ -129,16 +131,27 @@ export const Info = ({ market, onSelect }: InfoProps) => {
{
title: t('Market price'),
content: (
<>
<MarketInfoTable
data={pick(
data={{
...pick(
market.data,
'name',
'markPrice',
'bestBidPrice',
'bestOfferPrice'
)}
),
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}}
decimalPlaces={market.decimalPlaces}
/>
<p className="text-xs">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
)}
</p>
</>
),
},
{
@ -189,6 +202,10 @@ export const Info = ({ market, onSelect }: InfoProps) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const marketSpecPanels = [
{
title: t('Key details'),
@ -224,6 +241,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
{
title: t('Settlement asset'),
content: asset ? (
<>
<AssetDetailsTable
asset={asset}
inline={true}
@ -231,6 +249,13 @@ export const Info = ({ market, onSelect }: InfoProps) => {
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/>
<p className="text-xs">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
)}
</p>
</>
) : (
<Splash>{t('No data')}</Splash>
),
@ -342,12 +367,20 @@ export const Info = ({ market, onSelect }: InfoProps) => {
{
title: t('Liquidity price range'),
content: (
<>
<p className="text-xs mb-4">
{`For liquidity orders count towards a commitment they have to be
within either the liquidity or price monitoring bounds (whichever is
tighter).`}
</p>
<p className="text-xs mb-4">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<MarketInfoTable
data={{
liquidityPriceRange: formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
),
LPVolumeMin:
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
@ -355,8 +388,8 @@ export const Info = ({ market, onSelect }: InfoProps) => {
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
LPVolumeMax:
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
@ -364,9 +397,10 @@ export const Info = ({ market, onSelect }: InfoProps) => {
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
)} ${quoteUnit}`,
}}
></MarketInfoTable>
</>
),
},
{

View File

@ -16,6 +16,9 @@ export const tooltipMapping: Record<string, ReactNode> = {
markPrice: t(
'A concept derived from traditional markets. It is a calculated value for the current market price on a market.'
),
quoteUnit: t(
`The underlying that is being priced by the market, described by the market's oracle.`
),
openInterest: t(
'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).'
),
@ -98,7 +101,4 @@ export const tooltipMapping: Record<string, ReactNode> = {
`The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.`
),
suppliedStake: t('The current amount of liquidity supplied for this market.'),
liquidityPriceRange: t(
`Percentage move up and down from the mid price, which specifies the range of price levels over which automated liquidity provision orders will be deployed.`
),
};