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