feat: add broker frontend api hooks
This commit is contained in:
parent
b848d256d1
commit
50bff3dbe7
27
apps/frontend/src/api/broker.test.ts
Normal file
27
apps/frontend/src/api/broker.test.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { getBrokerOperations } from './broker';
|
||||||
|
|
||||||
|
describe('broker api', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serializes operations query parameters', async () => {
|
||||||
|
vi.spyOn(globalThis, 'fetch').mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: {
|
||||||
|
data: { accountId: 'acc-1', items: [], nextCursor: null, hasNext: false, asOf: 'now' },
|
||||||
|
meta: { fromCache: false, cachedAt: null },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
} as Response);
|
||||||
|
|
||||||
|
await getBrokerOperations('acc-1', { cursor: 'c1', limit: 50 });
|
||||||
|
|
||||||
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('/api/v1/broker/accounts/acc-1/operations?cursor=c1&limit=50'),
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
51
apps/frontend/src/api/broker.ts
Normal file
51
apps/frontend/src/api/broker.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { request } from './client';
|
||||||
|
import type {
|
||||||
|
ApiResponseMeta,
|
||||||
|
BrokerAccount,
|
||||||
|
BrokerOperationsPage,
|
||||||
|
BrokerPortfolio,
|
||||||
|
} from './responses';
|
||||||
|
|
||||||
|
export type BrokerOperationQuery = {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
cursor?: string;
|
||||||
|
limit?: number;
|
||||||
|
instrumentId?: string;
|
||||||
|
operationTypes?: string;
|
||||||
|
state?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getBrokerAccounts(): Promise<{
|
||||||
|
data: BrokerAccount[];
|
||||||
|
meta: ApiResponseMeta;
|
||||||
|
}> {
|
||||||
|
return request<BrokerAccount[]>('/api/v1/broker/accounts');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBrokerPortfolio(accountId: string): Promise<{
|
||||||
|
data: BrokerPortfolio;
|
||||||
|
meta: ApiResponseMeta;
|
||||||
|
}> {
|
||||||
|
return request<BrokerPortfolio>(
|
||||||
|
`/api/v1/broker/accounts/${encodeURIComponent(accountId)}/portfolio`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBrokerOperations(
|
||||||
|
accountId: string,
|
||||||
|
query: BrokerOperationQuery = {},
|
||||||
|
): Promise<{ data: BrokerOperationsPage; meta: ApiResponseMeta }> {
|
||||||
|
return request<BrokerOperationsPage>(
|
||||||
|
`/api/v1/broker/accounts/${encodeURIComponent(accountId)}/operations`,
|
||||||
|
{
|
||||||
|
from: query.from,
|
||||||
|
to: query.to,
|
||||||
|
cursor: query.cursor,
|
||||||
|
limit: query.limit ? String(query.limit) : undefined,
|
||||||
|
instrumentId: query.instrumentId,
|
||||||
|
operationTypes: query.operationTypes,
|
||||||
|
state: query.state,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -245,3 +245,94 @@ export interface ScreenerResult {
|
|||||||
pageSize: number;
|
pageSize: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BrokerMoney {
|
||||||
|
currency: string;
|
||||||
|
units: string;
|
||||||
|
nano: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrokerAccount {
|
||||||
|
id: string;
|
||||||
|
type: 'brokerage' | 'iis';
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
openedAt: string | null;
|
||||||
|
accessLevel: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrokerPosition {
|
||||||
|
figi: string | null;
|
||||||
|
instrumentUid: string | null;
|
||||||
|
positionUid: string | null;
|
||||||
|
ticker: string | null;
|
||||||
|
classCode: string | null;
|
||||||
|
instrumentType: string | null;
|
||||||
|
name: string | null;
|
||||||
|
quantity: number | null;
|
||||||
|
blockedLots: number | null;
|
||||||
|
currentPrice: BrokerMoney | null;
|
||||||
|
currentValue: BrokerMoney | null;
|
||||||
|
averagePositionPrice: BrokerMoney | null;
|
||||||
|
expectedYieldPercent: number | null;
|
||||||
|
dailyYield: BrokerMoney | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrokerPortfolio {
|
||||||
|
account: BrokerAccount;
|
||||||
|
totals: {
|
||||||
|
shares: BrokerMoney | null;
|
||||||
|
bonds: BrokerMoney | null;
|
||||||
|
etf: BrokerMoney | null;
|
||||||
|
currencies: BrokerMoney | null;
|
||||||
|
futures: BrokerMoney | null;
|
||||||
|
options: BrokerMoney | null;
|
||||||
|
structuredProducts: BrokerMoney | null;
|
||||||
|
dfa: BrokerMoney | null;
|
||||||
|
portfolio: BrokerMoney | null;
|
||||||
|
};
|
||||||
|
yields: {
|
||||||
|
expectedPercent: number | null;
|
||||||
|
daily: BrokerMoney | null;
|
||||||
|
dailyPercent: number | null;
|
||||||
|
};
|
||||||
|
cash: BrokerMoney[];
|
||||||
|
blockedCash: BrokerMoney[];
|
||||||
|
positions: BrokerPosition[];
|
||||||
|
asOf: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BrokerOperationCategory = 'trade' | 'income' | 'tax' | 'fee' | 'transfer' | 'other';
|
||||||
|
|
||||||
|
export interface BrokerOperation {
|
||||||
|
cursor: string | null;
|
||||||
|
accountId: string;
|
||||||
|
id: string | null;
|
||||||
|
parentOperationId: string | null;
|
||||||
|
date: string | null;
|
||||||
|
type: string;
|
||||||
|
category: BrokerOperationCategory;
|
||||||
|
description: string | null;
|
||||||
|
state: string | null;
|
||||||
|
instrumentUid: string | null;
|
||||||
|
figi: string | null;
|
||||||
|
ticker: string | null;
|
||||||
|
classCode: string | null;
|
||||||
|
instrumentType: string | null;
|
||||||
|
payment: BrokerMoney | null;
|
||||||
|
price: BrokerMoney | null;
|
||||||
|
commission: BrokerMoney | null;
|
||||||
|
yield: BrokerMoney | null;
|
||||||
|
accruedInt: BrokerMoney | null;
|
||||||
|
quantity: number | null;
|
||||||
|
quantityDone: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrokerOperationsPage {
|
||||||
|
accountId: string;
|
||||||
|
items: BrokerOperation[];
|
||||||
|
nextCursor: string | null;
|
||||||
|
hasNext: boolean;
|
||||||
|
asOf: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -383,6 +383,57 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
'/api/v1/broker/accounts': {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get open T-Bank brokerage and IIS accounts */
|
||||||
|
get: operations['TBankController_getAccounts'];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
'/api/v1/broker/accounts/{accountId}/portfolio': {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get T-Bank broker account portfolio with cash and positions */
|
||||||
|
get: operations['TBankController_getPortfolio'];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
'/api/v1/broker/accounts/{accountId}/operations': {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get paginated T-Bank broker account operations */
|
||||||
|
get: operations['TBankController_getOperations'];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
@ -689,6 +740,109 @@ export interface components {
|
|||||||
data: components['schemas']['AnalyticsResponseDto'];
|
data: components['schemas']['AnalyticsResponseDto'];
|
||||||
meta: components['schemas']['PortfolioResponseMetaDto'];
|
meta: components['schemas']['PortfolioResponseMetaDto'];
|
||||||
};
|
};
|
||||||
|
BrokerAccountResponseDto: {
|
||||||
|
id: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
type: 'brokerage' | 'iis';
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
openedAt: Record<string, never> | null;
|
||||||
|
accessLevel: Record<string, never> | null;
|
||||||
|
};
|
||||||
|
BrokerResponseMetaDto: {
|
||||||
|
cachedAt: Record<string, never> | null;
|
||||||
|
fromCache: boolean;
|
||||||
|
};
|
||||||
|
BrokerAccountsEnvelopeDto: {
|
||||||
|
data: components['schemas']['BrokerAccountResponseDto'][];
|
||||||
|
meta: components['schemas']['BrokerResponseMetaDto'];
|
||||||
|
};
|
||||||
|
BrokerMoneyDto: {
|
||||||
|
currency: string;
|
||||||
|
units: string;
|
||||||
|
nano: number;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
BrokerPortfolioTotalsDto: {
|
||||||
|
shares: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
bonds: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
etf: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
currencies: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
futures: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
options: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
structuredProducts: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
dfa: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
portfolio: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
};
|
||||||
|
BrokerPortfolioYieldsDto: {
|
||||||
|
expectedPercent: Record<string, never> | null;
|
||||||
|
daily: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
dailyPercent: Record<string, never> | null;
|
||||||
|
};
|
||||||
|
BrokerPositionResponseDto: {
|
||||||
|
figi: Record<string, never> | null;
|
||||||
|
instrumentUid: Record<string, never> | null;
|
||||||
|
positionUid: Record<string, never> | null;
|
||||||
|
ticker: Record<string, never> | null;
|
||||||
|
classCode: Record<string, never> | null;
|
||||||
|
instrumentType: Record<string, never> | null;
|
||||||
|
name: Record<string, never> | null;
|
||||||
|
quantity: Record<string, never> | null;
|
||||||
|
blockedLots: Record<string, never> | null;
|
||||||
|
currentPrice: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
currentValue: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
averagePositionPrice: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
expectedYieldPercent: Record<string, never> | null;
|
||||||
|
dailyYield: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
};
|
||||||
|
BrokerPortfolioResponseDto: {
|
||||||
|
account: components['schemas']['BrokerAccountResponseDto'];
|
||||||
|
totals: components['schemas']['BrokerPortfolioTotalsDto'];
|
||||||
|
yields: components['schemas']['BrokerPortfolioYieldsDto'];
|
||||||
|
cash: components['schemas']['BrokerMoneyDto'][];
|
||||||
|
blockedCash: components['schemas']['BrokerMoneyDto'][];
|
||||||
|
positions: components['schemas']['BrokerPositionResponseDto'][];
|
||||||
|
asOf: string;
|
||||||
|
};
|
||||||
|
BrokerPortfolioEnvelopeDto: {
|
||||||
|
data: components['schemas']['BrokerPortfolioResponseDto'];
|
||||||
|
meta: components['schemas']['BrokerResponseMetaDto'];
|
||||||
|
};
|
||||||
|
BrokerOperationResponseDto: {
|
||||||
|
cursor: Record<string, never> | null;
|
||||||
|
accountId: string;
|
||||||
|
id: Record<string, never> | null;
|
||||||
|
parentOperationId: Record<string, never> | null;
|
||||||
|
date: Record<string, never> | null;
|
||||||
|
type: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
category: 'trade' | 'income' | 'tax' | 'fee' | 'transfer' | 'other';
|
||||||
|
description: Record<string, never> | null;
|
||||||
|
state: Record<string, never> | null;
|
||||||
|
instrumentUid: Record<string, never> | null;
|
||||||
|
figi: Record<string, never> | null;
|
||||||
|
ticker: Record<string, never> | null;
|
||||||
|
classCode: Record<string, never> | null;
|
||||||
|
instrumentType: Record<string, never> | null;
|
||||||
|
payment: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
price: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
commission: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
yield: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
accruedInt: components['schemas']['BrokerMoneyDto'] | null;
|
||||||
|
quantity: Record<string, never> | null;
|
||||||
|
quantityDone: Record<string, never> | null;
|
||||||
|
};
|
||||||
|
BrokerOperationsPageResponseDto: {
|
||||||
|
accountId: string;
|
||||||
|
items: components['schemas']['BrokerOperationResponseDto'][];
|
||||||
|
nextCursor: Record<string, never> | null;
|
||||||
|
hasNext: boolean;
|
||||||
|
asOf: string;
|
||||||
|
};
|
||||||
|
BrokerOperationsEnvelopeDto: {
|
||||||
|
data: components['schemas']['BrokerOperationsPageResponseDto'];
|
||||||
|
meta: components['schemas']['BrokerResponseMetaDto'];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
responses: never;
|
responses: never;
|
||||||
parameters: never;
|
parameters: never;
|
||||||
@ -1300,4 +1454,73 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
TBankController_getAccounts: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['BrokerAccountsEnvelopeDto'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
TBankController_getPortfolio: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
accountId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['BrokerPortfolioEnvelopeDto'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
TBankController_getOperations: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
cursor?: string;
|
||||||
|
limit?: number;
|
||||||
|
instrumentId?: string;
|
||||||
|
operationTypes?: string;
|
||||||
|
state?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
accountId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['BrokerOperationsEnvelopeDto'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
41
apps/frontend/src/hooks/useBrokerAccounts.test.tsx
Normal file
41
apps/frontend/src/hooks/useBrokerAccounts.test.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import { type ReactNode } from 'react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { getBrokerAccounts } from '../api/broker';
|
||||||
|
import { useBrokerAccounts } from './useBrokerAccounts';
|
||||||
|
|
||||||
|
vi.mock('../api/broker', () => ({
|
||||||
|
getBrokerAccounts: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function createWrapper() {
|
||||||
|
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||||
|
|
||||||
|
return function Wrapper({ children }: { children: ReactNode }) {
|
||||||
|
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('useBrokerAccounts', () => {
|
||||||
|
it('returns broker accounts from API', async () => {
|
||||||
|
vi.mocked(getBrokerAccounts).mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 'acc-1',
|
||||||
|
type: 'brokerage',
|
||||||
|
name: 'Broker',
|
||||||
|
status: 'ACCOUNT_STATUS_OPEN',
|
||||||
|
openedAt: null,
|
||||||
|
accessLevel: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: { fromCache: false, cachedAt: null },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useBrokerAccounts(), { wrapper: createWrapper() });
|
||||||
|
|
||||||
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||||
|
expect(result.current.data?.[0].name).toBe('Broker');
|
||||||
|
});
|
||||||
|
});
|
||||||
13
apps/frontend/src/hooks/useBrokerAccounts.ts
Normal file
13
apps/frontend/src/hooks/useBrokerAccounts.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getBrokerAccounts } from '../api/broker';
|
||||||
|
import type { BrokerAccount } from '../api/responses';
|
||||||
|
|
||||||
|
export function useBrokerAccounts() {
|
||||||
|
return useQuery<BrokerAccount[]>({
|
||||||
|
queryKey: ['broker', 'accounts'],
|
||||||
|
queryFn: async () => (await getBrokerAccounts()).data,
|
||||||
|
staleTime: 3_600_000,
|
||||||
|
retry: 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
17
apps/frontend/src/hooks/useBrokerOperations.ts
Normal file
17
apps/frontend/src/hooks/useBrokerOperations.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getBrokerOperations, type BrokerOperationQuery } from '../api/broker';
|
||||||
|
import type { BrokerOperationsPage } from '../api/responses';
|
||||||
|
|
||||||
|
export function useBrokerOperations(
|
||||||
|
accountId: string | undefined,
|
||||||
|
query: BrokerOperationQuery = {},
|
||||||
|
) {
|
||||||
|
return useQuery<BrokerOperationsPage>({
|
||||||
|
queryKey: ['broker', 'operations', accountId, query],
|
||||||
|
enabled: Boolean(accountId),
|
||||||
|
queryFn: async () => (await getBrokerOperations(accountId!, query)).data,
|
||||||
|
staleTime: 300_000,
|
||||||
|
retry: 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
14
apps/frontend/src/hooks/useBrokerPortfolio.ts
Normal file
14
apps/frontend/src/hooks/useBrokerPortfolio.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getBrokerPortfolio } from '../api/broker';
|
||||||
|
import type { BrokerPortfolio } from '../api/responses';
|
||||||
|
|
||||||
|
export function useBrokerPortfolio(accountId: string | undefined) {
|
||||||
|
return useQuery<BrokerPortfolio>({
|
||||||
|
queryKey: ['broker', 'portfolio', accountId],
|
||||||
|
enabled: Boolean(accountId),
|
||||||
|
queryFn: async () => (await getBrokerPortfolio(accountId!)).data,
|
||||||
|
staleTime: 60_000,
|
||||||
|
retry: 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user