import { Injectable } from '@nestjs/common'; import { decimalFromString, divide, formatDecimal, multiply, subtract, } from '../../../../../common/decimal'; import { ISO_DATE_LENGTH, ISO_DATE_START } from '../../../../../common/time'; import { decimal } from '../../../../../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, observe = false, ): Promise { const result = await this.sources.section( userId, 'share', uid, 'quote', observe, ); 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', observe = false, ) { return this.history.read( userId, { uid, instrumentType: 'share', period }, observe, ); } } 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 currency = share?.currency?.toUpperCase(); const price = last?.price && currency ? { currency, value: formatDecimal(decimal(last.price)) } : null; const previousPrice = previous?.close && currency ? { currency, value: formatDecimal(decimal(previous.close)) } : 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 ? { currency, 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, }; }