fix(deal-ticket): disable iceberg for IOC and FOK (#4629)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
63bfcc8f65
commit
dc959025c6
@ -20,7 +20,8 @@ import {
|
|||||||
TradingSelect as Select,
|
TradingSelect as Select,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
|
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||||
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { ExpirySelector } from './expiry-selector';
|
import { ExpirySelector } from './expiry-selector';
|
||||||
import { SideSelector } from './side-selector';
|
import { SideSelector } from './side-selector';
|
||||||
@ -35,10 +36,10 @@ import { TypeToggle } from './type-selector';
|
|||||||
import {
|
import {
|
||||||
useDealTicketFormValues,
|
useDealTicketFormValues,
|
||||||
DealTicketType,
|
DealTicketType,
|
||||||
type StopOrderFormValues,
|
|
||||||
dealTicketTypeToOrderType,
|
dealTicketTypeToOrderType,
|
||||||
isStopOrderType,
|
isStopOrderType,
|
||||||
} from '../../hooks/use-form-values';
|
} from '../../hooks/use-form-values';
|
||||||
|
import type { StopOrderFormValues } from '../../hooks/use-form-values';
|
||||||
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
|
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
|
||||||
import { DealTicketButton } from './deal-ticket-button';
|
import { DealTicketButton } from './deal-ticket-button';
|
||||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||||
@ -632,11 +633,11 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
|||||||
/>
|
/>
|
||||||
<Size control={control} sizeStep={sizeStep} />
|
<Size control={control} sizeStep={sizeStep} />
|
||||||
<TimeInForce control={control} />
|
<TimeInForce control={control} />
|
||||||
<div className="flex gap-2 pb-3 justify-end">
|
<div className="flex justify-end pb-3 gap-2">
|
||||||
<ReduceOnly />
|
<ReduceOnly />
|
||||||
</div>
|
</div>
|
||||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||||
<div className="flex gap-2 pb-2 justify-between">
|
<div className="flex justify-between pb-2 gap-2">
|
||||||
<Controller
|
<Controller
|
||||||
name="oco"
|
name="oco"
|
||||||
control={control}
|
control={control}
|
||||||
@ -713,7 +714,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
|||||||
/>
|
/>
|
||||||
<Size control={control} sizeStep={sizeStep} oco />
|
<Size control={control} sizeStep={sizeStep} oco />
|
||||||
<TimeInForce control={control} oco />
|
<TimeInForce control={control} oco />
|
||||||
<div className="flex gap-2 mb-2 justify-end">
|
<div className="flex justify-end mb-2 gap-2">
|
||||||
<ReduceOnly />
|
<ReduceOnly />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -38,8 +38,6 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||||
import type { OrderInfo } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
validateExpiration,
|
validateExpiration,
|
||||||
validateMarketState,
|
validateMarketState,
|
||||||
@ -59,8 +57,6 @@ import {
|
|||||||
useMarketAccountBalance,
|
useMarketAccountBalance,
|
||||||
useAccountBalance,
|
useAccountBalance,
|
||||||
} from '@vegaprotocol/accounts';
|
} from '@vegaprotocol/accounts';
|
||||||
|
|
||||||
import { OrderType } from '@vegaprotocol/types';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import {
|
import {
|
||||||
DealTicketType,
|
DealTicketType,
|
||||||
@ -71,6 +67,7 @@ import type { OrderFormValues } from '../../hooks/use-form-values';
|
|||||||
import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
||||||
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||||
import noop from 'lodash/noop';
|
import noop from 'lodash/noop';
|
||||||
|
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||||
|
|
||||||
export const REDUCE_ONLY_TOOLTIP =
|
export const REDUCE_ONLY_TOOLTIP =
|
||||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||||
@ -230,8 +227,8 @@ export const DealTicket = ({
|
|||||||
});
|
});
|
||||||
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
||||||
const orders = activeOrders
|
const orders = activeOrders
|
||||||
? activeOrders.map<OrderInfo>((order) => ({
|
? activeOrders.map<Schema.OrderInfo>((order) => ({
|
||||||
isMarketOrder: order.type === OrderType.TYPE_MARKET,
|
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
|
||||||
price: order.price,
|
price: order.price,
|
||||||
remaining: order.remaining,
|
remaining: order.remaining,
|
||||||
side: order.side,
|
side: order.side,
|
||||||
@ -239,7 +236,7 @@ export const DealTicket = ({
|
|||||||
: [];
|
: [];
|
||||||
if (normalizedOrder) {
|
if (normalizedOrder) {
|
||||||
orders.push({
|
orders.push({
|
||||||
isMarketOrder: normalizedOrder.type === OrderType.TYPE_MARKET,
|
isMarketOrder: normalizedOrder.type === Schema.OrderType.TYPE_MARKET,
|
||||||
price: normalizedOrder.price ?? '0',
|
price: normalizedOrder.price ?? '0',
|
||||||
remaining: normalizedOrder.size,
|
remaining: normalizedOrder.size,
|
||||||
side: normalizedOrder.side,
|
side: normalizedOrder.side,
|
||||||
@ -307,12 +304,10 @@ export const DealTicket = ({
|
|||||||
pubKey,
|
pubKey,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const disablePostOnlyCheckbox = [
|
const nonPersistentOrder = isNonPersistentOrder(timeInForce);
|
||||||
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
const disablePostOnlyCheckbox = nonPersistentOrder;
|
||||||
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
const disableReduceOnlyCheckbox = !nonPersistentOrder;
|
||||||
].includes(timeInForce);
|
const disableIcebergCheckbox = nonPersistentOrder;
|
||||||
|
|
||||||
const disableReduceOnlyCheckbox = !disablePostOnlyCheckbox;
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
(formValues: OrderFormValues) => {
|
(formValues: OrderFormValues) => {
|
||||||
@ -468,6 +463,8 @@ export const DealTicket = ({
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
orderType={type}
|
orderType={type}
|
||||||
onSelect={(value) => {
|
onSelect={(value) => {
|
||||||
|
// If GTT is selected and no expiresAt time is set, or its
|
||||||
|
// behind current time then reset the value to current time
|
||||||
if (
|
if (
|
||||||
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||||
@ -476,6 +473,12 @@ export const DealTicket = ({
|
|||||||
shouldValidate: true,
|
shouldValidate: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iceberg orders must be persistent orders, so if user
|
||||||
|
// switches to to a non persisten tif value, remove iceberg selection
|
||||||
|
if (iceberg && isNonPersistentOrder(value)) {
|
||||||
|
setValue('iceberg', false);
|
||||||
|
}
|
||||||
field.onChange(value);
|
field.onChange(value);
|
||||||
}}
|
}}
|
||||||
market={market}
|
market={market}
|
||||||
@ -502,7 +505,7 @@ export const DealTicket = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2 pb-2 justify-between">
|
<div className="flex justify-between pb-2 gap-2">
|
||||||
<Controller
|
<Controller
|
||||||
name="postOnly"
|
name="postOnly"
|
||||||
control={control}
|
control={control}
|
||||||
@ -568,7 +571,7 @@ export const DealTicket = ({
|
|||||||
</div>
|
</div>
|
||||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-2 pb-2 justify-between">
|
<div className="flex justify-between pb-2 gap-2">
|
||||||
<Controller
|
<Controller
|
||||||
name="iceberg"
|
name="iceberg"
|
||||||
control={control}
|
control={control}
|
||||||
@ -577,6 +580,7 @@ export const DealTicket = ({
|
|||||||
name="iceberg"
|
name="iceberg"
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
|
disabled={disableIcebergCheckbox}
|
||||||
label={
|
label={
|
||||||
<Tooltip
|
<Tooltip
|
||||||
description={
|
description={
|
||||||
|
@ -18,6 +18,8 @@ export const ExpirySelector = ({
|
|||||||
onSelect,
|
onSelect,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
}: ExpirySelectorProps) => {
|
}: ExpirySelectorProps) => {
|
||||||
|
const minDateRef = useRef(new Date());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<TradingFormGroup
|
<TradingFormGroup
|
||||||
@ -31,7 +33,7 @@ export const ExpirySelector = ({
|
|||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={value && formatForInput(new Date(value))}
|
value={value && formatForInput(new Date(value))}
|
||||||
onChange={(e) => onSelect(e.target.value)}
|
onChange={(e) => onSelect(e.target.value)}
|
||||||
min={formatForInput(useRef(new Date()).current)}
|
min={formatForInput(minDateRef.current)}
|
||||||
hasError={!!errorMessage}
|
hasError={!!errorMessage}
|
||||||
/>
|
/>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
|
@ -9,6 +9,7 @@ import type {
|
|||||||
} from '../hooks/use-form-values';
|
} from '../hooks/use-form-values';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||||
|
import { isPersistentOrder } from './time-in-force-persistance';
|
||||||
|
|
||||||
export const mapFormValuesToOrderSubmission = (
|
export const mapFormValuesToOrderSubmission = (
|
||||||
order: OrderFormValues,
|
order: OrderFormValues,
|
||||||
@ -41,11 +42,8 @@ export const mapFormValuesToOrderSubmission = (
|
|||||||
? false
|
? false
|
||||||
: order.reduceOnly,
|
: order.reduceOnly,
|
||||||
icebergOpts:
|
icebergOpts:
|
||||||
(order.type === Schema.OrderType.TYPE_MARKET ||
|
order.type === Schema.OrderType.TYPE_LIMIT &&
|
||||||
[
|
isPersistentOrder(order.timeInForce) &&
|
||||||
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
|
||||||
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
|
||||||
].includes(order.timeInForce)) &&
|
|
||||||
order.iceberg &&
|
order.iceberg &&
|
||||||
order.peakSize &&
|
order.peakSize &&
|
||||||
order.minimumVisibleSize
|
order.minimumVisibleSize
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { mapFormValuesToOrderSubmission } from './map-form-values-to-submission';
|
import { mapFormValuesToOrderSubmission } from './map-form-values-to-submission';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
|
||||||
|
import type { OrderFormValues } from '../hooks';
|
||||||
|
|
||||||
describe('mapFormValuesToOrderSubmission', () => {
|
describe('mapFormValuesToOrderSubmission', () => {
|
||||||
it('sets and formats price only for limit orders', () => {
|
it('sets and formats price only for limit orders', () => {
|
||||||
@ -25,7 +27,7 @@ describe('mapFormValuesToOrderSubmission', () => {
|
|||||||
).toEqual('10000');
|
).toEqual('10000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets and formats expiresAt only for time in force orders', () => {
|
it('sets and formats expiresAt only for GTT orders', () => {
|
||||||
expect(
|
expect(
|
||||||
mapFormValuesToOrderSubmission(
|
mapFormValuesToOrderSubmission(
|
||||||
{
|
{
|
||||||
@ -49,6 +51,41 @@ describe('mapFormValuesToOrderSubmission', () => {
|
|||||||
).toEqual('1640995200000000000');
|
).toEqual('1640995200000000000');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets and formats icebergOpts only for persisted orders', () => {
|
||||||
|
expect(
|
||||||
|
mapFormValuesToOrderSubmission(
|
||||||
|
{
|
||||||
|
type: OrderType.TYPE_LIMIT,
|
||||||
|
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||||
|
iceberg: true,
|
||||||
|
peakSize: '10.00',
|
||||||
|
minimumVisibleSize: '10.00',
|
||||||
|
} as OrderFormValues,
|
||||||
|
'marketId',
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
).icebergOpts
|
||||||
|
).toEqual(undefined);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
mapFormValuesToOrderSubmission(
|
||||||
|
{
|
||||||
|
type: OrderType.TYPE_LIMIT,
|
||||||
|
timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||||
|
iceberg: true,
|
||||||
|
peakSize: '10.00',
|
||||||
|
minimumVisibleSize: '10.00',
|
||||||
|
} as OrderFormValues,
|
||||||
|
'marketId',
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
).icebergOpts
|
||||||
|
).toEqual({
|
||||||
|
peakSize: '1000',
|
||||||
|
minimumVisibleSize: '1000',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('formats size', () => {
|
it('formats size', () => {
|
||||||
expect(
|
expect(
|
||||||
mapFormValuesToOrderSubmission(
|
mapFormValuesToOrderSubmission(
|
||||||
|
23
libs/deal-ticket/src/utils/time-in-force-persistance.spec.ts
Normal file
23
libs/deal-ticket/src/utils/time-in-force-persistance.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { OrderTimeInForce } from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
isNonPersistentOrder,
|
||||||
|
isPersistentOrder,
|
||||||
|
} from './time-in-force-persistance';
|
||||||
|
|
||||||
|
it('isNonPeristentOrder', () => {
|
||||||
|
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(true);
|
||||||
|
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(true);
|
||||||
|
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(false);
|
||||||
|
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(false);
|
||||||
|
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(false);
|
||||||
|
expect(isNonPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isPeristentOrder', () => {
|
||||||
|
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(false);
|
||||||
|
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(false);
|
||||||
|
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(true);
|
||||||
|
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(true);
|
||||||
|
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(true);
|
||||||
|
expect(isPersistentOrder(OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(true);
|
||||||
|
});
|
12
libs/deal-ticket/src/utils/time-in-force-persistance.ts
Normal file
12
libs/deal-ticket/src/utils/time-in-force-persistance.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { OrderTimeInForce } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export const isNonPersistentOrder = (timeInForce: OrderTimeInForce) => {
|
||||||
|
return [
|
||||||
|
OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||||
|
OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||||
|
].includes(timeInForce);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPersistentOrder = (timeInForce: OrderTimeInForce) => {
|
||||||
|
return !isNonPersistentOrder(timeInForce);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user