chore(trading): market's tick size
This commit is contained in:
parent
bcb5351dfc
commit
513cebbcfc
@ -12,6 +12,7 @@ import {
|
|||||||
Pill,
|
Pill,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { useT } from '../../use-t';
|
import { useT } from '../../use-t';
|
||||||
|
import { determinePriceStep } from '../../utils/step';
|
||||||
|
|
||||||
export interface DealTicketPriceTakeProfitStopLossProps {
|
export interface DealTicketPriceTakeProfitStopLossProps {
|
||||||
control: Control<OrderFormValues>;
|
control: Control<OrderFormValues>;
|
||||||
@ -30,7 +31,7 @@ export const DealTicketPriceTakeProfitStopLoss = ({
|
|||||||
}: DealTicketPriceTakeProfitStopLossProps) => {
|
}: DealTicketPriceTakeProfitStopLossProps) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const validateAmount = useValidateAmount();
|
const validateAmount = useValidateAmount();
|
||||||
const priceStep = toDecimal(market?.decimalPlaces);
|
const priceStep = determinePriceStep(market);
|
||||||
|
|
||||||
const renderTakeProfitError = () => {
|
const renderTakeProfitError = () => {
|
||||||
if (takeProfitError) {
|
if (takeProfitError) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Controller, type Control } from 'react-hook-form';
|
import { Controller, type Control } from 'react-hook-form';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||||
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
|
import { useValidateAmount } from '@vegaprotocol/utils';
|
||||||
import {
|
import {
|
||||||
TradingFormGroup,
|
TradingFormGroup,
|
||||||
TradingInput,
|
TradingInput,
|
||||||
@ -9,6 +9,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { useT } from '../../use-t';
|
import { useT } from '../../use-t';
|
||||||
|
import { determineSizeStep } from '../../utils/step';
|
||||||
|
|
||||||
export interface DealTicketSizeIcebergProps {
|
export interface DealTicketSizeIcebergProps {
|
||||||
control: Control<OrderFormValues>;
|
control: Control<OrderFormValues>;
|
||||||
@ -29,7 +30,7 @@ export const DealTicketSizeIceberg = ({
|
|||||||
}: DealTicketSizeIcebergProps) => {
|
}: DealTicketSizeIcebergProps) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const validateAmount = useValidateAmount();
|
const validateAmount = useValidateAmount();
|
||||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
const sizeStep = determineSizeStep(market);
|
||||||
|
|
||||||
const renderPeakSizeError = () => {
|
const renderPeakSizeError = () => {
|
||||||
if (peakSizeError) {
|
if (peakSizeError) {
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
formatForInput,
|
formatForInput,
|
||||||
formatValue,
|
formatValue,
|
||||||
removeDecimal,
|
removeDecimal,
|
||||||
toDecimal,
|
|
||||||
useValidateAmount,
|
useValidateAmount,
|
||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { type Control, type UseFormWatch } from 'react-hook-form';
|
import { type Control, type UseFormWatch } from 'react-hook-form';
|
||||||
@ -59,6 +58,7 @@ import { KeyValue } from './key-value';
|
|||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { stopOrdersProvider } from '@vegaprotocol/orders';
|
import { stopOrdersProvider } from '@vegaprotocol/orders';
|
||||||
import { useT } from '../../use-t';
|
import { useT } from '../../use-t';
|
||||||
|
import { determinePriceStep, determineSizeStep } from '../../utils/step';
|
||||||
|
|
||||||
export interface StopOrderProps {
|
export interface StopOrderProps {
|
||||||
market: Market;
|
market: Market;
|
||||||
@ -904,8 +904,8 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
|||||||
market.tradableInstrument.instrument.metadata.tags
|
market.tradableInstrument.instrument.metadata.tags
|
||||||
);
|
);
|
||||||
|
|
||||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
const sizeStep = determineSizeStep(market);
|
||||||
const priceStep = toDecimal(market?.decimalPlaces);
|
const priceStep = determinePriceStep(market);
|
||||||
|
|
||||||
useController({
|
useController({
|
||||||
name: 'type',
|
name: 'type',
|
||||||
|
@ -32,7 +32,6 @@ import {
|
|||||||
toBigNum,
|
toBigNum,
|
||||||
removeDecimal,
|
removeDecimal,
|
||||||
useValidateAmount,
|
useValidateAmount,
|
||||||
toDecimal,
|
|
||||||
formatForInput,
|
formatForInput,
|
||||||
formatValue,
|
formatValue,
|
||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
@ -82,6 +81,7 @@ import { DocsLinks } from '@vegaprotocol/environment';
|
|||||||
import { useT } from '../../use-t';
|
import { useT } from '../../use-t';
|
||||||
import { DealTicketPriceTakeProfitStopLoss } from './deal-ticket-price-tp-sl';
|
import { DealTicketPriceTakeProfitStopLoss } from './deal-ticket-price-tp-sl';
|
||||||
import uniqueId from 'lodash/uniqueId';
|
import uniqueId from 'lodash/uniqueId';
|
||||||
|
import { determinePriceStep, determineSizeStep } from '../../utils/step';
|
||||||
|
|
||||||
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.';
|
||||||
@ -419,11 +419,12 @@ export const DealTicket = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const priceStep = toDecimal(market?.decimalPlaces);
|
const sizeStep = determineSizeStep(market);
|
||||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
|
||||||
const quoteName = getQuoteName(market);
|
const quoteName = getQuoteName(market);
|
||||||
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
|
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
|
||||||
|
|
||||||
|
const priceStep = determinePriceStep(market);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={
|
onSubmit={
|
||||||
|
21
libs/deal-ticket/src/utils/step.ts
Normal file
21
libs/deal-ticket/src/utils/step.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
|
import { toBigNum, toDecimal } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
|
export const determinePriceStep = (
|
||||||
|
market: Pick<Market, 'decimalPlaces' | 'tickSize'>
|
||||||
|
) => {
|
||||||
|
let priceStep = toDecimal(market.decimalPlaces);
|
||||||
|
|
||||||
|
const scaledTickSize = toBigNum(market.tickSize, market.decimalPlaces);
|
||||||
|
if (scaledTickSize.isGreaterThan(0)) {
|
||||||
|
priceStep = scaledTickSize.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return priceStep;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const determineSizeStep = (
|
||||||
|
market: Pick<Market, 'positionDecimalPlaces'>
|
||||||
|
) => {
|
||||||
|
return toDecimal(market.positionDecimalPlaces);
|
||||||
|
};
|
5
libs/markets/src/lib/__generated__/markets.ts
generated
5
libs/markets/src/lib/__generated__/markets.ts
generated
File diff suppressed because one or more lines are too long
@ -2,6 +2,7 @@ fragment MarketFields on Market {
|
|||||||
id
|
id
|
||||||
decimalPlaces
|
decimalPlaces
|
||||||
positionDecimalPlaces
|
positionDecimalPlaces
|
||||||
|
tickSize
|
||||||
state
|
state
|
||||||
tradingMode
|
tradingMode
|
||||||
parentMarketID
|
parentMarketID
|
||||||
|
@ -18,18 +18,42 @@ export const getUnlimitedThreshold = (decimalPlaces: number) =>
|
|||||||
const MIN_FRACTION_DIGITS = 2;
|
const MIN_FRACTION_DIGITS = 2;
|
||||||
const MAX_FRACTION_DIGITS = 20;
|
const MAX_FRACTION_DIGITS = 20;
|
||||||
|
|
||||||
export function toDecimal(numberOfDecimals: number) {
|
/**
|
||||||
return new BigNumber(1)
|
* Converts "raw" value to a decimal representation as a `BigNumber`
|
||||||
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
|
*
|
||||||
.toString(10);
|
* Example:
|
||||||
}
|
* `toBigNum(1, 3)` -> 0.001
|
||||||
|
* `toBigNum(1234, 2)` -> 12.34
|
||||||
|
*
|
||||||
|
* @param rawValue The "raw" value
|
||||||
|
* @param decimals The number of decimal places
|
||||||
|
*/
|
||||||
export function toBigNum(
|
export function toBigNum(
|
||||||
rawValue: string | number | BigNumber,
|
rawValue: string | number | BigNumber,
|
||||||
decimals: number
|
decimals: number
|
||||||
): BigNumber {
|
): BigNumber {
|
||||||
const divides = new BigNumber(10).exponentiatedBy(decimals);
|
const d = new BigNumber(10).exponentiatedBy(decimals);
|
||||||
return new BigNumber(rawValue || 0).dividedBy(divides);
|
return new BigNumber(rawValue || 0).dividedBy(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses `toBigNum` - converts given decimal representation
|
||||||
|
* as a "raw" value (`BigNumber`)
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* `formBigNum(5.4321, 4)` -> 54321
|
||||||
|
* `formBigNum(112.34, 2)` -> 11234
|
||||||
|
*/
|
||||||
|
export const fromBigNum = (
|
||||||
|
input: string | number | BigNumber,
|
||||||
|
decimals: number
|
||||||
|
) => {
|
||||||
|
const d = new BigNumber(10).exponentiatedBy(decimals);
|
||||||
|
return new BigNumber(input).times(d);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toDecimal(numberOfDecimals: number) {
|
||||||
|
return toBigNum(1, numberOfDecimals).toString(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addDecimal(
|
export function addDecimal(
|
||||||
|
38
libs/utils/src/lib/validate/validate-amount.spec.ts
Normal file
38
libs/utils/src/lib/validate/validate-amount.spec.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { validateAgainstStep } from './validate-amount';
|
||||||
|
|
||||||
|
describe('validateAgainstStep', () => {
|
||||||
|
it('fails when step is an empty string', () => {
|
||||||
|
expect(validateAgainstStep('', '1234')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[0, 0],
|
||||||
|
[1234567890, 0],
|
||||||
|
[0.03, 0.03],
|
||||||
|
[0.09, 0.03],
|
||||||
|
[0.27, 0.03],
|
||||||
|
[1, 1],
|
||||||
|
[123, 1],
|
||||||
|
[4, 2],
|
||||||
|
[8, 2],
|
||||||
|
])(
|
||||||
|
'checks whether given value (%s) IS a multiple of given step (%s)',
|
||||||
|
(value, step) => {
|
||||||
|
expect(validateAgainstStep(step, value)).toEqual(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[1, 2],
|
||||||
|
[0.1, 0.003],
|
||||||
|
[1.11, 0.1],
|
||||||
|
[123.1, 1],
|
||||||
|
[222, 221],
|
||||||
|
[NaN, 1],
|
||||||
|
])(
|
||||||
|
'checks whether given value (%s) IS NOT a multiple of given step (%s)',
|
||||||
|
(value, step) => {
|
||||||
|
expect(validateAgainstStep(step, value)).toEqual(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
@ -1,35 +1,24 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useT } from '../use-t';
|
import { useT } from '../use-t';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
export const useValidateAmount = () => {
|
export const useValidateAmount = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(step: number | string, field: string) => {
|
(step: number | string, field: string) => {
|
||||||
const [, stepDecimals = ''] = String(step).split('.');
|
|
||||||
|
|
||||||
return (value?: string) => {
|
return (value?: string) => {
|
||||||
if (Number(step) > 1) {
|
const isValid = validateAgainstStep(step, value);
|
||||||
if (Number(value) % Number(step) > 0) {
|
if (!isValid) {
|
||||||
return t(
|
if (new BigNumber(step).isEqualTo(1)) {
|
||||||
'{{field}} must be a multiple of {{step}} for this market',
|
|
||||||
{
|
|
||||||
field,
|
|
||||||
step,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const [, valueDecimals = ''] = (value || '').split('.');
|
|
||||||
if (stepDecimals.length < valueDecimals.length) {
|
|
||||||
if (stepDecimals === '') {
|
|
||||||
return t('{{field}} must be whole numbers for this market', {
|
return t('{{field}} must be whole numbers for this market', {
|
||||||
field,
|
field,
|
||||||
|
step,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return t('{{field}} accepts up to {{decimals}} decimal places', {
|
|
||||||
|
return t('{{field}} must be a multiple of {{step}} for this market', {
|
||||||
field,
|
field,
|
||||||
decimals: stepDecimals.length,
|
step,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -38,3 +27,24 @@ export const useValidateAmount = () => {
|
|||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMultipleOf = (value: BigNumber, multipleOf: BigNumber) =>
|
||||||
|
value.modulo(multipleOf).isZero();
|
||||||
|
|
||||||
|
export const validateAgainstStep = (
|
||||||
|
step: string | number,
|
||||||
|
input?: string | number
|
||||||
|
) => {
|
||||||
|
const stepValue = new BigNumber(step);
|
||||||
|
if (stepValue.isNaN()) {
|
||||||
|
// unable to check if step is not a number
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (stepValue.isZero()) {
|
||||||
|
// every number is valid when step is zero
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = new BigNumber(input || '');
|
||||||
|
return isMultipleOf(value, stepValue);
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user