339 lines
16 KiB
Markdown
339 lines
16 KiB
Markdown
# 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
|
||
|
||
1. Финансовая отчётность (MOEX CCI — IFRS/RAS)
|
||
2. Аутентификация, портфели, избранное
|
||
3. Сравнение инструментов (multi-chart)
|
||
4. Фьючерсы и опционы
|
||
5. Экспорт (CSV, PDF)
|
||
6. WebSocket для real-time данных
|
||
7. Redis для масштабирования
|