moex-vibe/apps/docs/docs/backend/portfolio.md

6.2 KiB
Raw Blame History

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).

Будущие этапы

  1. Positions v2 — pie chart, filtering
  2. Transactions — buy/sell history, average cost basis
  3. Analytics — portfolio value chart, XIRR, benchmark comparison
  4. Dividends & Coupons — aggregated forecast calendar
  5. Corporate Actions — splits, consolidations auto-adjustment