feat: add fee information to stop order
This commit is contained in:
@@ -4,7 +4,11 @@ import {
|
||||
useDealTicketTypeStore,
|
||||
} from '../../hooks/use-type-store';
|
||||
import { StopOrder } from './deal-ticket-stop-order';
|
||||
import { useStaticMarketData, useMarket } from '@vegaprotocol/markets';
|
||||
import {
|
||||
useStaticMarketData,
|
||||
useMarket,
|
||||
useMarketPrice,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
@@ -33,6 +37,7 @@ export const DealTicketContainer = ({
|
||||
loading: marketDataLoading,
|
||||
reload,
|
||||
} = useStaticMarketData(marketId);
|
||||
const { data: marketPrice } = useMarketPrice(market?.id);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
return (
|
||||
<AsyncRenderer
|
||||
@@ -46,12 +51,14 @@ export const DealTicketContainer = ({
|
||||
type === DealTicketType.StopMarket ? (
|
||||
<StopOrder
|
||||
market={market}
|
||||
marketPrice={marketPrice}
|
||||
submit={(stopOrdersSubmission) => create({ stopOrdersSubmission })}
|
||||
/>
|
||||
) : (
|
||||
<DealTicket
|
||||
{...props}
|
||||
market={market}
|
||||
marketPrice={marketPrice}
|
||||
marketData={marketData}
|
||||
submit={(orderSubmission) => create({ orderSubmission })}
|
||||
/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FeesBreakdown } from '@vegaprotocol/markets';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
@@ -76,26 +78,82 @@ export const DealTicketFeeDetail = ({
|
||||
};
|
||||
|
||||
export interface DealTicketFeeDetailsProps {
|
||||
assetSymbol: string;
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
market: Market;
|
||||
notionalSize: string | null;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
assetSymbol,
|
||||
order,
|
||||
market,
|
||||
notionalSize,
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
const feeEstimate = useEstimateFees(order);
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface DealTicketMarginDetailsProps {
|
||||
generalAccountBalance?: string;
|
||||
marginAccountBalance?: string;
|
||||
market: Market;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
assetSymbol: string;
|
||||
notionalSize: string | null;
|
||||
feeEstimate: EstimateFeesQuery['estimateFees'] | undefined;
|
||||
positionEstimate: EstimatePositionQuery['estimatePosition'];
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
export const DealTicketMarginDetails = ({
|
||||
marginAccountBalance,
|
||||
generalAccountBalance,
|
||||
assetSymbol,
|
||||
feeEstimate,
|
||||
market,
|
||||
onMarketClick,
|
||||
notionalSize,
|
||||
positionEstimate,
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
}: DealTicketMarginDetailsProps) => {
|
||||
const [breakdownDialog, setBreakdownDialog] = useState(false);
|
||||
const { pubKey: partyId } = useVegaWallet();
|
||||
const { data: currentMargins } = useDataProvider({
|
||||
@@ -110,7 +168,6 @@ export const DealTicketFeeDetails = ({
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
let marginRequiredBestCase: string | undefined = undefined;
|
||||
let marginRequiredWorstCase: string | undefined = undefined;
|
||||
if (marginEstimate) {
|
||||
@@ -251,41 +308,7 @@ export const DealTicketFeeDetails = ({
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Margin required')}
|
||||
value={formatRange(
|
||||
@@ -351,6 +374,6 @@ export const DealTicketFeeDetails = ({
|
||||
onClose={onAccountBreakdownDialogClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import { useRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||
import { formatNumber, toDecimal, validateAmount } from '@vegaprotocol/utils';
|
||||
import {
|
||||
formatNumber,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
@@ -14,12 +19,16 @@ import {
|
||||
Select,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { timeInForceLabel, useOrder } from '@vegaprotocol/orders';
|
||||
import { NoWalletWarning, REDUCE_ONLY_TOOLTIP } from './deal-ticket';
|
||||
import {
|
||||
NoWalletWarning,
|
||||
REDUCE_ONLY_TOOLTIP,
|
||||
useNotionalSize,
|
||||
} from './deal-ticket';
|
||||
import { TypeToggle } from './type-selector';
|
||||
import {
|
||||
useStopOrderFormValues,
|
||||
@@ -32,22 +41,25 @@ import {
|
||||
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-stop-order-submission';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import noop from 'lodash/noop';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
|
||||
export interface StopOrderProps {
|
||||
market: Market;
|
||||
marketPrice?: string | null;
|
||||
submit: (order: StopOrdersSubmission) => void;
|
||||
}
|
||||
|
||||
const defaultValues: Partial<StopOrderFormValues> = {
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
trigger: 'price',
|
||||
direction: 'risesAbove',
|
||||
expiryStrategy: 'submit',
|
||||
};
|
||||
|
||||
export const StopOrder = ({ market, submit }: StopOrderProps) => {
|
||||
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const setDealTicketType = useDealTicketTypeStore((state) => state.set);
|
||||
const [, updateOrder] = useOrder(market.id);
|
||||
@@ -91,7 +103,42 @@ export const StopOrder = ({ market, submit }: StopOrderProps) => {
|
||||
const expire = watch('expire');
|
||||
const trigger = watch('trigger');
|
||||
const triggerPrice = watch('triggerPrice');
|
||||
const timeInForce = watch('timeInForce');
|
||||
const type = watch('type');
|
||||
const rawPrice = watch('price');
|
||||
const rawSize = watch('size');
|
||||
|
||||
const size = removeDecimal(rawSize, market.positionDecimalPlaces);
|
||||
const price = useMemo(() => {
|
||||
return (
|
||||
marketPrice &&
|
||||
getDerivedPrice(
|
||||
{
|
||||
type,
|
||||
price: rawPrice && removeDecimal(rawPrice, market.decimalPlaces),
|
||||
},
|
||||
type === Schema.OrderType.TYPE_MARKET &&
|
||||
trigger === 'price' &&
|
||||
triggerPrice
|
||||
? removeDecimal(triggerPrice, market.decimalPlaces)
|
||||
: marketPrice
|
||||
)
|
||||
);
|
||||
}, [
|
||||
market.decimalPlaces,
|
||||
marketPrice,
|
||||
rawPrice,
|
||||
trigger,
|
||||
triggerPrice,
|
||||
type,
|
||||
]);
|
||||
|
||||
const notionalSize = useNotionalSize(
|
||||
price,
|
||||
size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch((value, { name, type }) => {
|
||||
@@ -235,7 +282,10 @@ export const StopOrder = ({ market, submit }: StopOrderProps) => {
|
||||
/>
|
||||
</FormGroup>
|
||||
) : (
|
||||
<div className="text-sm text-right pt-5" data-testid="price">
|
||||
<div
|
||||
className="text-sm text-right pt-7 leading-10"
|
||||
data-testid="price"
|
||||
>
|
||||
{priceFormatted && quoteName
|
||||
? `~${priceFormatted} ${quoteName}`
|
||||
: '-'}
|
||||
@@ -506,6 +556,19 @@ export const StopOrder = ({ market, submit }: StopOrderProps) => {
|
||||
)}
|
||||
<NoWalletWarning pubKey={pubKey} isReadOnly={isReadOnly} asset={asset} />
|
||||
<DealTicketButton side={side} label={t('Submit Stop Order')} />
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
marketId: market.id,
|
||||
price: price || undefined,
|
||||
side,
|
||||
size,
|
||||
timeInForce,
|
||||
type,
|
||||
}}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={asset.symbol}
|
||||
market={market}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,10 @@ import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
import {
|
||||
DealTicketFeeDetails,
|
||||
DealTicketMarginDetails,
|
||||
} from './deal-ticket-fee-details';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
@@ -71,16 +74,36 @@ export const REDUCE_ONLY_TOOLTIP =
|
||||
export interface DealTicketProps {
|
||||
market: Market;
|
||||
marketData: StaticMarketData;
|
||||
marketPrice?: string | null;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
submit: (order: OrderSubmission) => void;
|
||||
onClickCollateral?: () => void;
|
||||
onDeposit: (assetId: string) => void;
|
||||
}
|
||||
|
||||
export const useNotionalSize = (
|
||||
price: string | null | undefined,
|
||||
size: string | undefined,
|
||||
decimalPlaces: number,
|
||||
positionDecimalPlaces: number
|
||||
) =>
|
||||
useMemo(() => {
|
||||
if (price && size) {
|
||||
return removeDecimal(
|
||||
toBigNum(size, positionDecimalPlaces).multipliedBy(
|
||||
toBigNum(price, decimalPlaces)
|
||||
),
|
||||
decimalPlaces
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [price, size, decimalPlaces, positionDecimalPlaces]);
|
||||
|
||||
export const DealTicket = ({
|
||||
market,
|
||||
onMarketClick,
|
||||
marketData,
|
||||
marketPrice,
|
||||
submit,
|
||||
onClickCollateral,
|
||||
onDeposit,
|
||||
@@ -90,7 +113,6 @@ export const DealTicket = ({
|
||||
const updateStopOrderFormValues = useStopOrderFormValues(
|
||||
(state) => state.update
|
||||
);
|
||||
const { data: marketPrice } = useMarketPrice(market.id);
|
||||
// store last used tif for market so that when changing OrderType the previous TIF
|
||||
// selection for that type is used when switching back
|
||||
|
||||
@@ -141,27 +163,13 @@ export const DealTicket = ({
|
||||
);
|
||||
}, [normalizedOrder, marketPrice]);
|
||||
|
||||
const notionalSize = useMemo(() => {
|
||||
if (price && normalizedOrder?.size) {
|
||||
return removeDecimal(
|
||||
toBigNum(
|
||||
normalizedOrder.size,
|
||||
market.positionDecimalPlaces
|
||||
).multipliedBy(toBigNum(price, market.decimalPlaces)),
|
||||
market.decimalPlaces
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
const notionalSize = useNotionalSize(
|
||||
price,
|
||||
normalizedOrder?.size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces,
|
||||
]);
|
||||
|
||||
const feeEstimate = useEstimateFees(
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
|
||||
const { data: activeOrders } = useDataProvider({
|
||||
dataProvider: activeOrdersProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
@@ -582,10 +590,16 @@ export const DealTicket = ({
|
||||
/>
|
||||
<DealTicketButton side={order.side} />
|
||||
<DealTicketFeeDetails
|
||||
onMarketClick={onMarketClick}
|
||||
feeEstimate={feeEstimate}
|
||||
order={
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={assetSymbol}
|
||||
market={market}
|
||||
/>
|
||||
<DealTicketMarginDetails
|
||||
onMarketClick={onMarketClick}
|
||||
assetSymbol={assetSymbol}
|
||||
marginAccountBalance={marginAccountBalance}
|
||||
generalAccountBalance={generalAccountBalance}
|
||||
positionEstimate={positionEstimate?.estimatePosition}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useRef } from 'react';
|
||||
|
||||
interface ExpirySelectorProps {
|
||||
value?: string;
|
||||
@@ -13,7 +14,8 @@ export const ExpirySelector = ({
|
||||
onSelect,
|
||||
errorMessage,
|
||||
}: ExpirySelectorProps) => {
|
||||
const date = value ? new Date(value) : new Date();
|
||||
const now = useRef(new Date());
|
||||
const date = value ? new Date(value) : now.current;
|
||||
const dateFormatted = formatForInput(date);
|
||||
const minDate = formatForInput(date);
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
import { normalizeOrderSubmission } from '@vegaprotocol/wallet';
|
||||
import type { StopOrderFormValues } from '../hooks/use-stop-order-form-values';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { toNanoSeconds } from '@vegaprotocol/utils';
|
||||
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
|
||||
export const mapFormValuesToStopOrdersSubmission = (
|
||||
data: StopOrderFormValues,
|
||||
@@ -30,7 +30,7 @@ export const mapFormValuesToStopOrdersSubmission = (
|
||||
),
|
||||
};
|
||||
if (data.trigger === 'price') {
|
||||
stopOrderSetup.price = data.triggerPrice;
|
||||
stopOrderSetup.price = removeDecimal(data.triggerPrice, decimalPlaces);
|
||||
} else if (data.trigger === 'trailingPercentOffset') {
|
||||
stopOrderSetup.trailingPercentOffset = (
|
||||
Number(data.triggerTrailingPercentOffset) / 100
|
||||
|
||||
@@ -82,7 +82,6 @@ export const marketProvider = makeDerivedDataProvider<
|
||||
);
|
||||
|
||||
export const useMarket = (marketId?: string) => {
|
||||
console.log(marketId);
|
||||
const variables = useMemo(() => ({ marketId: marketId || '' }), [marketId]);
|
||||
return useDataProvider({
|
||||
dataProvider: marketProvider,
|
||||
|
||||
Reference in New Issue
Block a user