feat(trading): margin estimate update (#5664)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com> Co-authored-by: daro-maj <119658839+daro-maj@users.noreply.github.com>
This commit is contained in:
parent
8b91592b93
commit
1780f6fa7f
@ -38,7 +38,7 @@ def test_switch_cross_isolated_margin(
|
|||||||
expect(page.get_by_test_id("toast-content")).to_have_text(
|
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||||
"ConfirmedYour transaction has been confirmedView in block explorerUpdate margin modeBTC:DAI_2023Isolated margin mode, leverage: 1.0x")
|
"ConfirmedYour transaction has been confirmedView in block explorerUpdate margin modeBTC:DAI_2023Isolated margin mode, leverage: 1.0x")
|
||||||
expect(page.locator(margin_row).nth(1)
|
expect(page.locator(margin_row).nth(1)
|
||||||
).to_have_text("11,109.99996Isolated1.0x")
|
).to_have_text("22,109.99996Isolated1.0x")
|
||||||
# tbd - tooltip is not visible without this wait
|
# tbd - tooltip is not visible without this wait
|
||||||
page.wait_for_timeout(1000)
|
page.wait_for_timeout(1000)
|
||||||
page.get_by_test_id(tab_positions).get_by_text("Isolated").hover()
|
page.get_by_test_id(tab_positions).get_by_text("Isolated").hover()
|
||||||
|
@ -23,7 +23,6 @@ def continuous_market(vega):
|
|||||||
return setup_continuous_market(vega)
|
return setup_continuous_market(vega)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("marked id issue #5681")
|
|
||||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||||
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
|
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
|
||||||
page.goto(f"/#/markets/{continuous_market}")
|
page.goto(f"/#/markets/{continuous_market}")
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import type { Account } from './accounts-data-provider';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
accounts: Account[] | null;
|
|
||||||
marketId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getMarketAccount = ({ accounts, marketId }: Props) =>
|
|
||||||
accounts?.find((account) => {
|
|
||||||
return (
|
|
||||||
account.market?.id === marketId &&
|
|
||||||
account.type === Schema.AccountType.ACCOUNT_TYPE_MARGIN
|
|
||||||
);
|
|
||||||
}) || null;
|
|
@ -6,8 +6,7 @@ export * from './accounts-manager';
|
|||||||
export * from './breakdown-table';
|
export * from './breakdown-table';
|
||||||
export * from './use-account-balance';
|
export * from './use-account-balance';
|
||||||
export * from './get-settlement-account';
|
export * from './get-settlement-account';
|
||||||
export * from './use-market-account-balance';
|
export * from './use-margin-account-balance';
|
||||||
export * from './__generated__/Margins';
|
export * from './__generated__/Margins';
|
||||||
export { MarginHealthChart } from './margin-health-chart';
|
|
||||||
export * from './margin-data-provider';
|
export * from './margin-data-provider';
|
||||||
export * from './transfer-container';
|
export * from './transfer-container';
|
||||||
|
@ -83,3 +83,25 @@ export const marketMarginDataProvider = makeDerivedDataProvider<
|
|||||||
(margin) => margin.market.id === marketId
|
(margin) => margin.market.id === marketId
|
||||||
) || null
|
) || null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type MarginModeData = Pick<
|
||||||
|
MarginFieldsFragment,
|
||||||
|
'marginMode' | 'marginFactor'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const marginModeDataProvider = makeDerivedDataProvider<
|
||||||
|
MarginModeData,
|
||||||
|
never,
|
||||||
|
MarginsQueryVariables & { marketId: string }
|
||||||
|
>([marketMarginDataProvider], ([data], variables, previousData) =>
|
||||||
|
produce(previousData, (draft) => {
|
||||||
|
if (!data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const newData = {
|
||||||
|
marginMode: (data as MarginFieldsFragment).marginMode,
|
||||||
|
marginFactor: (data as MarginFieldsFragment).marginFactor,
|
||||||
|
};
|
||||||
|
return draft ? Object.assign(draft, newData) : newData;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
@ -1,253 +0,0 @@
|
|||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
import { Tooltip, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { marketMarginDataProvider } from './margin-data-provider';
|
|
||||||
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
|
||||||
import { useT, ns } from './use-t';
|
|
||||||
import { useAccountBalance } from './use-account-balance';
|
|
||||||
import { useMarketAccountBalance } from './use-market-account-balance';
|
|
||||||
import { Trans } from 'react-i18next';
|
|
||||||
|
|
||||||
const MarginHealthChartTooltipRow = ({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
decimals,
|
|
||||||
href,
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
decimals: number;
|
|
||||||
href?: string;
|
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className="float-left clear-left"
|
|
||||||
key="label"
|
|
||||||
data-testid="margin-health-tooltip-label"
|
|
||||||
>
|
|
||||||
{href ? (
|
|
||||||
<ExternalLink href={href} target="_blank">
|
|
||||||
{label}
|
|
||||||
</ExternalLink>
|
|
||||||
) : (
|
|
||||||
label
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="float-right"
|
|
||||||
key="value"
|
|
||||||
data-testid="margin-health-tooltip-value"
|
|
||||||
>
|
|
||||||
{addDecimalsFormatNumber(value, decimals)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const MarginHealthChartTooltip = ({
|
|
||||||
maintenanceLevel,
|
|
||||||
searchLevel,
|
|
||||||
initialLevel,
|
|
||||||
collateralReleaseLevel,
|
|
||||||
decimals,
|
|
||||||
marginAccountBalance,
|
|
||||||
}: {
|
|
||||||
maintenanceLevel: string;
|
|
||||||
searchLevel: string;
|
|
||||||
initialLevel: string;
|
|
||||||
collateralReleaseLevel: string;
|
|
||||||
decimals: number;
|
|
||||||
marginAccountBalance?: string;
|
|
||||||
}) => {
|
|
||||||
const t = useT();
|
|
||||||
const tooltipContent = [
|
|
||||||
<MarginHealthChartTooltipRow
|
|
||||||
key={'maintenance'}
|
|
||||||
label={t('maintenance level')}
|
|
||||||
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-maintenance"
|
|
||||||
value={maintenanceLevel}
|
|
||||||
decimals={decimals}
|
|
||||||
/>,
|
|
||||||
<MarginHealthChartTooltipRow
|
|
||||||
key={'search'}
|
|
||||||
label={t('search level')}
|
|
||||||
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-searching-for-collateral"
|
|
||||||
value={searchLevel}
|
|
||||||
decimals={decimals}
|
|
||||||
/>,
|
|
||||||
<MarginHealthChartTooltipRow
|
|
||||||
key={'initial'}
|
|
||||||
label={t('initial level')}
|
|
||||||
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-initial"
|
|
||||||
value={initialLevel}
|
|
||||||
decimals={decimals}
|
|
||||||
/>,
|
|
||||||
<MarginHealthChartTooltipRow
|
|
||||||
key={'release'}
|
|
||||||
label={t('release level')}
|
|
||||||
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-releasing-collateral"
|
|
||||||
value={collateralReleaseLevel}
|
|
||||||
decimals={decimals}
|
|
||||||
/>,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (marginAccountBalance) {
|
|
||||||
const balance = (
|
|
||||||
<MarginHealthChartTooltipRow
|
|
||||||
key={'balance'}
|
|
||||||
label={t('balance')}
|
|
||||||
value={marginAccountBalance}
|
|
||||||
decimals={decimals}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
if (BigInt(marginAccountBalance) < BigInt(searchLevel)) {
|
|
||||||
tooltipContent.splice(1, 0, balance);
|
|
||||||
} else if (BigInt(marginAccountBalance) < BigInt(initialLevel)) {
|
|
||||||
tooltipContent.splice(2, 0, balance);
|
|
||||||
} else if (BigInt(marginAccountBalance) < BigInt(collateralReleaseLevel)) {
|
|
||||||
tooltipContent.splice(3, 0, balance);
|
|
||||||
} else {
|
|
||||||
tooltipContent.push(balance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="overflow-hidden" data-testid="margin-health-tooltip">
|
|
||||||
{tooltipContent}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MarginHealthChart = ({
|
|
||||||
marketId,
|
|
||||||
assetId,
|
|
||||||
}: {
|
|
||||||
marketId: string;
|
|
||||||
assetId: string;
|
|
||||||
}) => {
|
|
||||||
const { data: assetsMap } = useAssetsMapProvider();
|
|
||||||
const { pubKey: partyId } = useVegaWallet();
|
|
||||||
const { data } = useDataProvider({
|
|
||||||
dataProvider: marketMarginDataProvider,
|
|
||||||
variables: { marketId, partyId: partyId ?? '' },
|
|
||||||
skip: !partyId,
|
|
||||||
});
|
|
||||||
const { accountBalance: rawGeneralAccountBalance } =
|
|
||||||
useAccountBalance(assetId);
|
|
||||||
const { accountBalance: rawMarginAccountBalance } =
|
|
||||||
useMarketAccountBalance(marketId);
|
|
||||||
const asset = assetsMap && assetsMap[assetId];
|
|
||||||
if (!data || !asset) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { decimals } = asset;
|
|
||||||
|
|
||||||
const collateralReleaseLevel = Number(data.collateralReleaseLevel);
|
|
||||||
const initialLevel = Number(data.initialLevel);
|
|
||||||
const maintenanceLevel = Number(data.maintenanceLevel);
|
|
||||||
const searchLevel = Number(data.searchLevel);
|
|
||||||
const marginAccountBalance = Number(rawMarginAccountBalance);
|
|
||||||
const generalAccountBalance = Number(rawGeneralAccountBalance);
|
|
||||||
const max = Math.max(
|
|
||||||
marginAccountBalance + generalAccountBalance,
|
|
||||||
collateralReleaseLevel
|
|
||||||
);
|
|
||||||
|
|
||||||
const red = maintenanceLevel / max;
|
|
||||||
const orange = (searchLevel - maintenanceLevel) / max;
|
|
||||||
const yellow = ((searchLevel + initialLevel) / 2 - searchLevel) / max;
|
|
||||||
const green = (collateralReleaseLevel - initialLevel) / max + yellow;
|
|
||||||
const balanceMarker = marginAccountBalance / max;
|
|
||||||
|
|
||||||
const tooltip = (
|
|
||||||
<MarginHealthChartTooltip
|
|
||||||
maintenanceLevel={data.maintenanceLevel}
|
|
||||||
searchLevel={data.searchLevel}
|
|
||||||
initialLevel={data.initialLevel}
|
|
||||||
collateralReleaseLevel={data.collateralReleaseLevel}
|
|
||||||
marginAccountBalance={rawMarginAccountBalance}
|
|
||||||
decimals={decimals}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-testid="margin-health-chart">
|
|
||||||
<Trans
|
|
||||||
defaults="{{balance}} above <0>maintenance level</0>"
|
|
||||||
components={[
|
|
||||||
<ExternalLink href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-maintenance">
|
|
||||||
maintenance level
|
|
||||||
</ExternalLink>,
|
|
||||||
]}
|
|
||||||
values={{
|
|
||||||
balance: addDecimalsFormatNumber(
|
|
||||||
(
|
|
||||||
BigInt(marginAccountBalance) - BigInt(maintenanceLevel)
|
|
||||||
).toString(),
|
|
||||||
decimals
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
ns={ns}
|
|
||||||
/>
|
|
||||||
<Tooltip description={tooltip}>
|
|
||||||
<div
|
|
||||||
data-testid="margin-health-chart-track"
|
|
||||||
className="relative bg-vega-green-650"
|
|
||||||
style={{
|
|
||||||
height: '6px',
|
|
||||||
marginBottom: '1px',
|
|
||||||
display: 'flex',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="margin-health-chart-red"
|
|
||||||
className="bg-vega-red-550"
|
|
||||||
style={{
|
|
||||||
height: '100%',
|
|
||||||
width: `${red * 100}%`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
data-testid="margin-health-chart-orange"
|
|
||||||
className="bg-vega-orange"
|
|
||||||
style={{
|
|
||||||
height: '100%',
|
|
||||||
width: `${orange * 100}%`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
data-testid="margin-health-chart-yellow"
|
|
||||||
className="bg-vega-yellow"
|
|
||||||
style={{
|
|
||||||
height: '100%',
|
|
||||||
width: `${yellow * 100}%`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
data-testid="margin-health-chart-green"
|
|
||||||
className="bg-vega-green-600"
|
|
||||||
style={{
|
|
||||||
height: '100%',
|
|
||||||
width: `${green * 100}%`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
{balanceMarker > 0 && balanceMarker < 100 && (
|
|
||||||
<div
|
|
||||||
data-testid="margin-health-chart-balance"
|
|
||||||
className="absolute bg-vega-blue"
|
|
||||||
style={{
|
|
||||||
height: '8px',
|
|
||||||
width: '8px',
|
|
||||||
top: '-1px',
|
|
||||||
transform: 'translate(-4px, 0px)',
|
|
||||||
borderRadius: '50%',
|
|
||||||
border: '1px solid white',
|
|
||||||
backgroundColor: 'blue',
|
|
||||||
left: `${balanceMarker * 100}%`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,158 +0,0 @@
|
|||||||
import {
|
|
||||||
MarginHealthChart,
|
|
||||||
MarginHealthChartTooltip,
|
|
||||||
} from './margin-health-chart';
|
|
||||||
import { act, render, screen } from '@testing-library/react';
|
|
||||||
import type { MarginFieldsFragment } from './__generated__/Margins';
|
|
||||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
|
||||||
import { MarginMode } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
const asset: AssetFieldsFragment = {
|
|
||||||
id: 'assetId',
|
|
||||||
decimals: 2,
|
|
||||||
} as AssetFieldsFragment;
|
|
||||||
const margins: MarginFieldsFragment = {
|
|
||||||
asset: {
|
|
||||||
id: 'assetId',
|
|
||||||
},
|
|
||||||
collateralReleaseLevel: '1000',
|
|
||||||
initialLevel: '800',
|
|
||||||
searchLevel: '600',
|
|
||||||
maintenanceLevel: '400',
|
|
||||||
marginFactor: '',
|
|
||||||
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
|
||||||
orderMarginLevel: '',
|
|
||||||
market: {
|
|
||||||
id: 'marketId',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockGetMargins = jest.fn(() => margins);
|
|
||||||
const mockGetBalance = jest.fn(() => '0');
|
|
||||||
|
|
||||||
jest.mock('./margin-data-provider', () => ({}));
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/assets', () => ({
|
|
||||||
useAssetsMapProvider: () => {
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
assetId: asset,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/wallet', () => ({
|
|
||||||
useVegaWallet: () => {
|
|
||||||
return {
|
|
||||||
pubKey: 'partyId',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
|
||||||
useDataProvider: () => {
|
|
||||||
return {
|
|
||||||
data: mockGetMargins(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('./use-account-balance', () => ({
|
|
||||||
useAccountBalance: () => {
|
|
||||||
return {
|
|
||||||
accountBalance: mockGetBalance(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('./use-market-account-balance', () => ({
|
|
||||||
useMarketAccountBalance: () => {
|
|
||||||
return {
|
|
||||||
accountBalance: '700',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('MarginHealthChart', () => {
|
|
||||||
it('should render correct values', async () => {
|
|
||||||
render(<MarginHealthChart marketId="marketId" assetId="assetId" />);
|
|
||||||
const chart = screen.getByTestId('margin-health-chart');
|
|
||||||
expect(chart).toHaveTextContent('3.00 above maintenance level');
|
|
||||||
const red = screen.getByTestId('margin-health-chart-red');
|
|
||||||
const orange = screen.getByTestId('margin-health-chart-orange');
|
|
||||||
const yellow = screen.getByTestId('margin-health-chart-yellow');
|
|
||||||
const green = screen.getByTestId('margin-health-chart-green');
|
|
||||||
const balance = screen.getByTestId('margin-health-chart-balance');
|
|
||||||
expect(parseInt(red.style.width)).toBe(40);
|
|
||||||
expect(parseInt(orange.style.width)).toBe(20);
|
|
||||||
expect(parseInt(yellow.style.width)).toBe(10);
|
|
||||||
expect(parseInt(green.style.width)).toBe(30);
|
|
||||||
expect(parseInt(balance.style.left)).toBe(70);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use correct scale', async () => {
|
|
||||||
mockGetBalance.mockReturnValueOnce('1300');
|
|
||||||
await act(async () => {
|
|
||||||
render(<MarginHealthChart marketId="marketId" assetId="assetId" />);
|
|
||||||
});
|
|
||||||
await screen.findByTestId('margin-health-chart');
|
|
||||||
const red = screen.getByTestId('margin-health-chart-red');
|
|
||||||
expect(parseInt(red.style.width)).toBe(20);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('MarginHealthChartTooltip', () => {
|
|
||||||
it('renders correct values and labels', async () => {
|
|
||||||
await act(async () => {
|
|
||||||
render(
|
|
||||||
<MarginHealthChartTooltip
|
|
||||||
{...margins}
|
|
||||||
decimals={asset.decimals}
|
|
||||||
marginAccountBalance="500"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const labels = await screen.findAllByTestId('margin-health-tooltip-label');
|
|
||||||
const expectedLabels = [
|
|
||||||
'maintenance level',
|
|
||||||
'balance',
|
|
||||||
'search level',
|
|
||||||
'initial level',
|
|
||||||
'release level',
|
|
||||||
];
|
|
||||||
labels.forEach((value, i) => {
|
|
||||||
expect(value).toHaveTextContent(expectedLabels[i]);
|
|
||||||
});
|
|
||||||
const values = await screen.findAllByTestId('margin-health-tooltip-value');
|
|
||||||
const expectedValues = ['4.00', '5.00', '6.00', '8.00', '10.00'];
|
|
||||||
values.forEach((value, i) => {
|
|
||||||
expect(value).toHaveTextContent(expectedValues[i]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders balance in correct place', async () => {
|
|
||||||
const { rerender } = render(
|
|
||||||
<MarginHealthChartTooltip
|
|
||||||
{...margins}
|
|
||||||
decimals={asset.decimals}
|
|
||||||
marginAccountBalance="700"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
let values = await screen.findAllByTestId('margin-health-tooltip-value');
|
|
||||||
expect(values[2]).toHaveTextContent('7.00');
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<MarginHealthChartTooltip
|
|
||||||
{...margins}
|
|
||||||
decimals={asset.decimals}
|
|
||||||
marginAccountBalance="900"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
values = await screen.findAllByTestId('margin-health-tooltip-value');
|
|
||||||
expect(values.length).toBe(5);
|
|
||||||
expect(values[3]).toHaveTextContent('9.00');
|
|
||||||
});
|
|
||||||
});
|
|
68
libs/accounts/src/lib/use-margin-account-balance.tsx
Normal file
68
libs/accounts/src/lib/use-margin-account-balance.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import { accountsDataProvider } from './accounts-data-provider';
|
||||||
|
import type { Account } from './accounts-data-provider';
|
||||||
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export const useMarginAccountBalance = (marketId: string) => {
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
const [marginAccountBalance, setMarginAccountBalance] = useState<string>('');
|
||||||
|
const [orderMarginAccountBalance, setOrderMarginAccountBalance] =
|
||||||
|
useState<string>('');
|
||||||
|
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
|
||||||
|
const update = useCallback(
|
||||||
|
({ data }: { data: Account[] | null }) => {
|
||||||
|
const marginAccount = data?.find((account) => {
|
||||||
|
return (
|
||||||
|
account.market?.id === marketId &&
|
||||||
|
account.type === AccountType.ACCOUNT_TYPE_MARGIN
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const orderMarginAccount = data?.find((account) => {
|
||||||
|
return (
|
||||||
|
account.market?.id === marketId &&
|
||||||
|
account.type === AccountType.ACCOUNT_TYPE_ORDER_MARGIN
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (marginAccount?.balance) {
|
||||||
|
setMarginAccountBalance(marginAccount?.balance || '');
|
||||||
|
}
|
||||||
|
if (orderMarginAccount?.balance) {
|
||||||
|
setOrderMarginAccountBalance(orderMarginAccount?.balance || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const decimals =
|
||||||
|
orderMarginAccount?.asset.decimals || marginAccount?.asset.decimals;
|
||||||
|
if (decimals) {
|
||||||
|
setAccountDecimals(decimals);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[marketId]
|
||||||
|
);
|
||||||
|
const { loading, error } = useDataProvider({
|
||||||
|
dataProvider: accountsDataProvider,
|
||||||
|
variables: { partyId: pubKey || '' },
|
||||||
|
skip: !pubKey || !marketId,
|
||||||
|
update,
|
||||||
|
});
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
marginAccountBalance: pubKey ? marginAccountBalance : '',
|
||||||
|
orderMarginAccountBalance: pubKey ? orderMarginAccountBalance : '',
|
||||||
|
accountDecimals: pubKey ? accountDecimals : null,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
marginAccountBalance,
|
||||||
|
orderMarginAccountBalance,
|
||||||
|
accountDecimals,
|
||||||
|
pubKey,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
@ -1,41 +0,0 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { accountsDataProvider } from './accounts-data-provider';
|
|
||||||
import type { Account } from './accounts-data-provider';
|
|
||||||
import { getMarketAccount } from './get-market-account';
|
|
||||||
|
|
||||||
export const useMarketAccountBalance = (marketId: string) => {
|
|
||||||
const { pubKey } = useVegaWallet();
|
|
||||||
const [accountBalance, setAccountBalance] = useState<string>('');
|
|
||||||
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
|
|
||||||
const update = useCallback(
|
|
||||||
({ data }: { data: Account[] | null }) => {
|
|
||||||
const account = getMarketAccount({ accounts: data, marketId });
|
|
||||||
if (account?.balance) {
|
|
||||||
setAccountBalance(account?.balance || '');
|
|
||||||
}
|
|
||||||
if (account?.asset.decimals) {
|
|
||||||
setAccountDecimals(account?.asset.decimals || null);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
[marketId]
|
|
||||||
);
|
|
||||||
const { loading, error } = useDataProvider({
|
|
||||||
dataProvider: accountsDataProvider,
|
|
||||||
variables: { partyId: pubKey || '' },
|
|
||||||
skip: !pubKey || !marketId,
|
|
||||||
update,
|
|
||||||
});
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() => ({
|
|
||||||
accountBalance: pubKey ? accountBalance : '',
|
|
||||||
accountDecimals: pubKey ? accountDecimals : null,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
}),
|
|
||||||
[accountBalance, accountDecimals, pubKey, loading, error]
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,47 +1,14 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { getAsset } from '@vegaprotocol/markets';
|
||||||
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
|
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
|
||||||
|
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
import { formatNumberPercentage, formatValue } from '@vegaprotocol/utils';
|
||||||
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
|
||||||
|
|
||||||
import {
|
|
||||||
formatNumberPercentage,
|
|
||||||
formatRange,
|
|
||||||
formatValue,
|
|
||||||
} from '@vegaprotocol/utils';
|
|
||||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
|
||||||
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
|
||||||
TOTAL_MARGIN_AVAILABLE,
|
|
||||||
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
|
|
||||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
|
||||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
|
||||||
} from '../../constants';
|
|
||||||
import { useEstimateFees } from '../../hooks/use-estimate-fees';
|
import { useEstimateFees } from '../../hooks/use-estimate-fees';
|
||||||
import { KeyValue } from './key-value';
|
import { KeyValue } from './key-value';
|
||||||
import {
|
import { Intent, Pill } from '@vegaprotocol/ui-toolkit';
|
||||||
Accordion,
|
|
||||||
AccordionChevron,
|
|
||||||
AccordionPanel,
|
|
||||||
Intent,
|
|
||||||
ExternalLink,
|
|
||||||
Pill,
|
|
||||||
Tooltip,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { FeesBreakdown } from '../fees-breakdown';
|
import { FeesBreakdown } from '../fees-breakdown';
|
||||||
import { getTotalDiscountFactor, getDiscountedFee } from '../discounts';
|
import { getTotalDiscountFactor, getDiscountedFee } from '../discounts';
|
||||||
import { useT, ns } from '../../use-t';
|
import { useT } from '../../use-t';
|
||||||
import { Trans } from 'react-i18next';
|
|
||||||
|
|
||||||
export const emptyValue = '-';
|
export const emptyValue = '-';
|
||||||
|
|
||||||
@ -119,337 +86,3 @@ export const DealTicketFeeDetails = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DealTicketMarginDetailsProps {
|
|
||||||
generalAccountBalance?: string;
|
|
||||||
marginAccountBalance?: string;
|
|
||||||
market: Market;
|
|
||||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
|
||||||
assetSymbol: string;
|
|
||||||
positionEstimate: EstimatePositionQuery['estimatePosition'];
|
|
||||||
side: Schema.Side;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DealTicketMarginDetails = ({
|
|
||||||
marginAccountBalance,
|
|
||||||
generalAccountBalance,
|
|
||||||
assetSymbol,
|
|
||||||
market,
|
|
||||||
onMarketClick,
|
|
||||||
positionEstimate,
|
|
||||||
side,
|
|
||||||
}: DealTicketMarginDetailsProps) => {
|
|
||||||
const t = useT();
|
|
||||||
const [breakdownDialog, setBreakdownDialog] = useState(false);
|
|
||||||
const { pubKey: partyId } = useVegaWallet();
|
|
||||||
const { data: currentMargins } = useDataProvider({
|
|
||||||
dataProvider: marketMarginDataProvider,
|
|
||||||
variables: { marketId: market.id, partyId: partyId || '' },
|
|
||||||
skip: !partyId,
|
|
||||||
});
|
|
||||||
const liquidationEstimate = positionEstimate?.liquidation;
|
|
||||||
const marginEstimate = positionEstimate?.margin;
|
|
||||||
const totalBalance =
|
|
||||||
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
|
|
||||||
const asset = getAsset(market);
|
|
||||||
const { decimals: assetDecimals, quantum } = asset;
|
|
||||||
let marginRequiredBestCase: string | undefined = undefined;
|
|
||||||
let marginRequiredWorstCase: string | undefined = undefined;
|
|
||||||
if (marginEstimate) {
|
|
||||||
if (currentMargins) {
|
|
||||||
marginRequiredBestCase = (
|
|
||||||
BigInt(marginEstimate.bestCase.initialLevel) -
|
|
||||||
BigInt(currentMargins.initialLevel)
|
|
||||||
).toString();
|
|
||||||
if (marginRequiredBestCase.startsWith('-')) {
|
|
||||||
marginRequiredBestCase = '0';
|
|
||||||
}
|
|
||||||
marginRequiredWorstCase = (
|
|
||||||
BigInt(marginEstimate.worstCase.initialLevel) -
|
|
||||||
BigInt(currentMargins.initialLevel)
|
|
||||||
).toString();
|
|
||||||
if (marginRequiredWorstCase.startsWith('-')) {
|
|
||||||
marginRequiredWorstCase = '0';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
marginRequiredBestCase = marginEstimate.bestCase.initialLevel;
|
|
||||||
marginRequiredWorstCase = marginEstimate.worstCase.initialLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalMarginAvailable = (
|
|
||||||
currentMargins
|
|
||||||
? totalBalance - BigInt(currentMargins.maintenanceLevel)
|
|
||||||
: totalBalance
|
|
||||||
).toString();
|
|
||||||
|
|
||||||
let deductionFromCollateral = null;
|
|
||||||
let projectedMargin = null;
|
|
||||||
if (marginAccountBalance) {
|
|
||||||
const deductionFromCollateralBestCase =
|
|
||||||
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) -
|
|
||||||
BigInt(marginAccountBalance);
|
|
||||||
|
|
||||||
const deductionFromCollateralWorstCase =
|
|
||||||
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) -
|
|
||||||
BigInt(marginAccountBalance);
|
|
||||||
|
|
||||||
deductionFromCollateral = (
|
|
||||||
<KeyValue
|
|
||||||
indent
|
|
||||||
label={t('Deduction from collateral')}
|
|
||||||
value={formatRange(
|
|
||||||
deductionFromCollateralBestCase > 0
|
|
||||||
? deductionFromCollateralBestCase.toString()
|
|
||||||
: '0',
|
|
||||||
deductionFromCollateralWorstCase > 0
|
|
||||||
? deductionFromCollateralWorstCase.toString()
|
|
||||||
: '0',
|
|
||||||
assetDecimals
|
|
||||||
)}
|
|
||||||
formattedValue={formatValue(
|
|
||||||
deductionFromCollateralWorstCase > 0
|
|
||||||
? deductionFromCollateralWorstCase.toString()
|
|
||||||
: '0',
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
)}
|
|
||||||
symbol={assetSymbol}
|
|
||||||
labelDescription={t(
|
|
||||||
'DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT',
|
|
||||||
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
|
||||||
{ assetSymbol }
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
projectedMargin = (
|
|
||||||
<KeyValue
|
|
||||||
label={t('Projected margin')}
|
|
||||||
value={formatRange(
|
|
||||||
marginEstimate?.bestCase.initialLevel,
|
|
||||||
marginEstimate?.worstCase.initialLevel,
|
|
||||||
assetDecimals
|
|
||||||
)}
|
|
||||||
formattedValue={formatValue(
|
|
||||||
marginEstimate?.worstCase.initialLevel,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
)}
|
|
||||||
symbol={assetSymbol}
|
|
||||||
labelDescription={t(
|
|
||||||
'EST_TOTAL_MARGIN_TOOLTIP_TEXT',
|
|
||||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let liquidationPriceEstimate = emptyValue;
|
|
||||||
let liquidationPriceEstimateRange = emptyValue;
|
|
||||||
|
|
||||||
if (liquidationEstimate) {
|
|
||||||
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
|
|
||||||
liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '')
|
|
||||||
);
|
|
||||||
const liquidationEstimateBestCaseIncludingSellOrders = BigInt(
|
|
||||||
liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '')
|
|
||||||
);
|
|
||||||
const liquidationEstimateBestCase =
|
|
||||||
side === Schema.Side.SIDE_BUY
|
|
||||||
? liquidationEstimateBestCaseIncludingBuyOrders
|
|
||||||
: liquidationEstimateBestCaseIncludingSellOrders;
|
|
||||||
|
|
||||||
const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt(
|
|
||||||
liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '')
|
|
||||||
);
|
|
||||||
const liquidationEstimateWorstCaseIncludingSellOrders = BigInt(
|
|
||||||
liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '')
|
|
||||||
);
|
|
||||||
const liquidationEstimateWorstCase =
|
|
||||||
side === Schema.Side.SIDE_BUY
|
|
||||||
? liquidationEstimateWorstCaseIncludingBuyOrders
|
|
||||||
: liquidationEstimateWorstCaseIncludingSellOrders;
|
|
||||||
|
|
||||||
liquidationPriceEstimate = formatValue(
|
|
||||||
liquidationEstimateWorstCase.toString(),
|
|
||||||
market.decimalPlaces,
|
|
||||||
undefined,
|
|
||||||
market.decimalPlaces
|
|
||||||
);
|
|
||||||
liquidationPriceEstimateRange = formatRange(
|
|
||||||
(liquidationEstimateBestCase < liquidationEstimateWorstCase
|
|
||||||
? liquidationEstimateBestCase
|
|
||||||
: liquidationEstimateWorstCase
|
|
||||||
).toString(),
|
|
||||||
(liquidationEstimateBestCase > liquidationEstimateWorstCase
|
|
||||||
? liquidationEstimateBestCase
|
|
||||||
: liquidationEstimateWorstCase
|
|
||||||
).toString(),
|
|
||||||
market.decimalPlaces,
|
|
||||||
undefined,
|
|
||||||
market.decimalPlaces
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onAccountBreakdownDialogClose = useCallback(
|
|
||||||
() => setBreakdownDialog(false),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const quoteName = getQuoteName(market);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col w-full gap-2 pt-2">
|
|
||||||
<Accordion>
|
|
||||||
<AccordionPanel
|
|
||||||
itemId="margin"
|
|
||||||
trigger={
|
|
||||||
<AccordionPrimitive.Trigger
|
|
||||||
data-testid="accordion-toggle"
|
|
||||||
className={classNames(
|
|
||||||
'w-full',
|
|
||||||
'flex items-center gap-2 text-xs',
|
|
||||||
'group'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid={`deal-ticket-fee-margin-required`}
|
|
||||||
key={'value-dropdown'}
|
|
||||||
className="flex items-center justify-between w-full gap-2"
|
|
||||||
>
|
|
||||||
<div className="flex items-center text-left gap-1">
|
|
||||||
<Tooltip
|
|
||||||
description={t(
|
|
||||||
'MARGIN_DIFF_TOOLTIP_TEXT',
|
|
||||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
|
||||||
{ assetSymbol }
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="text-muted">{t('Margin required')}</span>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<AccordionChevron size={10} />
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
description={
|
|
||||||
formatRange(
|
|
||||||
marginRequiredBestCase,
|
|
||||||
marginRequiredWorstCase,
|
|
||||||
assetDecimals
|
|
||||||
) ?? '-'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="font-mono text-right">
|
|
||||||
{formatValue(
|
|
||||||
marginRequiredWorstCase,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
)}{' '}
|
|
||||||
{assetSymbol || ''}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</AccordionPrimitive.Trigger>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col w-full gap-2">
|
|
||||||
<KeyValue
|
|
||||||
label={t('Total margin available')}
|
|
||||||
indent
|
|
||||||
value={formatValue(totalMarginAvailable, assetDecimals)}
|
|
||||||
formattedValue={formatValue(
|
|
||||||
totalMarginAvailable,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
)}
|
|
||||||
symbol={assetSymbol}
|
|
||||||
labelDescription={t(
|
|
||||||
'TOTAL_MARGIN_AVAILABLE',
|
|
||||||
TOTAL_MARGIN_AVAILABLE,
|
|
||||||
{
|
|
||||||
generalAccountBalance: formatValue(
|
|
||||||
generalAccountBalance,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
),
|
|
||||||
marginAccountBalance: formatValue(
|
|
||||||
marginAccountBalance,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
),
|
|
||||||
marginMaintenance: formatValue(
|
|
||||||
currentMargins?.maintenanceLevel,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
),
|
|
||||||
assetSymbol,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{deductionFromCollateral}
|
|
||||||
<KeyValue
|
|
||||||
label={t('Current margin allocation')}
|
|
||||||
indent
|
|
||||||
onClick={
|
|
||||||
generalAccountBalance
|
|
||||||
? () => setBreakdownDialog(true)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
value={formatValue(marginAccountBalance, assetDecimals)}
|
|
||||||
symbol={assetSymbol}
|
|
||||||
labelDescription={t(
|
|
||||||
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
|
|
||||||
MARGIN_ACCOUNT_TOOLTIP_TEXT
|
|
||||||
)}
|
|
||||||
formattedValue={formatValue(
|
|
||||||
marginAccountBalance,
|
|
||||||
assetDecimals,
|
|
||||||
quantum
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionPanel>
|
|
||||||
</Accordion>
|
|
||||||
{projectedMargin}
|
|
||||||
<KeyValue
|
|
||||||
label={t('Liquidation')}
|
|
||||||
value={liquidationPriceEstimateRange}
|
|
||||||
formattedValue={liquidationPriceEstimate}
|
|
||||||
symbol={quoteName}
|
|
||||||
labelDescription={
|
|
||||||
<>
|
|
||||||
<span>
|
|
||||||
{t(
|
|
||||||
'LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT',
|
|
||||||
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT
|
|
||||||
)}
|
|
||||||
</span>{' '}
|
|
||||||
<span>
|
|
||||||
<Trans
|
|
||||||
defaults="For full details please see <0>liquidation price estimate documentation</0>."
|
|
||||||
components={[
|
|
||||||
<ExternalLink
|
|
||||||
href={
|
|
||||||
'https://github.com/vegaprotocol/specs/blob/master/non-protocol-specs/0012-NP-LIPE-liquidation-price-estimate.md'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
liquidation price estimate documentation
|
|
||||||
</ExternalLink>,
|
|
||||||
]}
|
|
||||||
ns={ns}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{partyId && (
|
|
||||||
<AccountBreakdownDialog
|
|
||||||
assetId={breakdownDialog ? asset.id : undefined}
|
|
||||||
partyId={partyId}
|
|
||||||
onMarketClick={onMarketClick}
|
|
||||||
onClose={onAccountBreakdownDialogClose}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -26,12 +26,25 @@ import {
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useT, ns } from '../../use-t';
|
import { useT, ns } from '../../use-t';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
import type { DealTicketMarginDetailsProps } from './deal-ticket-fee-details';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { emptyValue } from './deal-ticket-fee-details';
|
import { emptyValue } from './deal-ticket-fee-details';
|
||||||
|
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
||||||
|
|
||||||
|
export interface DealTicketMarginDetailsProps {
|
||||||
|
generalAccountBalance?: string;
|
||||||
|
marginAccountBalance?: string;
|
||||||
|
orderMarginAccountBalance?: string;
|
||||||
|
market: Market;
|
||||||
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
assetSymbol: string;
|
||||||
|
positionEstimate: EstimatePositionQuery['estimatePosition'];
|
||||||
|
side: Schema.Side;
|
||||||
|
}
|
||||||
|
|
||||||
export const DealTicketMarginDetails = ({
|
export const DealTicketMarginDetails = ({
|
||||||
marginAccountBalance,
|
marginAccountBalance,
|
||||||
generalAccountBalance,
|
generalAccountBalance,
|
||||||
|
orderMarginAccountBalance,
|
||||||
assetSymbol,
|
assetSymbol,
|
||||||
market,
|
market,
|
||||||
onMarketClick,
|
onMarketClick,
|
||||||
@ -48,31 +61,44 @@ export const DealTicketMarginDetails = ({
|
|||||||
});
|
});
|
||||||
const liquidationEstimate = positionEstimate?.liquidation;
|
const liquidationEstimate = positionEstimate?.liquidation;
|
||||||
const marginEstimate = positionEstimate?.margin;
|
const marginEstimate = positionEstimate?.margin;
|
||||||
|
const totalMarginAccountBalance =
|
||||||
|
BigInt(marginAccountBalance || '0') +
|
||||||
|
BigInt(orderMarginAccountBalance || '0');
|
||||||
const totalBalance =
|
const totalBalance =
|
||||||
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
|
BigInt(generalAccountBalance || '0') + totalMarginAccountBalance;
|
||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
const { decimals: assetDecimals, quantum } = asset;
|
const { decimals: assetDecimals, quantum } = asset;
|
||||||
let marginRequiredBestCase: string | undefined = undefined;
|
let marginRequiredBestCase: string | undefined = undefined;
|
||||||
let marginRequiredWorstCase: string | undefined = undefined;
|
let marginRequiredWorstCase: string | undefined = undefined;
|
||||||
|
const marginEstimateBestCase =
|
||||||
|
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) +
|
||||||
|
BigInt(marginEstimate?.bestCase.orderMarginLevel ?? 0);
|
||||||
|
const marginEstimateWorstCase =
|
||||||
|
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) +
|
||||||
|
BigInt(marginEstimate?.worstCase.orderMarginLevel ?? 0);
|
||||||
if (marginEstimate) {
|
if (marginEstimate) {
|
||||||
if (currentMargins) {
|
if (currentMargins) {
|
||||||
|
const currentMargin =
|
||||||
|
BigInt(currentMargins.initialLevel) +
|
||||||
|
BigInt(currentMargins.orderMarginLevel);
|
||||||
|
|
||||||
marginRequiredBestCase = (
|
marginRequiredBestCase = (
|
||||||
BigInt(marginEstimate.bestCase.initialLevel) -
|
marginEstimateBestCase - currentMargin
|
||||||
BigInt(currentMargins.initialLevel)
|
|
||||||
).toString();
|
).toString();
|
||||||
if (marginRequiredBestCase.startsWith('-')) {
|
if (marginRequiredBestCase.startsWith('-')) {
|
||||||
marginRequiredBestCase = '0';
|
marginRequiredBestCase = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
marginRequiredWorstCase = (
|
marginRequiredWorstCase = (
|
||||||
BigInt(marginEstimate.worstCase.initialLevel) -
|
marginEstimateWorstCase - currentMargin
|
||||||
BigInt(currentMargins.initialLevel)
|
|
||||||
).toString();
|
).toString();
|
||||||
|
|
||||||
if (marginRequiredWorstCase.startsWith('-')) {
|
if (marginRequiredWorstCase.startsWith('-')) {
|
||||||
marginRequiredWorstCase = '0';
|
marginRequiredWorstCase = '0';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
marginRequiredBestCase = marginEstimate.bestCase.initialLevel;
|
marginRequiredBestCase = marginEstimateBestCase.toString();
|
||||||
marginRequiredWorstCase = marginEstimate.worstCase.initialLevel;
|
marginRequiredWorstCase = marginEstimateWorstCase.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,14 +110,12 @@ export const DealTicketMarginDetails = ({
|
|||||||
|
|
||||||
let deductionFromCollateral = null;
|
let deductionFromCollateral = null;
|
||||||
let projectedMargin = null;
|
let projectedMargin = null;
|
||||||
if (marginAccountBalance) {
|
if (totalMarginAccountBalance) {
|
||||||
const deductionFromCollateralBestCase =
|
const deductionFromCollateralBestCase =
|
||||||
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) -
|
marginEstimateBestCase - totalMarginAccountBalance;
|
||||||
BigInt(marginAccountBalance);
|
|
||||||
|
|
||||||
const deductionFromCollateralWorstCase =
|
const deductionFromCollateralWorstCase =
|
||||||
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) -
|
marginEstimateWorstCase - totalMarginAccountBalance;
|
||||||
BigInt(marginAccountBalance);
|
|
||||||
|
|
||||||
deductionFromCollateral = (
|
deductionFromCollateral = (
|
||||||
<KeyValue
|
<KeyValue
|
||||||
@ -125,12 +149,12 @@ export const DealTicketMarginDetails = ({
|
|||||||
<KeyValue
|
<KeyValue
|
||||||
label={t('Projected margin')}
|
label={t('Projected margin')}
|
||||||
value={formatRange(
|
value={formatRange(
|
||||||
marginEstimate?.bestCase.initialLevel,
|
marginEstimateBestCase.toString(),
|
||||||
marginEstimate?.worstCase.initialLevel,
|
marginEstimateWorstCase.toString(),
|
||||||
assetDecimals
|
assetDecimals
|
||||||
)}
|
)}
|
||||||
formattedValue={formatValue(
|
formattedValue={formatValue(
|
||||||
marginEstimate?.worstCase.initialLevel,
|
marginEstimateWorstCase.toString(),
|
||||||
assetDecimals,
|
assetDecimals,
|
||||||
quantum
|
quantum
|
||||||
)}
|
)}
|
||||||
@ -276,6 +300,11 @@ export const DealTicketMarginDetails = ({
|
|||||||
assetDecimals,
|
assetDecimals,
|
||||||
quantum
|
quantum
|
||||||
),
|
),
|
||||||
|
orderMarginAccountBalance: formatValue(
|
||||||
|
orderMarginAccountBalance,
|
||||||
|
assetDecimals,
|
||||||
|
quantum
|
||||||
|
),
|
||||||
marginMaintenance: formatValue(
|
marginMaintenance: formatValue(
|
||||||
currentMargins?.maintenanceLevel,
|
currentMargins?.maintenanceLevel,
|
||||||
assetDecimals,
|
assetDecimals,
|
||||||
@ -294,14 +323,17 @@ export const DealTicketMarginDetails = ({
|
|||||||
? () => setBreakdownDialog(true)
|
? () => setBreakdownDialog(true)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
value={formatValue(marginAccountBalance, assetDecimals)}
|
value={formatValue(
|
||||||
|
totalMarginAccountBalance.toString(),
|
||||||
|
assetDecimals
|
||||||
|
)}
|
||||||
symbol={assetSymbol}
|
symbol={assetSymbol}
|
||||||
labelDescription={t(
|
labelDescription={t(
|
||||||
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
|
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
|
||||||
MARGIN_ACCOUNT_TOOLTIP_TEXT
|
MARGIN_ACCOUNT_TOOLTIP_TEXT
|
||||||
)}
|
)}
|
||||||
formattedValue={formatValue(
|
formattedValue={formatValue(
|
||||||
marginAccountBalance,
|
totalMarginAccountBalance.toString(),
|
||||||
assetDecimals,
|
assetDecimals,
|
||||||
quantum
|
quantum
|
||||||
)}
|
)}
|
||||||
|
@ -58,8 +58,9 @@ import type {
|
|||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
|
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
|
||||||
import {
|
import {
|
||||||
useMarketAccountBalance,
|
useMarginAccountBalance,
|
||||||
useAccountBalance,
|
useAccountBalance,
|
||||||
|
marginModeDataProvider,
|
||||||
} from '@vegaprotocol/accounts';
|
} from '@vegaprotocol/accounts';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { type OrderFormValues } from '../../hooks';
|
import { type OrderFormValues } from '../../hooks';
|
||||||
@ -166,9 +167,10 @@ export const DealTicket = ({
|
|||||||
|
|
||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
const {
|
const {
|
||||||
accountBalance: marginAccountBalance,
|
orderMarginAccountBalance,
|
||||||
|
marginAccountBalance,
|
||||||
loading: loadingMarginAccountBalance,
|
loading: loadingMarginAccountBalance,
|
||||||
} = useMarketAccountBalance(market.id);
|
} = useMarginAccountBalance(market.id);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
accountBalance: generalAccountBalance,
|
accountBalance: generalAccountBalance,
|
||||||
@ -176,7 +178,9 @@ export const DealTicket = ({
|
|||||||
} = useAccountBalance(asset.id);
|
} = useAccountBalance(asset.id);
|
||||||
|
|
||||||
const balance = (
|
const balance = (
|
||||||
BigInt(marginAccountBalance) + BigInt(generalAccountBalance)
|
BigInt(marginAccountBalance) +
|
||||||
|
BigInt(generalAccountBalance) +
|
||||||
|
BigInt(orderMarginAccountBalance)
|
||||||
).toString();
|
).toString();
|
||||||
|
|
||||||
const { marketState, marketTradingMode } = marketData;
|
const { marketState, marketTradingMode } = marketData;
|
||||||
@ -241,7 +245,19 @@ export const DealTicket = ({
|
|||||||
variables: { partyId: pubKey || '', marketId: market.id },
|
variables: { partyId: pubKey || '', marketId: market.id },
|
||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
const { data: margin } = useDataProvider({
|
||||||
|
dataProvider: marginModeDataProvider,
|
||||||
|
variables: { partyId: pubKey || '', marketId: market.id },
|
||||||
|
skip: !pubKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { openVolume, averageEntryPrice } = useOpenVolume(
|
||||||
|
pubKey,
|
||||||
|
market.id
|
||||||
|
) || {
|
||||||
|
openVolume: '0',
|
||||||
|
averageEntryPrice: '0',
|
||||||
|
};
|
||||||
const orders = activeOrders
|
const orders = activeOrders
|
||||||
? activeOrders.map<Schema.OrderInfo>((order) => ({
|
? activeOrders.map<Schema.OrderInfo>((order) => ({
|
||||||
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
|
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
|
||||||
@ -259,21 +275,25 @@ export const DealTicket = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionEstimate = usePositionEstimate({
|
const positionEstimate = usePositionEstimate(
|
||||||
marketId: market.id,
|
{
|
||||||
openVolume,
|
marketId: market.id,
|
||||||
orders,
|
openVolume,
|
||||||
marginAccountBalance: marginAccountBalance,
|
averageEntryPrice,
|
||||||
generalAccountBalance: generalAccountBalance,
|
orders,
|
||||||
orderMarginAccountBalance: '0', // TODO: Get real balance
|
marginAccountBalance: marginAccountBalance || '0',
|
||||||
marginMode: Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN, // TODO: unhardcode this and get users margin mode for the market
|
generalAccountBalance: generalAccountBalance || '0',
|
||||||
averageEntryPrice: marketPrice || '0', // TODO: This assumes the order will be entirely filled at the current market price
|
orderMarginAccountBalance: orderMarginAccountBalance || '0',
|
||||||
skip:
|
marginFactor: margin?.marginFactor || '1',
|
||||||
!normalizedOrder ||
|
marginMode:
|
||||||
|
margin?.marginMode || Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
|
includeCollateralIncreaseInAvailableCollateral: true,
|
||||||
|
},
|
||||||
|
!normalizedOrder ||
|
||||||
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
|
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
|
||||||
(!normalizedOrder.price || normalizedOrder.price === '0')) ||
|
(!normalizedOrder.price || normalizedOrder.price === '0')) ||
|
||||||
normalizedOrder.size === '0',
|
normalizedOrder.size === '0'
|
||||||
});
|
);
|
||||||
|
|
||||||
const assetSymbol = getAsset(market).symbol;
|
const assetSymbol = getAsset(market).symbol;
|
||||||
|
|
||||||
@ -319,7 +339,9 @@ export const DealTicket = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasNoBalance =
|
const hasNoBalance =
|
||||||
!BigInt(generalAccountBalance) && !BigInt(marginAccountBalance);
|
!BigInt(generalAccountBalance) &&
|
||||||
|
!BigInt(marginAccountBalance) &&
|
||||||
|
!BigInt(orderMarginAccountBalance);
|
||||||
if (
|
if (
|
||||||
hasNoBalance &&
|
hasNoBalance &&
|
||||||
!(loadingMarginAccountBalance || loadingGeneralAccountBalance)
|
!(loadingMarginAccountBalance || loadingGeneralAccountBalance)
|
||||||
@ -349,6 +371,7 @@ export const DealTicket = ({
|
|||||||
marketTradingMode,
|
marketTradingMode,
|
||||||
generalAccountBalance,
|
generalAccountBalance,
|
||||||
marginAccountBalance,
|
marginAccountBalance,
|
||||||
|
orderMarginAccountBalance,
|
||||||
loadingMarginAccountBalance,
|
loadingMarginAccountBalance,
|
||||||
loadingGeneralAccountBalance,
|
loadingGeneralAccountBalance,
|
||||||
pubKey,
|
pubKey,
|
||||||
@ -707,10 +730,16 @@ export const DealTicket = ({
|
|||||||
asset={asset}
|
asset={asset}
|
||||||
marketTradingMode={marketData.marketTradingMode}
|
marketTradingMode={marketData.marketTradingMode}
|
||||||
balance={balance}
|
balance={balance}
|
||||||
margin={
|
margin={(
|
||||||
positionEstimate?.estimatePosition?.margin.bestCase.initialLevel ||
|
BigInt(
|
||||||
'0'
|
positionEstimate?.estimatePosition?.margin.bestCase.initialLevel ||
|
||||||
}
|
'0'
|
||||||
|
) +
|
||||||
|
BigInt(
|
||||||
|
positionEstimate?.estimatePosition?.margin.bestCase
|
||||||
|
.orderMarginLevel || '0'
|
||||||
|
)
|
||||||
|
).toString()}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
pubKey={pubKey}
|
pubKey={pubKey}
|
||||||
onDeposit={onDeposit}
|
onDeposit={onDeposit}
|
||||||
@ -743,6 +772,7 @@ export const DealTicket = ({
|
|||||||
onMarketClick={onMarketClick}
|
onMarketClick={onMarketClick}
|
||||||
assetSymbol={asset.symbol}
|
assetSymbol={asset.symbol}
|
||||||
marginAccountBalance={marginAccountBalance}
|
marginAccountBalance={marginAccountBalance}
|
||||||
|
orderMarginAccountBalance={orderMarginAccountBalance}
|
||||||
generalAccountBalance={generalAccountBalance}
|
generalAccountBalance={generalAccountBalance}
|
||||||
positionEstimate={positionEstimate?.estimatePosition}
|
positionEstimate={positionEstimate?.estimatePosition}
|
||||||
market={market}
|
market={market}
|
||||||
@ -768,8 +798,20 @@ interface SummaryMessageProps {
|
|||||||
|
|
||||||
export const NoWalletWarning = ({
|
export const NoWalletWarning = ({
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
}: Pick<SummaryMessageProps, 'isReadOnly'>) => {
|
noWalletConnected,
|
||||||
|
}: Pick<SummaryMessageProps, 'isReadOnly'> & {
|
||||||
|
noWalletConnected?: boolean;
|
||||||
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
|
if (noWalletConnected) {
|
||||||
|
return (
|
||||||
|
<div className="mb-2">
|
||||||
|
<InputError testId="deal-ticket-error-message-summary">
|
||||||
|
{t('You need a Vega wallet to start trading on this market')}
|
||||||
|
</InputError>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import {
|
import {
|
||||||
TradingButton as Button,
|
TradingButton as Button,
|
||||||
TradingInput as Input,
|
TradingInput as Input,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
LeverageSlider,
|
LeverageSlider,
|
||||||
|
Notification,
|
||||||
|
Intent,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { MarginMode, useVegaWallet } from '@vegaprotocol/wallet';
|
import { MarginMode, useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import * as Types from '@vegaprotocol/types';
|
import * as Types from '@vegaprotocol/types';
|
||||||
@ -15,15 +18,151 @@ import { Dialog } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useT } from '../../use-t';
|
import { useT } from '../../use-t';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
import {
|
||||||
import { useMaxLeverage } from '@vegaprotocol/positions';
|
marginModeDataProvider,
|
||||||
|
useAccountBalance,
|
||||||
|
useMarginAccountBalance,
|
||||||
|
} from '@vegaprotocol/accounts';
|
||||||
|
import { useMaxLeverage, useOpenVolume } from '@vegaprotocol/positions';
|
||||||
|
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||||
|
import { usePositionEstimate } from '../../hooks/use-position-estimate';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
import { getAsset, useMarket } from '@vegaprotocol/markets';
|
||||||
|
import { NoWalletWarning } from './deal-ticket';
|
||||||
|
|
||||||
const defaultLeverage = 10;
|
const defaultLeverage = 10;
|
||||||
|
|
||||||
|
export const MarginChange = ({
|
||||||
|
partyId,
|
||||||
|
marketId,
|
||||||
|
marginMode,
|
||||||
|
marginFactor,
|
||||||
|
}: {
|
||||||
|
partyId: string | null;
|
||||||
|
marketId: string;
|
||||||
|
marginMode: Types.MarginMode;
|
||||||
|
marginFactor: string;
|
||||||
|
}) => {
|
||||||
|
const t = useT();
|
||||||
|
const { data: market } = useMarket(marketId);
|
||||||
|
const asset = market && getAsset(market);
|
||||||
|
const {
|
||||||
|
marginAccountBalance,
|
||||||
|
orderMarginAccountBalance,
|
||||||
|
loading: marginAccountBalanceLoading,
|
||||||
|
} = useMarginAccountBalance(marketId);
|
||||||
|
const {
|
||||||
|
accountBalance: generalAccountBalance,
|
||||||
|
loading: generalAccountBalanceLoading,
|
||||||
|
} = useAccountBalance(asset?.id);
|
||||||
|
const { openVolume, averageEntryPrice } = useOpenVolume(
|
||||||
|
partyId,
|
||||||
|
marketId
|
||||||
|
) || {
|
||||||
|
openVolume: '0',
|
||||||
|
averageEntryPrice: '0',
|
||||||
|
};
|
||||||
|
const { data: activeOrders } = useDataProvider({
|
||||||
|
dataProvider: activeOrdersProvider,
|
||||||
|
variables: { partyId: partyId || '', marketId },
|
||||||
|
});
|
||||||
|
const orders = activeOrders
|
||||||
|
? activeOrders.map<Schema.OrderInfo>((order) => ({
|
||||||
|
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
|
||||||
|
price: order.price,
|
||||||
|
remaining: order.remaining,
|
||||||
|
side: order.side,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
const skip =
|
||||||
|
(!orders?.length && openVolume === '0') ||
|
||||||
|
marginAccountBalanceLoading ||
|
||||||
|
generalAccountBalanceLoading;
|
||||||
|
const estimateMargin = usePositionEstimate(
|
||||||
|
{
|
||||||
|
generalAccountBalance: generalAccountBalance || '0',
|
||||||
|
marginAccountBalance: marginAccountBalance || '0',
|
||||||
|
marginFactor,
|
||||||
|
marginMode,
|
||||||
|
averageEntryPrice,
|
||||||
|
openVolume,
|
||||||
|
marketId,
|
||||||
|
orderMarginAccountBalance: orderMarginAccountBalance || '0',
|
||||||
|
includeCollateralIncreaseInAvailableCollateral: true,
|
||||||
|
orders,
|
||||||
|
},
|
||||||
|
skip
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!asset ||
|
||||||
|
!estimateMargin?.estimatePosition?.collateralIncreaseEstimate.worstCase ||
|
||||||
|
estimateMargin.estimatePosition.collateralIncreaseEstimate.worstCase === '0'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const collateralIncreaseEstimate = BigInt(
|
||||||
|
estimateMargin.estimatePosition.collateralIncreaseEstimate.worstCase
|
||||||
|
);
|
||||||
|
if (!collateralIncreaseEstimate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let positionWarning = '';
|
||||||
|
if (orders?.length && openVolume !== '0') {
|
||||||
|
positionWarning = t(
|
||||||
|
'youHaveOpenPositionAndOrders',
|
||||||
|
'You have an existing position and open orders on this market.',
|
||||||
|
{
|
||||||
|
count: orders.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (!orders?.length) {
|
||||||
|
positionWarning = t('You have an existing position on this market.');
|
||||||
|
} else {
|
||||||
|
positionWarning = t(
|
||||||
|
'youHaveOpenOrders',
|
||||||
|
'You have open orders on this market.',
|
||||||
|
{
|
||||||
|
count: orders.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let marginChangeWarning = '';
|
||||||
|
const amount = addDecimalsFormatNumber(
|
||||||
|
collateralIncreaseEstimate.toString(),
|
||||||
|
asset?.decimals
|
||||||
|
);
|
||||||
|
const { symbol } = asset;
|
||||||
|
const interpolation = { amount, symbol };
|
||||||
|
if (marginMode === Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN) {
|
||||||
|
marginChangeWarning = t(
|
||||||
|
'Changing the margin mode will move {{amount}} {{symbol}} from your general account to fund the position.',
|
||||||
|
interpolation
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
marginChangeWarning = t(
|
||||||
|
'Changing the margin mode and leverage will move {{amount}} {{symbol}} from your general account to fund the position.',
|
||||||
|
interpolation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Notification
|
||||||
|
intent={Intent.Warning}
|
||||||
|
message={
|
||||||
|
<>
|
||||||
|
<p>{positionWarning}</p>
|
||||||
|
<p>{marginChangeWarning}</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface MarginDialogProps {
|
interface MarginDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
marketId: string;
|
marketId: string;
|
||||||
partyId: string;
|
|
||||||
create: VegaTransactionStore['create'];
|
create: VegaTransactionStore['create'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +172,7 @@ const CrossMarginModeDialog = ({
|
|||||||
marketId,
|
marketId,
|
||||||
create,
|
create,
|
||||||
}: MarginDialogProps) => {
|
}: MarginDialogProps) => {
|
||||||
|
const { pubKey: partyId, isReadOnly } = useVegaWallet();
|
||||||
const t = useT();
|
const t = useT();
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -60,15 +200,24 @@ const CrossMarginModeDialog = ({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<MarginChange
|
||||||
|
marketId={marketId}
|
||||||
|
partyId={partyId}
|
||||||
|
marginMode={Types.MarginMode.MARGIN_MODE_CROSS_MARGIN}
|
||||||
|
marginFactor="1"
|
||||||
|
/>
|
||||||
|
<NoWalletWarning noWalletConnected={!partyId} isReadOnly={isReadOnly} />
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
create({
|
partyId &&
|
||||||
updateMarginMode: {
|
!isReadOnly &&
|
||||||
marketId,
|
create({
|
||||||
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
updateMarginMode: {
|
||||||
},
|
marketId,
|
||||||
});
|
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
|
},
|
||||||
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -82,10 +231,10 @@ const IsolatedMarginModeDialog = ({
|
|||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
marketId,
|
marketId,
|
||||||
partyId,
|
|
||||||
marginFactor,
|
marginFactor,
|
||||||
create,
|
create,
|
||||||
}: MarginDialogProps & { marginFactor: string }) => {
|
}: MarginDialogProps & { marginFactor: string }) => {
|
||||||
|
const { pubKey: partyId, isReadOnly } = useVegaWallet();
|
||||||
const [leverage, setLeverage] = useState(
|
const [leverage, setLeverage] = useState(
|
||||||
Number((1 / Number(marginFactor)).toFixed(1))
|
Number((1 / Number(marginFactor)).toFixed(1))
|
||||||
);
|
);
|
||||||
@ -129,13 +278,15 @@ const IsolatedMarginModeDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
create({
|
partyId &&
|
||||||
updateMarginMode: {
|
!isReadOnly &&
|
||||||
marketId,
|
create({
|
||||||
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
|
updateMarginMode: {
|
||||||
marginFactor: `${1 / leverage}`,
|
marketId,
|
||||||
},
|
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
|
||||||
});
|
marginFactor: `${1 / leverage}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -144,7 +295,7 @@ const IsolatedMarginModeDialog = ({
|
|||||||
<LeverageSlider
|
<LeverageSlider
|
||||||
max={max}
|
max={max}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={[leverage]}
|
value={[leverage || 1]}
|
||||||
onValueChange={([value]) => setLeverage(value)}
|
onValueChange={([value]) => setLeverage(value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -154,10 +305,17 @@ const IsolatedMarginModeDialog = ({
|
|||||||
min={1}
|
min={1}
|
||||||
max={max}
|
max={max}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={leverage}
|
value={leverage || ''}
|
||||||
onChange={(e) => setLeverage(Number(e.target.value))}
|
onChange={(e) => setLeverage(Number(e.target.value))}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
<MarginChange
|
||||||
|
marketId={marketId}
|
||||||
|
partyId={partyId}
|
||||||
|
marginMode={Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN}
|
||||||
|
marginFactor={`${1 / leverage}`}
|
||||||
|
/>
|
||||||
|
<NoWalletWarning noWalletConnected={!partyId} isReadOnly={isReadOnly} />
|
||||||
<Button className="w-full" type="submit">
|
<Button className="w-full" type="submit">
|
||||||
{t('Confirm')}
|
{t('Confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -169,27 +327,21 @@ const IsolatedMarginModeDialog = ({
|
|||||||
export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
|
export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [dialog, setDialog] = useState<'cross' | 'isolated' | ''>();
|
const [dialog, setDialog] = useState<'cross' | 'isolated' | ''>();
|
||||||
const { pubKey: partyId, isReadOnly } = useVegaWallet();
|
const { pubKey: partyId } = useVegaWallet();
|
||||||
const { data: margin } = useDataProvider({
|
const { data: margin } = useDataProvider({
|
||||||
dataProvider: marketMarginDataProvider,
|
dataProvider: marginModeDataProvider,
|
||||||
variables: {
|
variables: {
|
||||||
partyId: partyId || '',
|
partyId: partyId || '',
|
||||||
marketId,
|
marketId,
|
||||||
},
|
},
|
||||||
skip: !partyId,
|
skip: !partyId,
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
|
||||||
if (!partyId) {
|
|
||||||
setDialog('');
|
|
||||||
}
|
|
||||||
}, [partyId]);
|
|
||||||
const create = useVegaTransactionStore((state) => state.create);
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
const marginMode = margin?.marginMode;
|
const marginMode = margin?.marginMode;
|
||||||
const marginFactor =
|
const marginFactor =
|
||||||
margin?.marginFactor && margin?.marginFactor !== '0'
|
margin?.marginFactor && margin?.marginFactor !== '0'
|
||||||
? margin?.marginFactor
|
? margin?.marginFactor
|
||||||
: undefined;
|
: undefined;
|
||||||
const disabled = isReadOnly;
|
|
||||||
const onClose = () => setDialog(undefined);
|
const onClose = () => setDialog(undefined);
|
||||||
const enabledModeClassName = 'bg-vega-clight-500 dark:bg-vega-cdark-500';
|
const enabledModeClassName = 'bg-vega-clight-500 dark:bg-vega-cdark-500';
|
||||||
|
|
||||||
@ -197,8 +349,8 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
|
|||||||
<>
|
<>
|
||||||
<div className="mb-4 grid h-8 leading-8 font-alpha text-xs grid-cols-2">
|
<div className="mb-4 grid h-8 leading-8 font-alpha text-xs grid-cols-2">
|
||||||
<button
|
<button
|
||||||
disabled={disabled}
|
type="button"
|
||||||
onClick={() => partyId && setDialog('cross')}
|
onClick={() => setDialog('cross')}
|
||||||
className={classnames('rounded', {
|
className={classnames('rounded', {
|
||||||
[enabledModeClassName]:
|
[enabledModeClassName]:
|
||||||
!marginMode ||
|
!marginMode ||
|
||||||
@ -208,8 +360,8 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
|
|||||||
{t('Cross')}
|
{t('Cross')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
disabled={disabled}
|
type="button"
|
||||||
onClick={() => partyId && setDialog('isolated')}
|
onClick={() => setDialog('isolated')}
|
||||||
className={classnames('rounded', {
|
className={classnames('rounded', {
|
||||||
[enabledModeClassName]:
|
[enabledModeClassName]:
|
||||||
marginMode === Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
|
marginMode === Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
|
||||||
@ -222,25 +374,23 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
|
|||||||
})}
|
})}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{partyId && (
|
{
|
||||||
<CrossMarginModeDialog
|
<CrossMarginModeDialog
|
||||||
partyId={partyId}
|
|
||||||
open={dialog === 'cross'}
|
open={dialog === 'cross'}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
marketId={marketId}
|
marketId={marketId}
|
||||||
create={create}
|
create={create}
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
{partyId && (
|
{
|
||||||
<IsolatedMarginModeDialog
|
<IsolatedMarginModeDialog
|
||||||
partyId={partyId}
|
|
||||||
open={dialog === 'isolated'}
|
open={dialog === 'isolated'}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
marketId={marketId}
|
marketId={marketId}
|
||||||
create={create}
|
create={create}
|
||||||
marginFactor={marginFactor || `${1 / defaultLeverage}`}
|
marginFactor={marginFactor || `${1 / defaultLeverage}`}
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT =
|
|||||||
'To cover the required margin, this amount will be drawn from your general ({{assetSymbol}}) account.';
|
'To cover the required margin, this amount will be drawn from your general ({{assetSymbol}}) account.';
|
||||||
|
|
||||||
export const TOTAL_MARGIN_AVAILABLE =
|
export const TOTAL_MARGIN_AVAILABLE =
|
||||||
'Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).';
|
'Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) + order margin balance ({{orderMarginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).';
|
||||||
|
|
||||||
export const CONTRACTS_MARGIN_TOOLTIP_TEXT =
|
export const CONTRACTS_MARGIN_TOOLTIP_TEXT =
|
||||||
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.';
|
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.';
|
||||||
|
@ -5,37 +5,15 @@ import {
|
|||||||
import { useEstimatePositionQuery } from '@vegaprotocol/positions';
|
import { useEstimatePositionQuery } from '@vegaprotocol/positions';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface PositionEstimateProps extends EstimatePositionQueryVariables {
|
export const usePositionEstimate = (
|
||||||
skip: boolean;
|
variables: EstimatePositionQueryVariables,
|
||||||
}
|
skip: boolean
|
||||||
|
) => {
|
||||||
export const usePositionEstimate = ({
|
|
||||||
marketId,
|
|
||||||
openVolume,
|
|
||||||
orders,
|
|
||||||
generalAccountBalance,
|
|
||||||
marginAccountBalance,
|
|
||||||
orderMarginAccountBalance,
|
|
||||||
averageEntryPrice,
|
|
||||||
marginMode,
|
|
||||||
marginFactor,
|
|
||||||
skip,
|
|
||||||
}: PositionEstimateProps) => {
|
|
||||||
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
|
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
const { data } = useEstimatePositionQuery({
|
const { data } = useEstimatePositionQuery({
|
||||||
variables: {
|
variables,
|
||||||
marketId,
|
|
||||||
openVolume,
|
|
||||||
orders,
|
|
||||||
generalAccountBalance,
|
|
||||||
marginAccountBalance,
|
|
||||||
orderMarginAccountBalance,
|
|
||||||
averageEntryPrice,
|
|
||||||
marginMode,
|
|
||||||
marginFactor,
|
|
||||||
},
|
|
||||||
skip,
|
skip,
|
||||||
fetchPolicy: 'no-cache',
|
fetchPolicy: 'no-cache',
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
"Any orders placed now will not trade until the auction ends": "Any orders placed now will not trade until the auction ends",
|
"Any orders placed now will not trade until the auction ends": "Any orders placed now will not trade until the auction ends",
|
||||||
"below": "below",
|
"below": "below",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
|
"Changing the margin mode will move {{amount}} {{symbol}} from your general account to fund the position.": "Changing the margin mode will move {{amount}} {{symbol}} from your general account to fund the position.",
|
||||||
|
"Changing the margin mode and leverage will move {{amount}} {{symbol}} from your general account to fund the position.": "Changing the margin mode and leverage will move {{amount}} {{symbol}} from your general account to fund the position.",
|
||||||
"Closed": "Closed",
|
"Closed": "Closed",
|
||||||
"Closing on {{time}}": "Closing on {{time}}",
|
"Closing on {{time}}": "Closing on {{time}}",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
@ -67,6 +69,13 @@
|
|||||||
"One cancels another": "One cancels another",
|
"One cancels another": "One cancels another",
|
||||||
"Only limit orders are permitted when market is in auction": "Only limit orders are permitted when market is in auction",
|
"Only limit orders are permitted when market is in auction": "Only limit orders are permitted when market is in auction",
|
||||||
"Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.": "Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.",
|
"Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.": "Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.",
|
||||||
|
"You have an existing position on this market.": "You have an existing position on this market.",
|
||||||
|
"youHaveOpenOrders_one": "You have an open order on this market.",
|
||||||
|
"youHaveOpenOrders_other": "You have open orders on this market.",
|
||||||
|
"youHaveOpenOrders": "You have open orders on this market.",
|
||||||
|
"youHaveOpenPositionAndOrders_one": "You have an existing position and and open order on this market.",
|
||||||
|
"youHaveOpenPositionAndOrders_other": "You have an existing position and open orders on this market.",
|
||||||
|
"youHaveOpenPositionAndOrders": "You have an existing position and open orders on this market.",
|
||||||
"Peak size": "Peak size",
|
"Peak size": "Peak size",
|
||||||
"Peak size cannot be greater than the size ({{size}})": "Peak size cannot be greater than the size ({{size}})",
|
"Peak size cannot be greater than the size ({{size}})": "Peak size cannot be greater than the size ({{size}})",
|
||||||
"Peak size cannot be lower than {{stepSize}}": "Peak size cannot be lower than {{stepSize}}",
|
"Peak size cannot be lower than {{stepSize}}": "Peak size cannot be lower than {{stepSize}}",
|
||||||
@ -124,7 +133,7 @@
|
|||||||
"Total": "Total",
|
"Total": "Total",
|
||||||
"Total fees": "Total fees",
|
"Total fees": "Total fees",
|
||||||
"Total margin available": "Total margin available",
|
"Total margin available": "Total margin available",
|
||||||
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
|
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) + order margin balance ({{orderMarginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
|
||||||
"No trading": "No trading",
|
"No trading": "No trading",
|
||||||
"Trailing percent offset cannot be higher than 99.9": "Trailing percent offset cannot be higher than 99.9",
|
"Trailing percent offset cannot be higher than 99.9": "Trailing percent offset cannot be higher than 99.9",
|
||||||
"Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}": "Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}",
|
"Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}": "Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}",
|
||||||
@ -140,8 +149,10 @@
|
|||||||
"You are setting this market to cross-margin mode.": "You are setting this market to cross-margin mode.",
|
"You are setting this market to cross-margin mode.": "You are setting this market to cross-margin mode.",
|
||||||
"You are setting this market to isolated margin mode.": "You are setting this market to isolated margin mode.",
|
"You are setting this market to isolated margin mode.": "You are setting this market to isolated margin mode.",
|
||||||
"You have only {{amount}}.": "You have only {{amount}}.",
|
"You have only {{amount}}.": "You have only {{amount}}.",
|
||||||
|
"You have an existing position and open orders on this market": "You have an existing position and open orders on this market",
|
||||||
"You may not have enough margin available to open this position.": "You may not have enough margin available to open this position.",
|
"You may not have enough margin available to open this position.": "You may not have enough margin available to open this position.",
|
||||||
"You need {{symbol}} in your wallet to trade in this market.": "You need {{symbol}} in your wallet to trade in this market.",
|
"You need {{symbol}} in your wallet to trade in this market.": "You need {{symbol}} in your wallet to trade in this market.",
|
||||||
|
"You need a Vega wallet to start trading on this market": "You need a Vega wallet to start trading on this market",
|
||||||
"You need provide a expiry time/date": "You need provide a expiry time/date",
|
"You need provide a expiry time/date": "You need provide a expiry time/date",
|
||||||
"You need provide a price": "You need provide a price",
|
"You need provide a price": "You need provide a price",
|
||||||
"You need provide a trailing percent offset": "You need provide a trailing percent offset",
|
"You need provide a trailing percent offset": "You need provide a trailing percent offset",
|
||||||
|
@ -41,24 +41,26 @@ subscription PositionsSubscription($partyId: ID!) {
|
|||||||
query EstimatePosition(
|
query EstimatePosition(
|
||||||
$marketId: ID!
|
$marketId: ID!
|
||||||
$openVolume: String!
|
$openVolume: String!
|
||||||
$orders: [OrderInfo!]
|
|
||||||
$averageEntryPrice: String!
|
$averageEntryPrice: String!
|
||||||
|
$orders: [OrderInfo!]
|
||||||
$marginAccountBalance: String!
|
$marginAccountBalance: String!
|
||||||
$generalAccountBalance: String!
|
$generalAccountBalance: String!
|
||||||
$orderMarginAccountBalance: String!
|
$orderMarginAccountBalance: String!
|
||||||
$marginMode: MarginMode!
|
$marginMode: MarginMode!
|
||||||
$marginFactor: String
|
$marginFactor: String
|
||||||
|
$includeCollateralIncreaseInAvailableCollateral: Boolean
|
||||||
) {
|
) {
|
||||||
estimatePosition(
|
estimatePosition(
|
||||||
marketId: $marketId
|
marketId: $marketId
|
||||||
openVolume: $openVolume
|
openVolume: $openVolume
|
||||||
orders: $orders
|
|
||||||
averageEntryPrice: $averageEntryPrice
|
averageEntryPrice: $averageEntryPrice
|
||||||
|
orders: $orders
|
||||||
marginAccountBalance: $marginAccountBalance
|
marginAccountBalance: $marginAccountBalance
|
||||||
generalAccountBalance: $generalAccountBalance
|
generalAccountBalance: $generalAccountBalance
|
||||||
orderMarginAccountBalance: $orderMarginAccountBalance
|
orderMarginAccountBalance: $orderMarginAccountBalance
|
||||||
marginMode: $marginMode
|
marginMode: $marginMode
|
||||||
marginFactor: $marginFactor
|
marginFactor: $marginFactor
|
||||||
|
includeCollateralIncreaseInAvailableCollateral: $includeCollateralIncreaseInAvailableCollateral
|
||||||
# Everywhere in the codebase we expect price values of the underlying to have the right
|
# Everywhere in the codebase we expect price values of the underlying to have the right
|
||||||
# number of digits for formatting with market.decimalPlaces. By default the estimatePosition
|
# number of digits for formatting with market.decimalPlaces. By default the estimatePosition
|
||||||
# query will return a full value requiring formatting using asset.decimals. For consistency
|
# query will return a full value requiring formatting using asset.decimals. For consistency
|
||||||
@ -71,14 +73,24 @@ query EstimatePosition(
|
|||||||
searchLevel
|
searchLevel
|
||||||
initialLevel
|
initialLevel
|
||||||
collateralReleaseLevel
|
collateralReleaseLevel
|
||||||
|
marginMode
|
||||||
|
marginFactor
|
||||||
|
orderMarginLevel
|
||||||
}
|
}
|
||||||
bestCase {
|
bestCase {
|
||||||
maintenanceLevel
|
maintenanceLevel
|
||||||
searchLevel
|
searchLevel
|
||||||
initialLevel
|
initialLevel
|
||||||
collateralReleaseLevel
|
collateralReleaseLevel
|
||||||
|
marginMode
|
||||||
|
marginFactor
|
||||||
|
orderMarginLevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
collateralIncreaseEstimate {
|
||||||
|
worstCase
|
||||||
|
bestCase
|
||||||
|
}
|
||||||
liquidation {
|
liquidation {
|
||||||
worstCase {
|
worstCase {
|
||||||
open_volume_only
|
open_volume_only
|
||||||
|
23
libs/positions/src/lib/__generated__/Positions.ts
generated
23
libs/positions/src/lib/__generated__/Positions.ts
generated
@ -22,17 +22,18 @@ export type PositionsSubscriptionSubscription = { __typename?: 'Subscription', p
|
|||||||
export type EstimatePositionQueryVariables = Types.Exact<{
|
export type EstimatePositionQueryVariables = Types.Exact<{
|
||||||
marketId: Types.Scalars['ID'];
|
marketId: Types.Scalars['ID'];
|
||||||
openVolume: Types.Scalars['String'];
|
openVolume: Types.Scalars['String'];
|
||||||
orders?: Types.InputMaybe<Array<Types.OrderInfo> | Types.OrderInfo>;
|
|
||||||
averageEntryPrice: Types.Scalars['String'];
|
averageEntryPrice: Types.Scalars['String'];
|
||||||
|
orders?: Types.InputMaybe<Array<Types.OrderInfo> | Types.OrderInfo>;
|
||||||
marginAccountBalance: Types.Scalars['String'];
|
marginAccountBalance: Types.Scalars['String'];
|
||||||
generalAccountBalance: Types.Scalars['String'];
|
generalAccountBalance: Types.Scalars['String'];
|
||||||
orderMarginAccountBalance: Types.Scalars['String'];
|
orderMarginAccountBalance: Types.Scalars['String'];
|
||||||
marginMode: Types.MarginMode;
|
marginMode: Types.MarginMode;
|
||||||
marginFactor?: Types.InputMaybe<Types.Scalars['String']>;
|
marginFactor?: Types.InputMaybe<Types.Scalars['String']>;
|
||||||
|
includeCollateralIncreaseInAvailableCollateral?: Types.InputMaybe<Types.Scalars['Boolean']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type EstimatePositionQuery = { __typename?: 'Query', estimatePosition?: { __typename?: 'PositionEstimate', margin: { __typename?: 'MarginEstimate', worstCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string }, bestCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string } }, liquidation?: { __typename?: 'LiquidationEstimate', worstCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string }, bestCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string } } | null } | null };
|
export type EstimatePositionQuery = { __typename?: 'Query', estimatePosition?: { __typename?: 'PositionEstimate', margin: { __typename?: 'MarginEstimate', worstCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginMode: Types.MarginMode, marginFactor: string, orderMarginLevel: string }, bestCase: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, marginMode: Types.MarginMode, marginFactor: string, orderMarginLevel: string } }, collateralIncreaseEstimate: { __typename?: 'CollateralIncreaseEstimate', worstCase: string, bestCase: string }, liquidation?: { __typename?: 'LiquidationEstimate', worstCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string }, bestCase: { __typename?: 'LiquidationPrice', open_volume_only: string, including_buy_orders: string, including_sell_orders: string } } | null } | null };
|
||||||
|
|
||||||
export const PositionFieldsFragmentDoc = gql`
|
export const PositionFieldsFragmentDoc = gql`
|
||||||
fragment PositionFields on Position {
|
fragment PositionFields on Position {
|
||||||
@ -129,17 +130,18 @@ export function usePositionsSubscriptionSubscription(baseOptions: Apollo.Subscri
|
|||||||
export type PositionsSubscriptionSubscriptionHookResult = ReturnType<typeof usePositionsSubscriptionSubscription>;
|
export type PositionsSubscriptionSubscriptionHookResult = ReturnType<typeof usePositionsSubscriptionSubscription>;
|
||||||
export type PositionsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<PositionsSubscriptionSubscription>;
|
export type PositionsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<PositionsSubscriptionSubscription>;
|
||||||
export const EstimatePositionDocument = gql`
|
export const EstimatePositionDocument = gql`
|
||||||
query EstimatePosition($marketId: ID!, $openVolume: String!, $orders: [OrderInfo!], $averageEntryPrice: String!, $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String) {
|
query EstimatePosition($marketId: ID!, $openVolume: String!, $averageEntryPrice: String!, $orders: [OrderInfo!], $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String, $includeCollateralIncreaseInAvailableCollateral: Boolean) {
|
||||||
estimatePosition(
|
estimatePosition(
|
||||||
marketId: $marketId
|
marketId: $marketId
|
||||||
openVolume: $openVolume
|
openVolume: $openVolume
|
||||||
orders: $orders
|
|
||||||
averageEntryPrice: $averageEntryPrice
|
averageEntryPrice: $averageEntryPrice
|
||||||
|
orders: $orders
|
||||||
marginAccountBalance: $marginAccountBalance
|
marginAccountBalance: $marginAccountBalance
|
||||||
generalAccountBalance: $generalAccountBalance
|
generalAccountBalance: $generalAccountBalance
|
||||||
orderMarginAccountBalance: $orderMarginAccountBalance
|
orderMarginAccountBalance: $orderMarginAccountBalance
|
||||||
marginMode: $marginMode
|
marginMode: $marginMode
|
||||||
marginFactor: $marginFactor
|
marginFactor: $marginFactor
|
||||||
|
includeCollateralIncreaseInAvailableCollateral: $includeCollateralIncreaseInAvailableCollateral
|
||||||
scaleLiquidationPriceToMarketDecimals: true
|
scaleLiquidationPriceToMarketDecimals: true
|
||||||
) {
|
) {
|
||||||
margin {
|
margin {
|
||||||
@ -148,14 +150,24 @@ export const EstimatePositionDocument = gql`
|
|||||||
searchLevel
|
searchLevel
|
||||||
initialLevel
|
initialLevel
|
||||||
collateralReleaseLevel
|
collateralReleaseLevel
|
||||||
|
marginMode
|
||||||
|
marginFactor
|
||||||
|
orderMarginLevel
|
||||||
}
|
}
|
||||||
bestCase {
|
bestCase {
|
||||||
maintenanceLevel
|
maintenanceLevel
|
||||||
searchLevel
|
searchLevel
|
||||||
initialLevel
|
initialLevel
|
||||||
collateralReleaseLevel
|
collateralReleaseLevel
|
||||||
|
marginMode
|
||||||
|
marginFactor
|
||||||
|
orderMarginLevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
collateralIncreaseEstimate {
|
||||||
|
worstCase
|
||||||
|
bestCase
|
||||||
|
}
|
||||||
liquidation {
|
liquidation {
|
||||||
worstCase {
|
worstCase {
|
||||||
open_volume_only
|
open_volume_only
|
||||||
@ -186,13 +198,14 @@ export const EstimatePositionDocument = gql`
|
|||||||
* variables: {
|
* variables: {
|
||||||
* marketId: // value for 'marketId'
|
* marketId: // value for 'marketId'
|
||||||
* openVolume: // value for 'openVolume'
|
* openVolume: // value for 'openVolume'
|
||||||
* orders: // value for 'orders'
|
|
||||||
* averageEntryPrice: // value for 'averageEntryPrice'
|
* averageEntryPrice: // value for 'averageEntryPrice'
|
||||||
|
* orders: // value for 'orders'
|
||||||
* marginAccountBalance: // value for 'marginAccountBalance'
|
* marginAccountBalance: // value for 'marginAccountBalance'
|
||||||
* generalAccountBalance: // value for 'generalAccountBalance'
|
* generalAccountBalance: // value for 'generalAccountBalance'
|
||||||
* orderMarginAccountBalance: // value for 'orderMarginAccountBalance'
|
* orderMarginAccountBalance: // value for 'orderMarginAccountBalance'
|
||||||
* marginMode: // value for 'marginMode'
|
* marginMode: // value for 'marginMode'
|
||||||
* marginFactor: // value for 'marginFactor'
|
* marginFactor: // value for 'marginFactor'
|
||||||
|
* includeCollateralIncreaseInAvailableCollateral: // value for 'includeCollateralIncreaseInAvailableCollateral'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import type { EstimatePositionQuery } from './__generated__/Positions';
|
import type { EstimatePositionQuery } from './__generated__/Positions';
|
||||||
|
import { MarginMode } from '@vegaprotocol/types';
|
||||||
|
|
||||||
export const estimatePositionQuery = (
|
export const estimatePositionQuery = (
|
||||||
override?: PartialDeep<EstimatePositionQuery>
|
override?: PartialDeep<EstimatePositionQuery>
|
||||||
@ -14,14 +15,24 @@ export const estimatePositionQuery = (
|
|||||||
initialLevel: '500000',
|
initialLevel: '500000',
|
||||||
maintenanceLevel: '200000',
|
maintenanceLevel: '200000',
|
||||||
searchLevel: '300000',
|
searchLevel: '300000',
|
||||||
|
marginFactor: '1',
|
||||||
|
orderMarginLevel: '0',
|
||||||
|
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
},
|
},
|
||||||
worstCase: {
|
worstCase: {
|
||||||
collateralReleaseLevel: '1100000',
|
collateralReleaseLevel: '1100000',
|
||||||
initialLevel: '600000',
|
initialLevel: '600000',
|
||||||
maintenanceLevel: '300000',
|
maintenanceLevel: '300000',
|
||||||
searchLevel: '400000',
|
searchLevel: '400000',
|
||||||
|
marginFactor: '1',
|
||||||
|
orderMarginLevel: '0',
|
||||||
|
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
collateralIncreaseEstimate: {
|
||||||
|
bestCase: '0',
|
||||||
|
worstCase: '0',
|
||||||
|
},
|
||||||
liquidation: {
|
liquidation: {
|
||||||
bestCase: {
|
bestCase: {
|
||||||
including_buy_orders: '1',
|
including_buy_orders: '1',
|
||||||
|
@ -9,32 +9,23 @@ import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
|||||||
import { MarginMode } from '@vegaprotocol/types';
|
import { MarginMode } from '@vegaprotocol/types';
|
||||||
|
|
||||||
describe('LiquidationPrice', () => {
|
describe('LiquidationPrice', () => {
|
||||||
const props = {
|
const variables = {
|
||||||
marketId: 'market-id',
|
marketId: 'market-id',
|
||||||
openVolume: '100',
|
openVolume: '100',
|
||||||
decimalPlaces: 2,
|
averageEntryPrice: '10',
|
||||||
averageEntryPrice: '100',
|
marginAccountBalance: '500',
|
||||||
generalAccountBalance: '100',
|
generalAccountBalance: '500',
|
||||||
marginAccountBalance: '100',
|
orderMarginAccountBalance: '0',
|
||||||
orderMarginAccountBalance: '100',
|
|
||||||
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
marginFactor: '1',
|
marginFactor: '1',
|
||||||
};
|
};
|
||||||
|
const props = { ...variables, decimalPlaces: 2 };
|
||||||
const worstCaseOpenVolume = '200';
|
const worstCaseOpenVolume = '200';
|
||||||
const bestCaseOpenVolume = '100';
|
const bestCaseOpenVolume = '100';
|
||||||
const mock: MockedResponse<EstimatePositionQuery> = {
|
const mock: MockedResponse<EstimatePositionQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: EstimatePositionDocument,
|
query: EstimatePositionDocument,
|
||||||
variables: {
|
variables,
|
||||||
marketId: props.marketId,
|
|
||||||
openVolume: props.openVolume,
|
|
||||||
averageEntryPrice: props.averageEntryPrice,
|
|
||||||
generalAccountBalance: props.generalAccountBalance,
|
|
||||||
marginAccountBalance: props.marginAccountBalance,
|
|
||||||
orderMarginAccountBalance: props.orderMarginAccountBalance,
|
|
||||||
marginMode: props.marginMode,
|
|
||||||
marginFactor: props.marginFactor,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
@ -45,14 +36,24 @@ describe('LiquidationPrice', () => {
|
|||||||
searchLevel: '100',
|
searchLevel: '100',
|
||||||
initialLevel: '100',
|
initialLevel: '100',
|
||||||
collateralReleaseLevel: '100',
|
collateralReleaseLevel: '100',
|
||||||
|
orderMarginLevel: '0',
|
||||||
|
marginFactor: '0',
|
||||||
|
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
},
|
},
|
||||||
bestCase: {
|
bestCase: {
|
||||||
maintenanceLevel: '100',
|
maintenanceLevel: '100',
|
||||||
searchLevel: '100',
|
searchLevel: '100',
|
||||||
initialLevel: '100',
|
initialLevel: '100',
|
||||||
collateralReleaseLevel: '100',
|
collateralReleaseLevel: '100',
|
||||||
|
orderMarginLevel: '0',
|
||||||
|
marginFactor: '0',
|
||||||
|
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
collateralIncreaseEstimate: {
|
||||||
|
bestCase: '0',
|
||||||
|
worstCase: '0',
|
||||||
|
},
|
||||||
liquidation: {
|
liquidation: {
|
||||||
worstCase: {
|
worstCase: {
|
||||||
open_volume_only: worstCaseOpenVolume,
|
open_volume_only: worstCaseOpenVolume,
|
||||||
|
@ -1,47 +1,35 @@
|
|||||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useEstimatePositionQuery } from './__generated__/Positions';
|
import {
|
||||||
|
type EstimatePositionQueryVariables,
|
||||||
|
useEstimatePositionQuery,
|
||||||
|
} from './__generated__/Positions';
|
||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
import { useT } from '../use-t';
|
import { useT } from '../use-t';
|
||||||
import { MarginMode } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
export const LiquidationPrice = ({
|
export const LiquidationPrice = ({
|
||||||
marketId,
|
|
||||||
openVolume,
|
|
||||||
averageEntryPrice,
|
|
||||||
generalAccountBalance,
|
|
||||||
marginAccountBalance,
|
|
||||||
orderMarginAccountBalance,
|
|
||||||
marginMode = MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
|
||||||
marginFactor,
|
|
||||||
decimalPlaces,
|
decimalPlaces,
|
||||||
className,
|
className,
|
||||||
}: {
|
...variables
|
||||||
marketId: string;
|
}: Pick<
|
||||||
openVolume: string;
|
EstimatePositionQueryVariables,
|
||||||
averageEntryPrice: string;
|
| 'marketId'
|
||||||
generalAccountBalance: string;
|
| 'openVolume'
|
||||||
marginAccountBalance: string;
|
| 'orderMarginAccountBalance'
|
||||||
orderMarginAccountBalance: string;
|
| 'generalAccountBalance'
|
||||||
marginMode: MarginMode;
|
| 'averageEntryPrice'
|
||||||
marginFactor: string;
|
| 'marginAccountBalance'
|
||||||
|
| 'marginMode'
|
||||||
|
| 'marginFactor'
|
||||||
|
> & {
|
||||||
decimalPlaces: number;
|
decimalPlaces: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
|
|
||||||
const { data: currentData, previousData } = useEstimatePositionQuery({
|
const { data: currentData, previousData } = useEstimatePositionQuery({
|
||||||
variables: {
|
variables,
|
||||||
marketId,
|
|
||||||
openVolume,
|
|
||||||
averageEntryPrice,
|
|
||||||
generalAccountBalance,
|
|
||||||
marginAccountBalance,
|
|
||||||
orderMarginAccountBalance,
|
|
||||||
marginMode,
|
|
||||||
marginFactor,
|
|
||||||
},
|
|
||||||
fetchPolicy: 'no-cache',
|
fetchPolicy: 'no-cache',
|
||||||
skip: !openVolume || openVolume === '0',
|
skip: !variables.openVolume || variables.openVolume === '0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = currentData || previousData;
|
const data = currentData || previousData;
|
||||||
|
@ -52,7 +52,7 @@ export interface Position {
|
|||||||
quantum: string;
|
quantum: string;
|
||||||
lossSocializationAmount: string;
|
lossSocializationAmount: string;
|
||||||
marginAccountBalance: string;
|
marginAccountBalance: string;
|
||||||
orderAccountBalance: string;
|
orderMarginAccountBalance: string;
|
||||||
generalAccountBalance: string;
|
generalAccountBalance: string;
|
||||||
marketDecimalPlaces: number;
|
marketDecimalPlaces: number;
|
||||||
marketId: string;
|
marketId: string;
|
||||||
@ -67,6 +67,7 @@ export interface Position {
|
|||||||
realisedPNL: string;
|
realisedPNL: string;
|
||||||
status: PositionStatus;
|
status: PositionStatus;
|
||||||
totalBalance: string;
|
totalBalance: string;
|
||||||
|
totalMarginAccountBalance: string;
|
||||||
unrealisedPNL: string;
|
unrealisedPNL: string;
|
||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
productType: ProductType;
|
productType: ProductType;
|
||||||
@ -119,7 +120,7 @@ export const getMetrics = (
|
|||||||
marginAccount?.balance ?? 0,
|
marginAccount?.balance ?? 0,
|
||||||
asset.decimals
|
asset.decimals
|
||||||
);
|
);
|
||||||
const orderAccountBalance = toBigNum(
|
const orderMarginAccountBalance = toBigNum(
|
||||||
orderAccount?.balance ?? 0,
|
orderAccount?.balance ?? 0,
|
||||||
asset.decimals
|
asset.decimals
|
||||||
);
|
);
|
||||||
@ -137,12 +138,14 @@ export const getMetrics = (
|
|||||||
: openVolume.multipliedBy(-1)
|
: openVolume.multipliedBy(-1)
|
||||||
).multipliedBy(markPrice)
|
).multipliedBy(markPrice)
|
||||||
: undefined;
|
: undefined;
|
||||||
const totalBalance = marginAccountBalance
|
const totalMarginAccountBalance = marginAccountBalance.plus(
|
||||||
.plus(generalAccountBalance)
|
orderMarginAccountBalance
|
||||||
.plus(orderAccountBalance);
|
);
|
||||||
|
const totalBalance = totalMarginAccountBalance.plus(generalAccountBalance);
|
||||||
|
|
||||||
const marginMode =
|
const marginMode =
|
||||||
margin?.marginMode || MarginMode.MARGIN_MODE_CROSS_MARGIN;
|
margin?.marginMode || MarginMode.MARGIN_MODE_CROSS_MARGIN;
|
||||||
const marginFactor = margin?.marginFactor;
|
const marginFactor = margin?.marginFactor || '1';
|
||||||
const currentLeverage =
|
const currentLeverage =
|
||||||
marginMode === MarginMode.MARGIN_MODE_ISOLATED_MARGIN
|
marginMode === MarginMode.MARGIN_MODE_ISOLATED_MARGIN
|
||||||
? (marginFactor && 1 / Number(marginFactor)) || undefined
|
? (marginFactor && 1 / Number(marginFactor)) || undefined
|
||||||
@ -153,7 +156,7 @@ export const getMetrics = (
|
|||||||
: undefined;
|
: undefined;
|
||||||
metrics.push({
|
metrics.push({
|
||||||
marginMode,
|
marginMode,
|
||||||
marginFactor: marginFactor || '0',
|
marginFactor,
|
||||||
maintenanceLevel: margin?.maintenanceLevel,
|
maintenanceLevel: margin?.maintenanceLevel,
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
assetSymbol: asset.symbol,
|
assetSymbol: asset.symbol,
|
||||||
@ -163,7 +166,7 @@ export const getMetrics = (
|
|||||||
quantum: asset.quantum,
|
quantum: asset.quantum,
|
||||||
lossSocializationAmount: position.lossSocializationAmount || '0',
|
lossSocializationAmount: position.lossSocializationAmount || '0',
|
||||||
marginAccountBalance: marginAccount?.balance ?? '0',
|
marginAccountBalance: marginAccount?.balance ?? '0',
|
||||||
orderAccountBalance: orderAccount?.balance ?? '0',
|
orderMarginAccountBalance: orderAccount?.balance ?? '0',
|
||||||
generalAccountBalance: generalAccount?.balance ?? '0',
|
generalAccountBalance: generalAccount?.balance ?? '0',
|
||||||
marketDecimalPlaces,
|
marketDecimalPlaces,
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
@ -180,6 +183,9 @@ export const getMetrics = (
|
|||||||
realisedPNL: position.realisedPNL,
|
realisedPNL: position.realisedPNL,
|
||||||
status: position.positionStatus,
|
status: position.positionStatus,
|
||||||
totalBalance: totalBalance.multipliedBy(10 ** asset.decimals).toFixed(),
|
totalBalance: totalBalance.multipliedBy(10 ** asset.decimals).toFixed(),
|
||||||
|
totalMarginAccountBalance: totalMarginAccountBalance
|
||||||
|
.multipliedBy(10 ** asset.decimals)
|
||||||
|
.toFixed(),
|
||||||
unrealisedPNL: position.unrealisedPNL,
|
unrealisedPNL: position.unrealisedPNL,
|
||||||
updatedAt: position.updatedAt || null,
|
updatedAt: position.updatedAt || null,
|
||||||
productType: market?.tradableInstrument.instrument.product
|
productType: market?.tradableInstrument.instrument.product
|
||||||
@ -269,13 +275,26 @@ const positionDataProvider = makeDerivedDataProvider<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type OpenVolumeData = Pick<
|
||||||
|
PositionFieldsFragment,
|
||||||
|
'openVolume' | 'averageEntryPrice'
|
||||||
|
>;
|
||||||
|
|
||||||
export const openVolumeDataProvider = makeDerivedDataProvider<
|
export const openVolumeDataProvider = makeDerivedDataProvider<
|
||||||
string,
|
OpenVolumeData,
|
||||||
never,
|
never,
|
||||||
PositionsQueryVariables & MarketDataQueryVariables
|
PositionsQueryVariables & MarketDataQueryVariables
|
||||||
>(
|
>([positionDataProvider], ([data], variables, previousData) =>
|
||||||
[positionDataProvider],
|
produce(previousData, (draft) => {
|
||||||
(data) => (data[0] as PositionFieldsFragment | null)?.openVolume || null
|
if (!data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const newData = {
|
||||||
|
openVolume: (data as PositionFieldsFragment).openVolume,
|
||||||
|
averageEntryPrice: (data as PositionFieldsFragment).averageEntryPrice,
|
||||||
|
};
|
||||||
|
return draft ? Object.assign(draft, newData) : newData;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const rejoinPositionData = (
|
export const rejoinPositionData = (
|
||||||
@ -376,6 +395,31 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getMaxLeverage = (market: MarketInfo | null) => {
|
||||||
|
if (!market || !market?.riskFactors) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const maxLeverage =
|
||||||
|
1 /
|
||||||
|
(Math.max(
|
||||||
|
Number(market.riskFactors.long),
|
||||||
|
Number(market.riskFactors.short)
|
||||||
|
) || 1);
|
||||||
|
return maxLeverage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const maxMarketLeverageProvider = makeDerivedDataProvider<
|
||||||
|
number,
|
||||||
|
never,
|
||||||
|
{ marketId: string }
|
||||||
|
>(
|
||||||
|
[
|
||||||
|
(callback, client, { marketId }) =>
|
||||||
|
marketInfoProvider(callback, client, { marketId }),
|
||||||
|
],
|
||||||
|
(parts) => getMaxLeverage(parts[0])
|
||||||
|
);
|
||||||
|
|
||||||
export const maxLeverageProvider = makeDerivedDataProvider<
|
export const maxLeverageProvider = makeDerivedDataProvider<
|
||||||
number,
|
number,
|
||||||
never,
|
never,
|
||||||
@ -392,15 +436,7 @@ export const maxLeverageProvider = makeDerivedDataProvider<
|
|||||||
const market: MarketInfo | null = parts[0];
|
const market: MarketInfo | null = parts[0];
|
||||||
const position: PositionFieldsFragment | null = parts[1];
|
const position: PositionFieldsFragment | null = parts[1];
|
||||||
const margin: MarginFieldsFragment | null = parts[2];
|
const margin: MarginFieldsFragment | null = parts[2];
|
||||||
if (!market || !market?.riskFactors) {
|
const maxLeverage = getMaxLeverage(market);
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const maxLeverage =
|
|
||||||
1 /
|
|
||||||
(Math.max(
|
|
||||||
Number(market.riskFactors.long),
|
|
||||||
Number(market.riskFactors.short)
|
|
||||||
) || 1);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
market &&
|
market &&
|
||||||
@ -432,10 +468,9 @@ export const maxLeverageProvider = makeDerivedDataProvider<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useMaxLeverage = (marketId: string, partyId?: string) => {
|
export const useMaxLeverage = (marketId: string, partyId: string | null) => {
|
||||||
return useDataProvider({
|
return useDataProvider({
|
||||||
dataProvider: maxLeverageProvider,
|
dataProvider: partyId ? maxLeverageProvider : maxMarketLeverageProvider,
|
||||||
variables: { marketId, partyId: partyId || '' },
|
variables: { marketId, partyId: partyId || '' },
|
||||||
skip: !partyId,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -137,58 +137,61 @@ const PositionMargin = ({ data }: { data: Position }) => {
|
|||||||
? (
|
? (
|
||||||
BigInt(data.marginAccountBalance) + BigInt(data.generalAccountBalance)
|
BigInt(data.marginAccountBalance) + BigInt(data.generalAccountBalance)
|
||||||
).toString()
|
).toString()
|
||||||
: BigInt(data.marginAccountBalance) > BigInt(data.orderAccountBalance)
|
: BigInt(data.marginAccountBalance) >
|
||||||
|
BigInt(data.orderMarginAccountBalance)
|
||||||
? data.marginAccountBalance
|
? data.marginAccountBalance
|
||||||
: data.orderAccountBalance;
|
: data.orderMarginAccountBalance;
|
||||||
const getWidth = (balance: string) =>
|
const getWidth = (balance: string) =>
|
||||||
BigNumber(balance).multipliedBy(100).dividedBy(max).toNumber();
|
BigNumber(balance).multipliedBy(100).dividedBy(max).toNumber();
|
||||||
const inCrossMode = data.marginMode === MarginMode.MARGIN_MODE_CROSS_MARGIN;
|
const inCrossMode = data.marginMode === MarginMode.MARGIN_MODE_CROSS_MARGIN;
|
||||||
const hasOrderAccountBalance =
|
const hasOrderMarginAccountBalance =
|
||||||
!inCrossMode && data.orderAccountBalance !== '0';
|
!inCrossMode && data.orderMarginAccountBalance !== '0';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MarginChart
|
{data.marginAccountBalance !== '0' && (
|
||||||
width={inCrossMode ? getWidth(data.marginAccountBalance) : undefined}
|
<MarginChart
|
||||||
label={t('Margin: {{balance}}', {
|
width={inCrossMode ? getWidth(data.marginAccountBalance) : undefined}
|
||||||
balance: addDecimalsFormatNumberQuantum(
|
label={t('Margin: {{balance}}', {
|
||||||
data.marginAccountBalance,
|
balance: addDecimalsFormatNumberQuantum(
|
||||||
data.assetDecimals,
|
data.marginAccountBalance,
|
||||||
data.quantum
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
other={
|
|
||||||
inCrossMode
|
|
||||||
? t('General account: {{balance}}', {
|
|
||||||
balance: addDecimalsFormatNumberQuantum(
|
|
||||||
data.generalAccountBalance,
|
|
||||||
data.assetDecimals,
|
|
||||||
data.quantum
|
|
||||||
),
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
className={classnames({ 'mb-2': hasOrderAccountBalance })}
|
|
||||||
marker={
|
|
||||||
data.maintenanceLevel ? getWidth(data.maintenanceLevel) : undefined
|
|
||||||
}
|
|
||||||
markerLabel={
|
|
||||||
data.maintenanceLevel &&
|
|
||||||
t('Liquidation: {{maintenanceLevel}}', {
|
|
||||||
maintenanceLevel: addDecimalsFormatNumberQuantum(
|
|
||||||
data.maintenanceLevel,
|
|
||||||
data.assetDecimals,
|
data.assetDecimals,
|
||||||
data.quantum
|
data.quantum
|
||||||
),
|
),
|
||||||
})
|
})}
|
||||||
}
|
other={
|
||||||
/>
|
inCrossMode
|
||||||
{hasOrderAccountBalance ? (
|
? t('General account: {{balance}}', {
|
||||||
|
balance: addDecimalsFormatNumberQuantum(
|
||||||
|
data.generalAccountBalance,
|
||||||
|
data.assetDecimals,
|
||||||
|
data.quantum
|
||||||
|
),
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
className={classnames({ 'mb-2': hasOrderMarginAccountBalance })}
|
||||||
|
marker={
|
||||||
|
data.maintenanceLevel ? getWidth(data.maintenanceLevel) : undefined
|
||||||
|
}
|
||||||
|
markerLabel={
|
||||||
|
data.maintenanceLevel &&
|
||||||
|
t('Liquidation: {{maintenanceLevel}}', {
|
||||||
|
maintenanceLevel: addDecimalsFormatNumberQuantum(
|
||||||
|
data.maintenanceLevel,
|
||||||
|
data.assetDecimals,
|
||||||
|
data.quantum
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{hasOrderMarginAccountBalance ? (
|
||||||
<MarginChart
|
<MarginChart
|
||||||
width={getWidth(data.orderAccountBalance)}
|
width={getWidth(data.orderMarginAccountBalance)}
|
||||||
label={t('Order: {{balance}}', {
|
label={t('Order: {{balance}}', {
|
||||||
balance: addDecimalsFormatNumber(
|
balance: addDecimalsFormatNumber(
|
||||||
data.orderAccountBalance,
|
data.orderMarginAccountBalance,
|
||||||
data.assetDecimals
|
data.assetDecimals
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
@ -340,20 +343,16 @@ export const PositionsTable = ({
|
|||||||
return !data
|
return !data
|
||||||
? undefined
|
? undefined
|
||||||
: toBigNum(
|
: toBigNum(
|
||||||
data.marginAccountBalance,
|
data.totalMarginAccountBalance,
|
||||||
data.assetDecimals
|
data.assetDecimals
|
||||||
).toNumber();
|
).toNumber();
|
||||||
},
|
},
|
||||||
cellRenderer: ({ data }: VegaICellRendererParams<Position>) => {
|
cellRenderer: ({ data }: VegaICellRendererParams<Position>) => {
|
||||||
if (
|
if (!data || !data.totalMarginAccountBalance) {
|
||||||
!data ||
|
|
||||||
!data.marginAccountBalance ||
|
|
||||||
!data.marketDecimalPlaces
|
|
||||||
) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const margin = addDecimalsFormatNumberQuantum(
|
const margin = addDecimalsFormatNumberQuantum(
|
||||||
data.marginAccountBalance,
|
data.totalMarginAccountBalance,
|
||||||
data.assetDecimals,
|
data.assetDecimals,
|
||||||
data.quantum
|
data.quantum
|
||||||
);
|
);
|
||||||
@ -364,7 +363,7 @@ export const PositionsTable = ({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
description={
|
description={
|
||||||
data &&
|
data &&
|
||||||
data.marginAccountBalance !== '0' && (
|
data.totalMarginAccountBalance !== '0' && (
|
||||||
<PositionMargin data={data} />
|
<PositionMargin data={data} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -410,10 +409,10 @@ export const PositionsTable = ({
|
|||||||
className="block text-right grow"
|
className="block text-right grow"
|
||||||
marketId={data.marketId}
|
marketId={data.marketId}
|
||||||
openVolume={data.openVolume}
|
openVolume={data.openVolume}
|
||||||
|
averageEntryPrice={data.averageEntryPrice}
|
||||||
generalAccountBalance={data.generalAccountBalance}
|
generalAccountBalance={data.generalAccountBalance}
|
||||||
marginAccountBalance={data.marginAccountBalance}
|
marginAccountBalance={data.marginAccountBalance}
|
||||||
orderMarginAccountBalance={data.orderAccountBalance}
|
orderMarginAccountBalance={data.orderMarginAccountBalance}
|
||||||
averageEntryPrice={data.averageEntryPrice}
|
|
||||||
marginFactor={data.marginFactor}
|
marginFactor={data.marginFactor}
|
||||||
marginMode={data.marginMode}
|
marginMode={data.marginMode}
|
||||||
decimalPlaces={data.marketDecimalPlaces}
|
decimalPlaces={data.marketDecimalPlaces}
|
||||||
|
@ -181,11 +181,11 @@ const marginsFields: MarginFieldsFragment[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const singleRow: Position = {
|
export const singleRow: Position = {
|
||||||
|
marginFactor: '1',
|
||||||
generalAccountBalance: '12345600',
|
generalAccountBalance: '12345600',
|
||||||
maintenanceLevel: '12300000',
|
maintenanceLevel: '12300000',
|
||||||
marginMode: Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
marginMode: Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
|
||||||
marginFactor: '1',
|
orderMarginAccountBalance: '0',
|
||||||
orderAccountBalance: '0',
|
|
||||||
partyId: 'partyId',
|
partyId: 'partyId',
|
||||||
assetId: 'asset-id',
|
assetId: 'asset-id',
|
||||||
assetSymbol: 'BTC',
|
assetSymbol: 'BTC',
|
||||||
@ -195,6 +195,7 @@ export const singleRow: Position = {
|
|||||||
quantum: '0.1',
|
quantum: '0.1',
|
||||||
lossSocializationAmount: '0',
|
lossSocializationAmount: '0',
|
||||||
marginAccountBalance: '12345600',
|
marginAccountBalance: '12345600',
|
||||||
|
totalMarginAccountBalance: '12345600',
|
||||||
marketDecimalPlaces: 1,
|
marketDecimalPlaces: 1,
|
||||||
marketId: 'string',
|
marketId: 'string',
|
||||||
marketCode: 'ETHBTC.QM21',
|
marketCode: 'ETHBTC.QM21',
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { openVolumeDataProvider } from './positions-data-providers';
|
import {
|
||||||
|
OpenVolumeData,
|
||||||
|
openVolumeDataProvider,
|
||||||
|
} from './positions-data-providers';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
|
||||||
export const useOpenVolume = (
|
export const useOpenVolume = (
|
||||||
partyId: string | null | undefined,
|
partyId: string | null | undefined,
|
||||||
marketId: string
|
marketId: string
|
||||||
) => {
|
) => {
|
||||||
const [openVolume, setOpenVolume] = useState<string | undefined>(undefined);
|
const [openVolume, setOpenVolume] = useState<OpenVolumeData | null>(null);
|
||||||
const update = useCallback(({ data }: { data: string | null }) => {
|
const update = useCallback(({ data }: { data: OpenVolumeData | null }) => {
|
||||||
setOpenVolume(data ?? undefined);
|
setOpenVolume(data);
|
||||||
return true;
|
return true;
|
||||||
}, []);
|
}, []);
|
||||||
useDataProvider({
|
useDataProvider({
|
||||||
|
@ -5,7 +5,9 @@ import classNames from 'classnames';
|
|||||||
export const LeverageSlider = (
|
export const LeverageSlider = (
|
||||||
props: Omit<SliderProps, 'min' | 'max'> & Required<Pick<SliderProps, 'max'>>
|
props: Omit<SliderProps, 'min' | 'max'> & Required<Pick<SliderProps, 'max'>>
|
||||||
) => {
|
) => {
|
||||||
const step = [2, 5, 10, 20, 25].find((step) => props.max / step <= 6);
|
const step = [2, 5, 10, 20, 25, 50, 100].find(
|
||||||
|
(step) => props.max / step <= 6
|
||||||
|
);
|
||||||
const min = 1;
|
const min = 1;
|
||||||
const value = props.value?.[0] || props.defaultValue?.[0];
|
const value = props.value?.[0] || props.defaultValue?.[0];
|
||||||
return (
|
return (
|
||||||
@ -28,6 +30,7 @@ export const LeverageSlider = (
|
|||||||
const higherThanValue = value && labelValue > value;
|
const higherThanValue = value && labelValue > value;
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
|
key={labelValue}
|
||||||
className="absolute flex flex-col items-center translate-x-[-50%]"
|
className="absolute flex flex-col items-center translate-x-[-50%]"
|
||||||
style={{
|
style={{
|
||||||
left: `${
|
left: `${
|
||||||
|
Loading…
Reference in New Issue
Block a user