feat: [console-lite] - trading - order review (#842)

* feat: [console-lite] - trading - order review

* feat: [console-lite] - trading - order review - fix margin calcs

* feat: [console-lite] - add order margin calcs

* feat: [console-lite] - add order margin calcs - improve margin calcs

* feat: [console-lite] - add order margin calcs - add int test

* feat: [console-lite] - add order margin calcs - improve int test

Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
macqbat 2022-07-27 18:09:27 +02:00 committed by GitHub
parent f1c3eab914
commit 6c976a0da3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 787 additions and 29 deletions

View File

@ -24,3 +24,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
NX_VEGA_ENV=TESTNET
NX_VEGA_REST=https://lb.testnet.vega.xyz/datanode/rest
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1

View File

@ -18,4 +18,22 @@ module.exports = defineConfig({
viewportWidth: 1440,
viewportHeight: 900,
},
env: {
TRADING_TEST_VEGA_WALLET_NAME: 'UI_Trading_Test',
ETHEREUM_PROVIDER_URL:
'https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
VEGA_PUBLIC_KEY:
'47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278',
VEGA_PUBLIC_KEY2:
'1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4',
TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278',
TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4',
ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158',
ETHERSCAN_URL: 'https://ropsten.etherscan.io',
tsConfig: 'tsconfig.json',
TAGS: 'not @todo and not @ignore and not @manual',
TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123',
ETH_WALLET_MNEMONIC:
'ugly gallery notice network true range brave clarify flat logic someone chunk',
},
});

View File

