diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts index 418d1c290..00d4f1c9d 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts @@ -16,6 +16,10 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { cy.wait('@Markets'); }); + beforeEach(() => { + cy.mockTradingPage(); + }); + describe('limit order', () => { before(() => { cy.getByTestId(toggleLimit).click(); @@ -98,7 +102,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { 'have.text', 'Total margin available' ); - cy.get('.text-neutral-500').should('have.text', '~100,000.01 tDAI'); + cy.get('.text-neutral-500').should('have.text', '100,000.01 tDAI'); }); }); }); diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts index c964a485a..dd6c8f04e 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts @@ -1,10 +1,6 @@ import * as Schema from '@vegaprotocol/types'; import { aliasGQLQuery } from '@vegaprotocol/cypress'; -import { - accountsQuery, - amendGeneralAccountBalance, - estimateOrderQuery, -} from '@vegaprotocol/mock'; +import { accountsQuery, amendGeneralAccountBalance } from '@vegaprotocol/mock'; import type { OrderSubmission } from '@vegaprotocol/wallet'; import { createOrder } from '../support/create-order'; @@ -44,13 +40,10 @@ describe( cy.setVegaWallet(); cy.mockTradingPage(); const accounts = accountsQuery(); - amendGeneralAccountBalance(accounts, 'market-0', '100000000'); + amendGeneralAccountBalance(accounts, 'market-0', '1'); cy.mockGQL((req) => { aliasGQLQuery(req, 'Accounts', accounts); }); - cy.mockGQL((req) => { - aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery()); - }); cy.mockSubscription(); cy.visit('/#/markets/market-0'); cy.wait('@Markets'); @@ -66,7 +59,7 @@ describe( ); cy.getByTestId('dealticket-warning-margin').should( 'contain.text', - 'You may not have enough margin available to open this position. 2,354.72283 tDAI is currently required. You have only 1,000.01 tDAI available.' + 'You may not have enough margin available to open this position. 5.00 tDAI is currently required. You have only 0.01001 tDAI available.' ); cy.getByTestId('deal-ticket-deposit-dialog-button').click(); cy.getByTestId('dialog-content') diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts index 17b9f35f0..d27952d71 100644 --- a/apps/trading-e2e/src/support/trading.ts +++ b/apps/trading-e2e/src/support/trading.ts @@ -10,7 +10,7 @@ import { chainIdQuery, chartQuery, depositsQuery, - estimateOrderQuery, + estimateFeesQuery, marginsQuery, marketCandlesQuery, marketDataQuery, @@ -22,11 +22,14 @@ import { networkParamsQuery, nodeGuardQuery, ordersQuery, + estimatePositionQuery, positionsQuery, proposalListQuery, statisticsQuery, tradesQuery, withdrawalsQuery, + protocolUpgradeProposalsQuery, + blockStatisticsQuery, } from '@vegaprotocol/mock'; import type { PartialDeep } from 'type-fest'; import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list'; @@ -157,9 +160,16 @@ const mockTradingPage = ( aliasGQLQuery(req, 'Candles', candlesQuery()); aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery()); aliasGQLQuery(req, 'NetworkParams', networkParamsQuery()); - aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery()); + aliasGQLQuery(req, 'EstimateFees', estimateFeesQuery()); + aliasGQLQuery(req, 'EstimatePosition', estimatePositionQuery()); aliasGQLQuery(req, 'ProposalsList', proposalListQuery()); aliasGQLQuery(req, 'Deposits', depositsQuery()); + aliasGQLQuery( + req, + 'ProtocolUpgradeProposals', + protocolUpgradeProposalsQuery() + ); + aliasGQLQuery(req, 'BlockStatistics', blockStatisticsQuery()); }; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace diff --git a/libs/accounts/src/lib/use-account-balance.tsx b/libs/accounts/src/lib/use-account-balance.tsx index 0807f8b39..2b50182bf 100644 --- a/libs/accounts/src/lib/use-account-balance.tsx +++ b/libs/accounts/src/lib/use-account-balance.tsx @@ -33,9 +33,9 @@ export const useAccountBalance = (assetId?: string) => { return useMemo( () => ({ - accountBalance, - accountDecimals, + accountBalance: pubKey ? accountBalance : '', + accountDecimals: pubKey ? accountDecimals : null, }), - [accountBalance, accountDecimals] + [accountBalance, accountDecimals, pubKey] ); }; diff --git a/libs/accounts/src/lib/use-market-account-balance.tsx b/libs/accounts/src/lib/use-market-account-balance.tsx index 1d6317da4..8491598a2 100644 --- a/libs/accounts/src/lib/use-market-account-balance.tsx +++ b/libs/accounts/src/lib/use-market-account-balance.tsx @@ -32,9 +32,9 @@ export const useMarketAccountBalance = (marketId: string) => { return useMemo( () => ({ - accountBalance, - accountDecimals, + accountBalance: pubKey ? accountBalance : '', + accountDecimals: pubKey ? accountDecimals : null, }), - [accountBalance, accountDecimals] + [accountBalance, accountDecimals, pubKey] ); }; diff --git a/libs/cypress/mock.ts b/libs/cypress/mock.ts index d2aff6cfc..8e28e912c 100644 --- a/libs/cypress/mock.ts +++ b/libs/cypress/mock.ts @@ -23,5 +23,8 @@ export * from '../orders/src/lib/components/order-data-provider/orders.mock'; export * from '../positions/src/lib/positions.mock'; export * from '../network-parameters/src/network-params.mock'; export * from '../wallet/src/connect-dialog/chain-id.mock'; +export * from '../positions/src/lib/estimate-position.mock'; export * from '../trades/src/lib/trades.mock'; export * from '../withdraws/src/lib/withdrawal.mock'; +export * from '../proposals/src/lib/protocol-upgrade-proposals/protocol-statistics-proposals.mock'; +export * from '../proposals/src/lib/protocol-upgrade-proposals/block-statistics.mock'; diff --git a/libs/cypress/src/lib/mock-gql.ts b/libs/cypress/src/lib/mock-gql.ts index 56c497533..1303ff282 100644 --- a/libs/cypress/src/lib/mock-gql.ts +++ b/libs/cypress/src/lib/mock-gql.ts @@ -17,7 +17,12 @@ const hasOperationName = ( operationName: string ) => { const { body } = req; - return 'operationName' in body && body.operationName === operationName; + return ( + typeof body === 'object' && + body !== null && + 'operationName' in body && + body.operationName === operationName + ); }; export function addMockGQLCommand() { diff --git a/libs/cypress/src/lib/mock-ws.ts b/libs/cypress/src/lib/mock-ws.ts index c651a4ea2..90124f4d5 100644 --- a/libs/cypress/src/lib/mock-ws.ts +++ b/libs/cypress/src/lib/mock-ws.ts @@ -20,8 +20,12 @@ const mockSocketServer = Cypress.env('VEGA_URL') : null; // DO NOT REMOVE: PASSTHROUGH for walletconnect -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const relayServer = new Server('wss://relay.walletconnect.com', { +new Server('wss://relay.walletconnect.com', { + mock: false, +}); + +// DO NOT REMOVE: PASSTHROUGH for hot module reload +new Server('ws://localhost:4200/_next/webpack-hmr', { mock: false, }); diff --git a/libs/deal-ticket/src/components/deal-ticket-estimates.tsx b/libs/deal-ticket/src/components/deal-ticket-estimates.tsx deleted file mode 100644 index 1df6b7834..000000000 --- a/libs/deal-ticket/src/components/deal-ticket-estimates.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react'; -import type { ReactNode } from 'react'; -import { t } from '@vegaprotocol/i18n'; -import { Icon, Tooltip, TrafficLight } from '@vegaprotocol/ui-toolkit'; -import { IconNames } from '@blueprintjs/icons'; -import * as constants from '../constants'; - -interface DealTicketEstimatesProps { - quoteName?: string; - price?: string; - estCloseOut?: string; - estMargin?: string; - fees?: string; - notionalSize?: string; - size?: string; - slippage?: string; -} - -export const DealTicketEstimates = ({ - price, - quoteName, - estCloseOut, - estMargin, - fees, - notionalSize, - size, - slippage, -}: DealTicketEstimatesProps) => ( -
- {size && ( -
- {t('Contracts')} - -
- )} - {price && ( -
- {t('Est. Price')} -
{price}
-
- )} - {notionalSize && ( -
- {t('Est. Position Size')} - -
- )} - {fees && ( -
- {t('Est. Fees')} - -
- )} - {estMargin && ( -
- {t('Est. Margin')} - -
- )} - {estCloseOut && ( -
- {t('Est. Close out')} - -
- )} - {slippage && ( -
- {t('Est. Price Impact / Slippage')} - - - {slippage}% - - -
- )} -
-); - -interface DataTitleProps { - children: ReactNode; - quoteName?: string; -} - -export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => ( -
- {children} - {quoteName && ({quoteName})} -
-); - -interface ValueTooltipProps { - value?: string; - children?: ReactNode; - description: string; - id?: string; -} - -export const ValueTooltipRow = ({ - value, - children, - description, - id, -}: ValueTooltipProps) => ( -
- {value || children} - -
- -
-
-
-); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx index 16d2a877c..52969be14 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx @@ -1,24 +1,8 @@ import { Tooltip } from '@vegaprotocol/ui-toolkit'; import classnames from 'classnames'; import type { ReactNode } from 'react'; -import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; -import type { Market, MarketData } from '@vegaprotocol/market-list'; -import { - getFeeDetailsValues, - useFeeDealTicketDetails, -} from '../../hooks/use-fee-deal-ticket-details'; - -interface DealTicketFeeDetailsProps { - order: OrderSubmissionBody['orderSubmission']; - market: Market; - marketData: MarketData; - currentInitialMargin?: string; - currentMaintenanceMargin?: string; - estimatedInitialMargin: string; - estimatedTotalInitialMargin: string; - marginAccountBalance: string; - generalAccountBalance: string; -} +import { getFeeDetailsValues } from '../../hooks/use-fee-deal-ticket-details'; +import type { FeeDetails } from '../../hooks/use-fee-deal-ticket-details'; export interface DealTicketFeeDetailProps { label: string; @@ -45,17 +29,8 @@ export const DealTicketFeeDetail = ({ ); -export const DealTicketFeeDetails = ({ - order, - market, - marketData, - ...args -}: DealTicketFeeDetailsProps) => { - const feeDetails = useFeeDealTicketDetails(order, market, marketData); - const details = getFeeDetailsValues({ - ...feeDetails, - ...args, - }); +export const DealTicketFeeDetails = (props: FeeDetails) => { + const details = getFeeDetailsValues(props); return (
{details.map(({ label, value, labelDescription, symbol, indent }) => ( diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index 2cba96fd7..6eabd2f58 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -25,6 +25,16 @@ import { TinyScroll, } from '@vegaprotocol/ui-toolkit'; +import { + useEstimatePositionQuery, + useOpenVolume, +} from '@vegaprotocol/positions'; +import { toBigNum, removeDecimal } from '@vegaprotocol/utils'; +import { activeOrdersProvider } from '@vegaprotocol/orders'; +import { useEstimateFees } from '../../hooks/use-fee-deal-ticket-details'; +import { getDerivedPrice } from '../../utils/get-price'; +import type { OrderInfo } from '@vegaprotocol/types'; + import { validateExpiration, validateMarketState, @@ -34,7 +44,6 @@ import { } from '../../utils'; import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error'; import { SummaryValidationType } from '../../constants'; -import { useInitialMargin } from '../../hooks/use-initial-margin'; import type { Market, MarketData } from '@vegaprotocol/market-list'; import { MarginWarning } from '../deal-ticket-validation/margin-warning'; import { @@ -104,7 +113,67 @@ export const DealTicket = ({ market.positionDecimalPlaces ); - const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder); + const price = useMemo(() => { + return normalizedOrder && getDerivedPrice(normalizedOrder, marketData); + }, [normalizedOrder, marketData]); + + const notionalSize = useMemo(() => { + if (price && normalizedOrder?.size) { + return removeDecimal( + toBigNum( + normalizedOrder.size, + market.positionDecimalPlaces + ).multipliedBy(toBigNum(price, market.decimalPlaces)), + asset.decimals + ); + } + return null; + }, [ + price, + normalizedOrder?.size, + market.decimalPlaces, + market.positionDecimalPlaces, + asset.decimals, + ]); + + const feeEstimate = useEstimateFees( + normalizedOrder && { ...normalizedOrder, price } + ); + const { data: activeOrders } = useDataProvider({ + dataProvider: activeOrdersProvider, + variables: { partyId: pubKey || '' }, + skip: !pubKey, + }); + const openVolume = useOpenVolume(pubKey, market.id) ?? '0'; + const orders = activeOrders + ? activeOrders.map(({ node: order }) => ({ + isMarketOrder: order.type === OrderType.TYPE_MARKET, + price: order.price, + remaining: order.remaining, + side: order.side, + })) + : []; + if (normalizedOrder) { + orders.push({ + isMarketOrder: normalizedOrder.type === OrderType.TYPE_MARKET, + price: normalizedOrder.price ?? '0', + remaining: normalizedOrder.size, + side: normalizedOrder.side, + }); + } + const { data: positionEstimate } = useEstimatePositionQuery({ + variables: { + marketId: market.id, + openVolume, + orders, + collateralAvailable: + marginAccountBalance || generalAccountBalance ? balance : undefined, + }, + skip: !normalizedOrder, + }); + + const assetSymbol = + market.tradableInstrument.instrument.product.settlementAsset.symbol; const { data: currentMargins } = useDataProvider({ dataProvider: marketMarginDataProvider, @@ -401,7 +470,10 @@ export const DealTicket = ({ asset={asset} marketTradingMode={marketData.marketTradingMode} balance={balance} - margin={totalMargin} + margin={ + positionEstimate?.estimatePosition?.margin.bestCase.initialLevel || + '0' + } isReadOnly={isReadOnly} pubKey={pubKey} onClickCollateral={onClickCollateral} @@ -413,15 +485,15 @@ export const DealTicket = ({ } /> diff --git a/libs/deal-ticket/src/components/index.ts b/libs/deal-ticket/src/components/index.ts index dbc74f55b..23656b03d 100644 --- a/libs/deal-ticket/src/components/index.ts +++ b/libs/deal-ticket/src/components/index.ts @@ -1,4 +1,3 @@ export * from './deal-ticket'; export * from './deal-ticket-validation'; export * from './trading-mode-tooltip'; -export * from './deal-ticket-estimates'; diff --git a/libs/deal-ticket/src/constants.ts b/libs/deal-ticket/src/constants.ts index a7802bcf8..1517df713 100644 --- a/libs/deal-ticket/src/constants.ts +++ b/libs/deal-ticket/src/constants.ts @@ -59,6 +59,10 @@ 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.' ); +export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t( + 'This is a approximation to the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.' +); + export const EST_SLIPPAGE = t( 'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.' ); diff --git a/libs/deal-ticket/src/hooks/EstimateOrder.graphql b/libs/deal-ticket/src/hooks/EstimateOrder.graphql index 88d3a6a4e..d7dcbe99f 100644 --- a/libs/deal-ticket/src/hooks/EstimateOrder.graphql +++ b/libs/deal-ticket/src/hooks/EstimateOrder.graphql @@ -1,4 +1,4 @@ -query EstimateOrder( +query EstimateFees( $marketId: ID! $partyId: ID! $price: String @@ -8,7 +8,7 @@ query EstimateOrder( $expiration: Timestamp $type: OrderType! ) { - estimateOrder( + estimateFees( marketId: $marketId partyId: $partyId price: $price @@ -18,14 +18,11 @@ query EstimateOrder( expiration: $expiration type: $type ) { - fee { + fees { makerFee infrastructureFee liquidityFee } - marginLevels { - initialLevel - } totalFeeAmount } } diff --git a/libs/deal-ticket/src/hooks/__generated__/EstimateOrder.ts b/libs/deal-ticket/src/hooks/__generated__/EstimateOrder.ts index abfddbe98..647e56be9 100644 --- a/libs/deal-ticket/src/hooks/__generated__/EstimateOrder.ts +++ b/libs/deal-ticket/src/hooks/__generated__/EstimateOrder.ts @@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; -export type EstimateOrderQueryVariables = Types.Exact<{ +export type EstimateFeesQueryVariables = Types.Exact<{ marketId: Types.Scalars['ID']; partyId: Types.Scalars['ID']; price?: Types.InputMaybe; @@ -15,12 +15,12 @@ export type EstimateOrderQueryVariables = Types.Exact<{ }>; -export type EstimateOrderQuery = { __typename?: 'Query', estimateOrder: { __typename?: 'OrderEstimate', totalFeeAmount: string, fee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, marginLevels: { __typename?: 'MarginLevels', initialLevel: string } } }; +export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }; -export const EstimateOrderDocument = gql` - query EstimateOrder($marketId: ID!, $partyId: ID!, $price: String, $size: String!, $side: Side!, $timeInForce: OrderTimeInForce!, $expiration: Timestamp, $type: OrderType!) { - estimateOrder( +export const EstimateFeesDocument = gql` + query EstimateFees($marketId: ID!, $partyId: ID!, $price: String, $size: String!, $side: Side!, $timeInForce: OrderTimeInForce!, $expiration: Timestamp, $type: OrderType!) { + estimateFees( marketId: $marketId partyId: $partyId price: $price @@ -30,30 +30,27 @@ export const EstimateOrderDocument = gql` expiration: $expiration type: $type ) { - fee { + fees { makerFee infrastructureFee liquidityFee } - marginLevels { - initialLevel - } totalFeeAmount } } `; /** - * __useEstimateOrderQuery__ + * __useEstimateFeesQuery__ * - * To run a query within a React component, call `useEstimateOrderQuery` and pass it any options that fit your needs. - * When your component renders, `useEstimateOrderQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useEstimateFeesQuery` and pass it any options that fit your needs. + * When your component renders, `useEstimateFeesQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useEstimateOrderQuery({ + * const { data, loading, error } = useEstimateFeesQuery({ * variables: { * marketId: // value for 'marketId' * partyId: // value for 'partyId' @@ -66,14 +63,14 @@ export const EstimateOrderDocument = gql` * }, * }); */ -export function useEstimateOrderQuery(baseOptions: Apollo.QueryHookOptions) { +export function useEstimateFeesQuery(baseOptions: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(EstimateOrderDocument, options); + return Apollo.useQuery(EstimateFeesDocument, options); } -export function useEstimateOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useEstimateFeesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(EstimateOrderDocument, options); + return Apollo.useLazyQuery(EstimateFeesDocument, options); } -export type EstimateOrderQueryHookResult = ReturnType; -export type EstimateOrderLazyQueryHookResult = ReturnType; -export type EstimateOrderQueryResult = Apollo.QueryResult; \ No newline at end of file +export type EstimateFeesQueryHookResult = ReturnType; +export type EstimateFeesLazyQueryHookResult = ReturnType; +export type EstimateFeesQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/deal-ticket/src/hooks/estimate-order.mock.ts b/libs/deal-ticket/src/hooks/estimate-order.mock.ts index 3fbf7562b..b644f3bc1 100644 --- a/libs/deal-ticket/src/hooks/estimate-order.mock.ts +++ b/libs/deal-ticket/src/hooks/estimate-order.mock.ts @@ -1,21 +1,20 @@ import type { PartialDeep } from 'type-fest'; import merge from 'lodash/merge'; -import type { EstimateOrderQuery } from './__generated__/EstimateOrder'; +import type { EstimateFeesQuery } from './__generated__/EstimateOrder'; -export const estimateOrderQuery = ( - override?: PartialDeep -): EstimateOrderQuery => { - const defaultResult: EstimateOrderQuery = { - estimateOrder: { - __typename: 'OrderEstimate', +export const estimateFeesQuery = ( + override?: PartialDeep +): EstimateFeesQuery => { + const defaultResult: EstimateFeesQuery = { + estimateFees: { + __typename: 'FeeEstimate', totalFeeAmount: '0.0006', - fee: { + fees: { __typename: 'TradeFee', makerFee: '100000', infrastructureFee: '100000', liquidityFee: '100000', }, - marginLevels: { __typename: 'MarginLevels', initialLevel: '1' }, }, }; return merge(defaultResult, override); diff --git a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx index 726930c3d..703735cec 100644 --- a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx +++ b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx @@ -1,14 +1,9 @@ import { FeesBreakdown } from '@vegaprotocol/market-info'; -import { - addDecimal, - addDecimalsFormatNumber, - formatNumber, - toBigNum, -} from '@vegaprotocol/utils'; +import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import { useMemo } from 'react'; -import type { Market, MarketData } from '@vegaprotocol/market-list'; +import type { Market } from '@vegaprotocol/market-list'; +import type { EstimatePositionQuery } from '@vegaprotocol/positions'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { EST_TOTAL_MARGIN_TOOLTIP_TEXT, @@ -17,58 +12,30 @@ import { MARGIN_DIFF_TOOLTIP_TEXT, DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT, TOTAL_MARGIN_AVAILABLE, + LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT, } from '../constants'; -import { useMarketAccountBalance } from '@vegaprotocol/accounts'; -import { getDerivedPrice } from '../utils/get-price'; -import { useEstimateOrderQuery } from './__generated__/EstimateOrder'; -import type { EstimateOrderQuery } from './__generated__/EstimateOrder'; -export const useFeeDealTicketDetails = ( - order: OrderSubmissionBody['orderSubmission'], - market: Market, - marketData: MarketData +import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; +import type { EstimateFeesQuery } from './__generated__/EstimateOrder'; + +export const useEstimateFees = ( + order?: OrderSubmissionBody['orderSubmission'] ) => { const { pubKey } = useVegaWallet(); - const { accountBalance } = useMarketAccountBalance(market.id); - const price = useMemo(() => { - return getDerivedPrice(order, marketData); - }, [order, marketData]); - - const { data: estMargin } = useEstimateOrderQuery({ - variables: { - marketId: market.id, + const { data } = useEstimateFeesQuery({ + variables: order && { + marketId: order.marketId, partyId: pubKey || '', - price, + price: order.price, size: order.size, side: order.side, timeInForce: order.timeInForce, type: order.type, }, - skip: !pubKey || !market || !order.size || !price, + skip: !pubKey || !order?.size || !order?.price, }); - - const notionalSize = useMemo(() => { - if (price && order.size) { - return toBigNum(order.size, market.positionDecimalPlaces) - .multipliedBy(addDecimal(price, market.decimalPlaces)) - .toString(); - } - return null; - }, [price, order.size, market.decimalPlaces, market.positionDecimalPlaces]); - - const assetSymbol = - market.tradableInstrument.instrument.product.settlementAsset.symbol; - - return useMemo(() => { - return { - market, - assetSymbol, - notionalSize, - accountBalance, - estimateOrder: estMargin?.estimateOrder, - }; - }, [market, assetSymbol, notionalSize, accountBalance, estMargin]); + return data?.estimateFees; }; export interface FeeDetails { @@ -77,42 +44,54 @@ export interface FeeDetails { market: Market; assetSymbol: string; notionalSize: string | null; - estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined; - estimatedInitialMargin: string; - estimatedTotalInitialMargin: string; + feeEstimate: EstimateFeesQuery['estimateFees'] | undefined; currentInitialMargin?: string; currentMaintenanceMargin?: string; + positionEstimate: EstimatePositionQuery['estimatePosition']; } +const emptyValue = '-'; +const formatValue = ( + value: string | number | null | undefined, + formatDecimals: number +): string => { + return isNumeric(value) + ? addDecimalsFormatNumber(value, formatDecimals) + : emptyValue; +}; +const formatRange = ( + min: string | number | null | undefined, + max: string | number | null | undefined, + formatDecimals: number +) => { + const minFormatted = formatValue(min, formatDecimals); + const maxFormatted = formatValue(max, formatDecimals); + if (minFormatted !== maxFormatted) { + return `${minFormatted} - ${maxFormatted}`; + } + if (minFormatted !== emptyValue) { + return minFormatted; + } + return maxFormatted; +}; + export const getFeeDetailsValues = ({ marginAccountBalance, generalAccountBalance, assetSymbol, - estimateOrder, + feeEstimate, market, notionalSize, - estimatedTotalInitialMargin, currentInitialMargin, currentMaintenanceMargin, + positionEstimate, }: FeeDetails) => { + const liquidationEstimate = positionEstimate?.liquidation; + const marginEstimate = positionEstimate?.margin; const totalBalance = BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0'); const assetDecimals = market.tradableInstrument.instrument.product.settlementAsset.decimals; - const formatValueWithMarketDp = ( - value: string | number | null | undefined - ): string => { - return value && !isNaN(Number(value)) - ? formatNumber(value, market.decimalPlaces) - : '-'; - }; - const formatValueWithAssetDp = ( - value: string | number | null | undefined - ): string => { - return value && !isNaN(Number(value)) - ? addDecimalsFormatNumber(value, assetDecimals) - : '-'; - }; const details: { label: string; value?: string | null; @@ -122,15 +101,15 @@ export const getFeeDetailsValues = ({ }[] = [ { label: t('Notional'), - value: formatValueWithMarketDp(notionalSize), + value: formatValue(notionalSize, assetDecimals), symbol: assetSymbol, labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol), }, { label: t('Fees'), value: - estimateOrder?.totalFeeAmount && - `~${formatValueWithAssetDp(estimateOrder?.totalFeeAmount)}`, + feeEstimate?.totalFeeAmount && + `~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`, labelDescription: ( <> @@ -139,7 +118,7 @@ export const getFeeDetailsValues = ({ )} 0 + ? deductionFromCollateralBestCase.toString() + : '0', + deductionFromCollateralWorstCase > 0 + ? deductionFromCollateralWorstCase.toString() + : '0', + assetDecimals ), + symbol: assetSymbol, + labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol), }); - if (marginAccountBalance) { - const deductionFromCollateral = - BigInt(estimatedTotalInitialMargin) - BigInt(marginAccountBalance); - - details.push({ - indent: true, - label: t('Deduction from collateral'), - value: `~${formatValueWithAssetDp( - deductionFromCollateral > 0 ? deductionFromCollateral.toString() : '0' - )}`, - symbol: assetSymbol, - labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol), - }); - } - details.push({ label: t('Projected margin'), - value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`, + value: formatRange( + marginEstimate?.bestCase.initialLevel, + marginEstimate?.worstCase.initialLevel, + assetDecimals + ), symbol: assetSymbol, labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT, }); } details.push({ label: t('Current margin allocation'), - value: `${formatValueWithAssetDp(marginAccountBalance)}`, + value: formatValue(marginAccountBalance, assetDecimals), symbol: assetSymbol, labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, }); + + let liquidationPriceEstimate = emptyValue; + + if (liquidationEstimate) { + const liquidationEstimateBestCaseIncludingBuyOrders = BigInt( + liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '') + ); + const liquidationEstimateBestCaseIncludingSellOrders = BigInt( + liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '') + ); + const liquidationEstimateBestCase = + liquidationEstimateBestCaseIncludingBuyOrders > + liquidationEstimateBestCaseIncludingSellOrders + ? liquidationEstimateBestCaseIncludingBuyOrders + : liquidationEstimateBestCaseIncludingSellOrders; + + const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt( + liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '') + ); + const liquidationEstimateWorstCaseIncludingSellOrders = BigInt( + liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '') + ); + const liquidationEstimateWorstCase = + liquidationEstimateWorstCaseIncludingBuyOrders > + liquidationEstimateWorstCaseIncludingSellOrders + ? liquidationEstimateWorstCaseIncludingBuyOrders + : liquidationEstimateWorstCaseIncludingSellOrders; + liquidationPriceEstimate = formatRange( + (liquidationEstimateBestCase < liquidationEstimateWorstCase + ? liquidationEstimateBestCase + : liquidationEstimateWorstCase + ).toString(), + (liquidationEstimateBestCase > liquidationEstimateWorstCase + ? liquidationEstimateBestCase + : liquidationEstimateWorstCase + ).toString(), + assetDecimals + ); + } + + details.push({ + label: t('Liquidation price estimate'), + value: liquidationPriceEstimate, + symbol: assetSymbol, + labelDescription: LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT, + }); return details; }; diff --git a/libs/deal-ticket/src/hooks/use-initial-margin.ts b/libs/deal-ticket/src/hooks/use-initial-margin.ts deleted file mode 100644 index 3b0806332..000000000 --- a/libs/deal-ticket/src/hooks/use-initial-margin.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useMemo } from 'react'; -import { useDataProvider } from '@vegaprotocol/data-provider'; -import { useVegaWallet } from '@vegaprotocol/wallet'; -import { marketDataProvider } from '@vegaprotocol/market-list'; -import { - calculateMargins, - // getDerivedPrice, - volumeAndMarginProvider, -} from '@vegaprotocol/positions'; -import { Side } from '@vegaprotocol/types'; -import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; -import { marketInfoProvider } from '@vegaprotocol/market-info'; - -export const useInitialMargin = ( - marketId: OrderSubmissionBody['orderSubmission']['marketId'], - order?: OrderSubmissionBody['orderSubmission'] -) => { - const { pubKey } = useVegaWallet(); - const { data: marketData } = useDataProvider({ - dataProvider: marketDataProvider, - variables: { marketId }, - }); - const { data: activeVolumeAndMargin } = useDataProvider({ - dataProvider: volumeAndMarginProvider, - variables: { marketId, partyId: pubKey || '' }, - skip: !pubKey, - }); - const { data: marketInfo } = useDataProvider({ - dataProvider: marketInfoProvider, - variables: { marketId }, - }); - let totalMargin = '0'; - let margin = '0'; - if (marketInfo?.riskFactors && marketData && order) { - const { - positionDecimalPlaces, - decimalPlaces, - tradableInstrument, - riskFactors, - } = marketInfo; - const { marginCalculator, instrument } = tradableInstrument; - const { decimals } = instrument.product.settlementAsset; - margin = totalMargin = calculateMargins({ - side: order.side, - size: order.size, - price: marketData.markPrice, // getDerivedPrice(order, marketData), same in positions-data-providers - positionDecimalPlaces, - decimalPlaces, - decimals, - scalingFactors: marginCalculator?.scalingFactors, - riskFactors, - }).initialMargin; - } - - if (activeVolumeAndMargin) { - let sellMargin = BigInt(activeVolumeAndMargin.sellInitialMargin); - let buyMargin = BigInt(activeVolumeAndMargin.buyInitialMargin); - if (order?.side === Side.SIDE_SELL) { - sellMargin += BigInt(totalMargin); - } else { - buyMargin += BigInt(totalMargin); - } - totalMargin = - sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString(); - } - - return useMemo( - () => ({ - totalMargin, - margin, - }), - [totalMargin, margin] - ); -}; diff --git a/libs/environment/src/hooks/use-node-health.ts b/libs/environment/src/hooks/use-node-health.ts index 1cffd7e6f..b10360943 100644 --- a/libs/environment/src/hooks/use-node-health.ts +++ b/libs/environment/src/hooks/use-node-health.ts @@ -38,7 +38,7 @@ export const useNodeHealth = () => { return; } - if (!('Cypress' in window)) { + if (!('Cypress' in window) && window.location.hostname !== 'localhost') { startPolling(POLL_INTERVAL); } }, [error, startPolling, stopPolling]); diff --git a/libs/network-info/src/network-info.tsx b/libs/network-info/src/network-info.tsx index 63d6c014c..02c84de6f 100644 --- a/libs/network-info/src/network-info.tsx +++ b/libs/network-info/src/network-info.tsx @@ -2,7 +2,6 @@ import { Fragment } from 'react'; import { t } from '@vegaprotocol/i18n'; import { Link, Lozenge } from '@vegaprotocol/ui-toolkit'; import { - NodeSwitcherDialog, useEnvironment, useNodeSwitcherStore, } from '@vegaprotocol/environment'; diff --git a/libs/positions/src/index.ts b/libs/positions/src/index.ts index 99f406606..e6b3b3a92 100644 --- a/libs/positions/src/index.ts +++ b/libs/positions/src/index.ts @@ -2,7 +2,6 @@ export * from './lib/__generated__/Positions'; export * from './lib/positions-container'; export * from './lib/positions-data-providers'; export * from './lib/margin-data-provider'; -export * from './lib/margin-calculator'; export * from './lib/positions-table'; export * from './lib/use-market-margin'; export * from './lib/use-open-volume'; diff --git a/libs/positions/src/lib/Positions.graphql b/libs/positions/src/lib/Positions.graphql index cc80a0aea..425c053f7 100644 --- a/libs/positions/src/lib/Positions.graphql +++ b/libs/positions/src/lib/Positions.graphql @@ -75,3 +75,44 @@ subscription MarginsSubscription($partyId: ID!) { timestamp } } + +query EstimatePosition( + $marketId: ID! + $openVolume: String! + $orders: [OrderInfo!] + $collateralAvailable: String +) { + estimatePosition( + marketId: $marketId + openVolume: $openVolume + orders: $orders + collateralAvailable: $collateralAvailable + ) { + margin { + worstCase { + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + } + bestCase { + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + } + } + liquidation { + worstCase { + open_volume_only + including_buy_orders + including_sell_orders + } + bestCase { + open_volume_only + including_buy_orders + including_sell_orders + } + } + } +} diff --git a/libs/positions/src/lib/__generated__/Positions.ts b/libs/positions/src/lib/__generated__/Positions.ts index 96a130c3a..856053d6b 100644 --- a/libs/positions/src/lib/__generated__/Positions.ts +++ b/libs/positions/src/lib/__generated__/Positions.ts @@ -35,6 +35,16 @@ export type MarginsSubscriptionSubscriptionVariables = Types.Exact<{ export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } }; +export type EstimatePositionQueryVariables = Types.Exact<{ + marketId: Types.Scalars['ID']; + openVolume: Types.Scalars['String']; + orders?: Types.InputMaybe | Types.OrderInfo>; + collateralAvailable?: Types.InputMaybe; +}>; + + +export type EstimatePositionQuery = { __typename?: 'Query', estimatePosition?: { __typename?: 'PositionEstimate', margin: { __typename?: 'MarginEstimate', worstCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string }, bestCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string } }, liquidation?: { __typename?: 'LiquidationEstimate', worstCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string }, bestCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string } } | null } | null }; + export const PositionFieldsFragmentDoc = gql` fragment PositionFields on Position { realisedPNL @@ -220,4 +230,72 @@ export function useMarginsSubscriptionSubscription(baseOptions: Apollo.Subscript return Apollo.useSubscription(MarginsSubscriptionDocument, options); } export type MarginsSubscriptionSubscriptionHookResult = ReturnType; -export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult; \ No newline at end of file +export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult; +export const EstimatePositionDocument = gql` + query EstimatePosition($marketId: ID!, $openVolume: String!, $orders: [OrderInfo!], $collateralAvailable: String) { + estimatePosition( + marketId: $marketId + openVolume: $openVolume + orders: $orders + collateralAvailable: $collateralAvailable + ) { + margin { + worstCase { + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + } + bestCase { + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + } + } + liquidation { + worstCase { + open_volume_only + including_buy_orders + including_sell_orders + } + bestCase { + open_volume_only + including_buy_orders + including_sell_orders + } + } + } +} + `; + +/** + * __useEstimatePositionQuery__ + * + * To run a query within a React component, call `useEstimatePositionQuery` and pass it any options that fit your needs. + * When your component renders, `useEstimatePositionQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useEstimatePositionQuery({ + * variables: { + * marketId: // value for 'marketId' + * openVolume: // value for 'openVolume' + * orders: // value for 'orders' + * collateralAvailable: // value for 'collateralAvailable' + * }, + * }); + */ +export function useEstimatePositionQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(EstimatePositionDocument, options); + } +export function useEstimatePositionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(EstimatePositionDocument, options); + } +export type EstimatePositionQueryHookResult = ReturnType; +export type EstimatePositionLazyQueryHookResult = ReturnType; +export type EstimatePositionQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/positions/src/lib/estimate-position.mock.ts b/libs/positions/src/lib/estimate-position.mock.ts new file mode 100644 index 000000000..d5536737b --- /dev/null +++ b/libs/positions/src/lib/estimate-position.mock.ts @@ -0,0 +1,40 @@ +import type { PartialDeep } from 'type-fest'; +import merge from 'lodash/merge'; +import type { EstimatePositionQuery } from './__generated__/Positions'; + +export const estimatePositionQuery = ( + override?: PartialDeep +): EstimatePositionQuery => { + const defaultResult: EstimatePositionQuery = { + estimatePosition: { + __typename: 'PositionEstimate', + margin: { + bestCase: { + collateralReleaseLevel: '1000000', + initialLevel: '500000', + maintenanceLevel: '200000', + searchLevel: '300000', + }, + worstCase: { + collateralReleaseLevel: '1100000', + initialLevel: '600000', + maintenanceLevel: '300000', + searchLevel: '400000', + }, + }, + liquidation: { + bestCase: { + including_buy_orders: '1', + including_sell_orders: '1', + open_volume_only: '1', + }, + worstCase: { + including_buy_orders: '1', + including_sell_orders: '1', + open_volume_only: '1', + }, + }, + }, + }; + return merge(defaultResult, override); +}; diff --git a/libs/positions/src/lib/margin-calculator.ts b/libs/positions/src/lib/margin-calculator.ts deleted file mode 100644 index 522d60e73..000000000 --- a/libs/positions/src/lib/margin-calculator.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { toBigNum } from '@vegaprotocol/utils'; -import { Side, MarketTradingMode, OrderType } from '@vegaprotocol/types'; -import type { ScalingFactors, RiskFactor } from '@vegaprotocol/types'; -import type { MarketData } from '@vegaprotocol/market-list'; - -export const isMarketInAuction = (marketTradingMode: MarketTradingMode) => { - return [ - MarketTradingMode.TRADING_MODE_BATCH_AUCTION, - MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, - MarketTradingMode.TRADING_MODE_OPENING_AUCTION, - ].includes(marketTradingMode); -}; - -/** - * Get the market price based on market mode (auction or not auction) - */ -export const getMarketPrice = ({ - marketTradingMode, - indicativePrice, - markPrice, -}: Pick) => { - if (isMarketInAuction(marketTradingMode)) { - // 0 can never be a valid uncrossing price - // as it would require there being orders on the book at that price. - if ( - indicativePrice && - indicativePrice !== '0' && - BigInt(indicativePrice) !== BigInt(0) - ) { - return indicativePrice; - } - } - return markPrice; -}; - -/** - * Gets the price for an order, order limit this is the user - * entered value, for market this will be the mark price or - * if in auction the indicative uncrossing price - */ -export const getDerivedPrice = ( - order: { - type?: OrderType | null; - price?: string; - }, - marketData: Pick< - MarketData, - 'marketTradingMode' | 'indicativePrice' | 'markPrice' - > -) => { - // If order type is market we should use either the mark price - // or the uncrossing price. If order type is limit use the price - // the user has input - - // Use the market price if order is a market order - if (order.type === OrderType.TYPE_LIMIT && order.price) { - return order.price; - } - return getMarketPrice(marketData); -}; - -export const calculateMargins = ({ - size, - side, - price, - decimals, - positionDecimalPlaces, - decimalPlaces, - scalingFactors, - riskFactors, -}: { - size: string; - side: Side; - positionDecimalPlaces: number; - decimalPlaces: number; - decimals: number; - price: string; - scalingFactors?: ScalingFactors; - riskFactors: RiskFactor; -}) => { - const maintenanceMargin = toBigNum(size, positionDecimalPlaces) - .multipliedBy( - side === Side.SIDE_SELL ? riskFactors.short : riskFactors.long - ) - .multipliedBy(toBigNum(price, decimalPlaces)); - return { - maintenanceMargin: maintenanceMargin - .multipliedBy(Math.pow(10, decimals)) - .toFixed(0), - initialMargin: maintenanceMargin - .multipliedBy(scalingFactors?.initialMargin ?? 1) - .multipliedBy(Math.pow(10, decimals)) - .toFixed(0), - }; -}; diff --git a/libs/positions/src/lib/positions-data-providers.ts b/libs/positions/src/lib/positions-data-providers.ts index c01976a49..b29ef4f64 100644 --- a/libs/positions/src/lib/positions-data-providers.ts +++ b/libs/positions/src/lib/positions-data-providers.ts @@ -5,7 +5,6 @@ import sortBy from 'lodash/sortBy'; import type { Account } from '@vegaprotocol/accounts'; import { accountsDataProvider } from '@vegaprotocol/accounts'; import { toBigNum, removePaginationWrapper } from '@vegaprotocol/utils'; -import type { Edge } from '@vegaprotocol/data-provider'; import { makeDataProvider, makeDerivedDataProvider, @@ -28,14 +27,6 @@ import { PositionsSubscriptionDocument, } from './__generated__/Positions'; import { marginsDataProvider } from './margin-data-provider'; -import { calculateMargins } from './margin-calculator'; -import { Side } from '@vegaprotocol/types'; -import { marketInfoProvider } from '@vegaprotocol/market-info'; -import type { MarketInfoQuery } from '@vegaprotocol/market-info'; -import { marketDataProvider } from '@vegaprotocol/market-list'; -import type { MarketData } from '@vegaprotocol/market-list'; -import { activeOrdersProvider } from '@vegaprotocol/orders'; -import type { OrderFieldsFragment } from '@vegaprotocol/orders'; import type { PositionStatus } from '@vegaprotocol/types'; type PositionMarginLevel = Pick< @@ -336,98 +327,3 @@ export const positionsMetricsProvider = makeDerivedDataProvider< return !(previousRow && isEqual(previousRow, row)); }) ); - -export const volumeAndMarginProvider = makeDerivedDataProvider< - { - buyVolume: string; - sellVolume: string; - buyInitialMargin: string; - sellInitialMargin: string; - }, - never, - PositionsQueryVariables & MarketDataQueryVariables ->( - [ - (callback, client, { partyId, marketId }) => - activeOrdersProvider(callback, client, { - partyId, - marketId, - }), - (callback, client, { marketId }) => - marketDataProvider(callback, client, { marketId }), - (callback, client, { marketId }) => - marketInfoProvider(callback, client, { marketId }), - openVolumeDataProvider, - ], - (data) => { - const orders = data[0] as (Edge | null)[] | null; - const marketData = data[1] as MarketData | null; - const marketInfo = data[2] as MarketInfoQuery['market']; - let openVolume = (data[3] as string | null) || '0'; - const shortPosition = openVolume?.startsWith('-'); - if (shortPosition) { - openVolume = openVolume.substring(1); - } - let buyVolume = BigInt(shortPosition ? 0 : openVolume); - let sellVolume = BigInt(shortPosition ? openVolume : 0); - let buyInitialMargin = BigInt(0); - let sellInitialMargin = BigInt(0); - if (marketInfo?.riskFactors && marketData) { - const { - positionDecimalPlaces, - decimalPlaces, - tradableInstrument, - riskFactors, - } = marketInfo; - const { marginCalculator, instrument } = tradableInstrument; - const { decimals } = instrument.product.settlementAsset; - const calculatorParams = { - positionDecimalPlaces, - decimalPlaces, - decimals, - scalingFactors: marginCalculator?.scalingFactors, - riskFactors, - }; - if (openVolume !== '0') { - const { initialMargin } = calculateMargins({ - side: shortPosition ? Side.SIDE_SELL : Side.SIDE_BUY, - size: openVolume, - price: marketData.markPrice, - ...calculatorParams, - }); - if (shortPosition) { - sellInitialMargin += BigInt(initialMargin); - } else { - buyInitialMargin += BigInt(initialMargin); - } - } - orders?.forEach((order) => { - if (!order) { - return; - } - const { side, remaining: size } = order.node; - const initialMargin = BigInt( - calculateMargins({ - side, - size, - price: marketData.markPrice, //getDerivedPrice(order.node, marketData), same use-initial-margin - ...calculatorParams, - }).initialMargin - ); - if (order.node.side === Side.SIDE_BUY) { - buyVolume += BigInt(size); - buyInitialMargin += initialMargin; - } else { - sellVolume += BigInt(size); - sellInitialMargin += initialMargin; - } - }); - } - return { - buyVolume: buyVolume.toString(), - sellVolume: sellVolume.toString(), - buyInitialMargin: buyInitialMargin.toString(), - sellInitialMargin: sellInitialMargin.toString(), - }; - } -); diff --git a/libs/proposals/src/lib/protocol-upgrade-proposals/block-statistics.mock.ts b/libs/proposals/src/lib/protocol-upgrade-proposals/block-statistics.mock.ts new file mode 100644 index 000000000..f49af820e --- /dev/null +++ b/libs/proposals/src/lib/protocol-upgrade-proposals/block-statistics.mock.ts @@ -0,0 +1,17 @@ +import type { BlockStatisticsQuery } from './__generated__/BlockStatistics'; +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; + +export const blockStatisticsQuery = ( + override?: PartialDeep +): BlockStatisticsQuery => { + const defaultResult = { + statistics: { + __typename: 'Statistics', + blockHeight: '100', + blockDuration: '100', + }, + }; + + return merge(defaultResult, override); +}; diff --git a/libs/proposals/src/lib/protocol-upgrade-proposals/protocol-statistics-proposals.mock.ts b/libs/proposals/src/lib/protocol-upgrade-proposals/protocol-statistics-proposals.mock.ts new file mode 100644 index 000000000..a11b10106 --- /dev/null +++ b/libs/proposals/src/lib/protocol-upgrade-proposals/protocol-statistics-proposals.mock.ts @@ -0,0 +1,13 @@ +import type { ProtocolUpgradeProposalsQuery } from './__generated__/ProtocolUpgradeProposals'; +import merge from 'lodash/merge'; +import type { PartialDeep } from 'type-fest'; + +export const protocolUpgradeProposalsQuery = ( + override?: PartialDeep +): ProtocolUpgradeProposalsQuery => { + const defaultResult: ProtocolUpgradeProposalsQuery = { + lastBlockHeight: '100', + }; + + return merge(defaultResult, override); +}; diff --git a/libs/proposals/src/lib/protocol-upgrade-proposals/use-next-protocol-upgrade-proposals.ts b/libs/proposals/src/lib/protocol-upgrade-proposals/use-next-protocol-upgrade-proposals.ts index 70bc3bff7..415d648fd 100644 --- a/libs/proposals/src/lib/protocol-upgrade-proposals/use-next-protocol-upgrade-proposals.ts +++ b/libs/proposals/src/lib/protocol-upgrade-proposals/use-next-protocol-upgrade-proposals.ts @@ -1,19 +1,30 @@ -import { useMemo } from 'react'; +import { useMemo, useEffect } from 'react'; import * as Schema from '@vegaprotocol/types'; import { removePaginationWrapper } from '@vegaprotocol/utils'; import { useProtocolUpgradeProposalsQuery } from './__generated__/ProtocolUpgradeProposals'; export const useNextProtocolUpgradeProposals = (since?: number) => { - const { data, loading, error } = useProtocolUpgradeProposalsQuery({ - pollInterval: 5000, - fetchPolicy: 'network-only', - errorPolicy: 'ignore', - variables: { - inState: - Schema.ProtocolUpgradeProposalStatus - .PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, - }, - }); + const { data, loading, error, startPolling, stopPolling } = + useProtocolUpgradeProposalsQuery({ + fetchPolicy: 'network-only', + errorPolicy: 'ignore', + variables: { + inState: + Schema.ProtocolUpgradeProposalStatus + .PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED, + }, + }); + + useEffect(() => { + if (error) { + stopPolling(); + return; + } + + if (!('Cypress' in window) && window.location.hostname !== 'localhost') { + startPolling(5000); + } + }, [error, startPolling, stopPolling]); const nextUpgrades = useMemo(() => { if (!data) return []; diff --git a/libs/proposals/src/lib/protocol-upgrade-proposals/use-time-to-upgrade.ts b/libs/proposals/src/lib/protocol-upgrade-proposals/use-time-to-upgrade.ts index 10b5a22f5..9a61b99a0 100644 --- a/libs/proposals/src/lib/protocol-upgrade-proposals/use-time-to-upgrade.ts +++ b/libs/proposals/src/lib/protocol-upgrade-proposals/use-time-to-upgrade.ts @@ -8,20 +8,29 @@ const durations = [] as number[]; const useAverageBlockDuration = (polls = DEFAULT_POLLS) => { const [avg, setAvg] = useState(undefined); - const { data } = useBlockStatisticsQuery({ - pollInterval: INTERVAL, + const { data, startPolling, stopPolling, error } = useBlockStatisticsQuery({ fetchPolicy: 'network-only', errorPolicy: 'ignore', skip: durations.length === polls, }); + useEffect(() => { + if (error) { + stopPolling(); + return; + } + + if (!('Cypress' in window) && window.location.hostname !== 'localhost') { + startPolling(INTERVAL); + } + }, [error, startPolling, stopPolling]); + useEffect(() => { if (durations.length < polls && data) { durations.push(parseFloat(data.statistics.blockDuration)); } if (durations.length === polls) { const averageBlockDuration = sum(durations) / durations.length; // ms - console.log('setting avg', averageBlockDuration); setAvg(averageBlockDuration); } }, [data, polls]); diff --git a/libs/utils/src/lib/format/number.ts b/libs/utils/src/lib/format/number.ts index 06b920e84..3cbf5a402 100644 --- a/libs/utils/src/lib/format/number.ts +++ b/libs/utils/src/lib/format/number.ts @@ -32,8 +32,10 @@ export function addDecimal( return toBigNum(value, decimals).toFixed(decimalPrecision); } -export function removeDecimal(value: string, decimals: number): string { - if (!decimals) return value; +export function removeDecimal( + value: string | BigNumber, + decimals: number +): string { return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0); }