126 lines
3.2 KiB
TypeScript
126 lines
3.2 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 { 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 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 > 0n ?
|
|
{
|
|
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(0, 10) ?? null,
|
|
unavailable,
|
|
observedAt,
|
|
};
|
|
}
|