moex-vibe/apps/backend/src/modules/securities/securities.service.ts
Sergey Krylov b79c8210dc
Some checks failed
CI / lint (pull_request) Successful in 9m42s
CI / build (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
style: apply prettier formatting across the codebase
2026-06-13 20:32:34 +03:00

83 lines
2.4 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { MoexClientService } from '../moex-client/moex-client.service';
import { CacheService } from '../cache/cache.service';
import { SecurityType } from './dto/search-query.dto';
export interface SearchResultItem {
secid: string;
isin: string;
shortName: string;
type: 'share' | 'bond';
listLevel: number;
currency: string | null;
price: number | null;
}
@Injectable()
export class SecuritiesService {
constructor(
private readonly moexClient: MoexClientService,
private readonly cache: CacheService,
) {}
async search(query: string, type: SecurityType, limit: number): Promise<SearchResultItem[]> {
const { data } = await this.cache.getOrFetch(
'search',
[query.toLowerCase()],
async () => {
const results = await this.moexClient.searchSecurities(query);
return results
.map((s): SearchResultItem | null => {
const type =
s.group === 'stock_shares' ||
s.type === 'common_share' ||
s.type === 'preferred_share'
? ('share' as const)
: s.group === 'stock_bonds'
? ('bond' as const)
: null;
if (!type) return null;
return {
secid: s.secid,
isin: s.isin,
shortName: s.shortName,
type,
listLevel: s.listLevel,
currency: s.faceUnit === 'SUR' ? 'RUB' : s.faceUnit || null,
price: null,
};
})
.filter((r): r is SearchResultItem => r !== null);
},
'searchTtl',
);
let filtered = data;
if (type === SecurityType.SHARE) {
filtered = data.filter((r) => r.type === 'share');
} else if (type === SecurityType.BOND) {
filtered = data.filter((r) => r.type === 'bond');
}
return filtered.slice(0, limit);
}
async getShareBrief(secid: string): Promise<SearchResultItem | null> {
try {
const desc = await this.moexClient.getSecurityDescription(secid);
if (!desc) return null;
return {
secid: desc.secid,
isin: desc.isin,
shortName: desc.shortName,
type: 'share',
listLevel: desc.listLevel,
currency: desc.faceUnit === 'SUR' ? 'RUB' : desc.faceUnit,
price: null,
};
} catch {
return null;
}
}
}