feat: hook up new EstimatePosition and EstimateFees api methods

This commit is contained in:
Bartłomiej Głownia
2023-05-11 09:41:16 +02:00
parent d44392bebf
commit cf71af8f9d
16 changed files with 351 additions and 550 deletions
@@ -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');
+2 -2
View File
@@ -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());
};
@@ -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) => (
<dl className="text-black dark:text-white">
{size && (
<div className="flex justify-between mb-2">
<DataTitle>{t('Contracts')}</DataTitle>
<ValueTooltipRow
value={size}
description={constants.CONTRACTS_MARGIN_TOOLTIP_TEXT}
id="contracts_tooltip_trigger"
/>
</div>
)}
{price && (
<div className="flex justify-between mb-2">
<DataTitle>{t('Est. Price')}</DataTitle>
<dd>{price}</dd>
</div>
)}
{notionalSize && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Position Size')}</DataTitle>
<ValueTooltipRow
value={notionalSize}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
{fees && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Fees')}</DataTitle>
<ValueTooltipRow
value={fees}
description={constants.EST_FEES_TOOLTIP_TEXT}
/>
</div>
)}
{estMargin && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Margin')}</DataTitle>
<ValueTooltipRow
value={estMargin}
description={constants.EST_MARGIN_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
{estCloseOut && (
<div className="flex justify-between mb-2">
<DataTitle quoteName={quoteName}>{t('Est. Close out')}</DataTitle>
<ValueTooltipRow
value={estCloseOut}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT(quoteName || '')}
/>
</div>
)}
{slippage && (
<div className="flex justify-between mb-2">
<DataTitle>{t('Est. Price Impact / Slippage')}</DataTitle>
<ValueTooltipRow description={constants.EST_SLIPPAGE}>
<TrafficLight value={parseFloat(slippage)} q1={1} q2={5}>
{slippage}%
</TrafficLight>
</ValueTooltipRow>
</div>
)}
</dl>
);
interface DataTitleProps {
children: ReactNode;
quoteName?: string;
}
export const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
<dt>
{children}
{quoteName && <small> ({quoteName})</small>}
</dt>
);
interface ValueTooltipProps {
value?: string;
children?: ReactNode;
description: string;
id?: string;
}
export const ValueTooltipRow = ({
value,
children,
description,
id,
}: ValueTooltipProps) => (
<dd className="flex gap-x-2 items-center">
{value || children}
<Tooltip align="center" description={description}>
<div className="cursor-help" id={id || ''} tabIndex={-1}>
<Icon
name={IconNames.ISSUE}
className="block rotate-180"
ariaLabel={description}
/>
</div>
</Tooltip>
</dd>
);
@@ -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 = ({
</div>
);
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 (
<div>
{details.map(({ label, value, labelDescription, symbol, indent }) => (
@@ -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<OrderInfo>(({ 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 = ({
}
/>
<DealTicketFeeDetails
order={normalizedOrder}
market={market}
marketData={marketData}
estimatedInitialMargin={margin}
estimatedTotalInitialMargin={totalMargin}
currentInitialMargin={currentMargins?.initialLevel}
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
estimateFees={estimateFees}
notionalSize={notionalSize}
assetSymbol={assetSymbol}
marginAccountBalance={marginAccountBalance}
generalAccountBalance={generalAccountBalance}
positionEstimate={positionEstimate?.estimatePosition}
market={market}
currentInitialMargin={currentMargins?.initialLevel}
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
/>
</form>
</TinyScroll>
-1
View File
@@ -1,4 +1,3 @@
export * from './deal-ticket';
export * from './deal-ticket-validation';
export * from './trading-mode-tooltip';
export * from './deal-ticket-estimates';
@@ -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
}
}
+17 -20
View File
@@ -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<Types.Scalars['String']>;
@@ -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<EstimateOrderQuery, EstimateOrderQueryVariables>) {
export function useEstimateFeesQuery(baseOptions: Apollo.QueryHookOptions<EstimateFeesQuery, EstimateFeesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<EstimateOrderQuery, EstimateOrderQueryVariables>(EstimateOrderDocument, options);
return Apollo.useQuery<EstimateFeesQuery, EstimateFeesQueryVariables>(EstimateFeesDocument, options);
}
export function useEstimateOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EstimateOrderQuery, EstimateOrderQueryVariables>) {
export function useEstimateFeesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EstimateFeesQuery, EstimateFeesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<EstimateOrderQuery, EstimateOrderQueryVariables>(EstimateOrderDocument, options);
return Apollo.useLazyQuery<EstimateFeesQuery, EstimateFeesQueryVariables>(EstimateFeesDocument, options);
}
export type EstimateOrderQueryHookResult = ReturnType<typeof useEstimateOrderQuery>;
export type EstimateOrderLazyQueryHookResult = ReturnType<typeof useEstimateOrderLazyQuery>;
export type EstimateOrderQueryResult = Apollo.QueryResult<EstimateOrderQuery, EstimateOrderQueryVariables>;
export type EstimateFeesQueryHookResult = ReturnType<typeof useEstimateFeesQuery>;
export type EstimateFeesLazyQueryHookResult = ReturnType<typeof useEstimateFeesLazyQuery>;
export type EstimateFeesQueryResult = Apollo.QueryResult<EstimateFeesQuery, EstimateFeesQueryVariables>;
@@ -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>
): EstimateOrderQuery => {
const defaultResult: EstimateOrderQuery = {
estimateOrder: {
__typename: 'OrderEstimate',
export const estimateFeesQuery = (
override?: PartialDeep<EstimateFeesQuery>
): 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);
@@ -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: (
<>
<span>
@@ -139,7 +106,7 @@ export const getFeeDetailsValues = ({
)}
</span>
<FeesBreakdown
fees={estimateOrder?.fee}
fees={estimateFees?.fees}
feeFactors={market.fees.factors}
symbol={assetSymbol}
decimals={assetDecimals}
@@ -148,19 +115,35 @@ export const getFeeDetailsValues = ({
),
symbol: assetSymbol,
},
{
];
if (marginEstimate) {
details.push({
label: t('Margin required'),
value: `~${formatValueWithAssetDp(
currentInitialMargin
? (
BigInt(estimatedTotalInitialMargin) - BigInt(currentInitialMargin)
BigInt(marginEstimate.bestCase.initialLevel) -
BigInt(currentInitialMargin)
).toString()
: estimatedTotalInitialMargin
: marginEstimate.bestCase.initialLevel
)}`,
symbol: assetSymbol,
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
},
];
});
details.push({
label: t('Margin required worst case'),
value: `~${formatValueWithAssetDp(
currentInitialMargin
? (
BigInt(marginEstimate.worstCase.initialLevel) -
BigInt(currentInitialMargin)
).toString()
: marginEstimate.worstCase.initialLevel
)}`,
symbol: assetSymbol,
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
});
}
if (totalBalance) {
const totalMarginAvailable = (
currentMaintenanceMargin
@@ -180,16 +163,36 @@ export const getFeeDetailsValues = ({
assetSymbol
),
});
}
if (marginEstimate) {
if (marginAccountBalance) {
const deductionFromCollateral =
BigInt(estimatedTotalInitialMargin) - BigInt(marginAccountBalance);
const deductionFromCollateralBestCase =
BigInt(marginEstimate.bestCase.initialLevel) -
BigInt(marginAccountBalance);
details.push({
indent: true,
label: t('Deduction from collateral'),
value: `~${formatValueWithAssetDp(
deductionFromCollateral > 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;
};
@@ -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]
);
};
-1
View File
@@ -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';
+41
View File
@@ -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
}
}
}
}
+79 -1
View File
@@ -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<Array<Types.OrderInfo> | Types.OrderInfo>;
collateralAvailable?: Types.InputMaybe<Types.Scalars['String']>;
}>;
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<MarginsSubscriptionSubscription, MarginsSubscriptionSubscriptionVariables>(MarginsSubscriptionDocument, options);
}
export type MarginsSubscriptionSubscriptionHookResult = ReturnType<typeof useMarginsSubscriptionSubscription>;
export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<MarginsSubscriptionSubscription>;
export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<MarginsSubscriptionSubscription>;
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<EstimatePositionQuery, EstimatePositionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<EstimatePositionQuery, EstimatePositionQueryVariables>(EstimatePositionDocument, options);
}
export function useEstimatePositionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EstimatePositionQuery, EstimatePositionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<EstimatePositionQuery, EstimatePositionQueryVariables>(EstimatePositionDocument, options);
}
export type EstimatePositionQueryHookResult = ReturnType<typeof useEstimatePositionQuery>;
export type EstimatePositionLazyQueryHookResult = ReturnType<typeof useEstimatePositionLazyQuery>;
export type EstimatePositionQueryResult = Apollo.QueryResult<EstimatePositionQuery, EstimatePositionQueryVariables>;
@@ -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<MarketData, 'marketTradingMode' | 'indicativePrice' | 'markPrice'>) => {
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),
};
};
@@ -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<OrderFieldsFragment> | 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(),
};
}
);