121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
|
|
import type { TbankBond } from '../../../../../integrations/tbank/tbank.api';
|
|
|
|
import { formatDecimal } from '../../../../../common/decimal';
|
|
import { decimal } from '../../../../../integrations/tbank/decimal';
|
|
import { CatalogDiscoveryService } from '../../application/catalog-discovery.service';
|
|
import { timestamp } from '../../application/instrument-market';
|
|
import {
|
|
BondDto,
|
|
BondResourceDto,
|
|
BondSearchResourceDto,
|
|
} from '../presentation/bond.dto';
|
|
|
|
type IdentifiedBond = TbankBond & { uid: string };
|
|
|
|
import { ISO_DATE_LENGTH, ISO_DATE_START } from '../../../../../common/time';
|
|
import { money } from '../infrastructure/bond-money.mapper';
|
|
|
|
const MIN_LOT_EXCLUSIVE = 0;
|
|
|
|
@Injectable()
|
|
export class BondCatalogService {
|
|
constructor(private readonly discovery: CatalogDiscoveryService) {}
|
|
async search(
|
|
userId: string,
|
|
query: string,
|
|
observe = false,
|
|
): Promise<BondSearchResourceDto> {
|
|
const result = await this.discovery.search(
|
|
userId,
|
|
{ instrumentType: 'bond', query },
|
|
observe,
|
|
);
|
|
return {
|
|
...result,
|
|
data:
|
|
result.data?.map((item) => {
|
|
const short = toBond(item);
|
|
const detail = item.detail
|
|
? toBond({ ...item.detail, uid: item.uid })
|
|
: short;
|
|
const last = item.price;
|
|
return {
|
|
...detail,
|
|
classCode: detail.classCode ?? short.classCode,
|
|
isin: detail.isin ?? short.isin,
|
|
apiTradeAvailable: short.apiTradeAvailable,
|
|
pricePercent: last?.price
|
|
? formatDecimal(decimal(last.price))
|
|
: null,
|
|
priceAt: timestamp(last?.time),
|
|
};
|
|
}) ?? null,
|
|
};
|
|
}
|
|
|
|
async get(
|
|
userId: string,
|
|
uid: string,
|
|
observe = false,
|
|
): Promise<BondResourceDto> {
|
|
const result = await this.discovery.detail(userId, 'bond', uid, observe);
|
|
return {
|
|
...result,
|
|
data: result.data
|
|
? { ...toBond(result.data), slug: result.data.slug }
|
|
: null,
|
|
};
|
|
}
|
|
|
|
async getByTicker(
|
|
userId: string,
|
|
ticker: string,
|
|
observe = false,
|
|
): Promise<BondResourceDto> {
|
|
const result = await this.discovery.byTicker(
|
|
userId,
|
|
'bond',
|
|
ticker,
|
|
observe,
|
|
);
|
|
return {
|
|
...result,
|
|
data: result.data
|
|
? { ...toBond(result.data), slug: result.data.slug }
|
|
: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
function toBond(bond: IdentifiedBond): BondDto {
|
|
return {
|
|
instrumentUid: bond.uid,
|
|
slug: bond.uid,
|
|
name: bond.name?.trim() || bond.ticker || bond.uid,
|
|
ticker: bond.ticker?.trim() || null,
|
|
isin: bond.isin?.trim() || null,
|
|
classCode: bond.classCode?.trim() || null,
|
|
realExchange: bond.realExchange || null,
|
|
apiTradeAvailable: bond.apiTradeAvailableFlag ?? null,
|
|
logoName: bond.brand?.logoName?.trim() || null,
|
|
currency: bond.currency?.toUpperCase() || null,
|
|
bondType: bond.bondType || null,
|
|
sector: bond.sector || null,
|
|
lot: bond.lot && bond.lot > MIN_LOT_EXCLUSIVE ? bond.lot : null,
|
|
historyFrom: timestamp(bond.first_1dayCandleDate),
|
|
nominal: money(bond.nominal),
|
|
initialNominal: money(bond.initialNominal),
|
|
aci: money(bond.aciValue),
|
|
maturityDate: timestamp(bond.maturityDate)?.slice(ISO_DATE_START, ISO_DATE_LENGTH) ?? null,
|
|
callDate: timestamp(bond.callDate)?.slice(ISO_DATE_START, ISO_DATE_LENGTH) ?? null,
|
|
couponQuantityPerYear: bond.couponQuantityPerYear ?? null,
|
|
floatingCoupon: bond.floatingCouponFlag ?? null,
|
|
perpetual: bond.perpetualFlag ?? null,
|
|
amortization: bond.amortizationFlag ?? null,
|
|
subordinated: bond.subordinatedFlag ?? null,
|
|
riskLevel: bond.riskLevel || null,
|
|
};
|
|
}
|