feat: [console-lite] - fixes in order close out calcs (#934)

* feat: [console-lite] - fixes in order close out calcs

* feat: [console-lite] - fixes in order close out calcs - adjust int test

* feat: [console-lite] - fixes in order close out calcs - adjust int test

* feat: [console-lite] - fixes in order close out calcs - adjust int test

* feat: [console-lite] - fixes in order close out calcs - add dash instead 0

* feat: [console-lite] - fixes in order close out calcs - add dash instead 0

Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
macqbat 2022-08-04 13:21:28 +02:00 committed by GitHub
parent 45d05b38f9
commit f0e37e4a2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 465 additions and 100 deletions

View File

@ -5,6 +5,8 @@ import { generateMarketTags } from '../support/mocks/generate-market-tags';
import { generateMarketPositions } from '../support/mocks/generate-market-positions';
import { generateEstimateOrder } from '../support/mocks/generate-estimate-order';
import { generatePartyBalance } from '../support/mocks/generate-party-balance';
import { generatePartyMarketData } from '../support/mocks/generate-party-market-data';
import { generateMarketMarkPrice } from '../support/mocks/generate-market-mark-price';
const connectVegaWallet = () => {
const form = 'rest-connector-form';
@ -28,6 +30,8 @@ describe('Market trade', () => {
aliasQuery(req, 'MarketPositions', generateMarketPositions());
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice());
});
cy.visit('/markets');
cy.wait('@SimpleMarkets').then((response) => {
@ -104,7 +108,7 @@ describe('Market trade', () => {
.find('dl')
.eq(3)
.find('dd div')
.should('have.text', '-785.81045');
.should('have.text', ' - ');
cy.getByTestId('place-order').click();
cy.getByTestId('dialog-title').should(
'have.text',

View File

@ -0,0 +1,9 @@
export const generateMarketMarkPrice = () => {
return {
market: {
decimalPlaces: 5,
data: { markPrice: '692748', __typename: 'MarketData' },
__typename: 'Market',
},
};
};

View File

@ -0,0 +1,19 @@
export const generatePartyMarketData = () => {
return {
party: {
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
accounts: [
{
type: 'General',
balance: '1200000',
asset: { id: 'fBTC', decimals: 5, __typename: 'Asset' },
market: null,
__typename: 'Account',
},
],
marginsConnection: { edges: null, __typename: 'MarginConnection' },
positionsConnection: { edges: null, __typename: 'PositionConnection' },
__typename: 'Party',
},
};
};

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { act } from 'react-dom/test-utils';
import {
render,
@ -60,6 +60,7 @@ describe('SimpleMarketList', () => {
};
await act(async () => {
render(
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[mocks, filterMock]}>
<SimpleMarketList />
</MockedProvider>,
@ -129,6 +130,7 @@ describe('SimpleMarketList', () => {
};
await act(async () => {
render(
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[mocks, filterMock]}>
<SimpleMarketList />
</MockedProvider>,

View File

@ -59,6 +59,7 @@ describe('SimpleMarketToolbar', () => {
it('should be properly rendered', async () => {
render(
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[filterMock]} addTypename={false}>
<WrappedCompForTest />
</MockedProvider>,
@ -83,6 +84,7 @@ describe('SimpleMarketToolbar', () => {
it('navigation should work well', async () => {
render(
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[filterMock]} addTypename={false}>
<WrappedCompForTest />
</MockedProvider>,

View File

@ -0,0 +1,52 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: MarketMarkPrice
// ====================================================
export interface MarketMarkPrice_market_data {
__typename: "MarketData";
/**
* the mark price (actually an unsigned int)
*/
markPrice: string;
}
export interface MarketMarkPrice_market {
__typename: "Market";
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* marketData for the given market
*/
data: MarketMarkPrice_market_data | null;
}
export interface MarketMarkPrice {
/**
* An instrument that is trading on the VEGA network
*/
market: MarketMarkPrice_market | null;
}
export interface MarketMarkPriceVariables {
marketId: string;
}

View File

@ -0,0 +1,118 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: PartyMarketData
// ====================================================
export interface PartyMarketData_party_accounts_asset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The precision of the asset
*/
decimals: number;
}
export interface PartyMarketData_party_accounts_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
}
export interface PartyMarketData_party_accounts {
__typename: "Account";
/**
* Account type (General, Margin, etc)
*/
type: AccountType;
/**
* Balance as string - current account balance (approx. as balances can be updated several times per second)
*/
balance: string;
/**
* Asset, the 'currency'
*/
asset: PartyMarketData_party_accounts_asset;
/**
* Market (only relevant to margin accounts)
*/
market: PartyMarketData_party_accounts_market | null;
}
export interface PartyMarketData_party_marginsConnection_edges_node_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
}
export interface PartyMarketData_party_marginsConnection_edges_node {
__typename: "MarginLevels";
/**
* market in which the margin is required for this party
*/
market: PartyMarketData_party_marginsConnection_edges_node_market;
/**
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
*/
initialLevel: string;
/**
* minimal margin for the position to be maintained in the network (unsigned int actually)
*/
maintenanceLevel: string;
/**
* if the margin is between maintenance and search, the network will initiate a collateral search (unsigned int actually)
*/
searchLevel: string;
}
export interface PartyMarketData_party_marginsConnection_edges {
__typename: "MarginEdge";
node: PartyMarketData_party_marginsConnection_edges_node;
}
export interface PartyMarketData_party_marginsConnection {
__typename: "MarginConnection";
/**
* The margin levels in this connection
*/
edges: PartyMarketData_party_marginsConnection_edges[] | null;
}
export interface PartyMarketData_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* Collateral accounts relating to a party
*/
accounts: PartyMarketData_party_accounts[] | null;
/**
* Margin level for a market
*/
marginsConnection: PartyMarketData_party_marginsConnection;
}
export interface PartyMarketData {
/**
* An entity that is trading on the VEGA network
*/
party: PartyMarketData_party | null;
}
export interface PartyMarketDataVariables {
partyId: string;
}

View File

@ -0,0 +1,38 @@
import { gql, useQuery } from '@apollo/client';
import { useMemo, useRef } from 'react';
import type {
MarketMarkPrice,
MarketMarkPriceVariables,
} from './__generated__/MarketMarkPrice';
const MARKET_MARK_PRICE = gql`
query MarketMarkPrice($marketId: ID!) {
market(id: $marketId) {
decimalPlaces
data {
markPrice
}
}
}
`;
export default (marketId: string) => {
const memoRef = useRef<MarketMarkPrice | null>(null);
const { data } = useQuery<MarketMarkPrice, MarketMarkPriceVariables>(
MARKET_MARK_PRICE,
{
pollInterval: 5000,
variables: { marketId },
skip: !marketId,
}
);
return useMemo(() => {
if (
data &&
data.market?.data?.markPrice !== memoRef.current?.market?.data?.markPrice
) {
memoRef.current = data;
}
return memoRef.current;
}, [data, memoRef]);
};

View File

@ -1,71 +0,0 @@
import { renderHook } from '@testing-library/react-hooks';
import useOrderCloseOut from './use-order-closeout';
import type { Order } from '@vegaprotocol/orders';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
describe('useOrderCloseOut Hook', () => {
const order = { size: '2', side: 'SIDE_BUY' };
const market = {
decimalPlaces: 5,
depth: {
lastTrade: {
price: '1000000',
},
},
tradableInstrument: {
instrument: {
product: {
settlementAsset: {
id: 'assetId',
},
},
},
},
};
const partyData = {
party: {
accounts: [
{
balance: '200000',
asset: {
id: 'assetId',
decimals: 5,
},
},
],
},
};
it('return proper buy value', () => {
const { result } = renderHook(() =>
useOrderCloseOut({
order: order as Order,
market: market as DealTicketQuery_market,
partyData: partyData as PartyBalanceQuery,
})
);
expect(result.current).toEqual('9.00000');
});
it('return proper sell value', () => {
const { result } = renderHook(() =>
useOrderCloseOut({
order: { ...order, side: 'SIDE_SELL' } as Order,
market: market as DealTicketQuery_market,
partyData: partyData as PartyBalanceQuery,
})
);
expect(result.current).toEqual('11.00000');
});
it('return proper empty value', () => {
const { result } = renderHook(() =>
useOrderCloseOut({
order: { ...order, side: 'SIDE_SELL' } as Order,
market: market as DealTicketQuery_market,
})
);
expect(result.current).toEqual(' - ');
});
});

View File

@ -0,0 +1,99 @@
import * as React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { MockedProvider } from '@apollo/client/testing';
import useOrderCloseOut from './use-order-closeout';
import type { Order } from '@vegaprotocol/orders';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
useVegaWallet: jest.fn().mockReturnValue('wallet-pub-key'),
}));
describe('useOrderCloseOut Hook', () => {
const order = { size: '2', side: 'SIDE_BUY' };
const market = {
decimalPlaces: 5,
depth: {
lastTrade: {
price: '1000000',
},
},
tradableInstrument: {
instrument: {
product: {
settlementAsset: {
id: 'assetId',
},
},
},
},
};
const partyData = {
party: {
accounts: [
{
balance: '200000',
asset: {
id: 'assetId',
decimals: 5,
},
},
],
},
};
it('return proper buy value', () => {
const { result } = renderHook(
() =>
useOrderCloseOut({
order: order as Order,
market: market as DealTicketQuery_market,
partyData: partyData as PartyBalanceQuery,
}),
{
wrapper: ({ children }: { children: React.ReactNode }) => (
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[]}>{children}</MockedProvider>
),
}
);
expect(result.current).toEqual(' - ');
});
it('return proper sell value', () => {
const { result } = renderHook(
() =>
useOrderCloseOut({
order: { ...order, side: 'SIDE_SELL' } as Order,
market: market as DealTicketQuery_market,
partyData: partyData as PartyBalanceQuery,
}),
{
wrapper: ({ children }: { children: React.ReactNode }) => (
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[]}>{children}</MockedProvider>
),
}
);
expect(result.current).toEqual('1.00000');
});
it('return proper empty value', () => {
const { result } = renderHook(
() =>
useOrderCloseOut({
order: { ...order, side: 'SIDE_SELL' } as Order,
market: market as DealTicketQuery_market,
}),
{
wrapper: ({ children }: { children: React.ReactNode }) => (
// @ts-ignore different versions of react types in apollo and app
<MockedProvider mocks={[]}>{children}</MockedProvider>
),
}
);
expect(result.current).toEqual('0.00000');
});
});

View File

@ -3,8 +3,46 @@ import type { Order } from '@vegaprotocol/orders';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
import { useSettlementAccount } from './use-settlement-account';
import { VegaWalletOrderSide } from '@vegaprotocol/wallet';
import { useVegaWallet, VegaWalletOrderSide } from '@vegaprotocol/wallet';
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
import { gql, useQuery } from '@apollo/client';
import useMarketPositions from './use-market-positions';
import useMarketData from './use-market-data';
import type {
PartyMarketData,
PartyMarketDataVariables,
} from './__generated__/PartyMarketData';
const CLOSEOUT_PRICE_QUERY = gql`
query PartyMarketData($partyId: ID!) {
party(id: $partyId) {
id
accounts {
type
balance
asset {
id
decimals
}
market {
id
}
}
marginsConnection {
edges {
node {
market {
id
}
initialLevel
maintenanceLevel
searchLevel
}
}
}
}
}
`;
interface Props {
order: Order;
@ -13,25 +51,65 @@ interface Props {
}
const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
const { keypair } = useVegaWallet();
const account = useSettlementAccount(
market.tradableInstrument.instrument.product.settlementAsset.id,
partyData?.party?.accounts || []
);
if (account?.balance && market.depth.lastTrade) {
const price = new BigNumber(
addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
);
const balance = new BigNumber(
addDecimal(account.balance, account.asset.decimals)
);
const { size, side } = order;
const bigOne = new BigNumber(1);
return formatNumber(
side === VegaWalletOrderSide.Buy
? bigOne.minus(balance.div(price.times(size))).times(price)
: bigOne.plus(balance.div(price.times(size))).times(price),
const { data } = useQuery<PartyMarketData, PartyMarketDataVariables>(
CLOSEOUT_PRICE_QUERY,
{
pollInterval: 5000,
variables: { partyId: keypair?.pub || '' },
skip: !keypair?.pub,
}
);
const markPriceData = useMarketData(market.id);
const marketPositions = useMarketPositions({
marketId: market.id,
partyId: keypair?.pub || '',
});
const marginMaintenanceLevel = new BigNumber(
addDecimal(
data?.party?.marginsConnection.edges?.find(
(nodes) => nodes.node.market.id === market.id
)?.node.maintenanceLevel || 0,
market.decimalPlaces
);
)
);
const positionAccount = data?.party?.accounts?.find(
(account) => account.market?.id === market.id
);
const positionAccountBalance = new BigNumber(
addDecimal(
positionAccount?.balance || 0,
positionAccount?.asset?.decimals || 0
)
);
const generalAccountBalance = new BigNumber(
addDecimal(account?.balance || 0, account?.asset.decimals || 0)
);
const volume = new BigNumber(
addDecimal(
marketPositions?.openVolume.toNumber() || 0,
market.positionDecimalPlaces
)
)[order.side === VegaWalletOrderSide.Buy ? 'plus' : 'minus'](order.size);
const markPrice = new BigNumber(
addDecimal(
markPriceData?.market?.data?.markPrice || 0,
markPriceData?.market?.decimalPlaces || 0
)
);
// regarding formula (marginMaintenanceLevel - positionAccountBalance - generalAccountBalance) / volume + markPrice
const marginDifference = marginMaintenanceLevel
.minus(positionAccountBalance)
.minus(generalAccountBalance);
const closeOut = marginDifference.div(volume).plus(markPrice);
if (closeOut.isPositive()) {
return formatNumber(closeOut, market.decimalPlaces);
}
return ' - ';
};

View File

@ -58,7 +58,7 @@ describe('useOrderMargin Hook', () => {
const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0)
.plus(order.size)
.toString();
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
expect((useQuery as jest.Mock).mock.calls[1][1].variables.size).toEqual(
calledSize
);
});
@ -74,7 +74,7 @@ describe('useOrderMargin Hook', () => {
);
expect(result.current).toEqual('200000');
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
expect((useQuery as jest.Mock).mock.calls[1][1].variables.size).toEqual(
order.size
);
});
@ -99,7 +99,7 @@ describe('useOrderMargin Hook', () => {
const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0)
.plus(order.size)
.toString();
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
expect((useQuery as jest.Mock).mock.calls[1][1].variables.size).toEqual(
calledSize
);
});

View File

@ -12,8 +12,9 @@ import {
VegaWalletOrderTimeInForce,
VegaWalletOrderType,
} from '@vegaprotocol/wallet';
import { addDecimal } from '@vegaprotocol/react-helpers';
import { addDecimal, removeDecimal } from '@vegaprotocol/react-helpers';
import useMarketPositions from './use-market-positions';
import useMarketData from './use-market-data';
export const ESTIMATE_ORDER_QUERY = gql`
query EstimateOrder(
@ -66,26 +67,35 @@ const types: Record<VegaWalletOrderType, OrderType> = {
const useOrderMargin = ({ order, market, partyId }: Props) => {
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
const markPriceData = useMarketData(market.id);
const { data } = useQuery<EstimateOrder, EstimateOrderVariables>(
ESTIMATE_ORDER_QUERY,
{
variables: {
marketId: market.id,
partyId,
price: market.depth.lastTrade?.price,
size: new BigNumber(marketPositions?.openVolume || 0)
[order.side === VegaWalletOrderSide.Buy ? 'plus' : 'minus'](
order.size
)
.toString(),
price: markPriceData?.market?.data?.markPrice || '',
size: removeDecimal(
BigNumber.maximum(
0,
new BigNumber(marketPositions?.openVolume || 0)[
order.side === VegaWalletOrderSide.Buy ? 'plus' : 'minus'
](order.size)
).toString(),
market.positionDecimalPlaces
),
side: order.side === VegaWalletOrderSide.Buy ? Side.Buy : Side.Sell,
timeInForce: times[order.timeInForce],
type: types[order.type],
},
skip:
!partyId || !market.id || !order.size || !market.depth.lastTrade?.price,
!partyId ||
!market.id ||
!order.size ||
!markPriceData?.market?.data?.markPrice,
}
);
if (data?.estimateOrder.marginLevels.initialLevel) {
return addDecimal(
BigNumber.maximum(

View File

@ -23,6 +23,9 @@ export function createClient(base?: string) {
const cache = new InMemoryCache({
typePolicies: {
Party: {
keyFields: false,
},
Query: {},
Account: {
keyFields: false,

View File

@ -4,7 +4,9 @@
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node", "@testing-library/jest-dom"],
"jsx": "react"
"jsx": "react",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": [
"**/*.test.ts",