import { Injectable, NotFoundException } from '@nestjs/common'; import { MoexClientService } from '../moex-client/moex-client.service'; import { CacheService } from '../cache/cache.service'; @Injectable() export class SharesService { constructor( private readonly moexClient: MoexClientService, private readonly cache: CacheService, ) {} async getShare(secid: string) { const desc = await this.moexClient.getSecurityDescription(secid); if ( !desc || !( desc.group === 'stock_shares' || desc.type === 'common_share' || desc.type === 'preferred_share' ) ) { throw new NotFoundException(`Share ${secid} not found`); } const { data: marketData } = await this.cache.getOrFetch( 'marketdata', ['shares', secid], () => this.moexClient.getShareMarketData(secid), 'marketDataTtl', ); const price = marketData?.last ?? (marketData ? null : 0); const change = marketData?.lastChange ?? 0; const changePercent = marketData?.lastChangePrcnt ?? 0; return { secid: desc.secid, isin: desc.isin, name: desc.name, shortName: desc.shortName, latName: desc.latName, listLevel: desc.listLevel, issueSize: desc.issueSize, faceValue: desc.faceValue, faceUnit: desc.faceUnit === 'SUR' ? 'RUB' : desc.faceUnit, type: desc.type, marketData: { price: price ?? 0, change, changePercent, open: marketData?.open ?? 0, high: marketData?.high ?? null, low: marketData?.low ?? null, volume: marketData?.volume ?? 0, value: marketData?.value ?? 0, issueCapitalization: marketData?.issueCapitalization ?? null, updatedAt: marketData?.updateTime ? new Date().toISOString().split('T')[0] + 'T' + marketData.updateTime : new Date().toISOString(), }, }; } async getMarketData(secid: string) { const { data: marketData, fromCache, cachedAt, } = await this.cache.getOrFetch( 'marketdata', ['shares', secid], () => this.moexClient.getShareMarketData(secid), 'marketDataTtl', ); if (!marketData) { throw new NotFoundException(`Market data for ${secid} not found`); } return { data: { price: marketData.last ?? 0, change: marketData.lastChange ?? 0, changePercent: marketData.lastChangePrcnt ?? 0, open: marketData.open ?? 0, high: marketData.high ?? null, low: marketData.low ?? null, volume: marketData.volume ?? 0, value: marketData.value ?? 0, issueCapitalization: marketData.issueCapitalization ?? null, updatedAt: marketData.updateTime ? new Date().toISOString().split('T')[0] + 'T' + marketData.updateTime : new Date().toISOString(), }, meta: { fromCache, cachedAt }, }; } async getDividends(secid: string) { const { data, fromCache, cachedAt } = await this.cache.getOrFetch( 'dividends', [secid], () => this.moexClient.getDividends(secid), 'dividendsTtl', ); return { data: data.map((d) => ({ registryCloseDate: d.registryCloseDate, value: d.value, currency: d.currencyId, })), meta: { fromCache, cachedAt }, }; } async getHistory(secid: string, from: string, till: string) { const { data, fromCache, cachedAt } = await this.cache.getOrFetch( 'history', ['shares', secid, from, till], () => this.moexClient.getHistory(secid, from, till), 'historyTtl', ); return { data: data.map((h) => ({ date: h.tradeDate, open: h.open ?? 0, high: h.high ?? 0, low: h.low ?? 0, close: h.close ?? 0, volume: h.volume, value: h.value, })), meta: { fromCache, cachedAt }, }; } }