16 KiB
16 KiB
MoexVibe — MVP Design Specification
Date: 2026-06-13 Status: Draft Author: AI Assistant (Staff+ Architect)
1. Product Requirements Document (PRD)
1.1 Product Vision
Веб-приложение для анализа ценных бумаг Московской биржи (MOEX). Позволяет искать акции и облигации, просматривать их текущие параметры, доходность, дивиденды/купоны, историю торгов и графики цены.
1.2 Target Audience
Частные инвесторы, интересующиеся российским фондовым рынком. B2C, read-only сервис без аутентификации.
1.3 MVP Scope
| In Scope | Out of Scope |
|---|---|
| Поиск по инструментам (акции + облигации) | Аутентификация / пользователи |
| Карточка акции (цена, капитализация, дивиденды, график) | Портфели и избранное |
| Карточка облигации (ISIN, купон, НКД, YTM, дюрация, график) | Сравнение инструментов |
| Часовые и дневные свечи (1 год истории) | Финансовая отчётность (МСФО/РСБУ) |
| Docker-ready деплой | Фьючерсы, опционы, валютный рынок |
| Документация (ADR, API, архитектура) | Real-time данные (WebSocket) |
| Экспорт данных | |
| Мобильные приложения |
1.4 User Stories
- US-001: Пользователь вводит текст в поиск и видит подходящие акции и облигации
- US-002: Пользователь переходит на карточку акции, видит текущую цену, изменение, капитализацию
- US-003: Пользователь видит историю дивидендных выплат по акции
- US-004: Пользователь видит график цены (дневные и часовые свечи) за последний год
- US-005: Пользователь переходит на карточку облигации, видит ISIN, номинал, купон, дату погашения
- US-006: Пользователь видит НКД, доходность к погашению, дюрацию
- US-007: Пользователь видит график цены облигации за последний год
1.5 Non-Functional Requirements
- Максимальное время ответа API: < 500ms (p95) при попадании в кеш
- Доступность: бэкенд stateless, готов к масштабированию
- Задержка данных: 15 минут (бесплатный MOEX ISS)
- Все ответы API кешируются на бэкенде
2. Domain Model
Security (abstract base)
├── secid: string — "SBER"
├── isin: string — "RU0009029540"
├── name: string — полное наименование
├── shortName: string — краткое наименование
├── latName: string | null
├── listLevel: 1 | 2 | 3 — уровень листинга
├── issueSize: number — объём выпуска
├── faceValue: number — номинал
├── faceUnit: string — "RUB" / "USD" / "SUR"
├── issueDate: string — ISO date
├── isQualifiedInvestors: boolean
├── morningSession: boolean
├── eveningSession: boolean
│
├── Stock
│ ├── type: "common_share" | "preferred_share"
│ ├── marketData: StockMarketData
│ │ ├── price: number
│ │ ├── change: number
│ │ ├── changePercent: number
│ │ ├── open: number
│ │ ├── high: number
│ │ ├── low: number
│ │ ├── volume: number
│ │ ├── value: number
│ │ └── issueCapitalization: number
│ └── dividends: Dividend[]
│ ├── registryCloseDate: string (ISO date)
│ ├── value: number (RUB per share)
│ └── currency: string
│
└── Bond
├── matDate: string — дата погашения
├── couponValue: number — размер купона (RUB)
├── couponPercent: number|null — ставка купона (%)
├── couponPeriod: number — дней между купонами
├── nextCoupon: string (ISO date)
├── accruedInt: number — НКД
├── bondType: string — "Фикс" / "Флоатер" / "Линкер" / etc
├── bondSubType: string — "До погашения" / "До оферты"
├── offerDate: string | null
├── buybackDate: string | null
├── marketData: BondMarketData
│ ├── price: number — % от номинала
│ ├── yieldToMaturity: number | null
│ ├── duration: number | null
│ ├── open: number
│ ├── high: number | null
│ ├── low: number | null
│ └── volume: number
└── history: BondHistoryEntry[]
├── date: string
├── closePrice: number
├── yieldClose: number
└── duration: number
Candle
├── open: number
├── high: number
├── low: number
├── close: number
├── volume: number
├── value: number
├── begin: string (ISO datetime)
└── end: string (ISO datetime)
SearchResult
├── secid: string
├── isin: string
├── shortName: string
├── type: "share" | "bond"
├── listLevel: number
├── currency: string | null
└── price: number | null
3. Architecture
┌──────────────┐ ┌─────────────────────────────────────┐ ┌──────────────┐
│ Browser │────▶│ NestJS Backend │────▶│ MOEX ISS │
│ (React SPA) │◀────│ (1 instance, stateless) │◀────│ (HTTP) │
└──────────────┘ │ │ └──────────────┘
│ ┌─────────────────────────────────┐ │
│ │ Core Modules │ │
│ │ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ Search │ │ SharesModule │ │ │
│ │ │ Module │ │ (stocks) │ │ │
│ │ └──────────┘ └───────────────┘ │ │
│ │ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ Bonds │ │ Candles │ │ │
│ │ │ Module │ │ Module │ │ │
│ │ └──────────┘ └───────────────┘ │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Shared Infrastructure │ │
│ │ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ MOEX │ │ Cache │ │ │
│ │ │ Client │ │ Manager │ │ │
│ │ │(rate-ltd│ │ (in-memory) │ │ │
│ │ │ circuit │ │ │ │ │
│ │ │breaker) │ │ │ │ │
│ │ └──────────┘ └───────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
3.1 Caching Strategy
| Data Type | Backend TTL | Frontend staleTime | Notes |
|---|---|---|---|
| MarketData | 900s (15m) | 900s | Совпадает с задержкой MOEX |
| History | 3600s (1h) | 3600s | Обновляется раз в день после торгов |
| Candles | 3600s (1h) | 3600s | Дневные свечи не меняются intraday |
| Security spec | 86400s (1d) | 86400s | Редко меняется |
| Search results | 3600s (1h) | 3600s | |
| Dividends | 86400s (1d) | 86400s |
3.2 Error Handling Strategy
- Все MOEX-ошибки маппятся в нормализованный
ErrorResponse - При пустых данных (выходные, праздники) —
200сnullзначениями, не404 - Circuit breaker: при 5+ последовательных ошибках MOEX — пауза 30s
- Graceful degradation: если MOEX недоступен, возвращать последние кешированные данные
3.3 Rate Limiting
- MOEX Client: очередь запросов ~10 req/s (конфигурируется)
- При превышении — автоматическое ожидание в очереди
- Отсутствие внешнего rate limiter на уровне NestJS (приложение публичное, read-only)
4. API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/health |
Healthcheck |
| GET | /api/v1/securities/search |
Поиск по инструментам |
| GET | /api/v1/securities/shares/:secid |
Спецификация акции |
| GET | /api/v1/securities/shares/:secid/marketdata |
Рыночные данные акции |
| GET | /api/v1/securities/shares/:secid/candles |
Свечи (1h/24h) |
| GET | /api/v1/securities/shares/:secid/history |
Дневная история |
| GET | /api/v1/securities/shares/:secid/dividends |
Дивиденды |
| GET | /api/v1/securities/bonds/:secid |
Спецификация облигации |
| GET | /api/v1/securities/bonds/:secid/marketdata |
Рыночные данные облигации |
| GET | /api/v1/securities/bonds/:secid/candles |
Свечи (1h/24h) |
| GET | /api/v1/securities/bonds/:secid/history |
Дневная история |
5. Repo Structure
moex-vibe/
├── apps/
│ ├── backend/
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── app.module.ts
│ │ │ ├── common/
│ │ │ │ ├── dto/
│ │ │ │ │ ├── api-response.dto.ts
│ │ │ │ │ └── pagination.dto.ts
│ │ │ │ ├── filters/
│ │ │ │ │ └── http-exception.filter.ts
│ │ │ │ ├── interceptors/
│ │ │ │ │ ├── logging.interceptor.ts
│ │ │ │ │ └── transform.interceptor.ts
│ │ │ │ └── middleware/
│ │ │ │ └── request-logging.middleware.ts
│ │ │ ├── config/
│ │ │ │ └── configuration.ts
│ │ │ └── modules/
│ │ │ ├── moex-client/
│ │ │ ├── cache/
│ │ │ ├── securities/
│ │ │ ├── shares/
│ │ │ ├── bonds/
│ │ │ └── health/
│ │ ├── test/
│ │ └── package.json
│ └── frontend/
│ ├── src/
│ │ ├── api/ # openapi-typescript generated
│ │ │ ├── types.ts
│ │ │ └── client.ts
│ │ ├── hooks/
│ │ │ ├── useStock.ts
│ │ │ ├── useBond.ts
│ │ │ ├── useSearch.ts
│ │ │ ├── useCandles.ts
│ │ │ └── useDividends.ts
│ │ ├── pages/
│ │ │ ├── HomePage.tsx
│ │ │ ├── StockPage.tsx
│ │ │ └── BondPage.tsx
│ │ ├── components/
│ │ │ ├── Layout/
│ │ │ ├── SearchBar/
│ │ │ ├── SecurityCard/
│ │ │ ├── PriceChart/
│ │ │ ├── StockDetails/
│ │ │ └── BondDetails/
│ │ ├── routes.tsx
│ │ └── main.tsx
│ └── package.json
├── docs/
│ ├── superpowers/specs/
│ ├── architecture/
│ │ ├── adr/
│ │ ├── diagrams/
│ │ └── domain-model.md
│ ├── openapi/
│ │ └── openapi.yaml
│ └── website/ (Docusaurus — post-MVP)
├── package.json
├── tsconfig.base.json
└── .gitignore
6. Sprint Plan
Sprint 1 — Backend Foundation
- NestJS project init + npm workspaces
- ConfigurationModule, Logging, Global filters
- MoexClientModule (rate-limited HTTP client)
- CacheModule (cache-manager in-memory)
- HealthController
- ESLint, Prettier, tsconfig
Sprint 2 — Securities API
- SecuritiesModule (search)
- SharesModule (spec + marketdata + dividends)
- OpenAPI decorators
- Unit tests
Sprint 3 — Bonds + History
- BondsModule (spec + marketdata)
- CandlesModule (shares + bonds)
- HistoryModule
- OpenAPI decorators
- Unit tests
Sprint 4 — Frontend Foundation
- Vite + React + TypeScript init
- openapi-typescript codegen
- TanStack Query + React Router
- Layout, SearchBar, HomePage
Sprint 5 — Frontend Details
- StockPage (price block, dividends table, chart)
- BondPage (bond details, chart)
- PriceChart component (lightweight-charts)
- Loading/error states
Sprint 6 — Docs + Infrastructure
- ADRs, architecture docs
- OpenAPI spec
- Dockerfile + docker-compose
- README
7. Risks
| Risk | Impact | Mitigation |
|---|---|---|
| MOEX ISS API changes | High | MoexClient abstraction layer |
| Rate limiting by MOEX | Medium | p-queue + circuit breaker |
| Empty data on holidays/weekends | Low | Graceful null handling |
| Large search result sets | Low | Server-side limit + frontend debounce |
8. Post-MVP Roadmap
- Финансовая отчётность (MOEX CCI — IFRS/RAS)
- Аутентификация, портфели, избранное
- Сравнение инструментов (multi-chart)
- Фьючерсы и опционы
- Экспорт (CSV, PDF)
- WebSocket для real-time данных
- Redis для масштабирования