139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { MoexClientService } from '../moex-client/moex-client.service';
|
|
import { CacheService } from '../cache/cache.service';
|
|
|
|
@Injectable()
|
|
export class SharesService {
|
|
constructor(
|
|
private readonly moexClient: MoexClientService,
|
|
private readonly cache: CacheService,
|
|
) {}
|
|
|
|
async getShare(secid: string) {
|
|
const desc = await this.moexClient.getSecurityDescription(secid);
|
|
if (
|
|
!desc ||
|
|
!(
|
|
desc.group === 'stock_shares' ||
|
|
desc.type === 'common_share' ||
|
|
desc.type === 'preferred_share'
|
|
)
|
|
) {
|
|
throw new NotFoundException(`Share ${secid} not found`);
|
|
}
|
|
|
|
const { data: marketData } = await this.cache.getOrFetch(
|
|
'marketdata',
|
|
['shares', secid],
|
|
() => this.moexClient.getShareMarketData(secid),
|
|
'marketDataTtl',
|
|
);
|
|
|
|
const price = marketData?.last ?? (marketData ? null : 0);
|
|
const change = marketData?.lastChange ?? 0;
|
|
const changePercent = marketData?.lastChangePrcnt ?? 0;
|
|
|
|
return {
|
|
secid: desc.secid,
|
|
isin: desc.isin,
|
|
name: desc.name,
|
|
shortName: desc.shortName,
|
|
latName: desc.latName,
|
|
listLevel: desc.listLevel,
|
|
issueSize: desc.issueSize,
|
|
faceValue: desc.faceValue,
|
|
faceUnit: desc.faceUnit === 'SUR' ? 'RUB' : desc.faceUnit,
|
|
type: desc.type,
|
|
marketData: {
|
|
price: price ?? 0,
|
|
change,
|
|
changePercent,
|
|
open: marketData?.open ?? 0,
|
|
high: marketData?.high ?? null,
|
|
low: marketData?.low ?? null,
|
|
volume: marketData?.volume ?? 0,
|
|
value: marketData?.value ?? 0,
|
|
issueCapitalization: marketData?.issueCapitalization ?? null,
|
|
updatedAt: marketData?.updateTime
|
|
? new Date().toISOString().split('T')[0] + 'T' + marketData.updateTime
|
|
: new Date().toISOString(),
|
|
},
|
|
};
|
|
}
|
|
|
|
async getMarketData(secid: string) {
|
|
const {
|
|
data: marketData,
|
|
fromCache,
|
|
cachedAt,
|
|
} = await this.cache.getOrFetch(
|
|
'marketdata',
|
|
['shares', secid],
|
|
() => this.moexClient.getShareMarketData(secid),
|
|
'marketDataTtl',
|
|
);
|
|
|
|
if (!marketData) {
|
|
throw new NotFoundException(`Market data for ${secid} not found`);
|
|
}
|
|
|
|
return {
|
|
data: {
|
|
price: marketData.last ?? 0,
|
|
change: marketData.lastChange ?? 0,
|
|
changePercent: marketData.lastChangePrcnt ?? 0,
|
|
open: marketData.open ?? 0,
|
|
high: marketData.high ?? null,
|
|
low: marketData.low ?? null,
|
|
volume: marketData.volume ?? 0,
|
|
value: marketData.value ?? 0,
|
|
issueCapitalization: marketData.issueCapitalization ?? null,
|
|
updatedAt: marketData.updateTime
|
|
? new Date().toISOString().split('T')[0] + 'T' + marketData.updateTime
|
|
: new Date().toISOString(),
|
|
},
|
|
meta: { fromCache, cachedAt },
|
|
};
|
|
}
|
|
|
|
async getDividends(secid: string) {
|
|
const { data, fromCache, cachedAt } = await this.cache.getOrFetch(
|
|
'dividends',
|
|
[secid],
|
|
() => this.moexClient.getDividends(secid),
|
|
'dividendsTtl',
|
|
);
|
|
|
|
return {
|
|
data: data.map((d) => ({
|
|
registryCloseDate: d.registryCloseDate,
|
|
value: d.value,
|
|
currency: d.currencyId,
|
|
})),
|
|
meta: { fromCache, cachedAt },
|
|
};
|
|
}
|
|
|
|
async getHistory(secid: string, from: string, till: string) {
|
|
const { data, fromCache, cachedAt } = await this.cache.getOrFetch(
|
|
'history',
|
|
['shares', secid, from, till],
|
|
() => this.moexClient.getHistory(secid, from, till),
|
|
'historyTtl',
|
|
);
|
|
|
|
return {
|
|
data: data.map((h) => ({
|
|
date: h.tradeDate,
|
|
open: h.open ?? 0,
|
|
high: h.high ?? 0,
|
|
low: h.low ?? 0,
|
|
close: h.close ?? 0,
|
|
volume: h.volume,
|
|
value: h.value,
|
|
})),
|
|
meta: { fromCache, cachedAt },
|
|
};
|
|
}
|
|
}
|