feat(positions): filter closed markets in positions table (#4569)
This commit is contained in:
parent
ab4f4e9084
commit
6d130c9cfc
@ -94,7 +94,11 @@ const MainGrid = memo(
|
|||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
<Tabs storageKey="console-trade-grid-bottom">
|
<Tabs storageKey="console-trade-grid-bottom">
|
||||||
<Tab id="positions" name={t('Positions')}>
|
<Tab
|
||||||
|
id="positions"
|
||||||
|
name={t('Positions')}
|
||||||
|
menu={<TradingViews.positions.menu />}
|
||||||
|
>
|
||||||
<TradingViews.positions.component />
|
<TradingViews.positions.component />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
|
@ -17,6 +17,7 @@ import type { OrderContainerProps } from '../../components/orders-container';
|
|||||||
import { OrdersContainer } from '../../components/orders-container';
|
import { OrdersContainer } from '../../components/orders-container';
|
||||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||||
import { AccountsMenu } from '../../components/accounts-menu';
|
import { AccountsMenu } from '../../components/accounts-menu';
|
||||||
|
import { PositionsMenu } from '../../components/positions-menu';
|
||||||
|
|
||||||
type MarketDependantView =
|
type MarketDependantView =
|
||||||
| typeof CandlesChartContainer
|
| typeof CandlesChartContainer
|
||||||
@ -57,7 +58,11 @@ export const TradingViews = {
|
|||||||
label: 'Trades',
|
label: 'Trades',
|
||||||
component: requiresMarket(TradesContainer),
|
component: requiresMarket(TradesContainer),
|
||||||
},
|
},
|
||||||
positions: { label: 'Positions', component: PositionsContainer },
|
positions: {
|
||||||
|
label: 'Positions',
|
||||||
|
component: PositionsContainer,
|
||||||
|
menu: PositionsMenu,
|
||||||
|
},
|
||||||
activeOrders: {
|
activeOrders: {
|
||||||
label: 'Active',
|
label: 'Active',
|
||||||
component: (props: OrderContainerProps) => (
|
component: (props: OrderContainerProps) => (
|
||||||
|
@ -5,6 +5,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
|
|||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
|
import type { StateCreator } from 'zustand';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
@ -13,6 +14,7 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
|
|||||||
const onMarketClick = useMarketClickHandler(true);
|
const onMarketClick = useMarketClickHandler(true);
|
||||||
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
|
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
|
||||||
|
|
||||||
|
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
|
||||||
const gridStore = usePositionsStore((store) => store.gridStore);
|
const gridStore = usePositionsStore((store) => store.gridStore);
|
||||||
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
|
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
|
||||||
const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore);
|
const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore);
|
||||||
@ -40,12 +42,35 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
|
|||||||
onMarketClick={onMarketClick}
|
onMarketClick={onMarketClick}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
gridProps={gridStoreCallbacks}
|
gridProps={gridStoreCallbacks}
|
||||||
|
showClosed={showClosed}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const usePositionsStore = create<DataGridSlice>()(
|
type PositionsStoreSlice = {
|
||||||
persist(createDataGridSlice, {
|
showClosedMarkets: boolean;
|
||||||
|
toggleClosedMarkets: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPositionStoreSlice: StateCreator<PositionsStoreSlice> = (set) => ({
|
||||||
|
showClosedMarkets: false,
|
||||||
|
toggleClosedMarkets: () => {
|
||||||
|
set((curr) => {
|
||||||
|
return {
|
||||||
|
showClosedMarkets: !curr.showClosedMarkets,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const usePositionsStore = create<PositionsStoreSlice & DataGridSlice>()(
|
||||||
|
persist(
|
||||||
|
(...args) => ({
|
||||||
|
...createPositionStoreSlice(...args),
|
||||||
|
...createDataGridSlice(...args),
|
||||||
|
}),
|
||||||
|
{
|
||||||
name: 'vega_positions_store',
|
name: 'vega_positions_store',
|
||||||
})
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
1
apps/trading/components/positions-menu/index.ts
Normal file
1
apps/trading/components/positions-menu/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './positions-menu';
|
18
apps/trading/components/positions-menu/positions-menu.tsx
Normal file
18
apps/trading/components/positions-menu/positions-menu.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { usePositionsStore } from '../positions-container';
|
||||||
|
|
||||||
|
export const PositionsMenu = () => {
|
||||||
|
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
|
||||||
|
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
|
||||||
|
return (
|
||||||
|
<TradingButton
|
||||||
|
intent={Intent.Primary}
|
||||||
|
size="extra-small"
|
||||||
|
data-testid="open-transfer"
|
||||||
|
onClick={toggle}
|
||||||
|
>
|
||||||
|
{showClosed ? t('Hide closed markets') : t('Show closed markets')}
|
||||||
|
</TradingButton>
|
||||||
|
);
|
||||||
|
};
|
@ -2,7 +2,12 @@ import * as Schema from '@vegaprotocol/types';
|
|||||||
import type { Account } from '@vegaprotocol/accounts';
|
import type { Account } from '@vegaprotocol/accounts';
|
||||||
import type { MarketWithData } from '@vegaprotocol/markets';
|
import type { MarketWithData } from '@vegaprotocol/markets';
|
||||||
import type { PositionFieldsFragment } from './__generated__/Positions';
|
import type { PositionFieldsFragment } from './__generated__/Positions';
|
||||||
import { getMetrics, rejoinPositionData } from './positions-data-providers';
|
import type { Position } from './positions-data-providers';
|
||||||
|
import {
|
||||||
|
getMetrics,
|
||||||
|
preparePositions,
|
||||||
|
rejoinPositionData,
|
||||||
|
} from './positions-data-providers';
|
||||||
import { PositionStatus } from '@vegaprotocol/types';
|
import { PositionStatus } from '@vegaprotocol/types';
|
||||||
|
|
||||||
const accounts = [
|
const accounts = [
|
||||||
@ -223,4 +228,29 @@ describe('getMetrics && rejoinPositionData', () => {
|
|||||||
);
|
);
|
||||||
expect(metrics[1].status).toEqual(positions[1].positionStatus);
|
expect(metrics[1].status).toEqual(positions[1].positionStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sorts and filters positions', () => {
|
||||||
|
const createPosition = (override?: Partial<Position>) =>
|
||||||
|
({
|
||||||
|
marketState: Schema.MarketState.STATE_ACTIVE,
|
||||||
|
marketCode: 'a',
|
||||||
|
...override,
|
||||||
|
} as Position);
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
createPosition(),
|
||||||
|
createPosition({
|
||||||
|
marketCode: 'c',
|
||||||
|
marketState: Schema.MarketState.STATE_CANCELLED,
|
||||||
|
}),
|
||||||
|
createPosition({ marketCode: 'd' }),
|
||||||
|
createPosition({ marketCode: 'b' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const withoutClosed = preparePositions(data, false);
|
||||||
|
expect(withoutClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'd']);
|
||||||
|
|
||||||
|
const withClosed = preparePositions(data, true);
|
||||||
|
expect(withClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -41,6 +41,7 @@ export interface Position {
|
|||||||
marketId: string;
|
marketId: string;
|
||||||
marketCode: string;
|
marketCode: string;
|
||||||
marketTradingMode: Schema.MarketTradingMode;
|
marketTradingMode: Schema.MarketTradingMode;
|
||||||
|
marketState: Schema.MarketState;
|
||||||
markPrice: string | undefined;
|
markPrice: string | undefined;
|
||||||
notional: string | undefined;
|
notional: string | undefined;
|
||||||
openVolume: string;
|
openVolume: string;
|
||||||
@ -119,6 +120,7 @@ export const getMetrics = (
|
|||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
marketCode: market.tradableInstrument.instrument.code,
|
marketCode: market.tradableInstrument.instrument.code,
|
||||||
marketTradingMode: market.tradingMode,
|
marketTradingMode: market.tradingMode,
|
||||||
|
marketState: market.state,
|
||||||
markPrice: marketData ? marketData.markPrice : undefined,
|
markPrice: marketData ? marketData.markPrice : undefined,
|
||||||
notional: notional
|
notional: notional
|
||||||
? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
|
? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
|
||||||
@ -248,6 +250,26 @@ export const rejoinPositionData = (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const preparePositions = (metrics: Position[], showClosed: boolean) => {
|
||||||
|
return sortBy(metrics, 'marketCode').filter((p) => {
|
||||||
|
if (showClosed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
|
Schema.MarketState.STATE_PENDING,
|
||||||
|
Schema.MarketState.STATE_SUSPENDED,
|
||||||
|
].includes(p.marketState)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const positionsMarketsProvider = makeDerivedDataProvider<
|
export const positionsMarketsProvider = makeDerivedDataProvider<
|
||||||
string[],
|
string[],
|
||||||
never,
|
never,
|
||||||
@ -265,7 +287,7 @@ export const positionsMarketsProvider = makeDerivedDataProvider<
|
|||||||
export const positionsMetricsProvider = makeDerivedDataProvider<
|
export const positionsMetricsProvider = makeDerivedDataProvider<
|
||||||
Position[],
|
Position[],
|
||||||
Position[],
|
Position[],
|
||||||
PositionsQueryVariables & { marketIds: string[] }
|
PositionsQueryVariables & { marketIds: string[]; showClosed: boolean }
|
||||||
>(
|
>(
|
||||||
[
|
[
|
||||||
(callback, client, variables) =>
|
(callback, client, variables) =>
|
||||||
@ -281,10 +303,10 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
|
|||||||
marketIds: variables.marketIds,
|
marketIds: variables.marketIds,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
([positions, accounts, marketsData]) => {
|
([positions, accounts, marketsData], variables) => {
|
||||||
const positionsData = rejoinPositionData(positions, marketsData);
|
const positionsData = rejoinPositionData(positions, marketsData);
|
||||||
const metrics = getMetrics(positionsData, accounts as Account[] | null);
|
const metrics = getMetrics(positionsData, accounts as Account[] | null);
|
||||||
return sortBy(metrics, 'marketCode');
|
return preparePositions(metrics, variables.showClosed);
|
||||||
},
|
},
|
||||||
(data, delta, previousData) =>
|
(data, delta, previousData) =>
|
||||||
data.filter((row) => {
|
data.filter((row) => {
|
||||||
|
@ -16,6 +16,7 @@ interface PositionsManagerProps {
|
|||||||
onMarketClick?: (marketId: string) => void;
|
onMarketClick?: (marketId: string) => void;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
gridProps?: ReturnType<typeof useDataGridEvents>;
|
gridProps?: ReturnType<typeof useDataGridEvents>;
|
||||||
|
showClosed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PositionsManager = ({
|
export const PositionsManager = ({
|
||||||
@ -23,6 +24,7 @@ export const PositionsManager = ({
|
|||||||
onMarketClick,
|
onMarketClick,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
gridProps,
|
gridProps,
|
||||||
|
showClosed = false,
|
||||||
}: PositionsManagerProps) => {
|
}: PositionsManagerProps) => {
|
||||||
const { pubKeys, pubKey } = useVegaWallet();
|
const { pubKeys, pubKey } = useVegaWallet();
|
||||||
const create = useVegaTransactionStore((store) => store.create);
|
const create = useVegaTransactionStore((store) => store.create);
|
||||||
@ -60,7 +62,7 @@ export const PositionsManager = ({
|
|||||||
|
|
||||||
const { data, error } = useDataProvider({
|
const { data, error } = useDataProvider({
|
||||||
dataProvider: positionsMetricsProvider,
|
dataProvider: positionsMetricsProvider,
|
||||||
variables: { partyIds, marketIds: marketIds || [] },
|
variables: { partyIds, marketIds: marketIds || [], showClosed },
|
||||||
skip: !marketIds,
|
skip: !marketIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,7 +70,7 @@ export const PositionsManager = ({
|
|||||||
<PositionsTable
|
<PositionsTable
|
||||||
pubKey={pubKey}
|
pubKey={pubKey}
|
||||||
pubKeys={pubKeys}
|
pubKeys={pubKeys}
|
||||||
rowData={error ? [] : data}
|
rowData={data}
|
||||||
onMarketClick={onMarketClick}
|
onMarketClick={onMarketClick}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
|
@ -27,6 +27,7 @@ const singleRow: Position = {
|
|||||||
marketId: 'string',
|
marketId: 'string',
|
||||||
marketCode: 'ETHBTC.QM21',
|
marketCode: 'ETHBTC.QM21',
|
||||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||||
|
marketState: Schema.MarketState.STATE_ACTIVE,
|
||||||
markPrice: '123',
|
markPrice: '123',
|
||||||
notional: '12300',
|
notional: '12300',
|
||||||
openVolume: '100',
|
openVolume: '100',
|
||||||
|
@ -473,8 +473,8 @@ export const PositionsTable = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
minWidth: 75,
|
minWidth: 55,
|
||||||
maxWidth: 75,
|
maxWidth: 55,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
];
|
];
|
||||||
|
@ -51,4 +51,8 @@
|
|||||||
|
|
||||||
- **Must** be able to see if your realised PnL was affected by loss socialisation (<a name="7004-POSI-018" href="#7004-POSI-018">7004-POSI-018</a>)
|
- **Must** be able to see if your realised PnL was affected by loss socialisation (<a name="7004-POSI-018" href="#7004-POSI-018">7004-POSI-018</a>)
|
||||||
|
|
||||||
- **Must** Must be able to see what type of product the position was opened on (<a name="7004-POSI-019" href="#7004-POSI-019">7004-POSI-019</a>)
|
- **Must** be able to see what type of product the position was opened on (<a name="7004-POSI-019" href="#7004-POSI-019">7004-POSI-019</a>)
|
||||||
|
|
||||||
|
- **Must** not see positions on markets which are closed (<a name="7004-POSI-020" href="#7004-POSI-020">7004-POSI-020</a>)
|
||||||
|
|
||||||
|
- **Must** be able to show closed markets (<a name="7004-POSI-021" href="#7004-POSI-021">7004-POSI-021</a>)
|
||||||
|
Loading…
Reference in New Issue
Block a user