From cf71af8f9de5e26b8bb970fbb7c274c74b339205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Fri, 5 May 2023 15:54:06 +0200 Subject: [PATCH] feat: hook up new EstimatePosition and EstimateFees api methods --- .../trading-deal-ticket-submit-account.cy.ts | 4 +- apps/trading-e2e/src/support/trading.ts | 4 +- .../src/components/deal-ticket-estimates.tsx | 132 ------------- .../deal-ticket/deal-ticket-fee-details.tsx | 33 +--- .../components/deal-ticket/deal-ticket.tsx | 87 ++++++++- libs/deal-ticket/src/components/index.ts | 1 - .../src/hooks/EstimateOrder.graphql | 9 +- .../src/hooks/__generated__/EstimateOrder.ts | 37 ++-- .../src/hooks/estimate-order.mock.ts | 17 +- .../src/hooks/use-fee-deal-ticket-details.tsx | 183 +++++++++++------- .../src/hooks/use-initial-margin.ts | 74 ------- libs/positions/src/index.ts | 1 - libs/positions/src/lib/Positions.graphql | 41 ++++ .../src/lib/__generated__/Positions.ts | 80 +++++++- libs/positions/src/lib/margin-calculator.ts | 95 --------- .../src/lib/positions-data-providers.ts | 103 ---------- 16 files changed, 351 insertions(+), 550 deletions(-) delete mode 100644 libs/deal-ticket/src/components/deal-ticket-estimates.tsx delete mode 100644 libs/deal-ticket/src/hooks/use-initial-margin.ts delete mode 100644 libs/positions/src/lib/margin-calculator.ts 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..b240ba7ba 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 @@ -3,7 +3,7 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { accountsQuery, amendGeneralAccountBalance, - estimateOrderQuery, + estimateFeesQuery, } from '@vegaprotocol/mock'; import type { OrderSubmission } from '@vegaprotocol/wallet'; import { createOrder } from '../support/create-order'; @@ -49,7 +49,7 @@ describe( aliasGQLQuery(req, 'Accounts', accounts); }); cy.mockGQL((req) => { - aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery()); + aliasGQLQuery(req, 'EstimateFee', estimateFeesQuery()); }); cy.mockSubscription(); cy.visit('/#/markets/market-0'); diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts index 17b9f35f0..42ab1b8fb 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, @@ -157,7 +157,7 @@ const mockTradingPage = ( aliasGQLQuery(req, 'Candles', candlesQuery()); aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery()); aliasGQLQuery(req, 'NetworkParams', networkParamsQuery()); - aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery()); + aliasGQLQuery(req, 'EstimateFees', estimateFeesQuery()); aliasGQLQuery(req, 'ProposalsList', proposalListQuery()); aliasGQLQuery(req, 'Deposits', depositsQuery()); }; 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..87131ec52 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 { addDecimal, toBigNum } from '@vegaprotocol/utils'; +import { activeOrdersProvider } from '@vegaprotocol/orders'; +import { useFeeDealTicketDetails } 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,62 @@ 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 toBigNum(normalizedOrder.size, market.positionDecimalPlaces) + .multipliedBy(addDecimal(price, market.decimalPlaces)) + .toString(); + } + return null; + }, [ + price, + normalizedOrder?.size, + market.decimalPlaces, + market.positionDecimalPlaces, + ]); + + const estimateFees = useFeeDealTicketDetails( + 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 +465,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 +480,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/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..0ff4607d1 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, formatNumber } 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, @@ -18,57 +13,28 @@ import { DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT, TOTAL_MARGIN_AVAILABLE, } from '../constants'; -import { useMarketAccountBalance } from '@vegaprotocol/accounts'; -import { getDerivedPrice } from '../utils/get-price'; -import { useEstimateOrderQuery } from './__generated__/EstimateOrder'; -import type { EstimateOrderQuery } from './__generated__/EstimateOrder'; + +import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; +import type { EstimateFeesQuery } from './__generated__/EstimateOrder'; export const useFeeDealTicketDetails = ( - order: OrderSubmissionBody['orderSubmission'], - market: Market, - marketData: MarketData + 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,24 +43,25 @@ export interface FeeDetails { market: Market; assetSymbol: string; notionalSize: string | null; - estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined; - estimatedInitialMargin: string; - estimatedTotalInitialMargin: string; + estimateFees: EstimateFeesQuery['estimateFees'] | undefined; currentInitialMargin?: string; currentMaintenanceMargin?: string; + positionEstimate: EstimatePositionQuery['estimatePosition']; } export const getFeeDetailsValues = ({ marginAccountBalance, generalAccountBalance, assetSymbol, - estimateOrder, + estimateFees, 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 = @@ -129,8 +96,8 @@ export const getFeeDetailsValues = ({ { label: t('Fees'), value: - estimateOrder?.totalFeeAmount && - `~${formatValueWithAssetDp(estimateOrder?.totalFeeAmount)}`, + estimateFees?.totalFeeAmount && + `~${formatValueWithAssetDp(estimateFees?.totalFeeAmount)}`, labelDescription: ( <> @@ -139,7 +106,7 @@ export const getFeeDetailsValues = ({ )} 0 ? deductionFromCollateral.toString() : '0' + deductionFromCollateralBestCase > 0 + ? deductionFromCollateralBestCase.toString() + : '0' + )}`, + symbol: assetSymbol, + labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol), + }); + + const deductionFromCollateralWorstCase = + BigInt(marginEstimate.worstCase.initialLevel) - + BigInt(marginAccountBalance); + + details.push({ + indent: true, + label: t('Deduction from collateral worst case'), + value: `~${formatValueWithAssetDp( + deductionFromCollateralWorstCase > 0 + ? deductionFromCollateralWorstCase.toString() + : '0' )}`, symbol: assetSymbol, labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol), @@ -198,7 +201,16 @@ export const getFeeDetailsValues = ({ details.push({ label: t('Projected margin'), - value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`, + value: `~${formatValueWithAssetDp(marginEstimate.bestCase.initialLevel)}`, + symbol: assetSymbol, + labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT, + }); + + details.push({ + label: t('Projected margin worst case'), + value: `~${formatValueWithAssetDp( + marginEstimate.worstCase.initialLevel + )}`, symbol: assetSymbol, labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT, }); @@ -209,5 +221,46 @@ export const getFeeDetailsValues = ({ symbol: assetSymbol, labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, }); + 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; + details.push({ + label: t('Liquidation price estimate'), + value: `${formatValueWithAssetDp( + liquidationEstimateBestCase.toString() + )}`, + symbol: assetSymbol, + labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, + }); + + const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt( + liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '') + ); + const liquidationEstimateWorstCaseIncludingSellOrders = BigInt( + liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '') + ); + const liquidationEstimateWorstCase = + liquidationEstimateWorstCaseIncludingBuyOrders > + liquidationEstimateWorstCaseIncludingSellOrders + ? liquidationEstimateWorstCaseIncludingBuyOrders + : liquidationEstimateWorstCaseIncludingSellOrders; + details.push({ + label: t('Liquidation price estimate worst case'), + value: `${formatValueWithAssetDp( + liquidationEstimateWorstCase.toString() + )}`, + symbol: assetSymbol, + labelDescription: MARGIN_ACCOUNT_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/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/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..a507aa94e 100644 --- a/libs/positions/src/lib/positions-data-providers.ts +++ b/libs/positions/src/lib/positions-data-providers.ts @@ -28,14 +28,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 +328,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(), - }; - } -);