feat(trading): show positions for all connected keys (#3858)
This commit is contained in:
parent
1ae1fdff5e
commit
f364dabe2f
@ -1,6 +1,7 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketsDataQuery } from '@vegaprotocol/mock';
|
||||
import { positionsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
@ -16,9 +17,27 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
});
|
||||
|
||||
it('renders positions on portfolio page', () => {
|
||||
cy.mockGQL((req) => {
|
||||
const positions = positionsQuery();
|
||||
if (positions.positions?.edges) {
|
||||
positions.positions.edges.push(
|
||||
...positions.positions.edges.map((edge) => ({
|
||||
...edge,
|
||||
node: {
|
||||
...edge.node,
|
||||
party: {
|
||||
...edge.node.party,
|
||||
id: 'vega-1',
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
aliasGQLQuery(req, 'Positions', positions);
|
||||
});
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('Positions').click();
|
||||
validatePositionsDisplayed();
|
||||
validatePositionsDisplayed(true);
|
||||
});
|
||||
describe('renders position among some graphql errors', () => {
|
||||
it('rows should be displayed despite errors', () => {
|
||||
@ -56,7 +75,9 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.getByTestId('tab-positions')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('[row-id="market-2"]')
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
@ -103,9 +124,11 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
const marketsSortedDefault = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedAsc = ['ACTIVE MARKET', 'Apple Monthly (30 Jun 2022)'];
|
||||
const marketsSortedDesc = [
|
||||
'SUSPENDED MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ACTIVE MARKET',
|
||||
];
|
||||
@ -119,9 +142,13 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
});
|
||||
it('sorting by notional', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = ['276,761.40348', '46,126.90058'];
|
||||
const marketsSortedAsc = ['46,126.90058', '276,761.40348'];
|
||||
const marketsSortedDesc = ['276,761.40348', '46,126.90058'];
|
||||
const marketsSortedDefault = [
|
||||
'276,761.40348',
|
||||
'46,126.90058',
|
||||
'1,688.20',
|
||||
];
|
||||
const marketsSortedAsc = ['1,688.20', '46,126.90058', '276,761.40348'];
|
||||
const marketsSortedDesc = ['276,761.40348', '46,126.90058', '1,688.20'];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'notional',
|
||||
@ -132,9 +159,9 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
});
|
||||
it('sorting by unrealisedPNL', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = ['8.95', '-0.22519'];
|
||||
const marketsSortedAsc = ['-0.22519', '8.95'];
|
||||
const marketsSortedDesc = ['8.95', '-0.22519'];
|
||||
const marketsSortedDefault = ['8.95', '-0.22519', '8.95'];
|
||||
const marketsSortedAsc = ['-0.22519', '8.95', '8.95'];
|
||||
const marketsSortedDesc = ['8.95', '8.95', '-0.22519'];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'unrealisedPNL',
|
||||
@ -145,7 +172,7 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
function validatePositionsDisplayed() {
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions').within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
@ -165,10 +192,11 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
|
||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||
@ -184,6 +212,6 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
});
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 2);
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
});
|
||||
|
@ -175,6 +175,10 @@ describe('Closed', () => {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: pubKey,
|
||||
},
|
||||
};
|
||||
};
|
||||
const position = createPosition();
|
||||
@ -182,18 +186,14 @@ describe('Closed', () => {
|
||||
request: {
|
||||
query: PositionsDocument,
|
||||
variables: {
|
||||
partyId: pubKey,
|
||||
partyIds: [pubKey],
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: pubKey,
|
||||
positionsConnection: {
|
||||
__typename: 'PositionConnection',
|
||||
edges: [{ __typename: 'PositionEdge', node: position }],
|
||||
},
|
||||
positions: {
|
||||
__typename: 'PositionConnection',
|
||||
edges: [{ __typename: 'PositionEdge', node: position }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ export const Closed = () => {
|
||||
});
|
||||
const { data: positionData } = usePositionsQuery({
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
partyIds: pubKey ? [pubKey] : [],
|
||||
},
|
||||
skip: !pubKey,
|
||||
});
|
||||
@ -72,11 +72,9 @@ export const Closed = () => {
|
||||
// find a position for each market and add the realised pnl to
|
||||
// a normalized object
|
||||
const rowData = compact(marketData).map((market) => {
|
||||
const position = positionData?.party?.positionsConnection?.edges?.find(
|
||||
(edge) => {
|
||||
return edge.node.market.id === market.id;
|
||||
}
|
||||
);
|
||||
const position = positionData?.positions?.edges?.find((edge) => {
|
||||
return edge.node.market.id === market.id;
|
||||
});
|
||||
|
||||
const instrument = market.tradableInstrument.instrument;
|
||||
|
||||
|
@ -54,6 +54,7 @@ export const Portfolio = () => {
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
storeKey="portfolioPositions"
|
||||
allKeys
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
|
@ -89,6 +89,9 @@ const cacheConfig: InMemoryCacheConfig = {
|
||||
Party: {
|
||||
keyFields: false,
|
||||
},
|
||||
Position: {
|
||||
keyFields: ['market', ['id'], 'party', ['id']],
|
||||
},
|
||||
Fees: {
|
||||
keyFields: false,
|
||||
},
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useRef, useMemo, memo, useCallback } from 'react';
|
||||
import { useRef, useMemo, memo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
|
||||
import type { PinnedAsset } from './accounts-table';
|
||||
import { AccountTable } from './accounts-table';
|
||||
@ -36,16 +35,8 @@ export const AccountManager = ({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
variables,
|
||||
});
|
||||
const setId = useCallback(
|
||||
(data: AccountFields, id: string) => ({
|
||||
...data,
|
||||
asset: { ...data.asset, id },
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
setId,
|
||||
disabled: noBottomPlaceholder,
|
||||
});
|
||||
|
||||
|
@ -154,7 +154,11 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No accounts')}
|
||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||
getRowId={({
|
||||
data,
|
||||
}: {
|
||||
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
|
||||
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
rowData={props.rowData?.filter(
|
||||
|
@ -164,7 +164,9 @@ interface DataProviderParams<
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables extends OperationVariables | undefined = undefined
|
||||
Variables extends OperationVariables | undefined = undefined,
|
||||
SubscriptionVariables extends OperationVariables | undefined = Variables,
|
||||
QueryVariables extends OperationVariables | undefined = Variables
|
||||
> {
|
||||
query: Query<QueryData>;
|
||||
subscriptionQuery?: Query<SubscriptionData>;
|
||||
@ -181,6 +183,10 @@ interface DataProviderParams<
|
||||
resetDelay?: number;
|
||||
additionalContext?: Record<string, unknown>;
|
||||
errorPolicyGuard?: (graphqlErrors: GraphQLErrors) => boolean;
|
||||
getQueryVariables?: (variables: Variables) => QueryVariables;
|
||||
getSubscriptionVariables?: (
|
||||
variables: Variables
|
||||
) => SubscriptionVariables | SubscriptionVariables[];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,7 +205,9 @@ function makeDataProviderInternal<
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables extends OperationVariables | undefined = undefined
|
||||
Variables extends OperationVariables | undefined = undefined,
|
||||
QueryVariables extends OperationVariables | undefined = Variables,
|
||||
SubscriptionVariables extends OperationVariables | undefined = Variables
|
||||
>({
|
||||
query,
|
||||
subscriptionQuery,
|
||||
@ -211,12 +219,16 @@ function makeDataProviderInternal<
|
||||
resetDelay,
|
||||
additionalContext,
|
||||
errorPolicyGuard,
|
||||
getQueryVariables,
|
||||
getSubscriptionVariables,
|
||||
}: DataProviderParams<
|
||||
QueryData,
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables
|
||||
Variables,
|
||||
QueryVariables,
|
||||
SubscriptionVariables
|
||||
>): Subscribe<Data, Delta, Variables> {
|
||||
// list of callbacks passed through subscribe call
|
||||
const callbacks: UpdateCallback<Data, Delta>[] = [];
|
||||
@ -230,7 +242,7 @@ function makeDataProviderInternal<
|
||||
let loading = true;
|
||||
let loaded = false;
|
||||
let client: ApolloClient<object>;
|
||||
let subscription: Subscription | undefined;
|
||||
let subscription: Subscription[] | undefined;
|
||||
let pageInfo: PageInfo | null = null;
|
||||
let totalCount: number | undefined;
|
||||
|
||||
@ -263,7 +275,7 @@ function makeDataProviderInternal<
|
||||
.query<QueryData>({
|
||||
query,
|
||||
variables: {
|
||||
...variables,
|
||||
...(getQueryVariables ? getQueryVariables(variables) : variables),
|
||||
...(pagination && {
|
||||
// let the variables pagination be prior to provider param
|
||||
pagination: {
|
||||
@ -343,6 +355,33 @@ function makeDataProviderInternal<
|
||||
}
|
||||
};
|
||||
|
||||
const subscriptionSubscribe = () => {
|
||||
if (!subscriptionQuery || !getDelta || !update) {
|
||||
return;
|
||||
}
|
||||
const subscriptionVariables = getSubscriptionVariables
|
||||
? getSubscriptionVariables(variables)
|
||||
: variables;
|
||||
subscription = ([] as (OperationVariables | undefined)[])
|
||||
.concat(subscriptionVariables)
|
||||
.map((variables) =>
|
||||
client
|
||||
.subscribe<SubscriptionData>({
|
||||
query: subscriptionQuery,
|
||||
variables,
|
||||
fetchPolicy,
|
||||
})
|
||||
.subscribe(onNext, onError)
|
||||
);
|
||||
};
|
||||
|
||||
const subscriptionUnsubscribe = () => {
|
||||
if (subscription) {
|
||||
subscription.forEach((subscription) => subscription.unsubscribe());
|
||||
}
|
||||
subscription = undefined;
|
||||
};
|
||||
|
||||
const initialFetch = async (isUpdate = false) => {
|
||||
if (!client) {
|
||||
return;
|
||||
@ -392,10 +431,7 @@ function makeDataProviderInternal<
|
||||
}
|
||||
// if error will occur data provider stops subscription
|
||||
error = e as Error;
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
subscription = undefined;
|
||||
subscriptionUnsubscribe();
|
||||
} finally {
|
||||
loading = false;
|
||||
notifyAll({ isUpdate });
|
||||
@ -439,10 +475,7 @@ function makeDataProviderInternal<
|
||||
|
||||
const onError = (e: Error) => {
|
||||
error = e;
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
}
|
||||
subscriptionUnsubscribe();
|
||||
notifyAll();
|
||||
};
|
||||
|
||||
@ -460,13 +493,7 @@ function makeDataProviderInternal<
|
||||
return;
|
||||
}
|
||||
if (subscriptionQuery && getDelta && update) {
|
||||
subscription = client
|
||||
.subscribe<SubscriptionData>({
|
||||
query: subscriptionQuery,
|
||||
variables,
|
||||
fetchPolicy,
|
||||
})
|
||||
.subscribe(onNext, onError);
|
||||
subscriptionSubscribe();
|
||||
}
|
||||
await initialFetch();
|
||||
};
|
||||
@ -475,8 +502,7 @@ function makeDataProviderInternal<
|
||||
if (!subscription) {
|
||||
return;
|
||||
}
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
subscriptionUnsubscribe();
|
||||
data = null;
|
||||
error = undefined;
|
||||
loading = false;
|
||||
@ -590,14 +616,18 @@ export function makeDataProvider<
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables extends OperationVariables | undefined = undefined
|
||||
Variables extends OperationVariables | undefined = undefined,
|
||||
SubscriptionVariables extends OperationVariables | undefined = Variables,
|
||||
QueryVariables extends OperationVariables | undefined = Variables
|
||||
>(
|
||||
params: DataProviderParams<
|
||||
QueryData,
|
||||
Data,
|
||||
SubscriptionData,
|
||||
Delta,
|
||||
Variables
|
||||
Variables,
|
||||
SubscriptionVariables,
|
||||
QueryVariables
|
||||
>
|
||||
): Subscribe<Data, Delta, Variables> {
|
||||
const getInstance = memoize<Data, Delta, Variables>(() =>
|
||||
|
@ -4,41 +4,34 @@ import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IsFullWidthRowParams, RowHeightParams } from 'ag-grid-community';
|
||||
|
||||
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
|
||||
const ROW_ID = 'bottomPlaceholder';
|
||||
const ROW_ID = 'bottom-placeholder';
|
||||
const fullWidthCellRenderer = () => null;
|
||||
const isFullWidthRow = (params: IsFullWidthRowParams) =>
|
||||
params.rowNode.data?.isLastPlaceholder;
|
||||
|
||||
interface Props<T> {
|
||||
interface Props {
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
setId?: (data: T, id: string) => T;
|
||||
disabled?: boolean;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export const useBottomPlaceholder = <T extends {}>({
|
||||
gridRef,
|
||||
setId,
|
||||
disabled,
|
||||
}: Props<T>) => {
|
||||
export const useBottomPlaceholder = ({ gridRef, disabled }: Props) => {
|
||||
const onBodyScrollEnd = useCallback(() => {
|
||||
const rowCont = gridRef.current?.api.getDisplayedRowCount() ?? 0;
|
||||
if (rowCont) {
|
||||
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(rowCont - 1);
|
||||
if (lastRow && lastRow.data) {
|
||||
const placeholderRow = setId
|
||||
? setId({ ...lastRow.data, isLastPlaceholder: true }, ROW_ID)
|
||||
: {
|
||||
...lastRow.data,
|
||||
isLastPlaceholder: true,
|
||||
id: ROW_ID,
|
||||
};
|
||||
const placeholderRow = {
|
||||
...lastRow.data,
|
||||
isLastPlaceholder: true,
|
||||
id: ROW_ID,
|
||||
};
|
||||
const transaction = gridRef.current?.api.getRowNode(ROW_ID)
|
||||
? { update: [placeholderRow] }
|
||||
: { add: [placeholderRow] };
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
}
|
||||
}, [gridRef, setId]);
|
||||
}, [gridRef]);
|
||||
|
||||
const onRowsChanged = useCallback(() => {
|
||||
const placeholderNode = gridRef.current?.api.getRowNode(ROW_ID);
|
||||
|
@ -5,7 +5,6 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { FillsTable } from './fills-table';
|
||||
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
|
||||
import { useFillsList } from './use-fills-list';
|
||||
import type { Trade } from './fills-data-provider';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
|
||||
interface FillsManagerProps {
|
||||
@ -65,7 +64,7 @@ export const FillsManager = ({
|
||||
}, []);
|
||||
|
||||
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules, getRowHeight } =
|
||||
useBottomPlaceholder<Trade>({
|
||||
useBottomPlaceholder({
|
||||
gridRef,
|
||||
});
|
||||
|
||||
|
@ -187,7 +187,7 @@ const marketFieldsFragments: MarketFieldsFragment[] = [
|
||||
name: 'Apple Monthly (30 Jun 2022)',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'asset-2',
|
||||
id: 'asset-id',
|
||||
name: '',
|
||||
symbol: 'tUSDC',
|
||||
decimals: 5,
|
||||
|
@ -87,7 +87,7 @@ export const OrderListManager = ({
|
||||
const {
|
||||
onFilterChanged: bottomPlaceholderOnFilterChanged,
|
||||
...bottomPlaceholderProps
|
||||
} = useBottomPlaceholder<Order>({
|
||||
} = useBottomPlaceholder({
|
||||
gridRef,
|
||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
|
||||
});
|
||||
|
@ -9,16 +9,16 @@ fragment PositionFields on Position {
|
||||
market {
|
||||
id
|
||||
}
|
||||
party {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
query Positions($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
positionsConnection {
|
||||
edges {
|
||||
node {
|
||||
...PositionFields
|
||||
}
|
||||
query Positions($partyIds: [ID!]!) {
|
||||
positions(filter: { partyIds: $partyIds }) {
|
||||
edges {
|
||||
node {
|
||||
...PositionFields
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,7 @@ subscription PositionsSubscription($partyId: ID!) {
|
||||
marketId
|
||||
lossSocializationAmount
|
||||
positionStatus
|
||||
partyId
|
||||
}
|
||||
}
|
||||
|
||||
|
27
libs/positions/src/lib/__generated__/Positions.ts
generated
27
libs/positions/src/lib/__generated__/Positions.ts
generated
@ -3,21 +3,21 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type PositionFieldsFragment = { __typename?: 'Position', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, positionStatus: Types.PositionStatus, lossSocializationAmount: string, market: { __typename?: 'Market', id: string } };
|
||||
export type PositionFieldsFragment = { __typename?: 'Position', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, positionStatus: Types.PositionStatus, lossSocializationAmount: string, market: { __typename?: 'Market', id: string }, party: { __typename?: 'Party', id: string } };
|
||||
|
||||
export type PositionsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
partyIds: Array<Types.Scalars['ID']> | Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type PositionsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, positionsConnection?: { __typename?: 'PositionConnection', edges?: Array<{ __typename?: 'PositionEdge', node: { __typename?: 'Position', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, positionStatus: Types.PositionStatus, lossSocializationAmount: string, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
|
||||
export type PositionsQuery = { __typename?: 'Query', positions?: { __typename?: 'PositionConnection', edges?: Array<{ __typename?: 'PositionEdge', node: { __typename?: 'Position', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, positionStatus: Types.PositionStatus, lossSocializationAmount: string, market: { __typename?: 'Market', id: string }, party: { __typename?: 'Party', id: string } } }> | null } | null };
|
||||
|
||||
export type PositionsSubscriptionSubscriptionVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type PositionsSubscriptionSubscription = { __typename?: 'Subscription', positions: Array<{ __typename?: 'PositionUpdate', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, marketId: string, lossSocializationAmount: string, positionStatus: Types.PositionStatus }> };
|
||||
export type PositionsSubscriptionSubscription = { __typename?: 'Subscription', positions: Array<{ __typename?: 'PositionUpdate', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, marketId: string, lossSocializationAmount: string, positionStatus: Types.PositionStatus, partyId: string }> };
|
||||
|
||||
export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } };
|
||||
|
||||
@ -57,6 +57,9 @@ export const PositionFieldsFragmentDoc = gql`
|
||||
market {
|
||||
id
|
||||
}
|
||||
party {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const MarginFieldsFragmentDoc = gql`
|
||||
@ -74,14 +77,11 @@ export const MarginFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const PositionsDocument = gql`
|
||||
query Positions($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
positionsConnection {
|
||||
edges {
|
||||
node {
|
||||
...PositionFields
|
||||
}
|
||||
query Positions($partyIds: [ID!]!) {
|
||||
positions(filter: {partyIds: $partyIds}) {
|
||||
edges {
|
||||
node {
|
||||
...PositionFields
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,7 +100,7 @@ export const PositionsDocument = gql`
|
||||
* @example
|
||||
* const { data, loading, error } = usePositionsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* partyIds: // value for 'partyIds'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@ -126,6 +126,7 @@ export const PositionsSubscriptionDocument = gql`
|
||||
marketId
|
||||
lossSocializationAmount
|
||||
positionStatus
|
||||
partyId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -7,12 +7,14 @@ export const PositionsContainer = ({
|
||||
onMarketClick,
|
||||
noBottomPlaceholder,
|
||||
storeKey,
|
||||
allKeys,
|
||||
}: {
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
noBottomPlaceholder?: boolean;
|
||||
storeKey?: string;
|
||||
allKeys?: boolean;
|
||||
}) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
@ -21,9 +23,19 @@ export const PositionsContainer = ({
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
const partyIds = [pubKey];
|
||||
if (allKeys && pubKeys) {
|
||||
partyIds.push(
|
||||
...pubKeys
|
||||
.map(({ publicKey }) => publicKey)
|
||||
.filter((publicKey) => publicKey !== pubKey)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PositionsManager
|
||||
partyId={pubKey}
|
||||
partyIds={partyIds}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
noBottomPlaceholder={noBottomPlaceholder}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { Account } from '@vegaprotocol/accounts';
|
||||
import type { MarketWithData } from '@vegaprotocol/markets';
|
||||
import type {
|
||||
PositionFieldsFragment,
|
||||
MarginFieldsFragment,
|
||||
} from './__generated__/Positions';
|
||||
import type { PositionFieldsFragment } from './__generated__/Positions';
|
||||
import { getMetrics, rejoinPositionData } from './positions-data-providers';
|
||||
import { PositionStatus } from '@vegaprotocol/types';
|
||||
|
||||
@ -79,6 +76,9 @@ const positions: PositionFieldsFragment[] = [
|
||||
__typename: 'Market',
|
||||
id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8',
|
||||
},
|
||||
party: {
|
||||
id: 'partyId',
|
||||
},
|
||||
lossSocializationAmount: '0',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
@ -93,6 +93,9 @@ const positions: PositionFieldsFragment[] = [
|
||||
__typename: 'Market',
|
||||
id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e',
|
||||
},
|
||||
party: {
|
||||
id: 'partyId',
|
||||
},
|
||||
lossSocializationAmount: '100',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_ORDERS_CLOSED,
|
||||
},
|
||||
@ -113,6 +116,8 @@ const marketsData = [
|
||||
product: {
|
||||
settlementAsset: {
|
||||
symbol: 'tDAI',
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
decimals: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -140,6 +145,8 @@ const marketsData = [
|
||||
product: {
|
||||
settlementAsset: {
|
||||
symbol: 'tDAI',
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
decimals: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -155,65 +162,23 @@ const marketsData = [
|
||||
},
|
||||
] as MarketWithData[];
|
||||
|
||||
const margins: MarginFieldsFragment[] = [
|
||||
{
|
||||
__typename: 'MarginLevels',
|
||||
maintenanceLevel: '0',
|
||||
searchLevel: '0',
|
||||
initialLevel: '0',
|
||||
collateralReleaseLevel: '0',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: 'tDAI-id',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'MarginLevels',
|
||||
maintenanceLevel: '0',
|
||||
searchLevel: '0',
|
||||
initialLevel: '0',
|
||||
collateralReleaseLevel: '0',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: 'tDAI-id',
|
||||
},
|
||||
},
|
||||
];
|
||||
describe('getMetrics && rejoinPositionData', () => {
|
||||
it('returns positions metrics', () => {
|
||||
const positionsRejoined = rejoinPositionData(
|
||||
positions,
|
||||
marketsData,
|
||||
margins
|
||||
);
|
||||
const positionsRejoined = rejoinPositionData(positions, marketsData);
|
||||
const metrics = getMetrics(positionsRejoined, accounts || null);
|
||||
expect(metrics.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('calculates metrics', () => {
|
||||
const positionsRejoined = rejoinPositionData(
|
||||
positions,
|
||||
marketsData,
|
||||
margins
|
||||
);
|
||||
const positionsRejoined = rejoinPositionData(positions, marketsData);
|
||||
const metrics = getMetrics(positionsRejoined, accounts || null);
|
||||
|
||||
expect(metrics[0].assetSymbol).toEqual('tDAI');
|
||||
expect(metrics[0].averageEntryPrice).toEqual('8993727');
|
||||
expect(metrics[0].capitalUtilisation).toEqual(4);
|
||||
expect(metrics[0].currentLeverage).toBeCloseTo(1.02);
|
||||
expect(metrics[0].marketDecimalPlaces).toEqual(5);
|
||||
expect(metrics[0].positionDecimalPlaces).toEqual(0);
|
||||
expect(metrics[0].decimals).toEqual(5);
|
||||
expect(metrics[0].lowMarginLevel).toEqual(false);
|
||||
expect(metrics[0].markPrice).toEqual('9431775');
|
||||
expect(metrics[0].marketId).toEqual(
|
||||
'5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8'
|
||||
@ -225,7 +190,6 @@ describe('getMetrics && rejoinPositionData', () => {
|
||||
expect(metrics[0].notional).toEqual('943177500');
|
||||
expect(metrics[0].openVolume).toEqual('100');
|
||||
expect(metrics[0].realisedPNL).toEqual('0');
|
||||
expect(metrics[0].searchPrice).toEqual('9098238');
|
||||
expect(metrics[0].totalBalance).toEqual('926178496');
|
||||
expect(metrics[0].unrealisedPNL).toEqual('43804770');
|
||||
expect(metrics[0].updatedAt).toEqual('2022-07-28T14:53:54.725477Z');
|
||||
@ -236,12 +200,10 @@ describe('getMetrics && rejoinPositionData', () => {
|
||||
|
||||
expect(metrics[1].assetSymbol).toEqual('tDAI');
|
||||
expect(metrics[1].averageEntryPrice).toEqual('840158');
|
||||
expect(metrics[1].capitalUtilisation).toEqual(0);
|
||||
expect(metrics[1].currentLeverage).toBeCloseTo(0.097);
|
||||
expect(metrics[1].marketDecimalPlaces).toEqual(5);
|
||||
expect(metrics[1].positionDecimalPlaces).toEqual(0);
|
||||
expect(metrics[1].decimals).toEqual(5);
|
||||
expect(metrics[1].lowMarginLevel).toEqual(false);
|
||||
expect(metrics[1].markPrice).toEqual('869762');
|
||||
expect(metrics[1].marketId).toEqual(
|
||||
'10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e'
|
||||
@ -251,7 +213,6 @@ describe('getMetrics && rejoinPositionData', () => {
|
||||
expect(metrics[1].notional).toEqual('86976200');
|
||||
expect(metrics[1].openVolume).toEqual('-100');
|
||||
expect(metrics[1].realisedPNL).toEqual('0');
|
||||
expect(metrics[1].searchPrice).toEqual('902503');
|
||||
expect(metrics[1].totalBalance).toEqual('896098819');
|
||||
expect(metrics[1].unrealisedPNL).toEqual('-9112700');
|
||||
expect(metrics[1].updatedAt).toEqual('2022-07-28T15:09:34.441143Z');
|
||||
|
@ -19,66 +19,41 @@ import type {
|
||||
PositionsQuery,
|
||||
PositionFieldsFragment,
|
||||
PositionsSubscriptionSubscription,
|
||||
MarginFieldsFragment,
|
||||
PositionsQueryVariables,
|
||||
PositionsSubscriptionSubscriptionVariables,
|
||||
} from './__generated__/Positions';
|
||||
import {
|
||||
PositionsDocument,
|
||||
PositionsSubscriptionDocument,
|
||||
} from './__generated__/Positions';
|
||||
import { marginsDataProvider } from './margin-data-provider';
|
||||
import type { PositionStatus } from '@vegaprotocol/types';
|
||||
|
||||
type PositionMarginLevel = Pick<
|
||||
MarginFieldsFragment,
|
||||
'maintenanceLevel' | 'searchLevel' | 'initialLevel'
|
||||
>;
|
||||
|
||||
interface PositionRejoined {
|
||||
realisedPNL: string;
|
||||
openVolume: string;
|
||||
unrealisedPNL: string;
|
||||
averageEntryPrice: string;
|
||||
updatedAt?: string | null;
|
||||
market: MarketMaybeWithData | null;
|
||||
margins: PositionMarginLevel | null;
|
||||
lossSocializationAmount: string | null;
|
||||
status: PositionStatus;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
marketName: string;
|
||||
assetId: string;
|
||||
assetSymbol: string;
|
||||
averageEntryPrice: string;
|
||||
marginAccountBalance: string;
|
||||
capitalUtilisation: number;
|
||||
currentLeverage: number | undefined;
|
||||
decimals: number;
|
||||
lossSocializationAmount: string;
|
||||
marginAccountBalance: string;
|
||||
marketDecimalPlaces: number;
|
||||
positionDecimalPlaces: number;
|
||||
totalBalance: string;
|
||||
assetSymbol: string;
|
||||
assetId: string;
|
||||
lowMarginLevel: boolean;
|
||||
marketId: string;
|
||||
marketName: string;
|
||||
marketTradingMode: Schema.MarketTradingMode;
|
||||
markPrice: string | undefined;
|
||||
notional: string | undefined;
|
||||
openVolume: string;
|
||||
partyId: string;
|
||||
positionDecimalPlaces: number;
|
||||
realisedPNL: string;
|
||||
unrealisedPNL: string;
|
||||
searchPrice: string | undefined;
|
||||
updatedAt: string | null;
|
||||
lossSocializationAmount: string;
|
||||
status: PositionStatus;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
party: PositionsQuery['party'] | null;
|
||||
positions: Position[] | null;
|
||||
totalBalance: string;
|
||||
unrealisedPNL: string;
|
||||
updatedAt: string | null;
|
||||
}
|
||||
|
||||
export const getMetrics = (
|
||||
data: PositionRejoined[] | null,
|
||||
data: ReturnType<typeof rejoinPositionData> | null,
|
||||
accounts: Account[] | null
|
||||
): Position[] => {
|
||||
if (!data || !data?.length) {
|
||||
@ -87,25 +62,32 @@ export const getMetrics = (
|
||||
const metrics: Position[] = [];
|
||||
data.forEach((position) => {
|
||||
const market = position.market;
|
||||
if (!market) {
|
||||
return;
|
||||
}
|
||||
const marketData = market?.data;
|
||||
const marginLevel = position.margins;
|
||||
const marginAccount = accounts?.find((account) => {
|
||||
return account.market?.id === market?.id;
|
||||
});
|
||||
if (!marginAccount || !marginLevel || !market) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
decimals,
|
||||
id: assetId,
|
||||
symbol: assetSymbol,
|
||||
} = market.tradableInstrument.instrument.product.settlementAsset;
|
||||
const generalAccount = accounts?.find(
|
||||
(account) =>
|
||||
account.asset.id === marginAccount.asset.id &&
|
||||
account.asset.id === assetId &&
|
||||
account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL
|
||||
);
|
||||
const decimals = marginAccount.asset.decimals;
|
||||
|
||||
const { positionDecimalPlaces, decimalPlaces: marketDecimalPlaces } =
|
||||
market;
|
||||
const openVolume = toBigNum(position.openVolume, positionDecimalPlaces);
|
||||
|
||||
const marginAccountBalance = toBigNum(marginAccount.balance ?? 0, decimals);
|
||||
const marginAccountBalance = toBigNum(
|
||||
marginAccount?.balance ?? 0,
|
||||
decimals
|
||||
);
|
||||
const generalAccountBalance = toBigNum(
|
||||
generalAccount?.balance ?? 0,
|
||||
decimals
|
||||
@ -126,54 +108,30 @@ export const getMetrics = (
|
||||
? new BigNumber(0)
|
||||
: notional.dividedBy(totalBalance)
|
||||
: undefined;
|
||||
const capitalUtilisation = totalBalance.isEqualTo(0)
|
||||
? new BigNumber(0)
|
||||
: marginAccountBalance.dividedBy(totalBalance).multipliedBy(100);
|
||||
|
||||
const marginSearch = toBigNum(marginLevel.searchLevel, decimals);
|
||||
const marginInitial = toBigNum(marginLevel.initialLevel, decimals);
|
||||
|
||||
const searchPrice = markPrice
|
||||
? marginSearch
|
||||
.minus(marginAccountBalance)
|
||||
.dividedBy(openVolume)
|
||||
.plus(markPrice)
|
||||
: undefined;
|
||||
|
||||
const lowMarginLevel =
|
||||
marginAccountBalance.isLessThan(
|
||||
marginSearch.plus(marginInitial.minus(marginSearch).dividedBy(2))
|
||||
) && generalAccountBalance.isLessThan(marginInitial.minus(marginSearch));
|
||||
|
||||
metrics.push({
|
||||
marketName: market.tradableInstrument.instrument.name,
|
||||
assetId,
|
||||
assetSymbol,
|
||||
averageEntryPrice: position.averageEntryPrice,
|
||||
marginAccountBalance: marginAccount.balance,
|
||||
capitalUtilisation: Math.round(capitalUtilisation.toNumber()),
|
||||
currentLeverage: currentLeverage ? currentLeverage.toNumber() : undefined,
|
||||
marketDecimalPlaces,
|
||||
positionDecimalPlaces,
|
||||
decimals,
|
||||
assetSymbol:
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||
assetId: market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||
totalBalance: totalBalance.multipliedBy(10 ** decimals).toFixed(),
|
||||
lowMarginLevel,
|
||||
lossSocializationAmount: position.lossSocializationAmount || '0',
|
||||
marginAccountBalance: marginAccount?.balance ?? '0',
|
||||
marketDecimalPlaces,
|
||||
marketId: market.id,
|
||||
marketName: market.tradableInstrument.instrument.name,
|
||||
marketTradingMode: market.tradingMode,
|
||||
markPrice: marketData ? marketData.markPrice : undefined,
|
||||
notional: notional
|
||||
? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
|
||||
: undefined,
|
||||
openVolume: position.openVolume,
|
||||
partyId: position.party.id,
|
||||
positionDecimalPlaces,
|
||||
realisedPNL: position.realisedPNL,
|
||||
status: position.positionStatus,
|
||||
totalBalance: totalBalance.multipliedBy(10 ** decimals).toFixed(),
|
||||
unrealisedPNL: position.unrealisedPNL,
|
||||
searchPrice: searchPrice
|
||||
? searchPrice.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
|
||||
: undefined,
|
||||
updatedAt: position.updatedAt || null,
|
||||
lossSocializationAmount: position.lossSocializationAmount || '0',
|
||||
status: position.status,
|
||||
});
|
||||
});
|
||||
return metrics;
|
||||
@ -186,7 +144,8 @@ export const update = (
|
||||
return produce(data || [], (draft) => {
|
||||
deltas.forEach((delta) => {
|
||||
const index = draft.findIndex(
|
||||
(node) => node.market.id === delta.marketId
|
||||
(node) =>
|
||||
node.market.id === delta.marketId && node.party.id === delta.partyId
|
||||
);
|
||||
if (index !== -1) {
|
||||
const currNode = draft[index];
|
||||
@ -208,49 +167,39 @@ export const update = (
|
||||
__typename: 'Market',
|
||||
id: delta.marketId,
|
||||
},
|
||||
party: {
|
||||
id: delta.partyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const positionsDataProvider = makeDataProvider<
|
||||
const getSubscriptionVariables = (
|
||||
variables: PositionsQueryVariables
|
||||
): PositionsSubscriptionSubscriptionVariables[] =>
|
||||
([] as string[]).concat(variables.partyIds).map((partyId) => ({ partyId }));
|
||||
|
||||
const positionsDataProvider = makeDataProvider<
|
||||
PositionsQuery,
|
||||
PositionFieldsFragment[],
|
||||
PositionsSubscriptionSubscription,
|
||||
PositionsSubscriptionSubscription['positions'],
|
||||
PositionsQueryVariables
|
||||
PositionsQueryVariables,
|
||||
PositionsSubscriptionSubscriptionVariables
|
||||
>({
|
||||
query: PositionsDocument,
|
||||
subscriptionQuery: PositionsSubscriptionDocument,
|
||||
update,
|
||||
getData: (responseData: PositionsQuery | null) =>
|
||||
removePaginationWrapper(responseData?.party?.positionsConnection?.edges) ||
|
||||
[],
|
||||
removePaginationWrapper(responseData?.positions?.edges) || [],
|
||||
getDelta: (subscriptionData: PositionsSubscriptionSubscription) =>
|
||||
subscriptionData.positions,
|
||||
getSubscriptionVariables,
|
||||
});
|
||||
|
||||
const upgradeMarginsConnection = (
|
||||
marketId: string,
|
||||
margins: MarginFieldsFragment[] | null
|
||||
) => {
|
||||
if (marketId && margins) {
|
||||
const index =
|
||||
margins.findIndex((node) => node.market.id === marketId) ?? -1;
|
||||
if (index >= 0) {
|
||||
const marginLevel = margins[index];
|
||||
return {
|
||||
maintenanceLevel: marginLevel.maintenanceLevel,
|
||||
searchLevel: marginLevel.searchLevel,
|
||||
initialLevel: marginLevel.initialLevel,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const positionDataProvider = makeDerivedDataProvider<
|
||||
const positionDataProvider = makeDerivedDataProvider<
|
||||
PositionFieldsFragment,
|
||||
never,
|
||||
PositionsQueryVariables & MarketDataQueryVariables
|
||||
@ -258,7 +207,7 @@ export const positionDataProvider = makeDerivedDataProvider<
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
positionsDataProvider(callback, client, {
|
||||
partyId: variables?.partyId || '',
|
||||
partyIds: variables.partyIds,
|
||||
}),
|
||||
],
|
||||
(data, variables) =>
|
||||
@ -278,22 +227,18 @@ export const openVolumeDataProvider = makeDerivedDataProvider<
|
||||
|
||||
export const rejoinPositionData = (
|
||||
positions: PositionFieldsFragment[] | null,
|
||||
marketsData: MarketMaybeWithData[] | null,
|
||||
margins: MarginFieldsFragment[] | null
|
||||
): PositionRejoined[] | null => {
|
||||
if (positions && marketsData && margins) {
|
||||
marketsData: MarketMaybeWithData[] | null
|
||||
):
|
||||
| (Omit<PositionFieldsFragment, 'market'> & {
|
||||
market: MarketMaybeWithData | null;
|
||||
})[]
|
||||
| null => {
|
||||
if (positions && marketsData) {
|
||||
return positions.map((node) => {
|
||||
return {
|
||||
realisedPNL: node.realisedPNL,
|
||||
openVolume: node.openVolume,
|
||||
unrealisedPNL: node.unrealisedPNL,
|
||||
averageEntryPrice: node.averageEntryPrice,
|
||||
updatedAt: node.updatedAt,
|
||||
...node,
|
||||
market:
|
||||
marketsData?.find((market) => market.id === node.market.id) || null,
|
||||
margins: upgradeMarginsConnection(node.market.id, margins),
|
||||
lossSocializationAmount: node.lossSocializationAmount,
|
||||
status: node.positionStatus,
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -307,13 +252,17 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
|
||||
>(
|
||||
[
|
||||
positionsDataProvider,
|
||||
accountsDataProvider,
|
||||
(callback, client, variables) =>
|
||||
accountsDataProvider(callback, client, {
|
||||
partyId: Array.isArray(variables.partyIds)
|
||||
? variables.partyIds[0]
|
||||
: variables.partyIds,
|
||||
}),
|
||||
(callback, client) =>
|
||||
allMarketsWithDataProvider(callback, client, undefined),
|
||||
marginsDataProvider,
|
||||
],
|
||||
([positions, accounts, marketsData, margins], variables) => {
|
||||
const positionsData = rejoinPositionData(positions, marketsData, margins);
|
||||
([positions, accounts, marketsData], variables) => {
|
||||
const positionsData = rejoinPositionData(positions, marketsData);
|
||||
if (!variables) {
|
||||
return [];
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Position } from './positions-data-providers';
|
||||
import { usePositionsData } from './use-positions-data';
|
||||
import { PositionsTable } from './positions-table';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
@ -8,9 +7,10 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
interface PositionsManagerProps {
|
||||
partyId: string;
|
||||
partyIds: string[];
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
noBottomPlaceholder?: boolean;
|
||||
@ -18,14 +18,15 @@ interface PositionsManagerProps {
|
||||
}
|
||||
|
||||
export const PositionsManager = ({
|
||||
partyId,
|
||||
partyIds,
|
||||
onMarketClick,
|
||||
isReadOnly,
|
||||
noBottomPlaceholder,
|
||||
storeKey,
|
||||
}: PositionsManagerProps) => {
|
||||
const { pubKeys, pubKey } = useVegaWallet();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data, error, loading, reload } = usePositionsData(partyId, gridRef);
|
||||
const { data, error, loading, reload } = usePositionsData(partyIds, gridRef);
|
||||
const [dataCount, setDataCount] = useState(data?.length ?? 0);
|
||||
const create = useVegaTransactionStore((store) => store.create);
|
||||
const onClose = ({
|
||||
@ -58,23 +59,19 @@ export const PositionsManager = ({
|
||||
},
|
||||
});
|
||||
|
||||
const setId = useCallback((data: Position, id: string) => {
|
||||
return {
|
||||
...data,
|
||||
marketId: id,
|
||||
};
|
||||
}, []);
|
||||
const bottomPlaceholderProps = useBottomPlaceholder<Position>({
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
setId,
|
||||
disabled: noBottomPlaceholder,
|
||||
});
|
||||
const updateRowCount = useCallback(() => {
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<PositionsTable
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys}
|
||||
rowData={error ? [] : data}
|
||||
ref={gridRef}
|
||||
onMarketClick={onMarketClick}
|
||||
@ -86,6 +83,7 @@ export const PositionsManager = ({
|
||||
onRowDataUpdated={updateRowCount}
|
||||
{...bottomPlaceholderProps}
|
||||
storeKey={storeKey}
|
||||
multipleKeys={partyIds.length > 1}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
|
@ -8,29 +8,27 @@ import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
|
||||
import type { ICellRendererParams } from 'ag-grid-community';
|
||||
|
||||
const singleRow: Position = {
|
||||
marketName: 'ETH/BTC (31 july 2022)',
|
||||
averageEntryPrice: '133',
|
||||
capitalUtilisation: 11,
|
||||
currentLeverage: 1.1,
|
||||
marketDecimalPlaces: 1,
|
||||
positionDecimalPlaces: 0,
|
||||
decimals: 2,
|
||||
totalBalance: '123456',
|
||||
assetSymbol: 'BTC',
|
||||
partyId: 'partyId',
|
||||
assetId: 'asset-id',
|
||||
lowMarginLevel: false,
|
||||
assetSymbol: 'BTC',
|
||||
averageEntryPrice: '133',
|
||||
currentLeverage: 1.1,
|
||||
decimals: 2,
|
||||
lossSocializationAmount: '0',
|
||||
marginAccountBalance: '12345600',
|
||||
marketDecimalPlaces: 1,
|
||||
marketId: 'string',
|
||||
marketName: 'ETH/BTC (31 july 2022)',
|
||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
markPrice: '123',
|
||||
notional: '12300',
|
||||
openVolume: '100',
|
||||
positionDecimalPlaces: 0,
|
||||
realisedPNL: '123',
|
||||
unrealisedPNL: '456',
|
||||
searchPrice: '0',
|
||||
updatedAt: '2022-07-27T15:02:58.400Z',
|
||||
marginAccountBalance: '12345600',
|
||||
status: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
lossSocializationAmount: '0',
|
||||
totalBalance: '123456',
|
||||
unrealisedPNL: '456',
|
||||
updatedAt: '2022-07-27T15:02:58.400Z',
|
||||
};
|
||||
|
||||
const singleRowData = [singleRow];
|
||||
@ -175,6 +173,7 @@ it('displays close button', async () => {
|
||||
render(
|
||||
<PositionsTable
|
||||
rowData={singleRowData}
|
||||
pubKey={singleRowData[0].partyId}
|
||||
onClose={() => {
|
||||
return;
|
||||
}}
|
||||
|
@ -15,69 +15,51 @@ const Template: Story = (args) => (
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
const longPosition: Position = {
|
||||
marketName: 'BTC/USD (31 july 2022)',
|
||||
assetId: 'BTC',
|
||||
assetSymbol: 'BTC',
|
||||
averageEntryPrice: '1134564',
|
||||
capitalUtilisation: 10,
|
||||
currentLeverage: 11,
|
||||
decimals: 2,
|
||||
marketDecimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
// generalAccountBalance: '0',
|
||||
totalBalance: '45353',
|
||||
assetSymbol: 'BTC',
|
||||
// leverageInitial: '0',
|
||||
// leverageMaintenance: '0',
|
||||
// leverageRelease: '0',
|
||||
// leverageSearch: '0',
|
||||
lowMarginLevel: false,
|
||||
lossSocializationAmount: '0',
|
||||
marginAccountBalance: new BigNumber('0').toString(),
|
||||
// marginMaintenance: '0',
|
||||
// marginSearch: '0',
|
||||
// marginInitial: '0',
|
||||
marketDecimalPlaces: 2,
|
||||
marketId: 'marketId1',
|
||||
marketName: 'BTC/USD (31 july 2022)',
|
||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
markPrice: '1131894',
|
||||
notional: '46667989',
|
||||
openVolume: '4123',
|
||||
positionDecimalPlaces: 2,
|
||||
realisedPNL: '45',
|
||||
unrealisedPNL: '45',
|
||||
searchPrice: '1132123',
|
||||
updatedAt: '2022-07-27T15:02:58.400Z',
|
||||
lossSocializationAmount: '0',
|
||||
status: Schema.PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
totalBalance: '45353',
|
||||
unrealisedPNL: '45',
|
||||
updatedAt: '2022-07-27T15:02:58.400Z',
|
||||
partyId: 'partyId',
|
||||
};
|
||||
|
||||
const shortPosition: Position = {
|
||||
marketName: 'ETH/USD (31 august 2022)',
|
||||
assetId: 'ETH',
|
||||
assetSymbol: 'ETH',
|
||||
averageEntryPrice: '23976',
|
||||
capitalUtilisation: 87,
|
||||
currentLeverage: 7,
|
||||
decimals: 2,
|
||||
marketDecimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
// generalAccountBalance: '0',
|
||||
totalBalance: '3856',
|
||||
assetSymbol: 'ETH',
|
||||
// leverageInitial: '0',
|
||||
// leverageMaintenance: '0',
|
||||
// leverageRelease: '0',
|
||||
// leverageSearch: '0',
|
||||
lowMarginLevel: false,
|
||||
lossSocializationAmount: '0',
|
||||
marginAccountBalance: new BigNumber('0').toString(),
|
||||
// marginMaintenance: '0',
|
||||
// marginSearch: '0',
|
||||
// marginInitial: '0',
|
||||
marketDecimalPlaces: 2,
|
||||
marketId: 'marketId2',
|
||||
marketName: 'ETH/USD (31 august 2022)',
|
||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
markPrice: '24123',
|
||||
notional: '836344',
|
||||
openVolume: '-3467',
|
||||
positionDecimalPlaces: 2,
|
||||
realisedPNL: '0',
|
||||
unrealisedPNL: '0',
|
||||
searchPrice: '0',
|
||||
updatedAt: '2022-07-26T14:01:34.800Z',
|
||||
lossSocializationAmount: '0',
|
||||
status: Schema.PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
totalBalance: '3856',
|
||||
unrealisedPNL: '0',
|
||||
updatedAt: '2022-07-26T14:01:34.800Z',
|
||||
partyId: 'partyId',
|
||||
};
|
||||
|
||||
Primary.args = {
|
||||
|
@ -42,6 +42,7 @@ import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionTableActions } from './position-actions-dropdown';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
|
||||
interface Props extends TypedDataAgGrid<Position> {
|
||||
onClose?: (data: Position) => void;
|
||||
@ -49,6 +50,9 @@ interface Props extends TypedDataAgGrid<Position> {
|
||||
style?: CSSProperties;
|
||||
isReadOnly: boolean;
|
||||
storeKey?: string;
|
||||
multipleKeys?: boolean;
|
||||
pubKeys?: VegaWalletContextShape['pubKeys'];
|
||||
pubKey?: VegaWalletContextShape['pubKey'];
|
||||
}
|
||||
|
||||
export interface AmountCellProps {
|
||||
@ -83,7 +87,18 @@ export const AmountCell = ({ valueFormatted }: AmountCellProps) => {
|
||||
AmountCell.displayName = 'AmountCell';
|
||||
|
||||
export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
({ onClose, onMarketClick, ...props }, ref) => {
|
||||
(
|
||||
{
|
||||
onClose,
|
||||
onMarketClick,
|
||||
multipleKeys,
|
||||
isReadOnly,
|
||||
pubKeys,
|
||||
pubKey,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return (
|
||||
<AgGrid
|
||||
@ -107,6 +122,21 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{multipleKeys ? (
|
||||
<AgGridColumn
|
||||
headerName={t('Vega key')}
|
||||
field="partyId"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'partyId'>) =>
|
||||
(data?.partyId &&
|
||||
pubKeys &&
|
||||
pubKeys.find((key) => key.publicKey === data.partyId)?.name) ||
|
||||
data?.partyId
|
||||
}
|
||||
minWidth={190}
|
||||
/>
|
||||
) : null}
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="marketName"
|
||||
@ -267,55 +297,61 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Leverage')}
|
||||
field="currentLeverage"
|
||||
type="rightAligned"
|
||||
filter="agNumberColumnFilter"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
|
||||
value === undefined ? undefined : formatNumber(value.toString(), 1)
|
||||
}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Margin allocated')}
|
||||
field="marginAccountBalance"
|
||||
type="rightAligned"
|
||||
filter="agNumberColumnFilter"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'marginAccountBalance'>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.marginAccountBalance, data.decimals).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'marginAccountBalance'>):
|
||||
| string
|
||||
| undefined => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
{multipleKeys ? null : (
|
||||
<AgGridColumn
|
||||
headerName={t('Leverage')}
|
||||
field="currentLeverage"
|
||||
type="rightAligned"
|
||||
filter="agNumberColumnFilter"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
|
||||
value === undefined
|
||||
? undefined
|
||||
: formatNumber(value.toString(), 1)
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.marginAccountBalance,
|
||||
data.decimals
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
minWidth={100}
|
||||
/>
|
||||
)}
|
||||
{multipleKeys ? null : (
|
||||
<AgGridColumn
|
||||
headerName={t('Margin allocated')}
|
||||
field="marginAccountBalance"
|
||||
type="rightAligned"
|
||||
filter="agNumberColumnFilter"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'marginAccountBalance'>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.marginAccountBalance, data.decimals).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'marginAccountBalance'>):
|
||||
| string
|
||||
| undefined => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.marginAccountBalance,
|
||||
data.decimals
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
)}
|
||||
<AgGridColumn
|
||||
headerName={t('Realised PNL')}
|
||||
field="realisedPNL"
|
||||
@ -385,13 +421,15 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
{onClose && !props.isReadOnly ? (
|
||||
{onClose && !isReadOnly ? (
|
||||
<AgGridColumn
|
||||
{...COL_DEFS.actions}
|
||||
cellRenderer={({ data }: VegaICellRendererParams<Position>) => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data?.openVolume && data?.openVolume !== '0' ? (
|
||||
{data?.openVolume &&
|
||||
data?.openVolume !== '0' &&
|
||||
data.partyId === pubKey ? (
|
||||
<ButtonLink
|
||||
data-testid="close-position"
|
||||
onClick={() => data && onClose(data)}
|
||||
|
@ -12,16 +12,12 @@ export const positionsQuery = (
|
||||
override?: PartialDeep<PositionsQuery>
|
||||
): PositionsQuery => {
|
||||
const defaultResult: PositionsQuery = {
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: 'vega-0', // VEGA PUBLIC KEY
|
||||
positionsConnection: {
|
||||
__typename: 'PositionConnection',
|
||||
edges: positionFields.map((node) => ({
|
||||
__typename: 'PositionEdge',
|
||||
node,
|
||||
})),
|
||||
},
|
||||
positions: {
|
||||
__typename: 'PositionConnection',
|
||||
edges: positionFields.map((node) => ({
|
||||
__typename: 'PositionEdge',
|
||||
node,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
@ -59,6 +55,10 @@ const positionFields: PositionFieldsFragment[] = [
|
||||
id: 'market-0',
|
||||
__typename: 'Market',
|
||||
},
|
||||
party: {
|
||||
id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||
__typename: 'Party',
|
||||
},
|
||||
lossSocializationAmount: '0',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
@ -73,6 +73,10 @@ const positionFields: PositionFieldsFragment[] = [
|
||||
id: 'market-1',
|
||||
__typename: 'Market',
|
||||
},
|
||||
party: {
|
||||
id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||
__typename: 'Party',
|
||||
},
|
||||
lossSocializationAmount: '0',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
@ -87,6 +91,10 @@ const positionFields: PositionFieldsFragment[] = [
|
||||
id: 'market-2',
|
||||
__typename: 'Market',
|
||||
},
|
||||
party: {
|
||||
id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||
__typename: 'Party',
|
||||
},
|
||||
lossSocializationAmount: '0',
|
||||
positionStatus: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
|
@ -14,7 +14,7 @@ export const useOpenVolume = (
|
||||
useDataProvider({
|
||||
dataProvider: openVolumeDataProvider,
|
||||
update,
|
||||
variables: { partyId: partyId || '', marketId },
|
||||
variables: { partyIds: partyId ? [partyId] : [], marketId },
|
||||
skip: !partyId,
|
||||
});
|
||||
return openVolume;
|
||||
|
@ -59,7 +59,7 @@ describe('usePositionData Hook', () => {
|
||||
.mockImplementation((id: string) =>
|
||||
mockData.find((position) => position.marketId === id)
|
||||
);
|
||||
const partyId = 'partyId';
|
||||
const partyIds = ['partyId'];
|
||||
const anUpdatedOne = {
|
||||
marketId: 'market-1',
|
||||
openVolume: '1',
|
||||
@ -75,14 +75,14 @@ describe('usePositionData Hook', () => {
|
||||
};
|
||||
|
||||
it('should return proper data', async () => {
|
||||
const { result } = renderHook(() => usePositionsData(partyId, gridRef), {
|
||||
const { result } = renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
expect(result.current.data?.length ?? 0).toEqual(5);
|
||||
});
|
||||
|
||||
it('should call mockRefreshInfiniteCache', async () => {
|
||||
renderHook(() => usePositionsData(partyId, gridRef), {
|
||||
renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
await waitFor(() => {
|
||||
@ -99,7 +99,7 @@ describe('usePositionData Hook', () => {
|
||||
data: mockData,
|
||||
loading: false,
|
||||
};
|
||||
const { result } = renderHook(() => usePositionsData(partyId, gridRef), {
|
||||
const { result } = renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
expect(result.current.data).toEqual([]);
|
||||
|
@ -9,15 +9,22 @@ import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { GetRowsParams } from '@vegaprotocol/datagrid';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
export const getRowId = ({ data }: { data: Position }) => data.marketId;
|
||||
export const getRowId = ({
|
||||
data,
|
||||
}: {
|
||||
data: Position & { isLastPlaceholder?: boolean; id?: string };
|
||||
}) =>
|
||||
data.isLastPlaceholder && data.id
|
||||
? data.id
|
||||
: `${data.partyId}-${data.marketId}`;
|
||||
|
||||
export const usePositionsData = (
|
||||
partyId: string,
|
||||
partyIds: string[],
|
||||
gridRef: RefObject<AgGridReact>
|
||||
) => {
|
||||
const variables = useMemo<PositionsQueryVariables>(
|
||||
() => ({ partyId }),
|
||||
[partyId]
|
||||
() => ({ partyIds }),
|
||||
[partyIds]
|
||||
);
|
||||
const dataRef = useRef<Position[] | null>(null);
|
||||
const update = useCallback(
|
||||
@ -28,14 +35,16 @@ export const usePositionsData = (
|
||||
|
||||
const update: Position[] = [];
|
||||
const add: Position[] = [];
|
||||
data?.forEach((d) => {
|
||||
const rowNode = gridRef.current?.api?.getRowNode(d.marketId);
|
||||
data?.forEach((row) => {
|
||||
const rowNode = gridRef.current?.api?.getRowNode(
|
||||
getRowId({ data: row })
|
||||
);
|
||||
if (rowNode) {
|
||||
if (!isEqual(rowNode.data, d)) {
|
||||
update.push(d);
|
||||
if (!isEqual(rowNode.data, row)) {
|
||||
update.push(row);
|
||||
}
|
||||
} else {
|
||||
add.push(d);
|
||||
add.push(row);
|
||||
}
|
||||
});
|
||||
gridRef.current?.api?.applyTransaction({
|
||||
|
Loading…
Reference in New Issue
Block a user