chore(trading): add quantum formatting to deal ticket (#4030)

This commit is contained in:
m.ray 2023-06-07 13:49:50 +03:00 committed by GitHub
parent 43d3754c64
commit 2ba0e9a1b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 206 additions and 33 deletions

View File

@ -100,9 +100,8 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
.within(() => {
cy.get('[data-state="closed"]').should(
'have.text',
'Total margin available'
'Total margin available100,000.01 tDAI'
);
cy.get('.text-neutral-500').should('have.text', '100,000.01 tDAI');
});
});
});

View File

@ -33,7 +33,15 @@ export const DealTicketFeeDetails = (props: FeeDetails) => {
const details = getFeeDetailsValues(props);
return (
<div>
{details.map(({ label, value, labelDescription, symbol, indent }) => (
{details.map(
({
label,
value,
labelDescription,
symbol,
indent,
formattedValue,
}) => (
<div
key={typeof label === 'string' ? label : 'value-dropdown'}
className={classnames(
@ -46,11 +54,14 @@ export const DealTicketFeeDetails = (props: FeeDetails) => {
<div>{label}</div>
</Tooltip>
</div>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
<div className="text-neutral-500 dark:text-neutral-300">{`${
value ?? '-'
formattedValue ?? '-'
} ${symbol || ''}`}</div>
</Tooltip>
</div>
))}
)
)}
</div>
);
};

View File

@ -0,0 +1,68 @@
import { formatRange, formatValue } from './use-fee-deal-ticket-details';
describe('useFeeDealTicketDetails', () => {
it.each([
{ v: 123000, d: 5, o: '1.23' },
{ v: 123000, d: 3, o: '123.00' },
{ v: 123000, d: 1, o: '12,300.0' },
{ v: 123001000, d: 2, o: '1,230,010.00' },
{ v: 123001, d: 2, o: '1,230.01' },
{
v: '123456789123456789',
d: 10,
o: '12,345,678.91234568',
},
])('formats values correctly', ({ v, d, o }) => {
expect(formatValue(v, d)).toStrictEqual(o);
});
it.each([
{ v: 123000, d: 5, o: '1.23', q: '0.1' },
{ v: 123000, d: 3, o: '123.00', q: '0.1' },
{ v: 123000, d: 1, o: '12,300.00', q: '0.1' },
{ v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' },
{ v: 123001, d: 2, o: '1,230', q: '100' },
{ v: 123001, d: 2, o: '1,230.01', q: '0.1' },
{
v: '123456789123456789',
d: 10,
o: '12,345,678.9123457',
q: '0.00003846',
},
])(
'formats with formatValue with quantum given number correctly',
({ v, d, o, q }) => {
expect(formatValue(v.toString(), d, q)).toStrictEqual(o);
}
);
it.each([
{ min: 123000, max: 12300011111, d: 5, o: '1.23 - 123,000.111', q: '0.1' },
{
min: 123000,
max: 12300011111,
d: 3,
o: '123.00 - 12,300,011.111',
q: '0.1',
},
{
min: 123000,
max: 12300011111,
d: 1,
o: '12,300.00 - 1,230,001,111.10',
q: '0.1',
},
{
min: 123001000,
max: 12300011111,
d: 2,
o: '1,230,010 - 123,000,111',
q: '100',
},
])(
'formats with formatValue with quantum given number correctly',
({ min, max, d, o, q }) => {
expect(formatRange(min, max, d, q)).toStrictEqual(o);
}
);
});

View File

@ -1,5 +1,9 @@
import { FeesBreakdown } from '@vegaprotocol/markets';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
isNumeric,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { Market } from '@vegaprotocol/markets';
@ -52,21 +56,25 @@ export interface FeeDetails {
}
const emptyValue = '-';
const formatValue = (
export const formatValue = (
value: string | number | null | undefined,
formatDecimals: number
formatDecimals: number,
quantum?: string
): string => {
return isNumeric(value)
? addDecimalsFormatNumber(value, formatDecimals)
: emptyValue;
if (!isNumeric(value)) return emptyValue;
if (!quantum) return addDecimalsFormatNumber(value, formatDecimals);
return addDecimalsFormatNumberQuantum(value, formatDecimals, quantum);
};
const formatRange = (
export const formatRange = (
min: string | number | null | undefined,
max: string | number | null | undefined,
formatDecimals: number
formatDecimals: number,
quantum?: string
) => {
const minFormatted = formatValue(min, formatDecimals);
const maxFormatted = formatValue(max, formatDecimals);
const minFormatted = formatValue(min, formatDecimals, quantum);
const maxFormatted = formatValue(max, formatDecimals, quantum);
if (minFormatted !== maxFormatted) {
return `${minFormatted} - ${maxFormatted}`;
}
@ -93,9 +101,12 @@ export const getFeeDetailsValues = ({
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const quantum =
market.tradableInstrument.instrument.product.settlementAsset.quantum;
const details: {
label: string;
value?: string | null;
formattedValue?: string | null;
symbol: string;
indent?: boolean;
labelDescription?: React.ReactNode;
@ -103,6 +114,7 @@ export const getFeeDetailsValues = ({
{
label: t('Notional'),
value: formatValue(notionalSize, assetDecimals),
formattedValue: formatValue(notionalSize, assetDecimals, quantum),
symbol: assetSymbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
},
@ -111,6 +123,9 @@ export const getFeeDetailsValues = ({
value:
feeEstimate?.totalFeeAmount &&
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`,
formattedValue:
feeEstimate?.totalFeeAmount &&
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`,
labelDescription: (
<>
<span>
@ -154,6 +169,12 @@ export const getFeeDetailsValues = ({
}
details.push({
label: t('Margin required'),
formattedValue: formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals,
quantum
),
value: formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
@ -172,12 +193,13 @@ export const getFeeDetailsValues = ({
details.push({
indent: true,
label: t('Total margin available'),
formattedValue: formatValue(totalMarginAvailable, assetDecimals, quantum),
value: formatValue(totalMarginAvailable, assetDecimals),
symbol: assetSymbol,
labelDescription: TOTAL_MARGIN_AVAILABLE(
formatValue(generalAccountBalance, assetDecimals),
formatValue(marginAccountBalance, assetDecimals),
formatValue(currentMaintenanceMargin, assetDecimals),
formatValue(generalAccountBalance, assetDecimals, quantum),
formatValue(marginAccountBalance, assetDecimals, quantum),
formatValue(currentMaintenanceMargin, assetDecimals, quantum),
assetSymbol
),
});
@ -203,6 +225,16 @@ export const getFeeDetailsValues = ({
: '0',
assetDecimals
),
formattedValue: formatRange(
deductionFromCollateralBestCase > 0
? deductionFromCollateralBestCase.toString()
: '0',
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
assetDecimals,
quantum
),
symbol: assetSymbol,
labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol),
});
@ -214,6 +246,12 @@ export const getFeeDetailsValues = ({
marginEstimate?.worstCase.initialLevel,
assetDecimals
),
formattedValue: formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
assetDecimals,
quantum
),
symbol: assetSymbol,
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
});
@ -223,9 +261,11 @@ export const getFeeDetailsValues = ({
value: formatValue(marginAccountBalance, assetDecimals),
symbol: assetSymbol,
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
formattedValue: formatValue(marginAccountBalance, assetDecimals, quantum),
});
let liquidationPriceEstimate = emptyValue;
let liquidationPriceEstimateFormatted;
if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
@ -262,11 +302,24 @@ export const getFeeDetailsValues = ({
).toString(),
assetDecimals
);
liquidationPriceEstimateFormatted = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
(liquidationEstimateBestCase > liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
assetDecimals,
quantum
);
}
details.push({
label: t('Liquidation price estimate'),
value: liquidationPriceEstimate,
formattedValue: liquidationPriceEstimateFormatted,
symbol: assetSymbol,
labelDescription: LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
});

View File

@ -32,6 +32,7 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
symbol: 'tDAI',
name: 'tDAI',
decimals: 5,
quantum: '1',
__typename: 'Asset',
},
dataSourceSpecForTradingTermination: {

View File

@ -75,6 +75,7 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
name: 'assset-id',
symbol: 'SYM',
decimals: 18,
quantum: '1',
},
quoteName: '',
dataSourceSpecForTradingTermination: {

View File

@ -7,12 +7,12 @@ export type DataSourceFilterFragment = { __typename?: 'Filter', key: { __typenam
export type DataSourceSpecFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
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, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: 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, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } };
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, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: 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, quantum: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null };
export const DataSourceFilterFragmentDoc = gql`
fragment DataSourceFilter on Filter {
@ -77,6 +77,7 @@ export const MarketFieldsFragmentDoc = gql`
symbol
name
decimals
quantum
}
quoteName
dataSourceSpecForTradingTermination {

View File

@ -58,6 +58,7 @@ fragment MarketFields on Market {
symbol
name
decimals
quantum
}
quoteName
dataSourceSpecForTradingTermination {

View File

@ -60,6 +60,7 @@ export const createMarketFragment = (
symbol: 'tDAI',
name: 'tDAI',
decimals: 5,
quantum: '1',
__typename: 'Asset',
},
dataSourceSpecForTradingTermination: {

View File

@ -47,6 +47,7 @@ export const generateOrder = (partialOrder?: PartialDeep<Order>) => {
decimals: 1,
symbol: 'XYZ',
name: 'XYZ',
quantum: '1',
},
dataSourceSpecForTradingTermination: {
__typename: 'DataSourceSpec',

View File

@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js';
import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
formatNumber,
formatNumberPercentage,
isNumeric,
@ -23,6 +24,28 @@ describe('number utils', () => {
}
);
it.each([
{ v: new BigNumber(123000), d: 5, o: '1.23', q: 0.1 },
{ v: new BigNumber(123000), d: 3, o: '123.00', q: 0.1 },
{ v: new BigNumber(123000), d: 1, o: '12,300.00', q: 0.1 },
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00', q: 0.1 },
{ v: new BigNumber(123001), d: 2, o: '1,230', q: 100 },
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 0.1 },
{
v: BigNumber('123456789123456789'),
d: 10,
o: '12,345,678.9123457',
q: '0.00003846',
},
])(
'formats with addDecimalsFormatNumberQuantum given number correctly',
({ v, d, o, q }) => {
expect(addDecimalsFormatNumberQuantum(v.toString(), d, q)).toStrictEqual(
o
);
}
);
it.each([
{ v: new BigNumber(123), d: 3, o: '123.00' },
{ v: new BigNumber(123.123), d: 3, o: '123.123' },

View File

@ -90,6 +90,18 @@ export const formatNumberFixed = (
return getFixedNumberFormat(formatDecimals).format(Number(rawValue));
};
export const addDecimalsFormatNumberQuantum = (
rawValue: string | number,
decimalPlaces: number,
quantum: number | string
) => {
if (isNaN(Number(quantum))) {
return addDecimalsFormatNumber(rawValue, decimalPlaces);
}
const numberDP = Math.max(0, Math.log10(100 / Number(quantum)));
return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP));
};
export const addDecimalsFormatNumber = (
rawValue: string | number,
decimalPlaces: number,

View File

@ -165,6 +165,7 @@ describe('WithdrawFormContainer', () => {
name: 'asset-id',
symbol: 'tUSDC',
decimals: 5,
quantum: '1',
},
dataSourceSpecForTradingTermination: {
__typename: 'DataSourceSpec',