From 143b4758c356f3a6ada9c2e8769c8effc1ca9ba1 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Mon, 15 Jun 2026 05:31:55 +0300 Subject: [PATCH] docs: refresh openapi contract artifacts --- apps/frontend/src/api/types.ts | 757 +++++++++++++++- docs/openapi/openapi.yaml | 1552 +++++++++++++++++++------------- 2 files changed, 1674 insertions(+), 635 deletions(-) diff --git a/apps/frontend/src/api/types.ts b/apps/frontend/src/api/types.ts index 0a51dc8..86eceb2 100644 --- a/apps/frontend/src/api/types.ts +++ b/apps/frontend/src/api/types.ts @@ -21,6 +21,92 @@ export interface paths { patch?: never; trace?: never; }; + '/api/v1/auth/register': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Register new user */ + post: operations['AuthController_register']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/auth/login': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Login with email and password */ + post: operations['AuthController_login']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/auth/refresh': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Refresh access token */ + post: operations['AuthController_refresh']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/auth/logout': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Logout user */ + post: operations['AuthController_logout']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/auth/me': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get current user profile */ + get: operations['AuthController_getProfile']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Update current user profile */ + patch: operations['AuthController_updateProfile']; + trace?: never; + }; '/api/v1/securities/search': { parameters: { query?: never; @@ -38,6 +124,23 @@ export interface paths { patch?: never; trace?: never; }; + '/api/v1/securities/screener': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Фильтр ценных бумаг по параметрам */ + get: operations['SecuritiesController_screener']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/v1/securities/shares/{secid}': { parameters: { query?: never; @@ -191,10 +294,316 @@ export interface paths { patch?: never; trace?: never; }; + '/api/v1/portfolios': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get all portfolios for current user */ + get: operations['PortfolioController_findAll']; + put?: never; + /** Create a new portfolio */ + post: operations['PortfolioController_create']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/portfolios/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get portfolio details with positions and prices */ + get: operations['PortfolioController_findOne']; + put?: never; + post?: never; + /** Delete portfolio */ + delete: operations['PortfolioController_remove']; + options?: never; + head?: never; + /** Update portfolio */ + patch: operations['PortfolioController_update']; + trace?: never; + }; + '/api/v1/portfolios/{id}/positions': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Add position to portfolio */ + post: operations['PortfolioController_addPosition']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/portfolios/{id}/positions/{positionId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Remove position from portfolio */ + delete: operations['PortfolioController_removePosition']; + options?: never; + head?: never; + /** Update position */ + patch: operations['PortfolioController_updatePosition']; + trace?: never; + }; + '/api/v1/portfolios/{id}/analytics': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get portfolio analytics with PnL */ + get: operations['PortfolioController_getAnalytics']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { - schemas: never; + schemas: { + RegisterDto: { + /** @example user@example.com */ + email: string; + /** @example securePass123 */ + password: string; + /** @example John */ + name?: string; + }; + LoginDto: { + /** @example user@example.com */ + email: string; + /** @example securePass123 */ + password: string; + }; + UpdateProfileDto: { + /** @example John Doe */ + name?: string; + }; + ScreenerItemDto: { + /** @example SBER */ + secid: string; + /** @example Сбербанк */ + shortName: string; + /** @example RU0009029540 */ + isin: string; + /** @enum {string} */ + type: 'share' | 'bond'; + /** @example 322.35 */ + price?: Record; + /** @example 1.15 */ + change?: Record; + /** @example 0.36 */ + changePercent?: Record; + /** @example 1925163 */ + volume: number; + /** @example 1 */ + listLevel: number; + /** @example 6958336818320 */ + capitalization?: Record; + /** @example 12.71 */ + yieldToMaturity?: Record; + /** @example 4.5 */ + duration?: Record; + /** @example 40.64 */ + couponValue?: Record; + /** @example 8.15 */ + couponPercent?: Record; + /** @example 29.48 */ + accruedInt?: Record; + /** @example 2027-02-03 */ + matDate?: Record; + /** @example ОФЗ-ПД */ + bondType?: Record; + }; + ScreenerResultDto: { + items: components['schemas']['ScreenerItemDto'][]; + total: number; + page: number; + pageSize: number; + totalPages: number; + }; + PortfolioListResponseDto: { + id: number; + name: string; + description?: Record; + /** @default RUB */ + currency: string; + createdAt: string; + updatedAt: string; + /** @description Total market value of all positions */ + totalValue: number; + /** @description Total number of positions */ + positionCount: number; + /** @description Number of share positions */ + shareCount: number; + /** @description Number of bond positions */ + bondCount: number; + }; + CreatePortfolioDto: { + /** @example Мой портфель */ + name: string; + /** @example Описание портфеля */ + description?: string; + /** + * @default RUB + * @enum {string} + */ + currency: 'RUB' | 'USD' | 'EUR' | 'CNY' | 'KZT' | 'BYN'; + }; + PositionWithPriceDto: { + id: number; + /** @example SBER */ + secid: string; + shortName?: Record; + /** + * @example share + * @enum {string} + */ + type: 'share' | 'bond'; + /** @example 10 */ + quantity: number; + buyPrice?: Record; + buyDate?: Record; + notes?: Record; + tags?: Record; + currentPrice?: Record; + totalCost?: Record; + currentValue?: Record; + weightPercent: number; + pnl?: Record; + pnlPercent?: Record; + dividendIncome?: Record; + totalReturn?: Record; + totalReturnPercent?: Record; + change?: Record; + changePercent?: Record; + yieldToMaturity?: Record; + duration?: Record; + couponValue?: Record; + couponPercent?: Record; + nextCouponDate?: Record; + matDate?: Record; + accruedInt?: Record; + bid?: Record; + offer?: Record; + couponPeriod?: Record; + bondType?: Record; + offerDate?: Record; + }; + PortfolioSummaryDto: { + totalInvested: number; + totalValue: number; + totalPnl: number; + totalPnlPercent: Record; + totalDividends: number; + totalReturn: number; + totalReturnPercent: Record; + positionCount: number; + weightedYield: Record; + }; + PortfolioDetailResponseDto: { + id: number; + name: string; + description?: Record; + /** @default RUB */ + currency: string; + createdAt: string; + updatedAt: string; + positions: components['schemas']['PositionWithPriceDto'][]; + totalValue: number; + analytics: components['schemas']['PortfolioSummaryDto']; + }; + UpdatePortfolioDto: { + /** @example Мой портфель */ + name?: string; + /** @example Обновлённое описание */ + description?: string; + /** + * @default RUB + * @enum {string} + */ + currency: 'RUB' | 'USD' | 'EUR' | 'CNY' | 'KZT' | 'BYN'; + }; + AddPositionDto: { + /** @example SBER */ + secid: string; + /** @example 10 */ + quantity: number; + /** @example 250.5 */ + buyPrice?: number; + /** @example 2026-06-01 */ + buyDate?: string; + /** @example Покупка на дип */ + notes?: string; + /** + * @example [ + * "DIVIDEND", + * "GROWTH" + * ] + * @enum {string} + */ + tags?: + | 'DIVIDEND' + | 'GROWTH' + | 'DEFENSIVE' + | 'SPECULATIVE' + | 'BOND' + | 'ETF' + | 'GOVERNMENT' + | 'CASH'; + }; + UpdatePositionDto: { + /** @example 15 */ + quantity?: number; + /** @example 260 */ + buyPrice?: number; + /** @example 2026-06-15 */ + buyDate?: string; + /** @example Докупка */ + notes?: string; + /** + * @example [ + * "DIVIDEND" + * ] + * @enum {string} + */ + tags?: + | 'DIVIDEND' + | 'GROWTH' + | 'DEFENSIVE' + | 'SPECULATIVE' + | 'BOND' + | 'ETF' + | 'GOVERNMENT' + | 'CASH'; + }; + }; responses: never; parameters: never; requestBodies: never; @@ -220,6 +629,120 @@ export interface operations { }; }; }; + AuthController_register: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['RegisterDto']; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + AuthController_login: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['LoginDto']; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + AuthController_refresh: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + AuthController_logout: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + AuthController_getProfile: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + AuthController_updateProfile: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['UpdateProfileDto']; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; SecuritiesController_search: { parameters: { query: { @@ -242,6 +765,49 @@ export interface operations { }; }; }; + SecuritiesController_screener: { + parameters: { + query: { + type: 'share' | 'bond'; + priceMin?: number; + priceMax?: number; + volumeMin?: number; + listLevel?: number; + changePercentMin?: number; + changePercentMax?: number; + capitalizationMin?: number; + yieldMin?: number; + yieldMax?: number; + durationMin?: number; + durationMax?: number; + couponMin?: number; + couponMax?: number; + couponPercentMin?: number; + couponPercentMax?: number; + maturityBefore?: string; + maturityAfter?: string; + bondType?: string; + sortBy?: string; + sortOrder?: string; + page?: number; + pageSize?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ScreenerResultDto']; + }; + }; + }; + }; SharesController_getShare: { parameters: { query?: never; @@ -427,4 +993,193 @@ export interface operations { }; }; }; + PortfolioController_findAll: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PortfolioListResponseDto'][]; + }; + }; + }; + }; + PortfolioController_create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CreatePortfolioDto']; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + PortfolioController_findOne: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PortfolioDetailResponseDto']; + }; + }; + }; + }; + PortfolioController_remove: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + PortfolioController_update: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['UpdatePortfolioDto']; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + PortfolioController_addPosition: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['AddPositionDto']; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + PortfolioController_removePosition: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + positionId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + PortfolioController_updatePosition: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + positionId: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['UpdatePositionDto']; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + PortfolioController_getAnalytics: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; } diff --git a/docs/openapi/openapi.yaml b/docs/openapi/openapi.yaml index aa3c486..c81835a 100644 --- a/docs/openapi/openapi.yaml +++ b/docs/openapi/openapi.yaml @@ -1,776 +1,1060 @@ -openapi: "3.0.3" -info: - title: MoexVibe API - description: | - API для анализа ценных бумаг Московской биржи. - Backend является единственной точкой доступа к MOEX ISS. - version: "1.0.0" - contact: - name: MoexVibe Team - -servers: - - url: http://localhost:3000/api/v1 - description: Local development - - url: https://api.moexvibe.example.com/api/v1 - description: Production - +openapi: 3.0.0 paths: - /health: + /api/v1/health: get: - operationId: healthCheck - tags: [Health] + operationId: HealthController_check summary: Проверка состояния сервиса + parameters: [] responses: - "200": - description: Сервис работает - content: - application/json: - schema: - $ref: "#/components/schemas/HealthResponse" - - /securities/search: + '200': + description: '' + tags: + - Health + /api/v1/auth/register: + post: + operationId: AuthController_register + summary: Register new user + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterDto' + responses: + '201': + description: '' + tags: + - Auth + /api/v1/auth/login: + post: + operationId: AuthController_login + summary: Login with email and password + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginDto' + responses: + '201': + description: '' + tags: + - Auth + /api/v1/auth/refresh: + post: + operationId: AuthController_refresh + summary: Refresh access token + parameters: [] + responses: + '200': + description: '' + tags: + - Auth + /api/v1/auth/logout: + post: + operationId: AuthController_logout + summary: Logout user + parameters: [] + responses: + '200': + description: '' + tags: + - Auth + security: + - bearer: [] + /api/v1/auth/me: get: - operationId: searchSecurities - tags: [Securities] + operationId: AuthController_getProfile + summary: Get current user profile + parameters: [] + responses: + '200': + description: '' + tags: + - Auth + security: + - bearer: [] + patch: + operationId: AuthController_updateProfile + summary: Update current user profile + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProfileDto' + responses: + '200': + description: '' + tags: + - Auth + security: + - bearer: [] + /api/v1/securities/search: + get: + operationId: SecuritiesController_search summary: Поиск по инструментам parameters: - name: q - in: query required: true - schema: - type: string - minLength: 1 - maxLength: 100 + in: query description: Поисковый запрос (тикер, название, ISIN) - - name: type - in: query - required: false schema: type: string - enum: [all, share, bond] - default: all - description: Фильтр по типу инструмента - - name: limit - in: query + - name: type required: false + in: query + schema: + default: all + enum: + - all + - share + - bond + type: string + - name: limit + required: false + in: query schema: - type: integer - minimum: 1 - maximum: 100 default: 20 - description: Максимальное количество результатов + type: number responses: - "200": - description: Результаты поиска + '200': + description: '' + tags: + - Securities + /api/v1/securities/screener: + get: + operationId: SecuritiesController_screener + summary: Фильтр ценных бумаг по параметрам + parameters: + - name: type + required: true + in: query + schema: + enum: + - share + - bond + type: string + - name: priceMin + required: false + in: query + schema: + type: number + - name: priceMax + required: false + in: query + schema: + type: number + - name: volumeMin + required: false + in: query + schema: + type: number + - name: listLevel + required: false + in: query + schema: + type: number + - name: changePercentMin + required: false + in: query + schema: + type: number + - name: changePercentMax + required: false + in: query + schema: + type: number + - name: capitalizationMin + required: false + in: query + schema: + type: number + - name: yieldMin + required: false + in: query + schema: + type: number + - name: yieldMax + required: false + in: query + schema: + type: number + - name: durationMin + required: false + in: query + schema: + type: number + - name: durationMax + required: false + in: query + schema: + type: number + - name: couponMin + required: false + in: query + schema: + type: number + - name: couponMax + required: false + in: query + schema: + type: number + - name: couponPercentMin + required: false + in: query + schema: + type: number + - name: couponPercentMax + required: false + in: query + schema: + type: number + - name: maturityBefore + required: false + in: query + schema: + type: string + - name: maturityAfter + required: false + in: query + schema: + type: string + - name: bondType + required: false + in: query + schema: + type: string + - name: sortBy + required: false + in: query + schema: + default: price + type: string + - name: sortOrder + required: false + in: query + schema: + default: asc + type: string + - name: page + required: false + in: query + schema: + default: 1 + type: number + - name: pageSize + required: false + in: query + schema: + default: 20 + type: number + responses: + '200': + description: '' content: application/json: schema: - $ref: "#/components/schemas/SearchResponse" - "400": - $ref: "#/components/responses/BadRequest" - - /securities/shares/{secid}: + $ref: '#/components/schemas/ScreenerResultDto' + tags: + - Securities + /api/v1/securities/shares/{secid}: get: - operationId: getShare - tags: [Shares] + operationId: SharesController_getShare summary: Получить спецификацию акции parameters: - name: secid - in: path required: true + in: path schema: type: string - description: SECID инструмента (e.g. SBER) responses: - "200": - description: Спецификация акции - content: - application/json: - schema: - $ref: "#/components/schemas/StockResponse" - "404": - $ref: "#/components/responses/NotFound" - - /securities/shares/{secid}/marketdata: + '200': + description: '' + tags: + - Shares + /api/v1/securities/shares/{secid}/marketdata: get: - operationId: getShareMarketData - tags: [Shares] + operationId: SharesController_getMarketData summary: Получить рыночные данные акции parameters: - name: secid - in: path required: true + in: path schema: type: string responses: - "200": - description: Рыночные данные - content: - application/json: - schema: - $ref: "#/components/schemas/StockMarketDataResponse" - "404": - $ref: "#/components/responses/NotFound" - - /securities/shares/{secid}/candles: + '200': + description: '' + tags: + - Shares + /api/v1/securities/shares/{secid}/dividends: get: - operationId: getShareCandles - tags: [Shares] - summary: Получить свечи для графика цены акции + operationId: SharesController_getDividends + summary: Получить дивиденды parameters: - name: secid + required: true in: path - required: true schema: type: string - - name: interval - in: query - required: true - schema: - type: string - enum: ["1h", "24h"] - description: Таймфрейм свечей - - name: from - in: query - required: true - schema: - type: string - format: date - description: Начальная дата (ISO 8601) - - name: till - in: query - required: true - schema: - type: string - format: date - description: Конечная дата (ISO 8601) responses: - "200": - description: Массив свечей - content: - application/json: - schema: - $ref: "#/components/schemas/CandlesResponse" - "400": - $ref: "#/components/responses/BadRequest" - - /securities/shares/{secid}/history: + '200': + description: '' + tags: + - Shares + /api/v1/securities/shares/{secid}/history: get: - operationId: getShareHistory - tags: [Shares] + operationId: SharesController_getHistory summary: Получить дневную историю торгов акции parameters: - name: secid - in: path required: true + in: path schema: type: string - name: from - in: query required: true + in: query schema: type: string - format: date - name: till + required: true in: query - required: true - schema: - type: string - format: date - responses: - "200": - description: Дневная история - content: - application/json: - schema: - $ref: "#/components/schemas/HistoryResponse" - - /securities/shares/{secid}/dividends: - get: - operationId: getShareDividends - tags: [Shares] - summary: Получить историю дивидендных выплат - parameters: - - name: secid - in: path - required: true schema: type: string responses: - "200": - description: Дивиденды - content: - application/json: - schema: - $ref: "#/components/schemas/DividendsResponse" - "404": - $ref: "#/components/responses/NotFound" - - /securities/bonds/{secid}: + '200': + description: '' + tags: + - Shares + /api/v1/securities/bonds/{secid}: get: - operationId: getBond - tags: [Bonds] + operationId: BondsController_getBond summary: Получить спецификацию облигации parameters: - name: secid - in: path required: true + in: path schema: type: string responses: - "200": - description: Спецификация облигации - content: - application/json: - schema: - $ref: "#/components/schemas/BondResponse" - "404": - $ref: "#/components/responses/NotFound" - - /securities/bonds/{secid}/marketdata: + '200': + description: '' + tags: + - Bonds + /api/v1/securities/bonds/{secid}/marketdata: get: - operationId: getBondMarketData - tags: [Bonds] + operationId: BondsController_getMarketData summary: Получить рыночные данные облигации parameters: - name: secid - in: path required: true + in: path schema: type: string responses: - "200": - description: Рыночные данные облигации - content: - application/json: - schema: - $ref: "#/components/schemas/BondMarketDataResponse" - "404": - $ref: "#/components/responses/NotFound" - - /securities/bonds/{secid}/candles: + '200': + description: '' + tags: + - Bonds + /api/v1/securities/bonds/{secid}/history: get: - operationId: getBondCandles - tags: [Bonds] - summary: Получить свечи для графика цены облигации - parameters: - - name: secid - in: path - required: true - schema: - type: string - - name: interval - in: query - required: true - schema: - type: string - enum: ["1h", "24h"] - - name: from - in: query - required: true - schema: - type: string - format: date - - name: till - in: query - required: true - schema: - type: string - format: date - responses: - "200": - description: Массив свечей - content: - application/json: - schema: - $ref: "#/components/schemas/CandlesResponse" - - /securities/bonds/{secid}/history: - get: - operationId: getBondHistory - tags: [Bonds] + operationId: BondsController_getHistory summary: Получить дневную историю торгов облигации parameters: - name: secid - in: path required: true + in: path schema: type: string - name: from - in: query required: true + in: query schema: type: string - format: date - name: till - in: query required: true + in: query schema: type: string - format: date responses: - "200": - description: Дневная история + '200': + description: '' + tags: + - Bonds + /api/v1/securities/shares/{secid}/candles: + get: + operationId: CandlesController_getShareCandles + summary: Получить свечи акции + parameters: + - name: secid + required: true + in: path + schema: + type: string + - name: interval + required: true + in: query + schema: + enum: + - 1h + - 24h + type: string + - name: from + required: true + in: query + schema: + format: date + example: '2025-06-13' + type: string + - name: till + required: true + in: query + schema: + format: date + example: '2026-06-13' + type: string + responses: + '200': + description: '' + tags: + - Candles + /api/v1/securities/bonds/{secid}/candles: + get: + operationId: CandlesController_getBondCandles + summary: Получить свечи облигации + parameters: + - name: secid + required: true + in: path + schema: + type: string + - name: interval + required: true + in: query + schema: + enum: + - 1h + - 24h + type: string + - name: from + required: true + in: query + schema: + format: date + example: '2025-06-13' + type: string + - name: till + required: true + in: query + schema: + format: date + example: '2026-06-13' + type: string + responses: + '200': + description: '' + tags: + - Candles + /api/v1/portfolios: + get: + operationId: PortfolioController_findAll + summary: Get all portfolios for current user + parameters: [] + responses: + '200': + description: '' content: application/json: schema: - $ref: "#/components/schemas/BondHistoryResponse" - + type: array + items: + $ref: '#/components/schemas/PortfolioListResponseDto' + tags: + - Portfolios + security: + - bearer: [] + post: + operationId: PortfolioController_create + summary: Create a new portfolio + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePortfolioDto' + responses: + '201': + description: '' + tags: + - Portfolios + security: + - bearer: [] + /api/v1/portfolios/{id}: + get: + operationId: PortfolioController_findOne + summary: Get portfolio details with positions and prices + parameters: + - name: id + required: true + in: path + schema: + type: number + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/PortfolioDetailResponseDto' + tags: + - Portfolios + security: + - bearer: [] + patch: + operationId: PortfolioController_update + summary: Update portfolio + parameters: + - name: id + required: true + in: path + schema: + type: number + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePortfolioDto' + responses: + '200': + description: '' + tags: + - Portfolios + security: + - bearer: [] + delete: + operationId: PortfolioController_remove + summary: Delete portfolio + parameters: + - name: id + required: true + in: path + schema: + type: number + responses: + '200': + description: '' + tags: + - Portfolios + security: + - bearer: [] + /api/v1/portfolios/{id}/positions: + post: + operationId: PortfolioController_addPosition + summary: Add position to portfolio + parameters: + - name: id + required: true + in: path + schema: + type: number + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddPositionDto' + responses: + '201': + description: '' + tags: + - Portfolios + security: + - bearer: [] + /api/v1/portfolios/{id}/positions/{positionId}: + patch: + operationId: PortfolioController_updatePosition + summary: Update position + parameters: + - name: id + required: true + in: path + schema: + type: number + - name: positionId + required: true + in: path + schema: + type: number + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePositionDto' + responses: + '200': + description: '' + tags: + - Portfolios + security: + - bearer: [] + delete: + operationId: PortfolioController_removePosition + summary: Remove position from portfolio + parameters: + - name: id + required: true + in: path + schema: + type: number + - name: positionId + required: true + in: path + schema: + type: number + responses: + '200': + description: '' + tags: + - Portfolios + security: + - bearer: [] + /api/v1/portfolios/{id}/analytics: + get: + operationId: PortfolioController_getAnalytics + summary: Get portfolio analytics with PnL + parameters: + - name: id + required: true + in: path + schema: + type: number + responses: + '200': + description: '' + tags: + - Portfolios + security: + - bearer: [] +info: + title: MoexVibe API + description: '' + version: 1.0.0 + contact: {} +tags: [] +servers: [] components: + securitySchemes: + bearer: + scheme: bearer + bearerFormat: JWT + type: http schemas: - # ── Health ── - HealthResponse: + RegisterDto: type: object properties: - status: + email: type: string - example: "ok" - timestamp: + example: user@example.com + password: type: string - format: date-time - uptime: - type: number - required: [status, timestamp, uptime] - - # ── Api Response Wrapper ── - ApiResponse: + example: securePass123 + name: + type: string + example: John + required: + - email + - password + LoginDto: type: object properties: - data: {} - meta: - type: object - properties: - cachedAt: - type: string - format: date-time - nullable: true - fromCache: - type: boolean - required: [data] - - # ── Search ── - SearchResult: + email: + type: string + example: user@example.com + password: + type: string + example: securePass123 + required: + - email + - password + UpdateProfileDto: + type: object + properties: + name: + type: string + example: John Doe + ScreenerItemDto: type: object properties: secid: type: string - example: "SBER" - isin: - type: string - example: "RU0009029540" + example: SBER shortName: type: string - example: "Сбербанк" + example: Сбербанк + isin: + type: string + example: RU0009029540 type: type: string - enum: [share, bond] - listLevel: - type: integer - example: 1 - currency: - type: string - nullable: true - example: "RUB" + enum: + - share + - bond price: - type: number - nullable: true - example: 322.35 - required: [secid, isin, shortName, type, listLevel] - - SearchResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/SearchResult" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - # ── Stock ── - StockMarketData: - type: object - properties: - price: - type: number + type: object example: 322.35 change: - type: number + type: object example: 1.15 changePercent: - type: number + type: object example: 0.36 - open: - type: number - example: 321.30 - high: - type: number - example: 322.66 - low: - type: number - nullable: true - example: 321.20 volume: - type: integer + type: number example: 1925163 - value: - type: number - example: 620184479 - issueCapitalization: - type: number - example: 6958336818320 - updatedAt: - type: string - format: date-time - example: "2026-06-13T18:03:11Z" - required: [price, change, changePercent, open, volume, value, updatedAt] - - Stock: - type: object - properties: - secid: - type: string - example: "SBER" - isin: - type: string - example: "RU0009029540" - name: - type: string - example: "Сбербанк России ПАО ао" - shortName: - type: string - example: "Сбербанк" - latName: - type: string - nullable: true - example: "Sberbank" listLevel: - type: integer + type: number example: 1 - issueSize: - type: integer - example: 21586948000 - faceValue: - type: number - example: 3 - faceUnit: - type: string - example: "RUB" - type: - type: string - example: "common_share" - marketData: - $ref: "#/components/schemas/StockMarketData" - required: [secid, isin, name, shortName, listLevel, type, marketData] - - StockResponse: - type: object - properties: - data: - $ref: "#/components/schemas/Stock" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - StockMarketDataResponse: - type: object - properties: - data: - $ref: "#/components/schemas/StockMarketData" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - # ── Bond ── - BondMarketData: - type: object - properties: - price: - type: number - example: 100.45 - description: Цена в % от номинала + capitalization: + type: object + example: 6958336818320 yieldToMaturity: - type: number - nullable: true + type: object example: 12.71 - yieldAtWaprice: - type: number - nullable: true duration: - type: number - nullable: true - accruedInt: - type: number - example: 29.48 + type: object + example: 4.5 couponValue: - type: number + type: object example: 40.64 couponPercent: - type: number - nullable: true + type: object example: 8.15 - nextCouponDate: - type: string - format: date - nullable: true - example: "2026-08-05" - open: - type: number - high: - type: number - nullable: true - low: - type: number - nullable: true - volume: - type: integer - updatedAt: - type: string - format: date-time - required: [price, accruedInt, couponValue, volume, updatedAt] - - Bond: + accruedInt: + type: object + example: 29.48 + matDate: + type: object + example: '2027-02-03' + bondType: + type: object + example: ОФЗ-ПД + required: + - secid + - shortName + - isin + - type + - volume + - listLevel + ScreenerResultDto: type: object properties: - secid: - type: string - isin: - type: string + items: + type: array + items: + $ref: '#/components/schemas/ScreenerItemDto' + total: + type: number + page: + type: number + pageSize: + type: number + totalPages: + type: number + required: + - items + - total + - page + - pageSize + - totalPages + PortfolioListResponseDto: + type: object + properties: + id: + type: number name: type: string - shortName: - type: string - latName: - type: string - nullable: true - listLevel: - type: integer - issueSize: - type: integer - faceValue: - type: number - faceUnit: - type: string - matDate: - type: string - format: date - example: "2027-02-03" - couponValue: - type: number - example: 40.64 - couponPercent: - type: number - nullable: true - example: 8.15 - couponPeriod: - type: integer - example: 182 - nextCoupon: - type: string - format: date - example: "2026-08-05" - accruedInt: - type: number - example: 29.48 - bondType: - type: string - example: "Фикс с известным купоном" - bondSubType: - type: string - example: "До погашения" - offerDate: - type: string - format: date - nullable: true - buybackDate: - type: string - format: date - nullable: true - marketData: - $ref: "#/components/schemas/BondMarketData" - required: [secid, isin, name, shortName, listLevel, matDate, couponValue, - couponPeriod, accruedInt, bondType, marketData] - - BondResponse: - type: object - properties: - data: - $ref: "#/components/schemas/Bond" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - BondMarketDataResponse: - type: object - properties: - data: - $ref: "#/components/schemas/BondMarketData" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - # ── Candle ── - Candle: - type: object - properties: - open: - type: number - example: 280.00 - high: - type: number - example: 280.41 - low: - type: number - example: 271.80 - close: - type: number - example: 272.25 - volume: - type: integer - example: 43086870 - value: - type: number - example: 11853565984.9 - begin: - type: string - format: date-time - example: "2025-01-03T00:00:00Z" - end: - type: string - format: date-time - example: "2025-01-03T23:59:59Z" - required: [open, high, low, close, volume, value, begin, end] - - CandlesResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/Candle" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - # ── History ── - HistoryEntry: - type: object - properties: - date: - type: string - format: date - open: - type: number - high: - type: number - low: - type: number - close: - type: number - volume: - type: integer - value: - type: number - required: [date, open, high, low, close, volume, value] - - HistoryResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/HistoryEntry" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - BondHistoryEntry: - type: object - properties: - date: - type: string - format: date - closePrice: - type: number - yieldClose: - type: number - nullable: true - duration: - type: number - nullable: true - required: [date, closePrice] - - BondHistoryResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/BondHistoryEntry" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - # ── Dividend ── - Dividend: - type: object - properties: - registryCloseDate: - type: string - format: date - example: "2025-07-18" - value: - type: number - example: 34.84 + description: + type: object currency: type: string - example: "RUB" - required: [registryCloseDate, value, currency] - - DividendsResponse: + default: RUB + createdAt: + type: string + updatedAt: + type: string + totalValue: + type: number + description: Total market value of all positions + positionCount: + type: number + description: Total number of positions + shareCount: + type: number + description: Number of share positions + bondCount: + type: number + description: Number of bond positions + required: + - id + - name + - currency + - createdAt + - updatedAt + - totalValue + - positionCount + - shareCount + - bondCount + CreatePortfolioDto: type: object properties: - data: + name: + type: string + example: Мой портфель + description: + type: string + example: Описание портфеля + currency: + type: string + default: RUB + enum: + - RUB + - USD + - EUR + - CNY + - KZT + - BYN + required: + - name + PositionWithPriceDto: + type: object + properties: + id: + type: number + secid: + type: string + example: SBER + shortName: + type: object + type: + type: string + example: share + enum: + - share + - bond + quantity: + type: number + example: 10 + buyPrice: + type: object + buyDate: + type: object + notes: + type: object + tags: + type: object + currentPrice: + type: object + totalCost: + type: object + currentValue: + type: object + weightPercent: + type: number + pnl: + type: object + pnlPercent: + type: object + dividendIncome: + type: object + totalReturn: + type: object + totalReturnPercent: + type: object + change: + type: object + changePercent: + type: object + yieldToMaturity: + type: object + duration: + type: object + couponValue: + type: object + couponPercent: + type: object + nextCouponDate: + type: object + matDate: + type: object + accruedInt: + type: object + bid: + type: object + offer: + type: object + couponPeriod: + type: object + bondType: + type: object + offerDate: + type: object + required: + - id + - secid + - type + - quantity + - weightPercent + PortfolioSummaryDto: + type: object + properties: + totalInvested: + type: number + totalValue: + type: number + totalPnl: + type: number + totalPnlPercent: + type: object + totalDividends: + type: number + totalReturn: + type: number + totalReturnPercent: + type: object + positionCount: + type: number + weightedYield: + type: object + required: + - totalInvested + - totalValue + - totalPnl + - totalPnlPercent + - totalDividends + - totalReturn + - totalReturnPercent + - positionCount + - weightedYield + PortfolioDetailResponseDto: + type: object + properties: + id: + type: number + name: + type: string + description: + type: object + currency: + type: string + default: RUB + createdAt: + type: string + updatedAt: + type: string + positions: type: array items: - $ref: "#/components/schemas/Dividend" - meta: - $ref: "#/components/schemas/ApiResponse/properties/meta" - - # ── Error ── - ErrorResponse: + $ref: '#/components/schemas/PositionWithPriceDto' + totalValue: + type: number + analytics: + $ref: '#/components/schemas/PortfolioSummaryDto' + required: + - id + - name + - currency + - createdAt + - updatedAt + - positions + - totalValue + - analytics + UpdatePortfolioDto: type: object properties: - statusCode: - type: integer - example: 404 - message: + name: type: string - example: "Instrument SBER_NOT_FOUND not found" - error: + example: Мой портфель + description: type: string - example: "Not Found" - timestamp: + example: Обновлённое описание + currency: type: string - format: date-time - path: + default: RUB + enum: + - RUB + - USD + - EUR + - CNY + - KZT + - BYN + AddPositionDto: + type: object + properties: + secid: type: string - example: "/api/v1/securities/shares/SBER_NOT_FOUND" - required: [statusCode, message, error, timestamp, path] - - responses: - BadRequest: - description: Неверный запрос - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - NotFound: - description: Инструмент не найден - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - -tags: - - name: Health - description: Мониторинг состояния сервиса - - name: Securities - description: Поиск инструментов - - name: Shares - description: Акции - - name: Bonds - description: Облигации + example: SBER + quantity: + type: number + example: 10 + buyPrice: + type: number + example: 250.5 + buyDate: + type: string + example: '2026-06-01' + notes: + type: string + example: Покупка на дип + tags: + type: string + example: + - DIVIDEND + - GROWTH + enum: + - DIVIDEND + - GROWTH + - DEFENSIVE + - SPECULATIVE + - BOND + - ETF + - GOVERNMENT + - CASH + required: + - secid + - quantity + UpdatePositionDto: + type: object + properties: + quantity: + type: number + example: 15 + buyPrice: + type: number + example: 260 + buyDate: + type: string + example: '2026-06-15' + notes: + type: string + example: Докупка + tags: + type: string + example: + - DIVIDEND + enum: + - DIVIDEND + - GROWTH + - DEFENSIVE + - SPECULATIVE + - BOND + - ETF + - GOVERNMENT + - CASH