From a53e75575e4871f2eb8d4c05ded8ce53b0489bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Tue, 25 Jul 2023 11:34:35 +0200 Subject: [PATCH] feat: add fee information to stop order --- .../deal-ticket/deal-ticket-container.tsx | 9 +- .../deal-ticket/deal-ticket-fee-details.tsx | 109 +++++++++++------- .../deal-ticket/deal-ticket-stop-order.tsx | 77 +++++++++++-- .../components/deal-ticket/deal-ticket.tsx | 56 +++++---- .../deal-ticket/expiry-selector.tsx | 4 +- ...ap-form-values-to-stop-order-submission.ts | 4 +- libs/markets/src/lib/markets-provider.ts | 1 - 7 files changed, 184 insertions(+), 76 deletions(-) diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx index f079ad361..66071c9d0 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx @@ -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 ( create({ stopOrdersSubmission })} /> ) : ( create({ orderSubmission })} /> 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 cf573debe..5a39ba7b6 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 @@ -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 ( + <> + + + + {t( + `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.` + )} + + + + } + 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 ( -
- - - - {t( - `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.` - )} - - - - } - symbol={assetSymbol} - /> + <> )} -
+ ); }; diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx index 1959f03a3..472ce38a0 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx @@ -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 = { 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) => { /> ) : ( -
+
{priceFormatted && quoteName ? `~${priceFormatted} ${quoteName}` : '-'} @@ -506,6 +556,19 @@ export const StopOrder = ({ market, submit }: StopOrderProps) => { )} + ); }; 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 c3a6d1ca0..faa5285bc 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -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 = ({ /> + { - 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 ( diff --git a/libs/deal-ticket/src/utils/map-form-values-to-stop-order-submission.ts b/libs/deal-ticket/src/utils/map-form-values-to-stop-order-submission.ts index f5f649f7f..0db0c910b 100644 --- a/libs/deal-ticket/src/utils/map-form-values-to-stop-order-submission.ts +++ b/libs/deal-ticket/src/utils/map-form-values-to-stop-order-submission.ts @@ -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 diff --git a/libs/markets/src/lib/markets-provider.ts b/libs/markets/src/lib/markets-provider.ts index 18a0a235f..cc80c6125 100644 --- a/libs/markets/src/lib/markets-provider.ts +++ b/libs/markets/src/lib/markets-provider.ts @@ -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,