feat(deal-ticket): add useMaxSize unit tests (#6053)

This commit is contained in:
Bartłomiej Głownia 2024-03-20 15:25:30 +01:00 committed by GitHub
parent ecdd917977
commit 204871b81c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 179 additions and 17 deletions

View File

@ -406,7 +406,6 @@ export const DealTicket = ({
riskFactors, riskFactors,
scalingFactors, scalingFactors,
side, side,
sizeStep,
type, type,
generalAccountBalance, generalAccountBalance,
openVolume, openVolume,
@ -523,7 +522,7 @@ export const DealTicket = ({
</FormGroup> </FormGroup>
<Slider <Slider
min={0} min={0}
max={maxSize.toNumber()} max={maxSize}
step={Number(sizeStep)} step={Number(sizeStep)}
value={[Number(field.value)]} value={[Number(field.value)]}
onValueChange={([value]) => field.onChange(value)} onValueChange={([value]) => field.onChange(value)}

View File

@ -0,0 +1,161 @@
import { MarginMode, OrderType, Side } from '@vegaprotocol/types';
import { type UseMaxSizeProps, useMaxSize } from './use-max-size';
import { renderHook } from '@testing-library/react';
import { removeDecimal } from '@vegaprotocol/utils';
describe('useMaxSize', () => {
const positionDecimalPlaces = 1;
const decimalPlaces = 2;
const accountDecimals = 3;
const initialProps: UseMaxSizeProps = {
openVolume: '0',
positionDecimalPlaces,
generalAccountBalance: removeDecimal('100', accountDecimals),
side: Side.SIDE_BUY,
marginMode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
marginFactor: '0.1',
type: OrderType.TYPE_MARKET,
marginAccountBalance: '0',
accountDecimals,
price: removeDecimal('8', decimalPlaces), // 8.0
marketPrice: removeDecimal('10', decimalPlaces), // 10.0
decimalPlaces,
activeOrders: [],
riskFactors: {
long: '0.9',
short: '0.8',
market: '',
},
scalingFactors: {
initialMargin: 1.5,
},
};
const renderUseMaxSizeHook = (initialProps: UseMaxSizeProps) =>
renderHook((props: UseMaxSizeProps) => useMaxSize(props), {
initialProps,
});
describe('in MARGIN_MODE_ISOLATED_MARGIN', () => {
it('calculates maxSize = collateral / marginFactor / price', () => {
const { result } = renderUseMaxSizeHook({ ...initialProps });
// available collateral / marginFactor / price = 100 / 0.1 / 8 = 125
expect(result.current).toEqual(125);
});
it('use only general account balance', () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
openVolume: removeDecimal('25', positionDecimalPlaces),
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// 75 / 0.1 / 8 = 125
expect(result.current).toEqual(93.7);
});
it('if reduce market order use general and margin account balance', () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
openVolume: `-${removeDecimal('25', positionDecimalPlaces)}`,
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// ((75 + 25) / 0.1 / 8) + 25 (reduced volume) = 125
expect(result.current).toEqual(150);
});
it('if reduce limit order use only general account balance', () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
type: OrderType.TYPE_LIMIT,
openVolume: `-${removeDecimal('25', positionDecimalPlaces)}`,
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// 75 / 0.1 / 8 = 125
expect(result.current).toEqual(93.7);
});
});
describe('in MARGIN_MODE_CROSS_MARGIN', () => {
it('calculates maxSize = availableMargin / riskFactor / initialMargin / marketPrice', () => {
const { result, rerender } = renderUseMaxSizeHook({
...initialProps,
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
});
// available collateral / marginFactor / price = 100 / 0.9 / 1.5 / 10 = 7.4
expect(result.current).toEqual(7.4);
rerender({
...initialProps,
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
side: Side.SIDE_SELL,
});
// available collateral / marginFactor / price = 100 / 0.8 / 1.5 / 10 = 8.3
expect(result.current).toEqual(8.3);
});
it('if increasing position subtract open volume', () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
openVolume: removeDecimal('1.8', positionDecimalPlaces),
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// 75 / 0.9 / 1.5 / 10 = 5.6
expect(result.current).toEqual(5.6);
});
it('if reduce market order use add reduced volume', () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
openVolume: `-${removeDecimal('1.8', positionDecimalPlaces)}`,
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// ((75 + 25) * 0.9 / 1.5 / 10) + 1.8 (reduced volume) = 9.2
expect(result.current).toEqual(9.2);
});
it("if reduce limit order subtract don't include existing volume", () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
type: OrderType.TYPE_LIMIT,
openVolume: `-${removeDecimal('1.8', positionDecimalPlaces)}`,
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// (75 + 25) / 0.9 / 1.5 / 10 = 5.6
expect(result.current).toEqual(7.4);
});
it('subtracts remaining orders', () => {
const { result } = renderUseMaxSizeHook({
...initialProps,
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
activeOrders: [
{
remaining: removeDecimal('0.5', positionDecimalPlaces),
side: Side.SIDE_SELL,
},
{
remaining: removeDecimal('0.9', positionDecimalPlaces),
side: Side.SIDE_BUY,
},
{
remaining: removeDecimal('0.9', positionDecimalPlaces),
side: Side.SIDE_BUY,
},
],
marginAccountBalance: removeDecimal('25', accountDecimals),
generalAccountBalance: removeDecimal('75', accountDecimals),
});
// ((50 + 50) / 0.9 / 1.5/ 10) - 1.8 = 5.6
expect(result.current).toEqual(5.6);
});
});
});

