6.2 KiB
PortfolioModule
PortfolioModule позволяет пользователям создавать и вести виртуальные инвестиционные портфели для
аналитики и отслеживания позиций.
Ручные портфели и брокерские портфели
PortfolioModule остаётся доменом ручных виртуальных портфелей. Брокерские счета T-Bank Invest
экспортируются отдельным TBankModule под /api/v1/broker/* и не сохраняются как записи
Portfolio.
Обзор
- Backend:
PortfolioModule(apps/backend/src/modules/portfolio/) - Frontend: защищённые страницы
/portfoliosи/portfolios/:id - База данных: модели
PortfolioиPosition(Prisma + SQLite)
API endpoints
Все endpoints требуют JWT authentication через JwtAuthGuard.
| Endpoint | Method | Описание |
|---|---|---|
/api/v1/portfolios |
GET | Список портфелей пользователя |
/api/v1/portfolios |
POST | Создать портфель |
/api/v1/portfolios/:id |
GET | Детали портфеля с обогащёнными позициями и analytics summary |
/api/v1/portfolios/:id/analytics |
GET | Детальная аналитика портфеля и PnL |
/api/v1/portfolios/:id |
PATCH | Обновить портфель (name, description, currency) |
/api/v1/portfolios/:id |
DELETE | Удалить портфель вместе с позициями |
/api/v1/portfolios/:id/positions |
POST | Добавить позицию (buyPrice, buyDate, notes, tags) |
/api/v1/portfolios/:id/positions/:positionId |
PATCH | Обновить позицию |
/api/v1/portfolios/:id/positions/:positionId |
DELETE | Удалить позицию |
Доменная модель
Portfolio
├── id, userId
├── name, description, currency
└── positions[]
Position
├── id, portfolioId
├── secid (MOEX security ID)
├── type ("share" | "bond") — auto-detected from MOEX
├── quantity (integer, >= 0)
├── buyPrice (optional, % for bonds, RUB for shares)
├── buyDate (optional)
├── notes (free text)
├── tags (JSON)
└── enriched: currentPrice, currentValue, weightPercent
+ analytics: totalCost, pnl, pnlPercent, totalReturn, totalReturnPercent
+ share: change, changePercent, shortName
+ bond: yieldToMaturity, duration, couponValue, couponPercent,
nextCouponDate, matDate, accruedInt, bid, offer,
couponPeriod, bondType, offerDate
Analytics и PnL
Backend рассчитывает метрики доходности для каждой позиции и портфеля целиком:
- Total cost:
buyPrice * quantity; для облигаций:(buyPrice / 100) * faceValue * quantity. - Unrealized PnL:
currentValue - totalCost. - PnL %:
(PnL / totalCost) * 100. - Weighted yield: средняя доходность портфеля с учётом весов позиций.
Analytics доступны через endpoint /analytics или как объект summary в детальном response
/portfolios/:id.
Определение типа инструмента
При добавлении позиции backend получает из MOEX getSecurityDescription(secid). Если
desc.group === 'stock_bonds', тип позиции становится bond; иначе используется share. От типа
зависит путь обогащения при чтении портфеля.
Расчёт цены акций
Для акций: currentPrice = marketData.last, currentValue = price * quantity; change и
changePercent берутся из lastChange и lastChangePrcnt.
Расчёт цены облигаций
Для облигаций MOEX возвращает цены в процентах от номинала: например, 98.5 означает 98.5% от 1000 RUB.
currentPrice = marketData.last(% of face value)currentValue = (price / 100) * faceValue * quantity
Обогащение облигаций также получает:
| Поле | Источник | Описание |
|---|---|---|
yieldToMaturity |
getBondMarketData().yield |
YTM (%) |
duration |
getBondMarketData().duration |
Modified duration в годах |
couponValue |
getBondData().couponValue |
Размер купона в RUB |
couponPercent |
getBondData().couponPercent |
Ставка купона (%) |
couponPeriod |
getBondData().couponPeriod |
Дней между выплатами |
nextCouponDate |
getBondData().nextCoupon |
Дата следующего купона |
matDate |
getBondData().matDate |
Дата погашения |
offerDate |
getBondData().offerDate |
Дата досрочного погашения |
accruedInt |
getBondData().accruedInt |
НКД на облигацию (RUB) |
bondType |
getBondData().bondType |
"ОФЗ", "Корпоративная", etc. |
bid / offer |
getBondMarketData() |
Текущий bid/ask в % от номинала |
Выбор MOEX board
Backend использует следующие MOEX boards по умолчанию:
- Акции:
TQBR(Т+: Акции — безадрес.) - Облигации:
TQCB(Т+: Корпоративные облигации — безадрес.)
Некоторые облигации, в первую очередь ОФЗ, торгуются на board TQOB. Если на запрошенном board нет
торговых данных, backend выбирает любой board с ненулевой ценой LAST. Данные кешируются со
стандартным market data TTL (900s).
Будущие этапы
- Positions v2 — pie chart, filtering
- Transactions — buy/sell history, average cost basis
- Analytics — portfolio value chart, XIRR, benchmark comparison
- Dividends & Coupons — aggregated forecast calendar
- Corporate Actions — splits, consolidations auto-adjustment