vega-frontend-monorepo/libs/positions/src/lib/positions-data-providers.ts
Art c83b5bf93c
chore(#1259): replaced market name with tradable instrument name (#1269)
* chore: replaced market name with tradable instrument name (1259)

* removed lodash import that must have added itself magically

* =aligned cypress tests with code changed

* =aligned console lite cypress mocks to the code changes

* changed cypress market info key details test

* changed cypress market info key details test

* generate markets for console lite
2022-09-07 11:37:39 -07:00

281 lines
7.5 KiB
TypeScript

import { gql } from '@apollo/client';
import produce from 'immer';
import BigNumber from 'bignumber.js';
import sortBy from 'lodash/sortBy';
import type { Accounts_party_accounts } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts';
import { toBigNum } from '@vegaprotocol/react-helpers';
import type { Positions, Positions_party } from './__generated__/Positions';
import {
makeDataProvider,
makeDerivedDataProvider,
} from '@vegaprotocol/react-helpers';
import type {
PositionsSubscription,
PositionsSubscription_positions,
} from './__generated__/PositionsSubscription';
import { AccountType } from '@vegaprotocol/types';
import type { MarketTradingMode } from '@vegaprotocol/types';
export interface Position {
marketName: string;
averageEntryPrice: string;
capitalUtilisation: number;
currentLeverage: number;
decimals: number;
marketDecimalPlaces: number;
positionDecimalPlaces: number;
totalBalance: string;
assetSymbol: string;
liquidationPrice: string;
lowMarginLevel: boolean;
marketId: string;
marketTradingMode: MarketTradingMode;
markPrice: string;
notional: string;
openVolume: string;
realisedPNL: string;
unrealisedPNL: string;
searchPrice: string;
updatedAt: string | null;
}
export interface Data {
party: Positions_party | null;
positions: Position[] | null;
}
const POSITION_FIELDS = gql`
fragment PositionFields on Position {
realisedPNL
openVolume
unrealisedPNL
averageEntryPrice
updatedAt
marginsConnection {
edges {
node {
market {
id
}
maintenanceLevel
searchLevel
initialLevel
collateralReleaseLevel
asset {
symbol
}
}
}
}
market {
id
decimalPlaces
positionDecimalPlaces
tradingMode
tradableInstrument {
instrument {
name
}
}
data {
markPrice
market {
id
}
}
}
}
`;
export const POSITIONS_QUERY = gql`
${POSITION_FIELDS}
query Positions($partyId: ID!) {
party(id: $partyId) {
id
positionsConnection {
edges {
node {
...PositionFields
}
}
}
}
}
`;
export const POSITIONS_SUBSCRIPTION = gql`
${POSITION_FIELDS}
subscription PositionsSubscription($partyId: ID!) {
positions(partyId: $partyId) {
...PositionFields
}
}
`;
export const getMetrics = (
data: Positions_party | null,
accounts: Accounts_party_accounts[] | null
): Position[] => {
if (!data || !data?.positionsConnection.edges) {
return [];
}
const metrics: Position[] = [];
data?.positionsConnection.edges.forEach((position) => {
const market = position.node.market;
const marketData = market.data;
const marginLevel = position.node.marginsConnection.edges?.find(
(margin) => margin.node.market.id === market.id
)?.node;
const marginAccount = accounts?.find((account) => {
return account.market?.id === market.id;
});
if (
!marginAccount ||
!marginLevel ||
!marketData ||
position.node.openVolume === '0'
) {
return;
}
const generalAccount = accounts?.find(
(account) =>
account.asset.id === marginAccount.asset.id &&
account.type === AccountType.ACCOUNT_TYPE_GENERAL
);
const decimals = marginAccount.asset.decimals;
const { positionDecimalPlaces, decimalPlaces: marketDecimalPlaces } =
market;
const openVolume = toBigNum(
position.node.openVolume,
positionDecimalPlaces
);
const marginAccountBalance = toBigNum(marginAccount.balance ?? 0, decimals);
const generalAccountBalance = toBigNum(
generalAccount?.balance ?? 0,
decimals
);
const markPrice = toBigNum(marketData.markPrice, marketDecimalPlaces);
const notional = (
openVolume.isGreaterThan(0) ? openVolume : openVolume.multipliedBy(-1)
).multipliedBy(markPrice);
const totalBalance = marginAccountBalance.plus(generalAccountBalance);
const currentLeverage = totalBalance.isEqualTo(0)
? new BigNumber(0)
: notional.dividedBy(totalBalance);
const capitalUtilisation = totalBalance.isEqualTo(0)
? new BigNumber(0)
: marginAccountBalance.dividedBy(totalBalance).multipliedBy(100);
const marginMaintenance = toBigNum(
marginLevel.maintenanceLevel,
marketDecimalPlaces
);
const marginSearch = toBigNum(marginLevel.searchLevel, marketDecimalPlaces);
const marginInitial = toBigNum(
marginLevel.initialLevel,
marketDecimalPlaces
);
const searchPrice = marginSearch
.minus(marginAccountBalance)
.dividedBy(openVolume)
.plus(markPrice);
const liquidationPrice = BigNumber.maximum(
0,
marginMaintenance
.minus(marginAccountBalance)
.minus(generalAccountBalance)
.dividedBy(openVolume)
.plus(markPrice)
);
const lowMarginLevel =
marginAccountBalance.isLessThan(
marginSearch.plus(marginInitial.minus(marginSearch).dividedBy(2))
) && generalAccountBalance.isLessThan(marginInitial.minus(marginSearch));
metrics.push({
marketName: market.tradableInstrument.instrument.name,
averageEntryPrice: position.node.averageEntryPrice,
capitalUtilisation: Math.round(capitalUtilisation.toNumber()),
currentLeverage: currentLeverage.toNumber(),
marketDecimalPlaces,
positionDecimalPlaces,
decimals,
assetSymbol: marginLevel.asset.symbol,
totalBalance: totalBalance.multipliedBy(10 ** decimals).toFixed(),
lowMarginLevel,
liquidationPrice: liquidationPrice
.multipliedBy(10 ** marketDecimalPlaces)
.toFixed(0),
marketId: position.node.market.id,
marketTradingMode: position.node.market.tradingMode,
markPrice: marketData.markPrice,
notional: notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0),
openVolume: position.node.openVolume,
realisedPNL: position.node.realisedPNL,
unrealisedPNL: position.node.unrealisedPNL,
searchPrice: searchPrice
.multipliedBy(10 ** marketDecimalPlaces)
.toFixed(0),
updatedAt: position.node.updatedAt,
});
});
return metrics;
};
export const update = (
data: Positions_party,
delta: PositionsSubscription_positions | null
) => {
return produce(data, (draft) => {
if (!draft.positionsConnection.edges || !delta) {
return;
}
const index = draft.positionsConnection.edges.findIndex(
(edge) => edge.node.market.id === delta.market.id
);
if (index !== -1) {
draft.positionsConnection.edges[index].node = delta;
} else {
draft.positionsConnection.edges.push({
__typename: 'PositionEdge',
node: delta,
});
}
});
};
export const positionDataProvider = makeDataProvider<
Positions,
Positions_party,
PositionsSubscription,
PositionsSubscription_positions
>({
query: POSITIONS_QUERY,
subscriptionQuery: POSITIONS_SUBSCRIPTION,
update,
getData: (responseData: Positions) => responseData.party,
getDelta: (subscriptionData: PositionsSubscription) =>
subscriptionData.positions,
});
export const positionsMetricsDataProvider = makeDerivedDataProvider<Position[]>(
[positionDataProvider, accountsDataProvider],
([positions, accounts]) => {
return sortBy(
getMetrics(
positions as Positions_party | null,
accounts as Accounts_party_accounts[] | null
),
'updatedAt'
).reverse();
}
);