refactor(frontend): migrate entities/stock to FSD

This commit is contained in:
Sergey Krylov 2026-06-20 14:02:53 +03:00
parent 93d18f1218
commit dd727f9fb2
18 changed files with 423 additions and 105 deletions

View File

@ -5,13 +5,8 @@ export {
setOnUnauthorized, setOnUnauthorized,
getHealth, getHealth,
searchSecurities, searchSecurities,
getShare,
getShareMarketData,
getShareDividends,
getShareHistory,
getBond, getBond,
getBondMarketData, getBondMarketData,
getBondHistory, getBondHistory,
getShareCandles,
getBondCandles, getBondCandles,
} from '../shared/api/client'; } from '../shared/api/client';

View File

@ -0,0 +1,53 @@
import { request } from '@/shared/api/client';
import type {
ApiResponseMeta,
ShareResponse,
StockMarketData,
DividendItem,
ShareHistoryItem,
CandleItem,
} from '@/shared/api/responses';
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`);
}
export function getShareMarketData(
secid: string,
): Promise<{ data: StockMarketData; meta: ApiResponseMeta }> {
return request<StockMarketData>(
`/api/v1/securities/shares/${encodeURIComponent(secid)}/marketdata`,
);
}
export function getShareDividends(
secid: string,
): Promise<{ data: DividendItem[]; meta: ApiResponseMeta }> {
return request<DividendItem[]>(
`/api/v1/securities/shares/${encodeURIComponent(secid)}/dividends`,
);
}
export function getShareHistory(
secid: string,
from: string,
till: string,
): Promise<{ data: ShareHistoryItem[]; meta: ApiResponseMeta }> {
return request<ShareHistoryItem[]>(
`/api/v1/securities/shares/${encodeURIComponent(secid)}/history`,
{ from, till },
);
}
export function getShareCandles(
secid: string,
interval: '1h' | '24h',
from: string,
till: string,
): Promise<{ data: CandleItem[]; meta: ApiResponseMeta }> {
return request<CandleItem[]>(`/api/v1/securities/shares/${encodeURIComponent(secid)}/candles`, {
interval,
from,
till,
});
}

View File

@ -0,0 +1,10 @@
export { useStock } from './model/useStock';
export { useStockCandles } from './model/useStockCandles';
export { useStockDividends } from './model/useStockDividends';
export {
getShare,
getShareMarketData,
getShareDividends,
getShareHistory,
getShareCandles,
} from './api/stockApi';

View File

@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/react-query';
import { getShare } from '../api/stockApi';
import type { ShareResponse } from '@/shared/api/responses';
export function useStock(secid: string) {
return useQuery<ShareResponse>({
queryKey: ['stock', secid],
queryFn: async () => {
const res = await getShare(secid);
return res.data;
},
staleTime: 900_000,
});
}

View File

@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react'; import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { server } from '../test/server'; import { server } from '../../../test/server';
import { useStockCandles } from './useStockCandles'; import { useStockCandles } from './useStockCandles';
import { type ReactNode } from 'react'; import { type ReactNode } from 'react';

View File

@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/react-query';
import { getShareCandles } from '../api/stockApi';
import type { CandleItem } from '@/shared/api/responses';
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
return useQuery<CandleItem[]>({
queryKey: ['stockCandles', secid, interval, from, till],
queryFn: async () => {
const res = await getShareCandles(secid, interval, from, till);
return res.data;
},
staleTime: 3600_000,
});
}

View File

@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react'; import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { server } from '../test/server'; import { server } from '../../../test/server';
import { useStockDividends } from './useStockDividends'; import { useStockDividends } from './useStockDividends';
import { type ReactNode } from 'react'; import { type ReactNode } from 'react';

View File

@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/react-query';
import { getShareDividends } from '../api/stockApi';
import type { DividendItem } from '@/shared/api/responses';
export function useStockDividends(secid: string) {
return useQuery<DividendItem[]>({
queryKey: ['stockDividends', secid],
queryFn: async () => {
const res = await getShareDividends(secid);
return res.data;
},
staleTime: 86400_000,
});
}

View File

@ -1,14 +1 @@
import { useQuery } from '@tanstack/react-query'; export { useStock } from '../entities/stock/model/useStock';
import { getShare } from '@/shared/api/client';
import type { ShareResponse } from '@/shared/api/responses';
export function useStock(secid: string) {
return useQuery<ShareResponse>({
queryKey: ['stock', secid],
queryFn: async () => {
const res = await getShare(secid);
return res.data;
},
staleTime: 900_000,
});
}

View File

@ -1,14 +1 @@
import { useQuery } from '@tanstack/react-query'; export { useStockCandles } from '../entities/stock/model/useStockCandles';
import { getShareCandles } from '@/shared/api/client';
import type { CandleItem } from '@/shared/api/responses';
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
return useQuery<CandleItem[]>({
queryKey: ['stockCandles', secid, interval, from, till],
queryFn: async () => {
const res = await getShareCandles(secid, interval, from, till);
return res.data;
},
staleTime: 3600_000,
});
}

View File

@ -1,14 +1 @@
import { useQuery } from '@tanstack/react-query'; export { useStockDividends } from '../entities/stock/model/useStockDividends';
import { getShareDividends } from '@/shared/api/client';
import type { DividendItem } from '@/shared/api/responses';
export function useStockDividends(secid: string) {
return useQuery<DividendItem[]>({
queryKey: ['stockDividends', secid],
queryFn: async () => {
const res = await getShareDividends(secid);
return res.data;
},
staleTime: 86400_000,
});
}

View File

@ -1,7 +1,5 @@
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useStock } from '../hooks/useStock'; import { useStock, useStockCandles, useStockDividends } from '../entities/stock';
import { useStockCandles } from '../hooks/useStockCandles';
import { useStockDividends } from '../hooks/useStockDividends';
import { StockDetails } from '../components/StockDetails'; import { StockDetails } from '../components/StockDetails';
import { PriceChart } from '../components/PriceChart'; import { PriceChart } from '../components/PriceChart';

View File

@ -2,10 +2,6 @@ import type {
ApiEnvelope, ApiEnvelope,
ApiResponseMeta, ApiResponseMeta,
AuthResponse, AuthResponse,
ShareResponse,
StockMarketData,
DividendItem,
ShareHistoryItem,
BondResponse, BondResponse,
BondMarketData, BondMarketData,
BondHistoryItem, BondHistoryItem,
@ -151,37 +147,6 @@ export function searchSecurities(
}); });
} }
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`);
}
export function getShareMarketData(
secid: string,
): Promise<{ data: StockMarketData; meta: ApiResponseMeta }> {
return request<StockMarketData>(
`/api/v1/securities/shares/${encodeURIComponent(secid)}/marketdata`,
);
}
export function getShareDividends(
secid: string,
): Promise<{ data: DividendItem[]; meta: ApiResponseMeta }> {
return request<DividendItem[]>(
`/api/v1/securities/shares/${encodeURIComponent(secid)}/dividends`,
);
}
export function getShareHistory(
secid: string,
from: string,
till: string,
): Promise<{ data: ShareHistoryItem[]; meta: ApiResponseMeta }> {
return request<ShareHistoryItem[]>(
`/api/v1/securities/shares/${encodeURIComponent(secid)}/history`,
{ from, till },
);
}
export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> { export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> {
return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`); return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`);
} }
@ -205,19 +170,6 @@ export function getBondHistory(
); );
} }
export function getShareCandles(
secid: string,
interval: '1h' | '24h',
from: string,
till: string,
): Promise<{ data: CandleItem[]; meta: ApiResponseMeta }> {
return request<CandleItem[]>(`/api/v1/securities/shares/${encodeURIComponent(secid)}/candles`, {
interval,
from,
till,
});
}
export function getBondCandles( export function getBondCandles(
secid: string, secid: string,
interval: '1h' | '24h', interval: '1h' | '24h',

View File

@ -5,14 +5,9 @@ export {
setOnUnauthorized, setOnUnauthorized,
getHealth, getHealth,
searchSecurities, searchSecurities,
getShare,
getShareMarketData,
getShareDividends,
getShareHistory,
getBond, getBond,
getBondMarketData, getBondMarketData,
getBondHistory, getBondHistory,
getShareCandles,
getBondCandles, getBondCandles,
} from './client'; } from './client';
export type { export type {

View File

@ -0,0 +1,178 @@
# FSD Entities Migration — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** Перенести stock, bond и portfolio домены в FSD-сущности (`entities/`) без изменения пользовательского поведения.
**Architecture:** Миграция по паттерну broker-пилота: создаётся FSD-каркас entity → код переносится → старые файлы становятся shim-ами → потребители переключаются на новые импорты → общий клиент очищается.
**Tech Stack:** React 18, TypeScript, React Router v6, TanStack Query v5, Vitest, Testing Library.
---
## Карта файлов
### Stock entity
- Create `apps/frontend/src/entities/stock/index.ts`
- Create `apps/frontend/src/entities/stock/api/stockApi.ts`
- Create `apps/frontend/src/entities/stock/model/useStock.ts`
- Create `apps/frontend/src/entities/stock/model/useStockCandles.ts`
- Create `apps/frontend/src/entities/stock/model/useStockDividends.ts`
### Bond entity
- Create `apps/frontend/src/entities/bond/index.ts`
- Create `apps/frontend/src/entities/bond/api/bondApi.ts`
- Create `apps/frontend/src/entities/bond/model/useBond.ts`
- Create `apps/frontend/src/entities/bond/model/useBondCandles.ts`
### Portfolio entity
- Create `apps/frontend/src/entities/portfolio/index.ts`
- Create `apps/frontend/src/entities/portfolio/api/portfolioApi.ts`
- Create `apps/frontend/src/entities/portfolio/model/usePortfolio.ts`
- Create `apps/frontend/src/entities/portfolio/model/usePortfolios.ts`
- Create `apps/frontend/src/entities/portfolio/model/usePortfolioAnalytics.ts`
- Create `apps/frontend/src/entities/portfolio/model/usePortfolioMutations.ts`
- Create `apps/frontend/src/entities/portfolio/model/usePositionMutations.ts`
### Shared cleanup
- Modify `apps/frontend/src/shared/api/client.ts` — удалить 9 stock/bond API-функций
- Modify `apps/frontend/src/shared/api/index.ts` — удалить re-exports stock/bond функций
- Modify `apps/frontend/src/api/client.ts` — удалить re-exports stock/bond функций (shim)
### Consumer migration
- Modify `apps/frontend/src/pages/StockPage.tsx` — переключить импорты на `../../entities/stock`
- Modify `apps/frontend/src/pages/BondPage.tsx` — переключить импорты на `../../entities/bond`
- Modify `apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx` — на `../../entities/portfolio`
- Modify `apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx` — на `../../entities/portfolio`
### Shim files (обратно-совместимые re-export)
- Modify `apps/frontend/src/hooks/useStock.ts` → shim
- Modify `apps/frontend/src/hooks/useStockCandles.ts` → shim
- Modify `apps/frontend/src/hooks/useStockDividends.ts` → shim
- Modify `apps/frontend/src/hooks/useBond.ts` → shim
- Modify `apps/frontend/src/hooks/useBondCandles.ts` → shim
- Modify `apps/frontend/src/hooks/usePortfolio.ts` → shim
- Modify `apps/frontend/src/hooks/usePortfolios.ts` → shim
- Modify `apps/frontend/src/hooks/usePortfolioAnalytics.ts` → shim
- Modify `apps/frontend/src/hooks/usePortfolioMutations.ts` → shim
- Modify `apps/frontend/src/hooks/usePositionMutations.ts` → shim
- Modify `apps/frontend/src/api/portfolio.ts` → shim
### Test relocation
- Move `apps/frontend/src/hooks/useStock.test.tsx``apps/frontend/src/entities/stock/model/useStock.test.tsx`
- Move `apps/frontend/src/hooks/useStockCandles.test.tsx``apps/frontend/src/entities/stock/model/useStockCandles.test.tsx`
- Move `apps/frontend/src/hooks/useStockDividends.test.tsx``apps/frontend/src/entities/stock/model/useStockDividends.test.tsx`
- Move `apps/frontend/src/hooks/useBond.test.tsx``apps/frontend/src/entities/bond/model/useBond.test.tsx`
- Move `apps/frontend/src/hooks/useBondCandles.test.tsx``apps/frontend/src/entities/bond/model/useBondCandles.test.tsx`
---
## Task 1: entities/stock/
### Файлы
- Create: `src/entities/stock/index.ts`
- Create: `src/entities/stock/api/stockApi.ts`
- Create: `src/entities/stock/model/useStock.ts`
- Create: `src/entities/stock/model/useStockCandles.ts`
- Create: `src/entities/stock/model/useStockDividends.ts`
- Move: `src/hooks/useStock.test.tsx``src/entities/stock/model/useStock.test.tsx`
- Move: `src/hooks/useStockCandles.test.tsx``src/entities/stock/model/useStockCandles.test.tsx`
- Move: `src/hooks/useStockDividends.test.tsx``src/entities/stock/model/useStockDividends.test.tsx`
### Связанные изменения
- Modify: `src/shared/api/client.ts` — удалить `getShare`, `getShareMarketData`, `getShareDividends`, `getShareHistory`, `getShareCandles`
- Modify: `src/shared/api/index.ts` — удалить re-exports stock функций
- Modify: `src/api/client.ts` — удалить re-exports stock функций
- Modify: `src/hooks/useStock.ts` — заменить на shim
- Modify: `src/hooks/useStockCandles.ts` — заменить на shim
- Modify: `src/hooks/useStockDividends.ts` — заменить на shim
- Modify: `src/pages/StockPage.tsx` — переключить импорты
- [ ] **Step 1: Создать api/stockApi.ts с функциями запросов stock**
- [ ] **Step 2: Создать model хуки для stock (useStock, useStockCandles, useStockDividends)**
- [ ] **Step 3: Создать entities/stock/index.ts barrel**
- [ ] **Step 4: Переместить тесты stock в model/ и обновить импорты**
- [ ] **Step 5: Удалить stock функции из shared/api/client.ts и обновить barrely (shared/api/index.ts, api/client.ts)**
- [ ] **Step 6: Превратить hooks/useStock* в shim-файлы**
- [ ] **Step 7: Переключить StockPage.tsx на импорты из ../../entities/stock**
- [ ] **Step 8: Запустить тесты: npm test -w apps/frontend -- useStock**
- [ ] **Step 9: Закоммитить**
## Task 2: entities/bond/
### Файлы
- Create: `src/entities/bond/index.ts`
- Create: `src/entities/bond/api/bondApi.ts`
- Create: `src/entities/bond/model/useBond.ts`
- Create: `src/entities/bond/model/useBondCandles.ts`
- Move: `src/hooks/useBond.test.tsx``src/entities/bond/model/useBond.test.tsx`
- Move: `src/hooks/useBondCandles.test.tsx``src/entities/bond/model/useBondCandles.test.tsx`
### Связанные изменения
- Modify: `src/shared/api/client.ts` — удалить `getBond`, `getBondMarketData`, `getBondHistory`, `getBondCandles`
- Modify: `src/shared/api/index.ts` — удалить re-exports bond функций
- Modify: `src/api/client.ts` — удалить re-exports bond функций
- Modify: `src/hooks/useBond.ts` — заменить на shim
- Modify: `src/hooks/useBondCandles.ts` — заменить на shim
- Modify: `src/pages/BondPage.tsx` — переключить импорты
- [ ] **Step 1: Создать api/bondApi.ts с функциями запросов bond**
- [ ] **Step 2: Создать model хуки для bond (useBond, useBondCandles)**
- [ ] **Step 3: Создать entities/bond/index.ts barrel**
- [ ] **Step 4: Переместить тесты bond в model/ и обновить импорты**
- [ ] **Step 5: Удалить bond функции из shared/api/client.ts и обновить barrely**
- [ ] **Step 6: Превратить hooks/useBond* в shim-файлы**
- [ ] **Step 7: Переключить BondPage.tsx на импорты из ../../entities/bond**
- [ ] **Step 8: Запустить тесты: npm test -w apps/frontend -- useBond**
- [ ] **Step 9: Закоммитить**
## Task 3: entities/portfolio/
### Файлы
- Create: `src/entities/portfolio/index.ts`
- Create: `src/entities/portfolio/api/portfolioApi.ts`
- Create: `src/entities/portfolio/model/usePortfolio.ts`
- Create: `src/entities/portfolio/model/usePortfolios.ts`
- Create: `src/entities/portfolio/model/usePortfolioAnalytics.ts`
- Create: `src/entities/portfolio/model/usePortfolioMutations.ts`
- Create: `src/entities/portfolio/model/usePositionMutations.ts`
### Связанные изменения
- Modify: `src/api/portfolio.ts` — заменить на shim (re-export из entities/portfolio/api/portfolioApi)
- Modify: `src/hooks/usePortfolio.ts` — заменить на shim
- Modify: `src/hooks/usePortfolios.ts` — заменить на shim
- Modify: `src/hooks/usePortfolioAnalytics.ts` — заменить на shim
- Modify: `src/hooks/usePortfolioMutations.ts` — заменить на shim
- Modify: `src/hooks/usePositionMutations.ts` — заменить на shim
- Modify: `src/pages/portfolios/PortfolioDetailPage.tsx` — переключить импорты
- Modify: `src/pages/portfolios/PortfoliosListPage.tsx` — переключить импорты
- [ ] **Step 1: Создать api/portfolioApi.ts — перенести все функции из api/portfolio.ts**
- [ ] **Step 2: Создать model хуки для portfolio (5 файлов)**
- [ ] **Step 3: Создать entities/portfolio/index.ts barrel**
- [ ] **Step 4: Превратить api/portfolio.ts в shim**
- [ ] **Step 5: Превратить hooks/usePortfolio* и usePositionMutations в shim-файлы**
- [ ] **Step 6: Переключить PortfolioDetailPage.tsx и PortfoliosListPage.tsx на импорты из ../../entities/portfolio**
- [ ] **Step 7: Запустить build: npm run build -w apps/frontend**
- [ ] **Step 8: Закоммитить**
## Task 4: Финальная проверка
- [ ] **Step 1: Полный прогон тестов: npm test -w apps/frontend**
- [ ] **Step 2: Линт: npm run lint -w apps/frontend**
- [ ] **Step 3: Сборка: npm run build -w apps/frontend**
- [ ] **Step 4: Проверить git diff --stat на предмет только целевых изменений**
- [ ] **Step 5: Обновить docs/features/fsd-entities-migration/tasks.md**

View File

@ -0,0 +1,81 @@
# FSD Entities Migration
Дата: 2026-06-20
Статус: в работе
## Контекст
Продолжается поэтапный переход frontend на Feature-Sliced Design (FSD). После успешного пилота
broker-домена (`frontend-fsd-broker-pilot`) и создания shared-слоя (`frontend-fsd-shared-layer`)
следующий шаг — миграция core domain entities: stock, bond, portfolio.
В текущей структуре их код размазан по техническим каталогам:
- API-функции акций и облигаций находятся в `shared/api/client.ts` (вместе с HTTP-инфраструктурой)
- API-функции портфелей находятся в `api/portfolio.ts` (в legacy-папке)
- TanStack Query хуки находятся в `hooks/` (5 файлов для portfolio + 3 для stock + 2 для bond)
- Типы уже централизованы в `shared/api/responses.ts`
Это усложняет навигацию, поддержку и дальнейшее масштабирование FSD-архитектуры.
## Цель
Перенести stock, bond и portfolio домены в FSD-сущности (`entities/`) по тому же паттерну, что и
broker-account/broker-position/broker-operation:
- `entities/stock/` — API-функции и query-хуки для акций
- `entities/bond/` — API-функции и query-хуки для облигаций
- `entities/portfolio/` — API-функции и query-хуки для портфелей
## Требования
### 1. Каждая сущность получает явную FSD-структуру
- `api/` — чистые функции запросов (без TanStack Query, только `request` вызовы)
- `model/` — TanStack Query хуки и бизнес-логика
- `index.ts` — barrel export (только re-exports, без логики)
### 2. API-функции выносятся из shared/api/client.ts
`shared/api/client.ts` должен содержать только инфраструктуру:
- `request` — базовый HTTP-клиент
- `setAccessToken`, `getAccessToken`, `setOnUnauthorized` — управление токенами
- `searchSecurities` — поиск (кросс-доменный)
- `getHealth` — health check
Все stock/bond API-функции переезжают в соответствующие entity `api/`.
### 3. Query-хуки переносятся из global hooks/
Все TanStack Query хуки для stock, bond и portfolio переезжают из `hooks/` в
`entities/{entity}/model/`.
### 4. Совместимость на время перехода
Старые файлы становятся re-export шимами, чтобы избежать big-bang переписывания.
Потребители (страницы) обновляются на новые FSD-импорты в рамках этой же фичи.
### 5. Тесты следуют за кодом
Unit-тесты и hook-тесты перемещаются рядом с новым расположением кода.
### 6. Поведение не меняется
Никакой функциональности не добавляется и не изменяется. Только перегруппировка кода.
## Ограничения
- Не затрагиваются `features/` и `app/` слои — они будут в следующих итерациях
- Не затрагиваются `useSearch`, `useScreener`, `api/auth.ts`, `api/screener.ts`
- Не меняются `shared/api/responses.ts` и generated types
- Не меняются backend-контракты
## Acceptance Criteria
- `entities/stock/` существует с api/model/index.ts, все тесты проходят
- `entities/bond/` существует с api/model/index.ts, все тесты проходят
- `entities/portfolio/` существует с api/model/index.ts
- `shared/api/client.ts` больше не содержит stock/bond API-функций
- Все страницы (StockPage, BondPage, PortfolioDetailPage, PortfoliosListPage) импортируют
из новых entity entrypoints
- Старые файлы стали re-export шимами
- Frontend lint, tests и build проходят

View File

@ -0,0 +1,53 @@
# FSD Entities Migration — задачи
Статус: запланировано
Подробные шаги, команды и expected results находятся в [plan.md](plan.md).
## Pre-flight
- [ ] Feature branch создана: `codex/fsd-entities-migration`
- [ ] spec.md написана
- [ ] plan.md написан
- [ ] tasks.md создан
- [ ] Все тесты проходят на текущем состоянии
## 1. entities/stock/
- [ ] Создать `entities/stock/api/stockApi.ts` с API-функциями stock
- [ ] Создать `entities/stock/model/useStock.ts`, `useStockCandles.ts`, `useStockDividends.ts`
- [ ] Создать `entities/stock/index.ts` barrel
- [ ] Переместить тесты в `entities/stock/model/`
- [ ] Удалить stock API из `shared/api/client.ts` и barrely
- [ ] Превратить `hooks/useStock*` в shim-файлы
- [ ] Переключить `StockPage.tsx` на новые импорты
- [ ] Тесты проходят
## 2. entities/bond/
- [ ] Создать `entities/bond/api/bondApi.ts` с API-функциями bond
- [ ] Создать `entities/bond/model/useBond.ts`, `useBondCandles.ts`
- [ ] Создать `entities/bond/index.ts` barrel
- [ ] Переместить тесты в `entities/bond/model/`
- [ ] Удалить bond API из `shared/api/client.ts` и barrely
- [ ] Превратить `hooks/useBond*` в shim-файлы
- [ ] Переключить `BondPage.tsx` на новые импорты
- [ ] Тесты проходят
## 3. entities/portfolio/
- [ ] Создать `entities/portfolio/api/portfolioApi.ts`
- [ ] Создать `entities/portfolio/model/` (5 хуков)
- [ ] Создать `entities/portfolio/index.ts` barrel
- [ ] Превратить `api/portfolio.ts` в shim
- [ ] Превратить `hooks/usePortfolio*` и `usePositionMutations` в shim-файлы
- [ ] Переключить страницы портфелей на новые импорты
- [ ] Build проходит
## 4. Финальная проверка
- [ ] Все тесты проходят
- [ ] Линт проходит
- [ ] Build проходит
- [ ] Только целевые изменения в diff
- [ ] Отмечено выполнение в tasks.md