feat(trading): liquidity SLA in market info (#4649)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: Joe Tsang <30622993+jtsang586@users.noreply.github.com>
Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
m.ray 2023-09-25 16:44:55 +03:00 committed by GitHub
parent 9cbd6baccd
commit a6303456c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 316 additions and 6 deletions

View File

@ -95,7 +95,6 @@ context('Market page', { tags: '@regression' }, function () {
);
cy.validate_element_from_table('Lowest Price', '0.00 fUSDC');
cy.validate_element_from_table('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href')
.and(

View File

@ -1,6 +1,8 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData,
@ -94,6 +96,10 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity SLA protocol')}</h2>
<LiquiditySLAParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} />
{showTwoOracles ? (

View File

@ -5,6 +5,8 @@ import {
InstrumentInfoPanel,
KeyDetailsInfoPanel,
LiquidityMonitoringParametersInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MetadataInfoPanel,
OracleInfoPanel,
PriceMonitoringBoundsInfoPanel,
@ -270,6 +272,21 @@ export const ProposalMarketData = ({
market={marketData}
parentMarket={parentMarketData}
/>
<h2 className={marketDataHeaderStyles}>
{t('Liquidity price range')}
</h2>
<LiquidityPriceRangeInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
<h2 className={marketDataHeaderStyles}>
{t('Liquidity SLA protocol')}
</h2>
<LiquiditySLAParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
</div>
</>
)}

View File

@ -13,7 +13,7 @@ import { createLog } from './logging';
import { getMarkets } from './get-markets';
import { createWalletClient } from './wallet-client';
import { createEthereumWallet } from './ethereum-wallet';
import { ASSET_ID_FOR_MARKET } from './contants';
import { ASSET_ID_FOR_MARKET } from './constants';
const log = createLog('create-market');

View File

@ -4,7 +4,7 @@ import { createLog } from './logging';
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
import { getProposal } from './get-proposal';
import { sendVegaTx } from './wallet-client';
import { ASSET_ID_FOR_MARKET, ASSET_SYMBOL } from './contants';
import { ASSET_ID_FOR_MARKET, ASSET_SYMBOL } from './constants';
const log = createLog('propose-market');

View File

@ -157,6 +157,12 @@ query MarketInfo($marketId: ID!) {
scalingFactor
}
}
liquiditySLAParameters {
priceRange
commitmentMinTimeFraction
performanceHysteresisEpochs
slaCompetitionFactor
}
tradableInstrument {
instrument {
id

File diff suppressed because one or more lines are too long

View File

@ -14,6 +14,7 @@ import {
Link as UILink,
Splash,
AccordionItem,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { generatePath, Link } from 'react-router-dom';
@ -29,6 +30,8 @@ import {
KeyDetailsInfoPanel,
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarketPriceInfoPanel,
MarketVolumeInfoPanel,
MetadataInfoPanel,
@ -265,6 +268,26 @@ export const MarketInfoAccordion = ({
title={t('Liquidity monitoring parameters')}
content={<LiquidityMonitoringParametersInfoPanel market={market} />}
/>
<AccordionItem
itemId="liquidity-price-range"
title={t('Liquidity price range')}
content={<LiquidityPriceRangeInfoPanel market={market} />}
/>
<AccordionItem
itemId="liquidity-sla-parameters"
title={
<span>
<Tooltip
description={t(
'SLA protocol = a part of the Vega protocol that creates similar incentives within the decentralised system to those achieved by a Service Level Agreement between parties in traditional finance. The SLA protocol involves no discussion, agreement, or contracts between parties but instead relies upon rules and an economic mechanism implemented in code running on the network'
)}
>
<span>{t('Liquidity SLA protocol')}</span>
</Tooltip>
</span>
}
content={<LiquiditySLAParametersInfoPanel market={market} />}
/>
<AccordionItem
itemId="liquidity"
title={t('Liquidity')}

View File

@ -15,6 +15,7 @@ import {
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
formatNumber,
formatNumberPercentage,
getDateTimeFormat,
@ -58,6 +59,10 @@ import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals'
import { getQuoteName, getAsset } from '../../market-utils';
import classNames from 'classnames';
import compact from 'lodash/compact';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import type { DataSourceFragment } from './__generated__/MarketInfo';
import { formatDuration } from 'date-fns';
@ -654,6 +659,193 @@ export const LiquidityMonitoringParametersInfoPanel = ({
return <MarketInfoTable data={marketData} parentData={parentMarketData} />;
};
export const LiquidityPriceRangeInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const marketLpPriceRange = market.liquiditySLAParameters?.priceRange;
const parentMarketLpPriceRange =
parentMarket?.liquiditySLAParameters?.priceRange;
const quoteUnit =
('quoteName' in market.tradableInstrument.instrument.product &&
market?.tradableInstrument.instrument.product?.quoteName) ||
'';
const parentQuoteUnit =
(parentMarket &&
'quoteName' in parentMarket.tradableInstrument.instrument.product &&
parentMarket?.tradableInstrument.instrument.product?.quoteName) ||
'';
const liquidityPriceRange =
marketLpPriceRange &&
formatNumberPercentage(new BigNumber(marketLpPriceRange).times(100));
const parentLiquidityPriceRange =
parentMarket && parentMarketLpPriceRange
? formatNumberPercentage(
new BigNumber(parentMarketLpPriceRange).times(100)
)
: null;
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
});
const { data: parentMarketData } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: parentMarket?.id || '' },
skip: !parentMarket,
});
let parentData;
if (parentMarket && parentMarketData && quoteUnit === parentQuoteUnit) {
parentData = {
liquidityPriceRange: `${parentLiquidityPriceRange} of mid price`,
lowestPrice:
parentMarketLpPriceRange &&
parentMarketData?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(parentMarketLpPriceRange)
.times(parentMarketData.midPrice)
.toString(),
parentMarket.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
parentMarketLpPriceRange &&
parentMarketData?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(parentMarketLpPriceRange)
.times(parentMarketData.midPrice)
.toString(),
parentMarket.decimalPlaces
)} ${quoteUnit}`,
};
}
return (
<>
<p className="text-xs mb-2 border-l-2 pl-2">
{`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`}
</p>
<MarketInfoTable
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
marketLpPriceRange &&
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(marketLpPriceRange)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
marketLpPriceRange &&
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(marketLpPriceRange)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
parentData={parentData}
/>
<p className="text-xs mb-2 border-l-2 pl-2 mt-2">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
</>
);
};
const fromNanoSecondsToSeconds = (nanoseconds: number | string) =>
t('%ss', [new BigNumber(nanoseconds).dividedBy(1e9).toString()]);
export const LiquiditySLAParametersInfoPanel = ({
market,
parentMarket,
}: MarketInfoProps) => {
const marketData = {
performanceHysteresisEpochs:
market.liquiditySLAParameters?.performanceHysteresisEpochs,
SLACompetitionFactor:
market.liquiditySLAParameters?.slaCompetitionFactor &&
formatNumberPercentage(
new BigNumber(
market.liquiditySLAParameters?.slaCompetitionFactor
).times(100)
),
commitmentMinimumTimeFraction:
market.liquiditySLAParameters?.commitmentMinTimeFraction &&
formatNumberPercentage(
new BigNumber(
market.liquiditySLAParameters?.commitmentMinTimeFraction
).times(100)
),
};
const parentMarketData = parentMarket
? {
performanceHysteresisEpochs:
parentMarket.liquiditySLAParameters?.performanceHysteresisEpochs,
slaCompetitionFactor:
parentMarket.liquiditySLAParameters?.slaCompetitionFactor,
commitmentMinimumTimeFraction:
parentMarket.liquiditySLAParameters?.commitmentMinTimeFraction,
}
: undefined;
const { params: networkParams } = useNetworkParams([
NetworkParams.market_liquidity_bondPenaltyParameter,
NetworkParams.market_liquidity_nonPerformanceBondPenaltySlope,
NetworkParams.market_liquidity_sla_nonPerformanceBondPenaltyMax,
NetworkParams.market_liquidity_maximumLiquidityFeeFactorLevel,
NetworkParams.market_liquidity_stakeToCcyVolume,
NetworkParams.validators_epoch_length,
NetworkParams.market_liquidity_earlyExitPenalty,
NetworkParams.market_liquidity_probabilityOfTrading_tau_scaling,
NetworkParams.market_liquidity_minimum_probabilityOfTrading_lpOrders,
NetworkParams.market_liquidity_feeCalculationTimeStep,
]);
const marketNetworkParamData = {
epochLength: networkParams['validators_epoch_length'],
bondPenaltyParameter:
networkParams['market_liquidity_bondPenaltyParameter'],
nonPerformanceBondPenaltySlope:
networkParams['market_liquidity_nonPerformanceBondPenaltySlope'],
nonPerformanceBondPenaltyMax:
networkParams['market_liquidity_sla_nonPerformanceBondPenaltyMax'],
maximumLiquidityFeeFactorLevel:
networkParams['market_liquidity_maximumLiquidityFeeFactorLevel'],
stakeToCCYVolume: networkParams['market_liquidity_stakeToCcyVolume'],
earlyExitPenalty: networkParams['market_liquidity_earlyExitPenalty'],
probabilityOfTradingTauScaling:
networkParams['market_liquidity_probabilityOfTrading_tau_scaling'],
minimumProbabilityOfTradingLPOrders:
networkParams['market_liquidity_minimum_probabilityOfTrading_lpOrders'],
feeCalculationTimeStep:
networkParams['market_liquidity_feeCalculationTimeStep'] &&
fromNanoSecondsToSeconds(
networkParams['market_liquidity_feeCalculationTimeStep']
),
};
return (
<>
<MarketInfoTable data={marketData} parentData={parentMarketData} />
<MarketInfoTable data={marketNetworkParamData} unformatted={true} />
</>
);
};
export const LiquidityInfoPanel = ({ market, children }: MarketInfoProps) => {
const asset = getAsset(market);
const { data } = useDataProvider({

View File

@ -101,6 +101,12 @@ export const marketInfoQuery = (
},
__typename: 'LiquidityMonitoringParameters',
},
liquiditySLAParameters: {
priceRange: '0.95',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 4,
slaCompetitionFactor: '0.5',
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {

View File

@ -106,4 +106,43 @@ export const tooltipMapping: Record<string, ReactNode> = {
insurancePoolFraction: t(
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
),
commitmentMinimumTimeFraction: t(
`Specifies the minimum fraction of time LPs must spend 'on the book' providing their committed liquidity. This is a market parameter.`
),
feeCalculationTimeStep: t(
'How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. '
),
performanceHysteresisEpochs: t(
'Number of epochs over which past performance will continue to affect rewards. This is a market parameter.'
),
SLACompetitionFactor: t(
`Maximum fraction of an LP's accrued fees that an LP would lose to liquidity providers that achieved a higher SLA performance than them. This is a market parameter. `
),
bondPenaltyParameter: t(
'Used to calculate the penalty to liquidity providers when they cannot support their open position with the assets in their margin and general accounts. This is a network parameter.'
),
nonPerformanceBondPenaltySlope: t(
'A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.'
),
nonPerformanceBondPenaltyMax: t(
`The maximum amount, as a fraction, that an LP's bond can be slashed by if they fail to reach the minimum SLA. This is a network parameter.`
),
maximumLiquidityFeeFactorLevel: t(
'Maximum value that a proposed fee amount can be, which is submitted as part of the LP commitment transaction. Note that a value of 0.05 = 5%. This is a network parameter.'
),
stakeToCCYVolume: t(
`Multiplier used to translate an LP's commitment amount to their liquidity obligation. This is a network parameter.`
),
epochLength: t(
'How long an epoch is. LP rewards from liquidity fees are paid out once per epoch. How much they receive depends on whether they met the liquidity SLA and their previous performance in recent epochs. This is a network parameter.'
),
earlyExitPenalty: t(
`How much an LP forfeits of their bond if they reduce their commitment while the market is below target stake, expressed as a factor. If set to 0 there is no penalty for early exit. If set to 1 an LP's entire bond is forfeited when an LP removes their full commitment. This is a network parameter.`
),
probabilityOfTradingTauScaling: t(
`Determines how the probability of trading is scaled from the risk model, and is used to measure the relative competitiveness of an LP's supplied volume. This is a network parameter.`
),
minimumProbabilityOfTradingLPOrders: t(
'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.'
),
};

View File

@ -105,9 +105,25 @@ export const NetworkParams = {
'spam_protection_minimumWithdrawalQuantumMultiple',
spam_protection_voting_min_tokens: 'spam_protection_voting_min_tokens',
spam_protection_proposal_min_tokens: 'spam_protection_proposal_min_tokens',
market_liquidity_stakeToCcyVolume: 'market_liquidity_stakeToCcyVolume',
market_liquidity_targetstake_triggering_ratio:
'market_liquidity_targetstake_triggering_ratio',
market_liquidity_bondPenaltyParameter:
'market_liquidity_bondPenaltyParameter',
market_liquidity_nonPerformanceBondPenaltySlope:
'market_liquidity_nonPerformanceBondPenaltySlope',
market_liquidity_sla_nonPerformanceBondPenaltyMax:
'market_liquidity_sla_nonPerformanceBondPenaltyMax',
market_liquidity_maximumLiquidityFeeFactorLevel:
'market_liquidity_maximumLiquidityFeeFactorLevel',
market_liquidity_stakeToCcyVolume: 'market_liquidity_stakeToCcyVolume',
validators_epoch_length: 'validators_epoch_length',
market_liquidity_earlyExitPenalty: 'market_liquidity_earlyExitPenalty',
market_liquidity_probabilityOfTrading_tau_scaling:
'market_liquidity_probabilityOfTrading_tau_scaling',
market_liquidity_minimum_probabilityOfTrading_lpOrders:
'market_liquidity_minimum_probabilityOfTrading_lpOrders',
market_liquidity_feeCalculationTimeStep:
'market_liquidity_feeCalculationTimeStep',
transfer_fee_factor: 'transfer_fee_factor',
network_validators_incumbentBonus: 'network_validators_incumbentBonus',
} as const;