feat(deal-ticket): show total margin available for an order, and where it will come from (#3318)
This commit is contained in:
parent
8463d371ad
commit
2aad6b1a14
@ -1,4 +1,5 @@
|
|||||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import classnames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import type { Market, MarketData } from '@vegaprotocol/market-list';
|
import type { Market, MarketData } from '@vegaprotocol/market-list';
|
||||||
@ -11,9 +12,12 @@ interface DealTicketFeeDetailsProps {
|
|||||||
order: OrderSubmissionBody['orderSubmission'];
|
order: OrderSubmissionBody['orderSubmission'];
|
||||||
market: Market;
|
market: Market;
|
||||||
marketData: MarketData;
|
marketData: MarketData;
|
||||||
margin: string;
|
currentInitialMargin?: string;
|
||||||
totalMargin: string;
|
currentMaintenanceMargin?: string;
|
||||||
balance: string;
|
estimatedInitialMargin: string;
|
||||||
|
estimatedTotalInitialMargin: string;
|
||||||
|
marginAccountBalance: string;
|
||||||
|
generalAccountBalance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DealTicketFeeDetailProps {
|
export interface DealTicketFeeDetailProps {
|
||||||
@ -45,23 +49,22 @@ export const DealTicketFeeDetails = ({
|
|||||||
order,
|
order,
|
||||||
market,
|
market,
|
||||||
marketData,
|
marketData,
|
||||||
margin,
|
...args
|
||||||
totalMargin,
|
|
||||||
balance,
|
|
||||||
}: DealTicketFeeDetailsProps) => {
|
}: DealTicketFeeDetailsProps) => {
|
||||||
const feeDetails = useFeeDealTicketDetails(order, market, marketData);
|
const feeDetails = useFeeDealTicketDetails(order, market, marketData);
|
||||||
const details = getFeeDetailsValues({
|
const details = getFeeDetailsValues({
|
||||||
...feeDetails,
|
...feeDetails,
|
||||||
margin,
|
...args,
|
||||||
totalMargin,
|
|
||||||
balance,
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{details.map(({ label, value, labelDescription, symbol }) => (
|
{details.map(({ label, value, labelDescription, symbol, indent }) => (
|
||||||
<div
|
<div
|
||||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||||
className="text-xs mt-2 flex justify-between items-center gap-4 flex-wrap"
|
className={classnames(
|
||||||
|
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||||
|
{ 'ml-2': indent }
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Tooltip description={labelDescription}>
|
<Tooltip description={labelDescription}>
|
||||||
|
@ -44,6 +44,9 @@ import {
|
|||||||
|
|
||||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
|
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
|
||||||
import { useOrderForm } from '../../hooks/use-order-form';
|
import { useOrderForm } from '../../hooks/use-order-form';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
import { marketMarginDataProvider } from '@vegaprotocol/positions';
|
||||||
|
|
||||||
export interface DealTicketProps {
|
export interface DealTicketProps {
|
||||||
market: Market;
|
market: Market;
|
||||||
@ -103,6 +106,12 @@ export const DealTicket = ({
|
|||||||
|
|
||||||
const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder);
|
const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder);
|
||||||
|
|
||||||
|
const { data: currentMargins } = useDataProvider({
|
||||||
|
dataProvider: marketMarginDataProvider,
|
||||||
|
variables: { marketId: market.id, partyId: pubKey || '' },
|
||||||
|
skip: !pubKey,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
setError('summary', {
|
setError('summary', {
|
||||||
@ -367,9 +376,12 @@ export const DealTicket = ({
|
|||||||
order={normalizedOrder}
|
order={normalizedOrder}
|
||||||
market={market}
|
market={market}
|
||||||
marketData={marketData}
|
marketData={marketData}
|
||||||
margin={margin}
|
estimatedInitialMargin={margin}
|
||||||
totalMargin={totalMargin}
|
estimatedTotalInitialMargin={totalMargin}
|
||||||
balance={marginAccountBalance}
|
currentInitialMargin={currentMargins?.initialLevel}
|
||||||
|
currentMaintenanceMargin={currentMargins?.maintenanceLevel}
|
||||||
|
marginAccountBalance={marginAccountBalance}
|
||||||
|
generalAccountBalance={generalAccountBalance}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</TinyScroll>
|
</TinyScroll>
|
||||||
|
@ -10,12 +10,36 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
|
|||||||
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
||||||
'Estimated total margin that will cover open position, active orders and this order.'
|
'Estimated total margin that will cover open position, active orders and this order.'
|
||||||
);
|
);
|
||||||
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance');
|
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
|
||||||
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||||
t(
|
t(
|
||||||
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).",
|
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).",
|
||||||
[settlementAsset]
|
[settlementAsset]
|
||||||
);
|
);
|
||||||
|
export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT = (
|
||||||
|
settlementAsset: string
|
||||||
|
) =>
|
||||||
|
t(
|
||||||
|
'To cover the required margin, this amount will be drawn from your general (%s) account.',
|
||||||
|
[settlementAsset]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TOTAL_MARGIN_AVAILABLE = (
|
||||||
|
generalAccountBalance: string,
|
||||||
|
marginAccountBalance: string,
|
||||||
|
marginMaintenance: string,
|
||||||
|
settlementAsset: string
|
||||||
|
) =>
|
||||||
|
t(
|
||||||
|
'Total margin available = general %s balance (%s) + margin balance (%s) - maintenance level (%s).',
|
||||||
|
[
|
||||||
|
settlementAsset,
|
||||||
|
`${generalAccountBalance} ${settlementAsset}`,
|
||||||
|
`${marginAccountBalance} ${settlementAsset}`,
|
||||||
|
`${marginMaintenance} ${settlementAsset}`,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
|
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
|
||||||
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
|
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
|
||||||
);
|
);
|
||||||
@ -40,7 +64,7 @@ export const EST_SLIPPAGE = t(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const ERROR_SIZE_DECIMAL = t(
|
export const ERROR_SIZE_DECIMAL = t(
|
||||||
'The size field accepts up to X decimal places'
|
'The size field accepts up to X decimal places.'
|
||||||
);
|
);
|
||||||
|
|
||||||
export enum MarketModeValidationType {
|
export enum MarketModeValidationType {
|
||||||
|
@ -15,6 +15,8 @@ import {
|
|||||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||||
|
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
||||||
|
TOTAL_MARGIN_AVAILABLE,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { useOrderCloseOut } from './use-order-closeout';
|
import { useOrderCloseOut } from './use-order-closeout';
|
||||||
import { useMarketAccountBalance } from '@vegaprotocol/accounts';
|
import { useMarketAccountBalance } from '@vegaprotocol/accounts';
|
||||||
@ -85,24 +87,32 @@ export const useFeeDealTicketDetails = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface FeeDetails {
|
export interface FeeDetails {
|
||||||
balance: string;
|
generalAccountBalance?: string;
|
||||||
|
marginAccountBalance?: string;
|
||||||
market: Market;
|
market: Market;
|
||||||
assetSymbol: string;
|
assetSymbol: string;
|
||||||
notionalSize: string | null;
|
notionalSize: string | null;
|
||||||
estCloseOut: string | null;
|
estCloseOut: string | null;
|
||||||
estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined;
|
estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined;
|
||||||
margin: string;
|
estimatedInitialMargin: string;
|
||||||
totalMargin: string;
|
estimatedTotalInitialMargin: string;
|
||||||
|
currentInitialMargin?: string;
|
||||||
|
currentMaintenanceMargin?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFeeDetailsValues = ({
|
export const getFeeDetailsValues = ({
|
||||||
balance,
|
marginAccountBalance,
|
||||||
|
generalAccountBalance,
|
||||||
assetSymbol,
|
assetSymbol,
|
||||||
estimateOrder,
|
estimateOrder,
|
||||||
market,
|
market,
|
||||||
notionalSize,
|
notionalSize,
|
||||||
totalMargin,
|
estimatedTotalInitialMargin,
|
||||||
|
currentInitialMargin,
|
||||||
|
currentMaintenanceMargin,
|
||||||
}: FeeDetails) => {
|
}: FeeDetails) => {
|
||||||
|
const totalBalance =
|
||||||
|
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
|
||||||
const assetDecimals =
|
const assetDecimals =
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
||||||
const formatValueWithMarketDp = (
|
const formatValueWithMarketDp = (
|
||||||
@ -123,7 +133,8 @@ export const getFeeDetailsValues = ({
|
|||||||
label: string;
|
label: string;
|
||||||
value?: string | null;
|
value?: string | null;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
labelDescription: React.ReactNode;
|
indent?: boolean;
|
||||||
|
labelDescription?: React.ReactNode;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
label: t('Notional'),
|
label: t('Notional'),
|
||||||
@ -153,38 +164,64 @@ export const getFeeDetailsValues = ({
|
|||||||
),
|
),
|
||||||
symbol: assetSymbol,
|
symbol: assetSymbol,
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
{
|
|
||||||
label: t('Initial margin'),
|
|
||||||
value: margin && `~${formatValueWithAssetDp(margin)}`,
|
|
||||||
symbol: assetSymbol,
|
|
||||||
labelDescription: EST_MARGIN_TOOLTIP_TEXT(assetSymbol),
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
label: t('Margin required'),
|
label: t('Margin required'),
|
||||||
value: `~${formatValueWithAssetDp(
|
value: `~${formatValueWithAssetDp(
|
||||||
balance
|
currentInitialMargin
|
||||||
? (BigInt(totalMargin) - BigInt(balance)).toString()
|
? (
|
||||||
: totalMargin
|
BigInt(estimatedTotalInitialMargin) - BigInt(currentInitialMargin)
|
||||||
|
).toString()
|
||||||
|
: estimatedTotalInitialMargin
|
||||||
)}`,
|
)}`,
|
||||||
symbol: assetSymbol,
|
symbol: assetSymbol,
|
||||||
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
|
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (balance) {
|
if (totalBalance) {
|
||||||
|
const totalMarginAvailable = (
|
||||||
|
currentMaintenanceMargin
|
||||||
|
? totalBalance - BigInt(currentMaintenanceMargin)
|
||||||
|
: totalBalance
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
details.push({
|
||||||
|
indent: true,
|
||||||
|
label: t('Total margin available'),
|
||||||
|
value: `~${formatValueWithAssetDp(totalMarginAvailable)}`,
|
||||||
|
symbol: assetSymbol,
|
||||||
|
labelDescription: TOTAL_MARGIN_AVAILABLE(
|
||||||
|
formatValueWithAssetDp(generalAccountBalance),
|
||||||
|
formatValueWithAssetDp(marginAccountBalance),
|
||||||
|
formatValueWithAssetDp(currentMaintenanceMargin),
|
||||||
|
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({
|
details.push({
|
||||||
label: t('Projected margin'),
|
label: t('Projected margin'),
|
||||||
value: `~${formatValueWithAssetDp(totalMargin)}`,
|
value: `~${formatValueWithAssetDp(estimatedTotalInitialMargin)}`,
|
||||||
symbol: assetSymbol,
|
symbol: assetSymbol,
|
||||||
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
details.push({
|
details.push({
|
||||||
label: t('Current margin allocation'),
|
label: t('Current margin allocation'),
|
||||||
value: balance
|
value: `${formatValueWithAssetDp(marginAccountBalance)}`,
|
||||||
? `~${formatValueWithAssetDp(balance)}`
|
|
||||||
: `${formatValueWithAssetDp(balance)}`,
|
|
||||||
symbol: assetSymbol,
|
symbol: assetSymbol,
|
||||||
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||||
});
|
});
|
||||||
|
@ -65,5 +65,11 @@ export const useInitialMargin = (
|
|||||||
sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString();
|
sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return useMemo(() => ({ totalMargin, margin }), [totalMargin, margin]);
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
totalMargin,
|
||||||
|
margin,
|
||||||
|
}),
|
||||||
|
[totalMargin, margin]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { Position } from '../';
|
import type { Position } from '../';
|
||||||
import { usePositionsData, PositionsTable } from '../';
|
import { usePositionsData, PositionsTable } from '../';
|
||||||
import type { FilterChangedEvent } from 'ag-grid-community';
|
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||||
@ -23,8 +22,8 @@ export const PositionsManager = ({
|
|||||||
noBottomPlaceholder,
|
noBottomPlaceholder,
|
||||||
}: PositionsManagerProps) => {
|
}: PositionsManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const [dataCount, setDataCount] = useState(0);
|
|
||||||
const { data, error, loading, reload } = usePositionsData(partyId, gridRef);
|
const { data, error, loading, reload } = usePositionsData(partyId, gridRef);
|
||||||
|
const [dataCount, setDataCount] = useState(data?.length ?? 0);
|
||||||
const create = useVegaTransactionStore((store) => store.create);
|
const create = useVegaTransactionStore((store) => store.create);
|
||||||
const onClose = ({
|
const onClose = ({
|
||||||
marketId,
|
marketId,
|
||||||
@ -67,10 +66,7 @@ export const PositionsManager = ({
|
|||||||
setId,
|
setId,
|
||||||
disabled: noBottomPlaceholder,
|
disabled: noBottomPlaceholder,
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
const updateRowCount = useCallback(() => {
|
||||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
|
||||||
}, [data]);
|
|
||||||
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
|
|
||||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
@ -83,7 +79,8 @@ export const PositionsManager = ({
|
|||||||
suppressLoadingOverlay
|
suppressLoadingOverlay
|
||||||
suppressNoRowsOverlay
|
suppressNoRowsOverlay
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
onFilterChanged={onFilterChanged}
|
onFilterChanged={updateRowCount}
|
||||||
|
onRowDataUpdated={updateRowCount}
|
||||||
{...bottomPlaceholderProps}
|
{...bottomPlaceholderProps}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
Loading…
Reference in New Issue
Block a user