fix: deal ticket set market price (#2228)
This commit is contained in:
parent
d28ff4cfe7
commit
787a8ea1b3
@ -70,11 +70,6 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
|||||||
const step = toDecimal(market.positionDecimalPlaces);
|
const step = toDecimal(market.positionDecimalPlaces);
|
||||||
const order = watch();
|
const order = watch();
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const estMargin = useOrderMargin({
|
|
||||||
order,
|
|
||||||
market,
|
|
||||||
partyId: pubKey || '',
|
|
||||||
});
|
|
||||||
const { message: invalidText, isDisabled } = useOrderValidation({
|
const { message: invalidText, isDisabled } = useOrderValidation({
|
||||||
market,
|
market,
|
||||||
order,
|
order,
|
||||||
@ -127,11 +122,18 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
|||||||
);
|
);
|
||||||
return new BigNumber(market?.depth?.lastTrade?.price)
|
return new BigNumber(market?.depth?.lastTrade?.price)
|
||||||
.multipliedBy(multiplier)
|
.multipliedBy(multiplier)
|
||||||
.toNumber();
|
.toString();
|
||||||
}
|
}
|
||||||
return null;
|
return undefined;
|
||||||
}, [market?.depth?.lastTrade?.price, order.side, slippage]);
|
}, [market?.depth?.lastTrade?.price, order.side, slippage]);
|
||||||
|
|
||||||
|
const estMargin = useOrderMargin({
|
||||||
|
order,
|
||||||
|
market,
|
||||||
|
partyId: pubKey || '',
|
||||||
|
derivedPrice: price,
|
||||||
|
});
|
||||||
|
|
||||||
const formattedPrice =
|
const formattedPrice =
|
||||||
price && addDecimalsFormatNumber(price, market.decimalPlaces);
|
price && addDecimalsFormatNumber(price, market.decimalPlaces);
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="text-sm text-vega-orange mb-4"
|
className="text-xs text-vega-orange mb-4"
|
||||||
data-testid="dealticket-warning-margin"
|
data-testid="dealticket-warning-margin"
|
||||||
>
|
>
|
||||||
<p className="mb-2">
|
<p className="mb-2">
|
||||||
|
@ -5,8 +5,8 @@ import {
|
|||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
|
import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
import { isMarketInAuction, validateAmount } from '../../utils';
|
import { isMarketInAuction, validateAmount } from '../../utils';
|
||||||
|
|
||||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||||
|
import { getMarketPrice } from '../../utils/get-price';
|
||||||
|
|
||||||
export type DealTicketMarketAmountProps = Omit<
|
export type DealTicketMarketAmountProps = Omit<
|
||||||
DealTicketAmountProps,
|
DealTicketAmountProps,
|
||||||
@ -21,21 +21,7 @@ export const DealTicketMarketAmount = ({
|
|||||||
const quoteName =
|
const quoteName =
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||||
|
const price = getMarketPrice(market);
|
||||||
let price;
|
|
||||||
if (isMarketInAuction(market)) {
|
|
||||||
// 0 can never be a valid uncrossing price
|
|
||||||
// as it would require there being orders on the book at that price.
|
|
||||||
if (
|
|
||||||
market.data?.indicativePrice &&
|
|
||||||
market.data.indicativePrice !== '0' &&
|
|
||||||
BigInt(market.data?.indicativePrice) !== BigInt(0)
|
|
||||||
) {
|
|
||||||
price = market.data.indicativePrice;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
price = market.depth?.lastTrade?.price;
|
|
||||||
}
|
|
||||||
|
|
||||||
const priceFormatted = price
|
const priceFormatted = price
|
||||||
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
||||||
|
@ -69,7 +69,7 @@ describe('DealTicket', () => {
|
|||||||
// Assert last price is shown
|
// Assert last price is shown
|
||||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
`~${addDecimal(market!.depth!.lastTrade!.price, market.decimalPlaces)} ${
|
`~${addDecimal(market!.data.markPrice, market.decimalPlaces)} ${
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.symbol
|
market.tradableInstrument.instrument.product.settlementAsset.symbol
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
@ -63,9 +63,11 @@ export const DealTicket = ({
|
|||||||
const order = watch();
|
const order = watch();
|
||||||
// When order state changes persist it in local storage
|
// When order state changes persist it in local storage
|
||||||
useEffect(() => setPersistedOrder(order), [order, setPersistedOrder]);
|
useEffect(() => setPersistedOrder(order), [order, setPersistedOrder]);
|
||||||
|
|
||||||
const hasNoBalance = useHasNoBalance(
|
const hasNoBalance = useHasNoBalance(
|
||||||
market.tradableInstrument.instrument.product.settlementAsset.id
|
market.tradableInstrument.instrument.product.settlementAsset.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
(order: OrderSubmissionBody['orderSubmission']) => {
|
(order: OrderSubmissionBody['orderSubmission']) => {
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FeesBreakdown } from '@vegaprotocol/market-info';
|
import { FeesBreakdown } from '@vegaprotocol/market-info';
|
||||||
import {
|
import {
|
||||||
|
addDecimal,
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
t,
|
t,
|
||||||
@ -20,34 +21,39 @@ import { useCalculateSlippage } from './use-calculate-slippage';
|
|||||||
import { useOrderCloseOut } from './use-order-closeout';
|
import { useOrderCloseOut } from './use-order-closeout';
|
||||||
import { useOrderMargin } from './use-order-margin';
|
import { useOrderMargin } from './use-order-margin';
|
||||||
import type { OrderMargin } from './use-order-margin';
|
import type { OrderMargin } from './use-order-margin';
|
||||||
|
import { getDerivedPrice } from '../utils/get-price';
|
||||||
|
|
||||||
export const useFeeDealTicketDetails = (
|
export const useFeeDealTicketDetails = (
|
||||||
order: OrderSubmissionBody['orderSubmission'],
|
order: OrderSubmissionBody['orderSubmission'],
|
||||||
market: MarketDealTicket
|
market: MarketDealTicket
|
||||||
) => {
|
) => {
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
const slippage = useCalculateSlippage({ marketId: market.id, order });
|
const slippage = useCalculateSlippage({ marketId: market.id, order });
|
||||||
|
|
||||||
const price = useMemo(() => {
|
const derivedPrice = useMemo(() => {
|
||||||
const estPrice = order.price || market.data.markPrice;
|
return getDerivedPrice(order, market);
|
||||||
if (estPrice) {
|
}, [order, market]);
|
||||||
|
|
||||||
|
// Note this isn't currently used anywhere
|
||||||
|
const slippageAdjustedPrice = useMemo(() => {
|
||||||
|
if (derivedPrice) {
|
||||||
if (slippage && parseFloat(slippage) !== 0) {
|
if (slippage && parseFloat(slippage) !== 0) {
|
||||||
const isLong = order.side === Schema.Side.SIDE_BUY;
|
const isLong = order.side === Schema.Side.SIDE_BUY;
|
||||||
const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus'](
|
const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus'](
|
||||||
parseFloat(slippage) / 100
|
parseFloat(slippage) / 100
|
||||||
);
|
);
|
||||||
return new BigNumber(estPrice).multipliedBy(multiplier).toNumber();
|
return new BigNumber(derivedPrice).multipliedBy(multiplier).toNumber();
|
||||||
}
|
}
|
||||||
return order.price;
|
return derivedPrice;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [market.data.markPrice, order.price, order.side, slippage]);
|
}, [derivedPrice, order.side, slippage]);
|
||||||
|
|
||||||
const estMargin: OrderMargin | null = useOrderMargin({
|
const estMargin: OrderMargin | null = useOrderMargin({
|
||||||
order,
|
order,
|
||||||
market,
|
market,
|
||||||
partyId: pubKey || '',
|
partyId: pubKey || '',
|
||||||
|
derivedPrice,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: partyBalance } = usePartyBalanceQuery({
|
const { data: partyBalance } = usePartyBalanceQuery({
|
||||||
@ -62,11 +68,13 @@ export const useFeeDealTicketDetails = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const notionalSize = useMemo(() => {
|
const notionalSize = useMemo(() => {
|
||||||
if (order.price && order.size) {
|
if (derivedPrice && order.size) {
|
||||||
return new BigNumber(order.size).multipliedBy(order.price).toString();
|
return new BigNumber(order.size)
|
||||||
|
.multipliedBy(addDecimal(derivedPrice, market.decimalPlaces))
|
||||||
|
.toString();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [order.price, order.size]);
|
}, [derivedPrice, order.size, market.decimalPlaces]);
|
||||||
|
|
||||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||||
|
|
||||||
@ -78,7 +86,7 @@ export const useFeeDealTicketDetails = (
|
|||||||
estMargin,
|
estMargin,
|
||||||
estCloseOut,
|
estCloseOut,
|
||||||
slippage,
|
slippage,
|
||||||
price,
|
slippageAdjustedPrice,
|
||||||
partyData: partyBalance,
|
partyData: partyBalance,
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
@ -88,7 +96,7 @@ export const useFeeDealTicketDetails = (
|
|||||||
estMargin,
|
estMargin,
|
||||||
estCloseOut,
|
estCloseOut,
|
||||||
slippage,
|
slippage,
|
||||||
price,
|
slippageAdjustedPrice,
|
||||||
partyBalance,
|
partyBalance,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@ -100,7 +108,6 @@ export interface FeeDetails {
|
|||||||
estMargin: OrderMargin | null;
|
estMargin: OrderMargin | null;
|
||||||
estCloseOut: string | null;
|
estCloseOut: string | null;
|
||||||
slippage: string | null;
|
slippage: string | null;
|
||||||
price?: string | number | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFeeDetailsValues = ({
|
export const getFeeDetailsValues = ({
|
||||||
|
@ -5,6 +5,7 @@ import type { PositionMargin } from './use-market-positions';
|
|||||||
import type { Props } from './use-order-margin';
|
import type { Props } from './use-order-margin';
|
||||||
import { useOrderMargin } from './use-order-margin';
|
import { useOrderMargin } from './use-order-margin';
|
||||||
import { Schema } from '@vegaprotocol/types';
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
import type { MarketDealTicket } from '@vegaprotocol/market-list';
|
||||||
|
|
||||||
let mockEstimateData = {
|
let mockEstimateData = {
|
||||||
estimateOrder: {
|
estimateOrder: {
|
||||||
@ -58,7 +59,7 @@ describe('useOrderMargin', () => {
|
|||||||
indicativePrice: '100',
|
indicativePrice: '100',
|
||||||
markPrice: '200',
|
markPrice: '200',
|
||||||
},
|
},
|
||||||
},
|
} as MarketDealTicket,
|
||||||
partyId: 'partyId',
|
partyId: 'partyId',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,27 +1,16 @@
|
|||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { Schema } from '@vegaprotocol/types';
|
|
||||||
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||||
import { useMarketPositions } from './use-market-positions';
|
import { useMarketPositions } from './use-market-positions';
|
||||||
import type { EstimateOrderQuery } from './__generated__/EstimateOrder';
|
import type { EstimateOrderQuery } from './__generated__/EstimateOrder';
|
||||||
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
|
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
|
||||||
import { isMarketInAuction } from '../utils';
|
import type { MarketDealTicket } from '@vegaprotocol/market-list';
|
||||||
|
|
||||||
interface Market {
|
|
||||||
id: string;
|
|
||||||
decimalPlaces: number;
|
|
||||||
positionDecimalPlaces: number;
|
|
||||||
tradingMode: Schema.MarketTradingMode;
|
|
||||||
data: {
|
|
||||||
indicativePrice: string;
|
|
||||||
markPrice: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
order: OrderSubmissionBody['orderSubmission'];
|
order: OrderSubmissionBody['orderSubmission'];
|
||||||
market: Market;
|
market: MarketDealTicket;
|
||||||
partyId: string;
|
partyId: string;
|
||||||
|
derivedPrice?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const addFees = (feeObj: EstimateOrderQuery['estimateOrder']['fee']) => {
|
const addFees = (feeObj: EstimateOrderQuery['estimateOrder']['fee']) => {
|
||||||
@ -44,21 +33,21 @@ export const useOrderMargin = ({
|
|||||||
order,
|
order,
|
||||||
market,
|
market,
|
||||||
partyId,
|
partyId,
|
||||||
|
derivedPrice,
|
||||||
}: Props): OrderMargin | null => {
|
}: Props): OrderMargin | null => {
|
||||||
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
|
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
|
||||||
const priceForEstimate = getPriceForEstimate(order, market);
|
|
||||||
|
|
||||||
const { data } = useEstimateOrderQuery({
|
const { data } = useEstimateOrderQuery({
|
||||||
variables: {
|
variables: {
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
partyId,
|
partyId,
|
||||||
price: priceForEstimate,
|
price: derivedPrice,
|
||||||
size: removeDecimal(order.size, market.positionDecimalPlaces),
|
size: removeDecimal(order.size, market.positionDecimalPlaces),
|
||||||
side: order.side,
|
side: order.side,
|
||||||
timeInForce: order.timeInForce,
|
timeInForce: order.timeInForce,
|
||||||
type: order.type,
|
type: order.type,
|
||||||
},
|
},
|
||||||
skip: !partyId || !market.id || !order.size || !priceForEstimate,
|
skip: !partyId || !market.id || !order.size || !derivedPrice,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data?.estimateOrder.marginLevels.initialLevel) {
|
if (data?.estimateOrder.marginLevels.initialLevel) {
|
||||||
@ -86,32 +75,3 @@ export const useOrderMargin = ({
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a price to use for estimating order margin and fees.
|
|
||||||
* Market orders should use the markPrice or if in auction mode
|
|
||||||
* the indicative price. If its a limit order use user input price.
|
|
||||||
*/
|
|
||||||
const getPriceForEstimate = (
|
|
||||||
order: {
|
|
||||||
type: Schema.OrderType;
|
|
||||||
price?: string | undefined;
|
|
||||||
},
|
|
||||||
market: Market
|
|
||||||
) => {
|
|
||||||
// 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
|
|
||||||
let price;
|
|
||||||
if (order.type === Schema.OrderType.TYPE_LIMIT && order.price) {
|
|
||||||
price = removeDecimal(order.price, market.decimalPlaces);
|
|
||||||
} else {
|
|
||||||
if (isMarketInAuction(market)) {
|
|
||||||
price = market.data.indicativePrice;
|
|
||||||
} else {
|
|
||||||
price = market.data.markPrice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return price === '0' ? undefined : price;
|
|
||||||
};
|
|
||||||
|
51
libs/deal-ticket/src/utils/get-price.ts
Normal file
51
libs/deal-ticket/src/utils/get-price.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import type { MarketDealTicket } from '@vegaprotocol/market-list';
|
||||||
|
import { removeDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
import { isMarketInAuction } from './is-market-in-auction';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the market price based on market mode (auction or not auction)
|
||||||
|
*/
|
||||||
|
export const getMarketPrice = (market: MarketDealTicket) => {
|
||||||
|
if (isMarketInAuction(market)) {
|
||||||
|
// 0 can never be a valid uncrossing price
|
||||||
|
// as it would require there being orders on the book at that price.
|
||||||
|
if (
|
||||||
|
market.data?.indicativePrice &&
|
||||||
|
market.data.indicativePrice !== '0' &&
|
||||||
|
BigInt(market.data?.indicativePrice) !== BigInt(0)
|
||||||
|
) {
|
||||||
|
return market.data.indicativePrice;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return market.data.markPrice;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: Schema.OrderType;
|
||||||
|
price?: string | undefined;
|
||||||
|
},
|
||||||
|
market: MarketDealTicket
|
||||||
|
) => {
|
||||||
|
// 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
|
||||||
|
let price;
|
||||||
|
if (order.type === Schema.OrderType.TYPE_LIMIT && order.price) {
|
||||||
|
price = removeDecimal(order.price, market.decimalPlaces);
|
||||||
|
} else {
|
||||||
|
price = getMarketPrice(market);
|
||||||
|
}
|
||||||
|
|
||||||
|
return price === '0' ? undefined : price;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user