import { Injectable } from '@nestjs/common'; import { decimalFromString, divide, formatDecimal, multiply, subtract, } from '#src/common/decimal'; import { ISO_DATE_LENGTH, ISO_DATE_START } from '#src/common/time'; import { rubMoney } from '#src/integrations/tbank/decimal'; import { CandleService } from '../../application/candle.service'; import { CatalogEnrichmentService, type CatalogPayload, } from '../../application/catalog-enrichment.service'; import { timestamp } from '../../application/instrument-market'; import { StockCandlesQueryDto, StockQuoteDto, StockQuoteResourceDto, } from '../presentation/stock.dto'; const ZERO_AMOUNT = 0n; const CHANGE_PERCENT_DECIMAL_PLACES = 6; @Injectable() export class StockMarketService { constructor( private readonly history: CandleService, private readonly sources: CatalogEnrichmentService, ) {} async quote( userId: string, uid: string, ): Promise { const result = await this.sources.section( userId, 'share', uid, 'quote', ); return { data: result.values.instrument ? stockQuote(result.values, result.unavailable, result.observedAt) : null, meta: result.meta, }; } candles( userId: string, uid: string, period: StockCandlesQueryDto['period'] = 'month', ) { return this.history.read( userId, { uid, instrumentType: 'share', period }, ); } } export function stockQuote( values: CatalogPayload, unavailable: string[], observedAt: string, ): StockQuoteDto { const { price: last, candle: previous, tradingStatus = null } = values; const share = values.instrument; const priceAt = timestamp(last?.time); const price = last?.price ? rubMoney({ ...last.price, currency: share?.currency }) : null; const previousPrice = previous?.close ? rubMoney({ ...previous.close, currency: share?.currency }) : null; const difference = price && previousPrice ? subtract( decimalFromString(price.value), decimalFromString(previousPrice.value), ) : null; return { price, priceAt, tradingStatus, previousCloseDate: timestamp(previous?.time)?.slice(ISO_DATE_START, ISO_DATE_LENGTH) ?? null, previousClose: previousPrice, change: difference ? { currency: 'RUB', value: formatDecimal(difference) } : null, changePercent: difference && previousPrice && decimalFromString(previousPrice.value).value > ZERO_AMOUNT ? formatDecimal( divide( multiply(difference, decimalFromString('100')), decimalFromString(previousPrice.value), CHANGE_PERCENT_DECIMAL_PLACES, ), ) : null, unavailable, observedAt, }; }