Files
backend/src/modules/investments/instruments/bonds/application/bond-market.service.ts
T

129 lines
3.3 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import type { TbankBond } from '../../../../../integrations/tbank/tbank.api';
import {
add,
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 { money } from '../infrastructure/bond-money.mapper';
import {
BondCandlesQueryDto,
BondQuoteDto,
BondQuoteResourceDto,
} from '../presentation/bond.dto';
const ZERO_AMOUNT = 0n;
const PRICE_DECIMAL_PLACES = 9;
@Injectable()
export class BondMarketService {
constructor(
private readonly history: CandleService,
private readonly sources: CatalogEnrichmentService,
) {}
async quote(
userId: string,
uid: string,
observe = false,
): Promise<BondQuoteResourceDto> {
const result = await this.sources.section(
userId,
'bond',
uid,
'quote',
observe,
);
return {
data: result.values.instrument
? bondQuote(result.values, result.unavailable, result.observedAt)
: null,
meta: result.meta,
};
}
candles(
userId: string,
uid: string,
period: BondCandlesQueryDto['period'] = 'month',
observe = false,
) {
return this.history.read(
userId,
{ uid, instrumentType: 'bond', period },
observe,
);
}
}
export function bondQuote(
values: CatalogPayload,
unavailable: string[],
observedAt: string,
): BondQuoteDto {
const { price: last, candle: previous, tradingStatus = null } = values;
const instrument = values.instrument as TbankBond | undefined;
const priceAt = timestamp(last?.time);
const pricePercent = last?.price ? formatDecimal(decimal(last.price)) : null;
const nominal = money(instrument?.nominal);
const aci = money(instrument?.aciValue);
const cleanPrice =
pricePercent !== null &&
nominal &&
decimalFromString(nominal.value).value > ZERO_AMOUNT
? {
currency: nominal.currency,
value: formatDecimal(
divide(
multiply(
decimalFromString(pricePercent),
decimalFromString(nominal.value),
),
decimalFromString('100'),
PRICE_DECIMAL_PLACES,
),
),
}
: null;
const dirtyPrice =
cleanPrice && aci && aci.currency === cleanPrice.currency
? {
currency: cleanPrice.currency,
value: formatDecimal(
add(
decimalFromString(cleanPrice.value),
decimalFromString(aci.value),
),
),
}
: null;
return {
pricePercent,
cleanPrice,
aci,
dirtyPrice,
priceAt,
tradingStatus,
changePercentagePoints:
last?.price && previous?.close
? formatDecimal(subtract(decimal(last.price), decimal(previous.close)))
: null,
previousCloseDate: timestamp(previous?.time)?.slice(ISO_DATE_START, ISO_DATE_LENGTH) ?? null,
unavailable,
observedAt,
};
}