206 lines
6.3 KiB
TypeScript
206 lines
6.3 KiB
TypeScript
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
|
import {
|
|
IDataGatheringItem,
|
|
IDataProviderHistoricalResponse,
|
|
IDataProviderResponse
|
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
|
import {
|
|
DATE_FORMAT,
|
|
isGhostfolioScraperApiSymbol,
|
|
isRakutenRapidApiSymbol
|
|
} from '@ghostfolio/common/helper';
|
|
import { Granularity } from '@ghostfolio/common/types';
|
|
import { Injectable } from '@nestjs/common';
|
|
import { DataSource, MarketData } from '@prisma/client';
|
|
import { format } from 'date-fns';
|
|
|
|
import { AlphaVantageService } from './alpha-vantage/alpha-vantage.service';
|
|
import { GhostfolioScraperApiService } from './ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
|
import { RakutenRapidApiService } from './rakuten-rapid-api/rakuten-rapid-api.service';
|
|
import {
|
|
convertToYahooFinanceSymbol,
|
|
YahooFinanceService
|
|
} from './yahoo-finance/yahoo-finance.service';
|
|
|
|
@Injectable()
|
|
export class DataProviderService {
|
|
public constructor(
|
|
private readonly alphaVantageService: AlphaVantageService,
|
|
private readonly configurationService: ConfigurationService,
|
|
private readonly ghostfolioScraperApiService: GhostfolioScraperApiService,
|
|
private readonly prismaService: PrismaService,
|
|
private readonly rakutenRapidApiService: RakutenRapidApiService,
|
|
private readonly yahooFinanceService: YahooFinanceService
|
|
) {
|
|
this.rakutenRapidApiService?.setPrisma(this.prismaService);
|
|
}
|
|
|
|
public async get(
|
|
aSymbols: string[]
|
|
): Promise<{ [symbol: string]: IDataProviderResponse }> {
|
|
if (aSymbols.length === 1) {
|
|
const symbol = aSymbols[0];
|
|
|
|
if (isGhostfolioScraperApiSymbol(symbol)) {
|
|
return this.ghostfolioScraperApiService.get(aSymbols);
|
|
} else if (isRakutenRapidApiSymbol(symbol)) {
|
|
return this.rakutenRapidApiService.get(aSymbols);
|
|
} else {
|
|
const yahooFinanceSymbol = convertToYahooFinanceSymbol(symbol);
|
|
return this.yahooFinanceService.get([yahooFinanceSymbol]);
|
|
}
|
|
}
|
|
|
|
const yahooFinanceSymbols = aSymbols.filter((symbol) => {
|
|
return (
|
|
!isGhostfolioScraperApiSymbol(symbol) &&
|
|
!isRakutenRapidApiSymbol(symbol)
|
|
);
|
|
});
|
|
|
|
const response = await this.yahooFinanceService.get(yahooFinanceSymbols);
|
|
|
|
const ghostfolioScraperApiSymbols = aSymbols.filter((symbol) => {
|
|
return isGhostfolioScraperApiSymbol(symbol);
|
|
});
|
|
|
|
for (const symbol of ghostfolioScraperApiSymbols) {
|
|
if (symbol) {
|
|
const ghostfolioScraperApiResult =
|
|
await this.ghostfolioScraperApiService.get([symbol]);
|
|
response[symbol] = ghostfolioScraperApiResult[symbol];
|
|
}
|
|
}
|
|
|
|
const rakutenRapidApiSymbols = aSymbols.filter((symbol) => {
|
|
return isRakutenRapidApiSymbol(symbol);
|
|
});
|
|
|
|
for (const symbol of rakutenRapidApiSymbols) {
|
|
if (symbol) {
|
|
const rakutenRapidApiResult =
|
|
await this.ghostfolioScraperApiService.get([symbol]);
|
|
response[symbol] = rakutenRapidApiResult[symbol];
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
public async getHistorical(
|
|
aSymbols: string[],
|
|
aGranularity: Granularity = 'month',
|
|
from: Date,
|
|
to: Date
|
|
): Promise<{
|
|
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
|
}> {
|
|
let response: {
|
|
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
|
} = {};
|
|
|
|
const granularityQuery =
|
|
aGranularity === 'month'
|
|
? `AND (date_part('day', date) = 1 OR date >= TIMESTAMP 'yesterday')`
|
|
: '';
|
|
|
|
const rangeQuery =
|
|
from && to
|
|
? `AND date >= '${format(from, DATE_FORMAT)}' AND date <= '${format(
|
|
to,
|
|
DATE_FORMAT
|
|
)}'`
|
|
: '';
|
|
|
|
try {
|
|
const queryRaw = `SELECT * FROM "MarketData" WHERE "symbol" IN ('${aSymbols.join(
|
|
`','`
|
|
)}') ${granularityQuery} ${rangeQuery} ORDER BY date;`;
|
|
|
|
const marketDataByGranularity: MarketData[] =
|
|
await this.prismaService.$queryRaw(queryRaw);
|
|
|
|
response = marketDataByGranularity.reduce((r, marketData) => {
|
|
const { date, marketPrice, symbol } = marketData;
|
|
|
|
r[symbol] = {
|
|
...(r[symbol] || {}),
|
|
[format(new Date(date), DATE_FORMAT)]: { marketPrice }
|
|
};
|
|
|
|
return r;
|
|
}, {});
|
|
} catch (error) {
|
|
console.error(error);
|
|
} finally {
|
|
return response;
|
|
}
|
|
}
|
|
|
|
public async getHistoricalRaw(
|
|
aDataGatheringItems: IDataGatheringItem[],
|
|
from: Date,
|
|
to: Date
|
|
): Promise<{
|
|
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
|
}> {
|
|
const result: {
|
|
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
|
} = {};
|
|
|
|
const promises: Promise<{
|
|
data: { [date: string]: IDataProviderHistoricalResponse };
|
|
symbol: string;
|
|
}>[] = [];
|
|
for (const { dataSource, symbol } of aDataGatheringItems) {
|
|
const dataProvider = this.getDataProvider(dataSource);
|
|
if (dataProvider.canHandle(symbol)) {
|
|
promises.push(
|
|
dataProvider
|
|
.getHistorical([symbol], undefined, from, to)
|
|
.then((data) => ({ data: data?.[symbol], symbol }))
|
|
);
|
|
}
|
|
}
|
|
|
|
const allData = await Promise.all(promises);
|
|
for (const { data, symbol } of allData) {
|
|
result[symbol] = data;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public async search(aSymbol: string): Promise<{ items: LookupItem[] }> {
|
|
const { items } = await this.getDataProvider(
|
|
<DataSource>this.configurationService.get('DATA_SOURCES')[0]
|
|
).search(aSymbol);
|
|
|
|
const filteredItems = items.filter((item) => {
|
|
// Only allow symbols with supported currency
|
|
return item.currency ? true : false;
|
|
});
|
|
|
|
return {
|
|
items: filteredItems
|
|
};
|
|
}
|
|
|
|
private getDataProvider(providerName: DataSource) {
|
|
switch (providerName) {
|
|
case DataSource.ALPHA_VANTAGE:
|
|
return this.alphaVantageService;
|
|
case DataSource.GHOSTFOLIO:
|
|
return this.ghostfolioScraperApiService;
|
|
case DataSource.RAKUTEN:
|
|
return this.rakutenRapidApiService;
|
|
case DataSource.YAHOO:
|
|
return this.yahooFinanceService;
|
|
default:
|
|
throw new Error('No data provider has been found.');
|
|
}
|
|
}
|
|
}
|