View File

@ -1,13 +1,13 @@
import type { MarketInfo } from '@vegaprotocol/markets'; import type { MarketInfo } from '@vegaprotocol/markets';
import type { OrderFieldsFragment } from '@vegaprotocol/orders'; import type { OrderFieldsFragment } from '@vegaprotocol/orders';
import { MarginMode, OrderType, Side } from '@vegaprotocol/types'; import { MarginMode, OrderType, Side } from '@vegaprotocol/types';
import { toBigNum } from '@vegaprotocol/utils'; import { determineSizeStep, toBigNum } from '@vegaprotocol/utils';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { useMemo } from 'react'; import { useMemo } from 'react';
interface UseMaxSizeProps { export interface UseMaxSizeProps {
accountDecimals?: number; accountDecimals?: number;
activeOrders?: OrderFieldsFragment[]; activeOrders?: Pick<OrderFieldsFragment, 'remaining' | 'side'>[];
decimalPlaces: number; decimalPlaces: number;
generalAccountBalance: string; generalAccountBalance: string;
marginAccountBalance: string; marginAccountBalance: string;
@ -18,11 +18,13 @@ interface UseMaxSizeProps {
positionDecimalPlaces: number; positionDecimalPlaces: number;
price?: string; price?: string;
riskFactors: MarketInfo['riskFactors']; riskFactors: MarketInfo['riskFactors'];
scalingFactors?: NonNullable< scalingFactors?: Pick<
MarketInfo['tradableInstrument']['marginCalculator'] NonNullable<
>['scalingFactors']; MarketInfo['tradableInstrument']['marginCalculator']
>['scalingFactors'],
'initialMargin'
>;
side: Side; side: Side;
sizeStep: string;
type: OrderType; type: OrderType;
} }
@ -38,7 +40,6 @@ export const useMaxSize = ({
accountDecimals, accountDecimals,
price, price,
decimalPlaces, decimalPlaces,
sizeStep,
activeOrders, activeOrders,
riskFactors, riskFactors,
scalingFactors, scalingFactors,
@ -52,7 +53,7 @@ export const useMaxSize = ({
(!openVolume.startsWith('-') && side === Side.SIDE_SELL); (!openVolume.startsWith('-') && side === Side.SIDE_SELL);
if (marginMode === MarginMode.MARGIN_MODE_ISOLATED_MARGIN) { if (marginMode === MarginMode.MARGIN_MODE_ISOLATED_MARGIN) {
if (!marginFactor || !price) { if (!marginFactor || !price) {
return maxSize; return 0;
} }
const availableMargin = const availableMargin =
accountDecimals !== undefined accountDecimals !== undefined
@ -72,13 +73,13 @@ export const useMaxSize = ({
!marketPrice || !marketPrice ||
accountDecimals === undefined accountDecimals === undefined
) { ) {
return maxSize; return 0;
} }
const availableMargin = toBigNum( const availableMargin = toBigNum(
generalAccountBalance, generalAccountBalance,
accountDecimals accountDecimals
).plus(toBigNum(marginAccountBalance, accountDecimals)); ).plus(toBigNum(marginAccountBalance, accountDecimals));
// maxSize = availableMargin / scalingFactors.initialMargin / marketPrice // maxSize = availableMargin / riskFactor / scalingFactors.initialMargin / marketPrice
maxSize = availableMargin maxSize = availableMargin
.div( .div(
BigNumber( BigNumber(
@ -99,7 +100,7 @@ export const useMaxSize = ({
) )
) )
.minus( .minus(
// subtract open volume // subtract open volume if increasing position
side === Side.SIDE_BUY side === Side.SIDE_BUY
? volume.isGreaterThan(0) ? volume.isGreaterThan(0)
? volume ? volume
@ -110,12 +111,14 @@ export const useMaxSize = ({
); );
} }
// round to size step // round to size step
maxSize = maxSize.minus(maxSize.mod(sizeStep)); maxSize = maxSize.minus(
maxSize.mod(determineSizeStep({ positionDecimalPlaces }))
);
if (reducingPosition && type === OrderType.TYPE_MARKET) { if (reducingPosition && type === OrderType.TYPE_MARKET) {
// add open volume if position will be reduced // add open volume if position will be reduced
maxSize = maxSize.plus(volume.abs()); maxSize = maxSize.plus(volume.abs());
} }
return maxSize; return maxSize.toNumber();
}, [ }, [
openVolume, openVolume,
positionDecimalPlaces, positionDecimalPlaces,
@ -128,7 +131,6 @@ export const useMaxSize = ({
accountDecimals, accountDecimals,
price, price,
decimalPlaces, decimalPlaces,
sizeStep,
activeOrders, activeOrders,
riskFactors, riskFactors,
scalingFactors, scalingFactors,