fix(positions): rework of the liquidation tooltip (#6070)

Co-authored-by: bwallacee <ben@vega.xyz>
This commit is contained in:
Art 2024-03-22 14:36:05 +01:00 committed by GitHub
parent 891e0d3d2f
commit 01e87443ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 229 additions and 160 deletions

View File

@ -341,31 +341,33 @@ describe('Closed', () => {
oracleDataMock,
]);
const actionCell = screen
.getAllByRole('gridcell')
.find((el) => el.getAttribute('col-id') === 'market-actions');
await waitFor(async () => {
const actionCell = screen
.getAllByRole('gridcell')
.find((el) => el.getAttribute('col-id') === 'market-actions');
await userEvent.click(
within(actionCell as HTMLElement).getByTestId('dropdown-menu')
);
await userEvent.click(
within(actionCell as HTMLElement).getByTestId('dropdown-menu')
);
expect(screen.getByRole('menu')).toBeInTheDocument();
expect(screen.getByRole('menu')).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'Copy Market ID' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View on Explorer' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View settlement asset details' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View parent market' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View successor market' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'Copy Market ID' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View on Explorer' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View settlement asset details' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View parent market' })
).toBeInTheDocument();
expect(
screen.getByRole('menuitem', { name: 'View successor market' })
).toBeInTheDocument();
});
});
it('successor market should be visible', async () => {

View File

@ -1,6 +1,6 @@
import { OrderbookManager } from '@vegaprotocol/market-depth';
import { ViewType, useSidebar } from '../sidebar';
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
import { useDealTicketFormValues } from '@vegaprotocol/react-helpers';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {

View File

@ -4,7 +4,9 @@ from vega_sim.null_service import VegaServiceNull
from datetime import datetime, timedelta
from conftest import init_vega, cleanup_container
from fixtures.market import setup_continuous_market
from actions.utils import wait_for_toast_confirmation
from actions.vega import submit_order
from actions.utils import wait_for_toast_confirmation, change_keys
from wallet_config import PARTY_C, MM_WALLET
order_size = "order-size"
order_price = "order-price"
@ -149,3 +151,27 @@ def test_connect_vega_wallet(continuous_market, page: Page):
# TODO: accept wallet connection and assert wallet is connected.
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
expect(page.get_by_test_id("order-price")).to_have_value("101")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_liquidated_tooltip(continuous_market, vega: VegaServiceNull, page: Page):
tdai_id = vega.find_asset_id(symbol="tDAI")
vega.mint(
PARTY_C.name,
asset=tdai_id,
amount=20,
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
submit_order(vega, PARTY_C.name, continuous_market, "SIDE_BUY", 1, 110)
submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 1, 110)
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.goto(f"/#/markets/{continuous_market}")
change_keys(page, vega, PARTY_C.name)
submit_order(vega, MM_WALLET.name, continuous_market, "SIDE_BUY", 100, 90)
submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 100, 90)
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.locator('[id="cell-openVolume-0"]').hover()
expect(page.get_by_test_id("tooltip-content").first).to_contain_text("")

View File

@ -1,7 +1,11 @@
import BigNumber from 'bignumber.js';
export const positiveClassNames =
'text-market-green-600 dark:text-market-green';
export const negativeClassNames = 'text-market-red dark:text-market-red';
export const zeroClassNames = 'text-vega-orange dark:text-vega-orange';
const isPositive = ({ value }: { value: string | bigint | number }) =>
!!value &&
((typeof value === 'string' && !value.startsWith('-')) ||
@ -12,6 +16,9 @@ const isNegative = ({ value }: { value: string | bigint | number }) =>
((typeof value === 'string' && value.startsWith('-')) ||
((typeof value === 'number' || typeof value === 'bigint') && value < 0));
export const isZero = ({ value }: { value: string | bigint | number }) =>
BigNumber(value.toString()).isZero();
export const signedNumberCssClass = (value: string | bigint | number) => {
if (isPositive({ value })) {
return positiveClassNames;

View File

@ -2,7 +2,7 @@ import { useVegaTransactionStore } from '@vegaprotocol/web3';
import {
isStopOrderType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
} from '@vegaprotocol/react-helpers';
import { StopOrder } from './deal-ticket-stop-order';
import {
useStaticMarketData,

View File

@ -1,6 +1,6 @@
import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import type { OrderFormValues } from '@vegaprotocol/react-helpers';
import { determinePriceStep, useValidateAmount } from '@vegaprotocol/utils';
import {
TradingFormGroup,

View File

@ -1,6 +1,6 @@
import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import type { OrderFormValues } from '@vegaprotocol/react-helpers';
import { useValidateAmount } from '@vegaprotocol/utils';
import {
TradingFormGroup,

View File

@ -5,11 +5,11 @@ import { generateMarket } from '../../test-helpers';
import { StopOrder } from './deal-ticket-stop-order';
import * as Schema from '@vegaprotocol/types';
import { MockedProvider } from '@apollo/client/testing';
import type { StopOrderFormValues } from '../../hooks/use-form-values';
import {
type StopOrderFormValues,
DealTicketType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
} from '@vegaprotocol/react-helpers';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { formatForInput } from '@vegaprotocol/utils';
import {

View File

@ -48,8 +48,8 @@ import {
DealTicketType,
dealTicketTypeToOrderType,
isStopOrderType,
} from '../../hooks/use-form-values';
import { type StopOrderFormValues } from '../../hooks/use-form-values';
type StopOrderFormValues,
} from '@vegaprotocol/react-helpers';
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
import { validateExpiration } from '../../utils';

View File

@ -16,7 +16,7 @@ import type { OrdersQuery } from '@vegaprotocol/orders';
import {
DealTicketType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
} from '@vegaprotocol/react-helpers';
import * as positionsTools from '@vegaprotocol/positions';
import { OrdersDocument } from '@vegaprotocol/orders';
import { formatForInput } from '@vegaprotocol/utils';

View File

@ -67,14 +67,7 @@ import {
marginModeDataProvider,
} from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { type OrderFormValues } from '../../hooks';
import {
DealTicketType,
dealTicketTypeToOrderType,
isStopOrderType,
useDealTicketFormValues,
usePositionEstimate,
} from '../../hooks';
import { usePositionEstimate } from '../../hooks';
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
import noop from 'lodash/noop';
import { isNonPersistentOrder } from '../../utils/time-in-force-persistence';
@ -85,6 +78,13 @@ import { DealTicketPriceTakeProfitStopLoss } from './deal-ticket-price-tp-sl';
import uniqueId from 'lodash/uniqueId';
import { determinePriceStep, determineSizeStep } from '@vegaprotocol/utils';
import { useMaxSize } from '../../hooks/use-max-size';
import {
DealTicketType,
type OrderFormValues,
dealTicketTypeToOrderType,
isStopOrderType,
useDealTicketFormValues,
} from '@vegaprotocol/react-helpers';
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.';

View File

@ -15,7 +15,7 @@ import {
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
import { compileGridData } from '../trading-mode-tooltip';
import { MarketModeValidationType } from '../../constants';
import { DealTicketType } from '../../hooks/use-form-values';
import { DealTicketType } from '@vegaprotocol/react-helpers';
import * as RadioGroup from '@radix-ui/react-radio-group';
import classNames from 'classnames';
import { useFeatureFlags } from '@vegaprotocol/environment';

View File

@ -1,4 +1,3 @@
export * from './__generated__/EstimateOrder';
export * from './use-estimate-fees';
export * from './use-form-values';
export * from './use-position-estimate';

View File

@ -6,7 +6,7 @@ import type {
import type {
OrderFormValues,
StopOrderFormValues,
} from '../hooks/use-form-values';
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
import { isPersistentOrder } from './time-in-force-persistence';

View File

@ -8,7 +8,7 @@ import {
} from './map-form-values-to-submission';
import * as Schema from '@vegaprotocol/types';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import type { OrderFormValues } from '../hooks';
import type { OrderFormValues } from '@vegaprotocol/react-helpers';
import { type MarketFieldsFragment } from '@vegaprotocol/markets';
describe('mapFormValuesToOrderSubmission', () => {

View File

@ -28,9 +28,9 @@
"View settlement asset details": "View settlement asset details",
"Worst case": "Worst case",
"Worst case liquidation price": "Worst case liquidation price",
"You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.": "You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.",
"You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.": "You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.",
"Your open orders were cancelled.": "Your open orders were cancelled.",
"Your position is distressed.": "Your position is distressed.",
"Your position was closed.": "Your position was closed."
"Your position was closed.": "Your position was closed.",
"You did not have enough {{assetSymbol}} to meet the margin required for your position, so it was liquidated by the network at {{price}}.": "You did not have enough {{assetSymbol}} to meet the margin required for your position, so it was liquidated by the network at {{price}}."
}

View File

@ -7,6 +7,7 @@ import { PositionStatus } from '@vegaprotocol/types';
import type { ICellRendererParams } from 'ag-grid-community';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { singleRow } from './positions.mock';
import { useLatestTrade } from '@vegaprotocol/trades';
jest.mock('./liquidation-price', () => ({
LiquidationPrice: () => (
@ -262,26 +263,15 @@ describe('Positions', () => {
expect(screen.queryByTestId(/icon-/)).not.toBeInTheDocument();
});
it('renders status with warning tooltip if orders were closed', () => {
const props = {
data: {
...singleRow,
status: PositionStatus.POSITION_STATUS_ORDERS_CLOSED,
},
valueFormatted: '100',
} as ICellRendererParams;
render(<OpenVolumeCell {...props} />);
const content = screen.getByText(props.valueFormatted as string);
expect(content).toBeInTheDocument();
expect(screen.getByTestId(/icon-/)).toBeInTheDocument();
});
it('renders status with warning tooltip if position was closed out', async () => {
const props = {
(useLatestTrade as jest.Mock).mockReturnValue({
data: {
...singleRow,
status: PositionStatus.POSITION_STATUS_CLOSED_OUT,
type: 'TYPE_NETWORK_CLOSE_OUT_BAD',
price: '100',
},
});
const props = {
data: singleRow,
valueFormatted: '100',
} as ICellRendererParams;
render(<OpenVolumeCell {...props} />);
@ -303,31 +293,23 @@ describe('Positions', () => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
});
it.each([
{
status: PositionStatus.POSITION_STATUS_CLOSED_OUT,
text: 'Your position was closed.',
},
{
status: PositionStatus.POSITION_STATUS_ORDERS_CLOSED,
text: 'Your open orders were cancelled.',
},
{
status: PositionStatus.POSITION_STATUS_DISTRESSED,
text: 'Your position is distressed.',
},
])('renders content for $status', async (data) => {
await renderComponent({
...singleRow,
status: data.status,
it('renders tooltip when positions has been closed out (liquidated)', async () => {
(useLatestTrade as jest.Mock).mockReturnValue({
data: {
type: 'TYPE_NETWORK_CLOSE_OUT_BAD',
price: '100',
},
});
await renderComponent(singleRow);
const cells = screen.getAllByRole('gridcell');
const cell = cells[1];
const tooltipTrigger = cell.querySelector('[data-state="closed"]');
expect(tooltipTrigger).not.toBeNull();
await userEvent.hover(tooltipTrigger as Element);
const tooltip = within(await screen.findByRole('tooltip'));
expect(tooltip.getByText(data.text)).toBeInTheDocument();
expect(
tooltip.getByText('Your position was closed.')
).toBeInTheDocument();
});
});

View File

@ -13,6 +13,8 @@ import {
type VegaValueGetterParams,
type TypedDataAgGrid,
type VegaICellRendererParams,
zeroClassNames,
isZero,
} from '@vegaprotocol/datagrid';
import {
ButtonLink,
@ -36,6 +38,7 @@ import {
MarketTradingMode,
PositionStatus,
PositionStatusMapping,
TradeType,
} from '@vegaprotocol/types';
import { DocsLinks, useFeatureFlags } from '@vegaprotocol/environment';
import { PositionActionsDropdown } from './position-actions-dropdown';
@ -43,6 +46,7 @@ import { LiquidationPrice } from './liquidation-price';
import { useT } from '../use-t';
import classnames from 'classnames';
import BigNumber from 'bignumber.js';
import { useLatestTrade } from '@vegaprotocol/trades';
interface Props extends TypedDataAgGrid<Position> {
onClose?: (data: Position) => void;
@ -258,7 +262,10 @@ export const PositionsTable = ({
field: 'openVolume',
type: 'rightAligned',
cellClass: 'font-mono text-right',
cellClassRules: signedNumberCssClassRules,
cellClassRules: {
...signedNumberCssClassRules,
[zeroClassNames]: isZero,
},
filter: 'agNumberColumnFilter',
sortable: false,
filterValueGetter: ({ data }: { data: Position }) => {
@ -616,49 +623,51 @@ export const OpenVolumeCell = ({
data,
}: VegaICellRendererParams<Position, 'openVolume'>) => {
const t = useT();
const { data: latestTrade } = useLatestTrade(data?.marketId, data?.partyId);
if (!valueFormatted || !data || !data.notional) {
return <>-</>;
}
const POSITION_RESOLUTION_LINK = DocsLinks?.POSITION_RESOLUTION ?? '';
let primaryTooltip;
switch (data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
primaryTooltip = t('Your position was closed.');
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
primaryTooltip = t('Your open orders were cancelled.');
break;
case PositionStatus.POSITION_STATUS_DISTRESSED:
primaryTooltip = t('Your position is distressed.');
break;
let positionStatus = PositionStatus.POSITION_STATUS_UNSPECIFIED;
if (latestTrade?.type === TradeType.TYPE_NETWORK_CLOSE_OUT_BAD) {
positionStatus = PositionStatus.POSITION_STATUS_CLOSED_OUT;
}
let secondaryTooltip;
switch (data.status) {
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
secondaryTooltip = t(
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
{ assetSymbol: data.assetSymbol }
);
break;
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
secondaryTooltip = t(
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
);
break;
case PositionStatus.POSITION_STATUS_DISTRESSED:
secondaryTooltip = t(
'The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.'
);
break;
default:
secondaryTooltip = t('Maintained by network');
const POSITION_RESOLUTION_LINK = DocsLinks?.POSITION_RESOLUTION ?? '';
const notional = addDecimalsFormatNumber(
data.notional,
data.marketDecimalPlaces
);
const cellContent = (
<StackedCell primary={valueFormatted} secondary={notional} />
);
if (positionStatus !== PositionStatus.POSITION_STATUS_CLOSED_OUT) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{cellContent}</>;
}
const description = (
const closeOutPrice = addDecimalsFormatNumber(
latestTrade?.price || '0',
data.marketDecimalPlaces
);
const description = positionStatus ===
PositionStatus.POSITION_STATUS_CLOSED_OUT && (
<>
<p className="mb-2">{primaryTooltip}</p>
<p className="mb-2">{secondaryTooltip}</p>
<p className="mb-2">{t('Your position was closed.')}</p>
<p className="mb-2">
{t(
'You did not have enough {{assetSymbol}} to meet the margin required for your position, so it was liquidated by the network at {{price}}.',
{
assetSymbol: data.assetSymbol,
price: closeOutPrice,
}
)}
</p>
<p className="mb-2">
{t('Status: {{status}}', {
nsSeparator: '*',
@ -675,20 +684,6 @@ export const OpenVolumeCell = ({
</>
);
const notional = addDecimalsFormatNumber(
data.notional,
data.marketDecimalPlaces
);
const cellContent = (
<StackedCell primary={valueFormatted} secondary={notional} />
);
if (data.status === PositionStatus.POSITION_STATUS_UNSPECIFIED) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{cellContent}</>;
}
return (
<Tooltip description={description}>
<div>

View File

@ -15,3 +15,12 @@ i18n.use(initReactI18next).init({
});
global.ResizeObserver = ResizeObserver;
jest.mock('@vegaprotocol/trades', () => ({
...jest.requireActual('@vegaprotocol/trades'),
useLatestTrade: jest.fn(() => ({
data: undefined,
loading: false,
error: undefined,
})),
}));

View File

@ -9,5 +9,5 @@ export default {
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/react-helpers',
setupFilesAfterEnv: ['./jest.setup.js'],
setupFilesAfterEnv: ['./src/setup-tests.ts'],
};

View File

@ -15,3 +15,4 @@ export * from './use-previous';
export { useScript } from './use-script';
export { useUserAgent } from './use-user-agent';
export * from './use-duration';
export * from './use-form-values';

View File

@ -0,0 +1,17 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import { tradesProvider } from '../lib/trades-data-provider';
import first from 'lodash/first';
export const useLatestTrade = (marketId?: string, partyId?: string) => {
const { data, loading, error } = useDataProvider({
dataProvider: tradesProvider,
variables: {
marketIds: [marketId || ''],
partyIds: [partyId || ''],
},
skip: !marketId || !partyId,
});
const latest = first(data);
return { data: latest, loading, error };
};

View File

@ -1,2 +1,3 @@
export * from './lib/trades-manager';
export * from './lib/__generated__/Trades';
export * from './hooks/use-latest-trade';

View File

@ -7,10 +7,14 @@ fragment TradeFields on Trade {
market {
id
}
type
}
query Trades($marketId: ID!, $pagination: Pagination) {
trades(filter: { marketIds: [$marketId] }, pagination: $pagination) {
query Trades($marketIds: [ID!], $partyIds: [ID!], $pagination: Pagination) {
trades(
filter: { marketIds: $marketIds, partyIds: $partyIds }
pagination: $pagination
) {
edges {
node {
...TradeFields
@ -33,10 +37,11 @@ fragment TradeUpdateFields on TradeUpdate {
createdAt
marketId
aggressor
type
}
subscription TradesUpdate($marketId: ID!) {
tradesStream(filter: { marketIds: [$marketId] }) {
subscription TradesUpdate($marketIds: [ID!], $partyIds: [ID!]) {
tradesStream(filter: { marketIds: $marketIds, partyIds: $partyIds }) {
...TradeUpdateFields
}
}

View File

@ -3,24 +3,26 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type TradeFieldsFragment = { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } };
export type TradeFieldsFragment = { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, type: Types.TradeType, market: { __typename?: 'Market', id: string } };
export type TradesQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
partyIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
pagination?: Types.InputMaybe<Types.Pagination>;
}>;
export type TradesQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
export type TradesQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, price: string, size: string, createdAt: any, aggressor: Types.Side, type: Types.TradeType, market: { __typename?: 'Market', id: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
export type TradeUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side };
export type TradeUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side, type: Types.TradeType };
export type TradesUpdateSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
partyIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
}>;
export type TradesUpdateSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side }> | null };
export type TradesUpdateSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, price: string, size: string, createdAt: any, marketId: string, aggressor: Types.Side, type: Types.TradeType }> | null };
export const TradeFieldsFragmentDoc = gql`
fragment TradeFields on Trade {
@ -32,6 +34,7 @@ export const TradeFieldsFragmentDoc = gql`
market {
id
}
type
}
`;
export const TradeUpdateFieldsFragmentDoc = gql`
@ -42,11 +45,15 @@ export const TradeUpdateFieldsFragmentDoc = gql`
createdAt
marketId
aggressor
type
}
`;
export const TradesDocument = gql`
query Trades($marketId: ID!, $pagination: Pagination) {
trades(filter: {marketIds: [$marketId]}, pagination: $pagination) {
query Trades($marketIds: [ID!], $partyIds: [ID!], $pagination: Pagination) {
trades(
filter: {marketIds: $marketIds, partyIds: $partyIds}
pagination: $pagination
) {
edges {
node {
...TradeFields
@ -75,12 +82,13 @@ export const TradesDocument = gql`
* @example
* const { data, loading, error } = useTradesQuery({
* variables: {
* marketId: // value for 'marketId'
* marketIds: // value for 'marketIds'
* partyIds: // value for 'partyIds'
* pagination: // value for 'pagination'
* },
* });
*/
export function useTradesQuery(baseOptions: Apollo.QueryHookOptions<TradesQuery, TradesQueryVariables>) {
export function useTradesQuery(baseOptions?: Apollo.QueryHookOptions<TradesQuery, TradesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<TradesQuery, TradesQueryVariables>(TradesDocument, options);
}
@ -92,8 +100,8 @@ export type TradesQueryHookResult = ReturnType<typeof useTradesQuery>;
export type TradesLazyQueryHookResult = ReturnType<typeof useTradesLazyQuery>;
export type TradesQueryResult = Apollo.QueryResult<TradesQuery, TradesQueryVariables>;
export const TradesUpdateDocument = gql`
subscription TradesUpdate($marketId: ID!) {
tradesStream(filter: {marketIds: [$marketId]}) {
subscription TradesUpdate($marketIds: [ID!], $partyIds: [ID!]) {
tradesStream(filter: {marketIds: $marketIds, partyIds: $partyIds}) {
...TradeUpdateFields
}
}
@ -111,11 +119,12 @@ export const TradesUpdateDocument = gql`
* @example
* const { data, loading, error } = useTradesUpdateSubscription({
* variables: {
* marketId: // value for 'marketId'
* marketIds: // value for 'marketIds'
* partyIds: // value for 'partyIds'
* },
* });
*/
export function useTradesUpdateSubscription(baseOptions: Apollo.SubscriptionHookOptions<TradesUpdateSubscription, TradesUpdateSubscriptionVariables>) {
export function useTradesUpdateSubscription(baseOptions?: Apollo.SubscriptionHookOptions<TradesUpdateSubscription, TradesUpdateSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<TradesUpdateSubscription, TradesUpdateSubscriptionVariables>(TradesUpdateDocument, options);
}

View File

@ -23,10 +23,14 @@ export const MAX_TRADES = 500;
const getData = (
responseData: TradesQuery | null
): (TradeFieldsFragment & Cursor)[] =>
responseData?.trades?.edges.map<TradeFieldsFragment & Cursor>((edge) => ({
...edge.node,
cursor: edge.cursor,
})) || [];
orderBy(
responseData?.trades?.edges.map<TradeFieldsFragment & Cursor>((edge) => ({
...edge.node,
cursor: edge.cursor,
})),
'createdAt',
'desc'
) || [];
const getDelta = (subscriptionData: TradesUpdateSubscription) =>
subscriptionData?.tradesStream || [];
@ -101,7 +105,10 @@ export const tradesProvider = makeDataProvider<
last: MAX_TRADES,
},
fetchPolicy: 'no-cache',
getSubscriptionVariables: ({ marketId }) => ({ marketId }),
getSubscriptionVariables: ({ marketIds, partyIds }) => ({
marketIds,
partyIds,
}),
});
export const tradesWithMarketProvider = makeDerivedDataProvider<

View File

@ -1,7 +1,7 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import { tradesWithMarketProvider } from './trades-data-provider';
import { TradesTable } from './trades-table';
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
import { useDealTicketFormValues } from '@vegaprotocol/react-helpers';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useT } from './use-t';
@ -19,7 +19,7 @@ export const TradesManager = ({
const { data, error } = useDataProvider({
dataProvider: tradesWithMarketProvider,
variables: { marketId },
variables: { marketIds: [marketId] },
});
return (

View File

@ -2,7 +2,7 @@ import { act, render, screen } from '@testing-library/react';
import { getTimeFormat } from '@vegaprotocol/utils';
import { SELL_CLASS, TradesTable, BUY_CLASS } from './trades-table';
import type { Trade } from './trades-data-provider';
import { Side } from '@vegaprotocol/types';
import { Side, TradeType } from '@vegaprotocol/types';
const trade: Trade = {
__typename: 'Trade',
@ -17,6 +17,7 @@ const trade: Trade = {
decimalPlaces: 2,
positionDecimalPlaces: 2,
} as Trade['market'],
type: TradeType.TYPE_DEFAULT,
};
describe('TradesTable', () => {

View File

@ -1,4 +1,4 @@
import { Side } from '@vegaprotocol/types';
import { Side, TradeType } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
@ -45,6 +45,7 @@ export const tradesUpdateSubscription = (
createdAt: '2022-04-06T16:19:42.692598951Z',
marketId: 'market-0',
aggressor: Side.SIDE_BUY,
type: TradeType.TYPE_DEFAULT,
},
],
};
@ -62,6 +63,7 @@ const trades: TradeFieldsFragment[] = [
id: 'market-0',
__typename: 'Market',
},
type: TradeType.TYPE_DEFAULT,
__typename: 'Trade',
},
{
@ -74,6 +76,7 @@ const trades: TradeFieldsFragment[] = [
id: 'market-0',
__typename: 'Market',
},
type: TradeType.TYPE_DEFAULT,
__typename: 'Trade',
},
{
@ -86,6 +89,7 @@ const trades: TradeFieldsFragment[] = [
id: 'market-0',
__typename: 'Market',
},
type: TradeType.TYPE_DEFAULT,
__typename: 'Trade',
},
];

View File

@ -106,6 +106,7 @@ export const DepositStatusMapping: {
} = {
STATUS_CANCELLED: 'Cancelled',
STATUS_FINALIZED: 'Finalized',
STATUS_DUPLICATE_REJECTED: 'Duplicate rejected',
STATUS_OPEN: 'Open',
STATUS_DUPLICATE_REJECTED: 'Rejected due to duplicate',
};

View File

@ -1,10 +1,13 @@
import BigNumber from 'bignumber.js';
/**
* Returns a number prefixed with either a '-' or a '+'. The open volume field
* already comes with a '-' if negative so we only need to actually prefix if
* its a positive value
*/
export function volumePrefix(value: string): string {
if (value === '0' || value.startsWith('-')) {
const isZero = BigNumber(value).isZero();
if (isZero || value.startsWith('-')) {
return value;
}