@ -1,6 +1,22 @@
import { aliasQuery } from '@vegaprotocol/cypress';
import { generateSimpleMarkets } from '../support/mocks/generate-markets';
import { generateDealTicket } from '../support/mocks/generate-deal-ticket';
import { generateMarketTags } from '../support/mocks/generate-market-tags';
import { generateMarketPositions } from '../support/mocks/generate-market-positions';
import { generateEstimateOrder } from '../support/mocks/generate-estimate-order';
import { generatePartyBalance } from '../support/mocks/generate-party-balance';
const connectVegaWallet = () => {
const form = 'rest-connector-form';
const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME');
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId(form).find('#wallet').click().type(walletName);
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
};
describe('Market trade', () => {
let markets;
@ -8,6 +24,10 @@ describe('Market trade', () => {
cy.mockGQL((req) => {
aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets());
aliasQuery(req, 'DealTicketQuery', generateDealTicket());
aliasQuery(req, 'MarketTags', generateMarketTags());
aliasQuery(req, 'MarketPositions', generateMarketPositions());
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
aliasQuery(req, 'PartyBalanceQuery', generatePartyBalance());
});
cy.visit('/markets');
cy.wait('@SimpleMarkets').then((response) => {
@ -64,4 +84,32 @@ describe('Market trade', () => {
);
}
});
it('order review should display proper calculations', () => {
if (markets?.length) {
cy.visit(`/trading/${markets[0].id}`);
connectVegaWallet();
cy.get('h3').contains('Review Trade').click();
cy.getByTestId('key-value-table')
.find('dl')
.eq(1)
.find('dd div')
.should('have.text', '3.44055');
cy.getByTestId('key-value-table')
.find('dl')
.eq(2)
.find('dd div')
.should('have.text', '1.00000');
cy.getByTestId('key-value-table')
.find('dl')
.eq(3)
.find('dd div')
.should('have.text', '-785.81045');
cy.getByTestId('place-order').click();
cy.getByTestId('dialog-title').should(
'have.text',
'Confirm transaction in wallet'
);
}
});
});

View File

@ -0,0 +1,12 @@
export const generateEstimateOrder = () => {
return {
estimateOrder: {
totalFeeAmount: '16085.09240212.7380425.46',
marginLevels: {
initialLevel: '2844054.80937741220203',
__typename: 'MarginLevels',
},
__typename: 'OrderEstimate',
},
};
};

View File

@ -0,0 +1,43 @@
export const generateMarketPositions = () => {
return {
party: {
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
positionsConnection: {
edges: [
{
node: {
openVolume: '3',
market: {
id: '2751c508f9759761f912890f37fb3f97a00300bf7685c02a56a86e05facfe221',
accounts: [
{ balance: '0', __typename: 'Account' },
{ balance: '0', __typename: 'Account' },
],
__typename: 'Market',
},
__typename: 'Position',
},
__typename: 'PositionEdge',
},
{
node: {
openVolume: '12',
market: {
id: 'first-btcusd-id',
accounts: [
{ balance: '10', __typename: 'Account' },
{ balance: '15', __typename: 'Account' },
],
__typename: 'Market',
},
__typename: 'Position',
},
__typename: 'PositionEdge',
},
],
__typename: 'PositionConnection',
},
__typename: 'Party',
},
};
};

View File

@ -0,0 +1,25 @@
export const generateMarketTags = () => {
return {
market: {
tradableInstrument: {
instrument: {
metadata: {
tags: [
'formerly:2839D9B2329C9E70',
'base:AAVE',
'quote:DAI',
'class:fx/crypto',
'monthly',
'sector:defi',
'settlement:2022-08-01',
],
__typename: 'InstrumentMetadata',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
};
};

View File

@ -0,0 +1,53 @@
export const generatePartyBalance = () => {
return {
party: {
accounts: [
{
balance: '88474051',
asset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
{
balance: '100000000',
asset: {
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
symbol: 'tEURO',
name: 'tEURO TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
{
balance: '3412867',
asset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
{
balance: '70007',
asset: {
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
symbol: 'tDAI',
name: 'tDAI TEST',
decimals: 5,
__typename: 'Asset',
},
__typename: 'Account',
},
],
__typename: 'Party',
},
};
};

View File

@ -21,3 +21,4 @@ NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
NX_VEGA_ENV = 'TESTNET'
NX_VEGA_URL="https://lb.testnet.vega.xyz/query"
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1

View File

@ -0,0 +1,51 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: MarketTags
// ====================================================
export interface MarketTags_market_tradableInstrument_instrument_metadata {
__typename: "InstrumentMetadata";
/**
* An arbitrary list of tags to associated to associate to the Instrument (string list)
*/
tags: string[] | null;
}
export interface MarketTags_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* Metadata for this instrument
*/
metadata: MarketTags_market_tradableInstrument_instrument_metadata;
}
export interface MarketTags_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: MarketTags_market_tradableInstrument_instrument;
}
export interface MarketTags_market {
__typename: "Market";
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: MarketTags_market_tradableInstrument;
}
export interface MarketTags {
/**
* An instrument that is trading on the VEGA network
*/
market: MarketTags_market | null;
}
export interface MarketTagsVariables {
marketId: string;
}

View File

@ -61,7 +61,7 @@ export const DealTicketContainer = () => {
isWalletConnected={!!keypair?.pub}
/>
)}
<DealTicketSteps market={data.market} />
<DealTicketSteps market={data.market} partyData={partyData} />
</DealTicketManager>
)}
</Container>

View File

@ -1,9 +1,16 @@
import * as React from 'react';
import React, { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form';
import { Stepper } from '../stepper';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
import { DealTicketAmount, MarketSelector } from '@vegaprotocol/deal-ticket';
import { InputError } from '@vegaprotocol/ui-toolkit';
import {
DealTicketAmount,
getDialogTitle,
getDialogIntent,
getDialogIcon,
MarketSelector,
} from '@vegaprotocol/deal-ticket';
import type { Order } from '@vegaprotocol/orders';
import { VegaTxStatus } from '@vegaprotocol/wallet';
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
@ -11,17 +18,22 @@ import {
getDefaultOrder,
useOrderValidation,
useOrderSubmit,
OrderFeedback,
} from '@vegaprotocol/orders';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
import SideSelector, { SIDE_NAMES } from './side-selector';
import ReviewTrade from './review-trade';
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
interface DealTicketMarketProps {
market: DealTicketQuery_market;
partyData?: PartyBalanceQuery;
}
export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
export const DealTicketSteps = ({
market,
partyData,
}: DealTicketMarketProps) => {
const navigate = useNavigate();
const setMarket = useCallback(
(marketId) => {
@ -45,6 +57,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
const orderType = watch('type');
const orderTimeInForce = watch('timeInForce');
const orderSide = watch('side');
const order = watch();
const { message: invalidText, isDisabled } = useOrderValidation({
step,
@ -54,7 +67,8 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
fieldErrors: errors,
});
const { submit, transaction } = useOrderSubmit(market);
const { submit, transaction, finalizedOrder, TransactionDialog } =
useOrderSubmit(market);
const transactionStatus =
transaction.status === VegaTxStatus.Requested ||
@ -101,7 +115,7 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
component: (
<DealTicketAmount
orderType={orderType}
step={0.02}
step={step}
register={register}
price={
market.depth.lastTrade
@ -121,17 +135,20 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
{invalidText}
</InputError>
)}
<Button
className="w-full mb-8"
variant="primary"
type="submit"
disabled={transactionStatus === 'pending' || isDisabled}
data-testid="place-order"
<ReviewTrade
market={market}
isDisabled={isDisabled}
transactionStatus={transactionStatus}
order={order}
partyData={partyData}
/>
<TransactionDialog
title={getDialogTitle(finalizedOrder?.status)}
intent={getDialogIntent(finalizedOrder?.status)}
icon={getDialogIcon(finalizedOrder?.status)}
>
{transactionStatus === 'pending'
? t('Pending...')
: t('Place order')}
</Button>
<OrderFeedback transaction={transaction} order={finalizedOrder} />
</TransactionDialog>
</div>
),
disabled: true,

View File

@ -0,0 +1,146 @@
import { addDecimal, formatNumber, t } from '@vegaprotocol/react-helpers';
import {
Button,
Icon,
KeyValueTable,
KeyValueTableRow,
} from '@vegaprotocol/ui-toolkit';
import * as React from 'react';
import classNames from 'classnames';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import type { Order } from '@vegaprotocol/orders';
import { SIDE_NAMES } from './side-selector';
import { useVegaWallet, VegaWalletOrderSide } from '@vegaprotocol/wallet';
import SimpleMarketExpires from '../simple-market-list/simple-market-expires';
import { gql, useQuery } from '@apollo/client';
import type {
MarketTags,
MarketTagsVariables,
} from './__generated__/MarketTags';
import useOrderMargin from '../../hooks/use-order-margin';
import useOrderCloseOut from '../../hooks/use-order-closeout';
import { IconNames } from '@blueprintjs/icons';
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
export const MARKET_TAGS_QUERY = gql`
query MarketTags($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
metadata {
tags
}
}
}
}
}
`;
interface Props {
market: DealTicketQuery_market;
isDisabled: boolean;
transactionStatus?: string;
order: Order;
partyData?: PartyBalanceQuery;
}
export default ({
isDisabled,
market,
order,
transactionStatus,
partyData,
}: Props) => {
const { keypair } = useVegaWallet();
const { data: tagsData } = useQuery<MarketTags, MarketTagsVariables>(
MARKET_TAGS_QUERY,
{
variables: { marketId: market.id },
}
);
const estMargin = useOrderMargin({
order,
market,
partyId: keypair?.pub || '',
});
const estCloseOut = useOrderCloseOut({ order, market, partyData });
return (
<div className="mb-8 text-black dark:text-white">
<KeyValueTable>
<KeyValueTableRow noBorder>
<div className="flex flex-none gap-x-5 items-center">
<div
className={classNames(
{
'buyButton dark:buyButtonDark':
order.side === VegaWalletOrderSide.Buy,
'sellButton dark:sellButtonDark':
order.side === VegaWalletOrderSide.Sell,
},
'px-8 py-4 inline text-ui-small'
)}
>
{SIDE_NAMES[order.side]}
</div>
<div>{market.tradableInstrument.instrument.product.quoteName}</div>
<div>
{tagsData?.market?.tradableInstrument.instrument.metadata
.tags && (
<SimpleMarketExpires
tags={
tagsData?.market.tradableInstrument.instrument.metadata.tags
}
/>
)}
</div>
</div>
<div className="text-blue">
@{' '}
{market.depth.lastTrade
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
: ' - '}{' '}
<span className="text-ui-small inline">(EST)</span>
</div>
</KeyValueTableRow>
<KeyValueTableRow noBorder>
<>{t('Est. margin')}</>
<div className="text-black dark:text-white flex gap-x-5 items-center">
{estMargin}
<Icon name={IconNames.ISSUE} className="rotate-180" />
</div>
</KeyValueTableRow>
<KeyValueTableRow noBorder>
<>
{t('Size')}{' '}
<div className="text-ui-small inline">
({market.tradableInstrument.instrument.product.quoteName})
</div>
</>
<div className="text-black dark:text-white flex gap-x-5 items-center">
{formatNumber(order.size, market.decimalPlaces)}
<Icon name={IconNames.ISSUE} className="rotate-180" />
</div>
</KeyValueTableRow>
<KeyValueTableRow noBorder>
<>{t('Est. close out')}</>
<div className="text-black dark:text-white flex gap-x-5 items-center">
{estCloseOut}
<Icon name={IconNames.ISSUE} className="rotate-180" />
</div>
</KeyValueTableRow>
</KeyValueTable>
<Button
className="w-full !py-8 mt-64 max-w-sm"
boxShadow={false}
variant="secondary"
type="submit"
disabled={transactionStatus === 'pending' || isDisabled}
data-testid="place-order"
appendIconName="arrow-top-right"
>
{transactionStatus === 'pending' ? t('Pending...') : t('Submit')}
</Button>
</div>
);
};

View File

@ -29,6 +29,7 @@ export default ({ value, onSelect }: SideSelectorProps) => {
variant="inline-link"
aria-label={t('Open long position')}
className={classNames(
'py-8',
'buyButton hover:buyButton dark:buyButtonDark dark:hover:buyButtonDark',
{ selected: value === VegaWalletOrderSide.Buy }
)}
@ -40,6 +41,7 @@ export default ({ value, onSelect }: SideSelectorProps) => {
variant="inline-link"
aria-label={t('Open short position')}
className={classNames(
'py-8',
'sellButton hover:sellButton dark:sellButtonDark dark:hover:sellButtonDark',
{ selected: value === VegaWalletOrderSide.Sell }
)}

View File

@ -23,7 +23,7 @@ describe('SimpleMarketExpires', () => {
'settlement-date:2022-04-25T1200',
];
render(<SimpleMarketExpires tags={tags} />);
expect(screen.getByText('April 25')).toBeInTheDocument();
expect(screen.getByText('Apr 25')).toBeInTheDocument();
});
it('last one proper tag should matter', () => {
@ -33,7 +33,7 @@ describe('SimpleMarketExpires', () => {
'settlement-expiry-date:2022-03-25T12:00:00',
];
render(<SimpleMarketExpires tags={tags} />);
expect(screen.getByText('March 25')).toBeInTheDocument();
expect(screen.getByText('Mar 25')).toBeInTheDocument();
});
it('when no proper tag nor date should be null', () => {

View File

@ -1,7 +1,7 @@
import type { SimpleMarkets_markets } from '../components/simple-market-list/__generated__/SimpleMarkets';
export const DATE_FORMAT = 'dd MMMM yyyy HH:mm';
export const EXPIRE_DATE_FORMAT = 'MMMM dd';
export const EXPIRE_DATE_FORMAT = 'MMM dd';
export const TRADABLE_STATES = {
Active: true,

View File

@ -0,0 +1,48 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { Side, OrderTimeInForce, OrderType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: EstimateOrder
// ====================================================
export interface EstimateOrder_estimateOrder_marginLevels {
__typename: "MarginLevels";
/**
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
*/
initialLevel: string;
}
export interface EstimateOrder_estimateOrder {
__typename: "OrderEstimate";
/**
* The total estimated amount of fee if the order was to trade
*/
totalFeeAmount: string;
/**
* The margin requirement for this order
*/
marginLevels: EstimateOrder_estimateOrder_marginLevels;
}
export interface EstimateOrder {
/**
* return an estimation of the potential cost for a new order
*/
estimateOrder: EstimateOrder_estimateOrder;
}
export interface EstimateOrderVariables {
marketId: string;
partyId: string;
price?: string | null;
size: string;
side: Side;
timeInForce: OrderTimeInForce;
expiration?: string | null;
type: OrderType;
}

View File

@ -0,0 +1,76 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: MarketPositions
// ====================================================
export interface MarketPositions_party_positionsConnection_edges_node_market_accounts {
__typename: "Account";
/**
* Balance as string - current account balance (approx. as balances can be updated several times per second)
*/
balance: string;
}
export interface MarketPositions_party_positionsConnection_edges_node_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Get account for a party or market
*/
accounts: MarketPositions_party_positionsConnection_edges_node_market_accounts[] | null;
}
export interface MarketPositions_party_positionsConnection_edges_node {
__typename: "Position";
/**
* Open volume (uint64)
*/
openVolume: string;
/**
* Market relating to this position
*/
market: MarketPositions_party_positionsConnection_edges_node_market;
}
export interface MarketPositions_party_positionsConnection_edges {
__typename: "PositionEdge";
node: MarketPositions_party_positionsConnection_edges_node;
}
export interface MarketPositions_party_positionsConnection {
__typename: "PositionConnection";
/**
* The positions in this connection
*/
edges: MarketPositions_party_positionsConnection_edges[] | null;
}
export interface MarketPositions_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* Trading positions relating to a party
*/
positionsConnection: MarketPositions_party_positionsConnection;
}
export interface MarketPositions {
/**
* An entity that is trading on the VEGA network
*/
party: MarketPositions_party | null;
}
export interface MarketPositionsVariables {
partyId: string;
}

View File

@ -0,0 +1,70 @@
import { gql, useQuery } from '@apollo/client';
import { BigNumber } from 'bignumber.js';
import type {
MarketPositions,
MarketPositionsVariables,
} from './__generated__/marketPositions';
const MARKET_POSITIONS_QUERY = gql`
query MarketPositions($partyId: ID!) {
party(id: $partyId) {
id
positionsConnection {
edges {
node {
openVolume
market {
id
accounts {
balance
}
}
}
}
}
}
}
`;
interface Props {
marketId: string;
partyId: string;
}
type PositionMargin = {
openVolume: BigNumber;
balanceSum: BigNumber;
} | null;
export default ({ marketId, partyId }: Props): PositionMargin => {
const { data } = useQuery<MarketPositions, MarketPositionsVariables>(
MARKET_POSITIONS_QUERY,
{
pollInterval: 15000,
variables: { partyId },
skip: !partyId,
}
);
const markets =
data?.party?.positionsConnection?.edges
?.filter((nodes) => nodes.node.market.id === marketId)
.map((nodes) => nodes.node) || [];
return markets.length
? markets.reduce(
(agg, item) => {
const balance = item.market.accounts?.reduce(
(acagg, account) => acagg.plus(account.balance || 0),
new BigNumber(0)
);
if (balance) {
agg.balanceSum = agg.balanceSum.plus(balance);
agg.openVolume = agg.openVolume.plus(item.openVolume);
}
return agg;
},
{ openVolume: new BigNumber(0), balanceSum: new BigNumber(0) }
)
: null;
};

View File

@ -0,0 +1,39 @@
import { BigNumber } from 'bignumber.js';
import type { Order } from '@vegaprotocol/orders';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import type { PartyBalanceQuery } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
import { useSettlementAccount } from './use-settlement-account';
import { VegaWalletOrderSide } from '@vegaprotocol/wallet';
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
interface Props {
order: Order;
market: DealTicketQuery_market;
partyData?: PartyBalanceQuery;
}
const useOrderCloseOut = ({ order, market, partyData }: Props): string => {
const account = useSettlementAccount(
market.tradableInstrument.instrument.product.settlementAsset.id,
partyData?.party?.accounts || []
);
if (account?.balance && market.depth.lastTrade) {
const price = new BigNumber(
addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
);
const balance = new BigNumber(
addDecimal(account.balance, account.asset.decimals)
);
const { size, side } = order;
const bigOne = new BigNumber(1);
return formatNumber(
side === VegaWalletOrderSide.Buy
? bigOne.minus(balance.div(price.times(size))).times(price)
: bigOne.plus(balance.div(price.times(size))).times(price),
market.decimalPlaces
);
}
return ' - ';
};
export default useOrderCloseOut;

View File

@ -0,0 +1,112 @@
import type { Order } from '@vegaprotocol/orders';
import { gql, useQuery } from '@apollo/client';
import type {
EstimateOrder,
EstimateOrderVariables,
} from './__generated__/estimateOrder';
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
import { OrderTimeInForce, OrderType, Side } from '@vegaprotocol/types';
import {
VegaWalletOrderSide,
VegaWalletOrderTimeInForce,
VegaWalletOrderType,
} from '@vegaprotocol/wallet';
import { addDecimal, formatNumber } from '@vegaprotocol/react-helpers';
import useMarketPositions from './use-market-positions';
import { BigNumber } from 'bignumber.js';
export const ESTIMATE_ORDER_QUERY = gql`
query EstimateOrder(
$marketId: ID!
$partyId: ID!
$price: String
$size: String!
$side: Side!
$timeInForce: OrderTimeInForce!
$expiration: String
$type: OrderType!
) {
estimateOrder(
marketId: $marketId
partyId: $partyId
price: $price
size: $size
side: $side
timeInForce: $timeInForce
expiration: $expiration
type: $type
) {
totalFeeAmount
marginLevels {
initialLevel
}
}
}
`;
interface Props {
order: Order;
market: DealTicketQuery_market;
partyId: string;
}
const times: Record<VegaWalletOrderTimeInForce, OrderTimeInForce> = {
[VegaWalletOrderTimeInForce.GTC]: OrderTimeInForce.GTC,
[VegaWalletOrderTimeInForce.GTT]: OrderTimeInForce.GTT,
[VegaWalletOrderTimeInForce.IOC]: OrderTimeInForce.IOC,
[VegaWalletOrderTimeInForce.FOK]: OrderTimeInForce.FOK,
[VegaWalletOrderTimeInForce.GFN]: OrderTimeInForce.GFN,
[VegaWalletOrderTimeInForce.GFA]: OrderTimeInForce.GFA,
};
const types: Record<VegaWalletOrderType, OrderType> = {
[VegaWalletOrderType.Market]: OrderType.Market,
[VegaWalletOrderType.Limit]: OrderType.Limit,
};
const useOrderMargin = ({ order, market, partyId }: Props) => {
const marketPositions = useMarketPositions({ marketId: market.id, partyId });
console.log('marketPositions', marketPositions);
console.log(
'marketPositions.openVolume',
marketPositions?.openVolume.toNumber()
);
console.log(
'marketPositions.balanceSum',
marketPositions?.balanceSum.toNumber()
);
const { data } = useQuery<EstimateOrder, EstimateOrderVariables>(
ESTIMATE_ORDER_QUERY,
{
variables: {
marketId: market.id,
partyId,
price: market.depth.lastTrade?.price,
size: order.size + (marketPositions?.openVolume.toNumber() || 0),
side: order.side === VegaWalletOrderSide.Buy ? Side.Buy : Side.Sell,
timeInForce: times[order.timeInForce],
type: types[order.type],
},
skip: !partyId || !market.id || !order.size,
}
);
if (data?.estimateOrder.marginLevels.initialLevel) {
return formatNumber(
Math.max(
0,
new BigNumber(
addDecimal(
data.estimateOrder.marginLevels.initialLevel,
market.decimalPlaces
)
)
.minus(marketPositions?.balanceSum.toNumber() || 0)
.toNumber()
),
market.decimalPlaces
);
}
return ' - ';
};
export default useOrderMargin;

View File

@ -44,7 +44,7 @@ export const DealTicketManager = ({
);
};
const getDialogTitle = (status?: OrderStatus): string | undefined => {
export const getDialogTitle = (status?: OrderStatus): string | undefined => {
if (!status) {
return;
}
@ -63,7 +63,7 @@ const getDialogTitle = (status?: OrderStatus): string | undefined => {
}
};
const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
export const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
if (!status) {
return;
}
@ -81,7 +81,7 @@ const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
}
};
const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => {
export const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => {
if (!status) {
return;
}

View File

@ -45,8 +45,6 @@ const vegaCustomClassesLite = plugin(function ({ addUtilities }) {
backgroundColor: 'rgba(0, 143, 74, 0.1)',
border: `1px solid ${theme.colors.darkerGreen}`,
color: theme.colors.darkerGreen,
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
'&:hover': {
backgroundColor: theme.colors.darkerGreen,
color: theme.colors.white.DEFAULT,
@ -68,8 +66,6 @@ const vegaCustomClassesLite = plugin(function ({ addUtilities }) {
'.sellButton': {
textTransform: 'uppercase',
textDecoration: 'none',
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
backgroundColor: 'rgba(255, 8, 126, 0.1)',
border: `1px solid ${theme.colors.pink}`,
color: theme.colors.pink,