moex-vibe/docs/superpowers/specs/2026-06-13-moex-vibe-design.md

339 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 для масштабирования