|
|
|
|
@ -12,6 +12,7 @@ import {
|
|
|
|
|
IDataProviderHistoricalResponse,
|
|
|
|
|
IDataProviderResponse
|
|
|
|
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
|
|
|
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
|
|
|
|
import {
|
|
|
|
|
DEFAULT_CURRENCY,
|
|
|
|
|
REPLACE_NAME_PARTS
|
|
|
|
|
@ -49,7 +50,8 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
|
private readonly configurationService: ConfigurationService,
|
|
|
|
|
private readonly cryptocurrencyService: CryptocurrencyService
|
|
|
|
|
private readonly cryptocurrencyService: CryptocurrencyService,
|
|
|
|
|
private readonly prismaService: PrismaService
|
|
|
|
|
) {
|
|
|
|
|
this.apiKey = this.configurationService.get(
|
|
|
|
|
'API_KEY_FINANCIAL_MODELING_PREP'
|
|
|
|
|
@ -220,7 +222,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|
|
|
|
|
|
|
|
|
public getDataProviderInfo(): DataProviderInfo {
|
|
|
|
|
return {
|
|
|
|
|
dataSource: DataSource.FINANCIAL_MODELING_PREP,
|
|
|
|
|
dataSource: this.getName(),
|
|
|
|
|
isPremium: true,
|
|
|
|
|
name: 'Financial Modeling Prep',
|
|
|
|
|
url: 'https://financialmodelingprep.com/developer/docs'
|
|
|
|
|
@ -359,25 +361,41 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|
|
|
|
[symbol: string]: Pick<SymbolProfile, 'currency'>;
|
|
|
|
|
} = {};
|
|
|
|
|
|
|
|
|
|
const quotes = await fetch(
|
|
|
|
|
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
|
|
|
|
|
{
|
|
|
|
|
signal: AbortSignal.timeout(requestTimeout)
|
|
|
|
|
}
|
|
|
|
|
).then((res) => res.json());
|
|
|
|
|
const [assetProfileResolutions, quotes] = await Promise.all([
|
|
|
|
|
this.prismaService.assetProfileResolution.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
dataSourceTarget: this.getDataProviderInfo().dataSource,
|
|
|
|
|
symbolTarget: { in: symbols }
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
fetch(
|
|
|
|
|
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
|
|
|
|
|
{
|
|
|
|
|
signal: AbortSignal.timeout(requestTimeout)
|
|
|
|
|
}
|
|
|
|
|
).then((res) => res.json())
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
quotes.map(({ symbol }) => {
|
|
|
|
|
return this.getAssetProfile({
|
|
|
|
|
requestTimeout,
|
|
|
|
|
symbol
|
|
|
|
|
}).then((assetProfile) => {
|
|
|
|
|
if (assetProfile?.currency) {
|
|
|
|
|
currencyBySymbolMap[symbol] = { currency: assetProfile.currency };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
if (assetProfileResolutions.length === symbols.length) {
|
|
|
|
|
for (const { currency, symbolTarget } of assetProfileResolutions) {
|
|
|
|
|
currencyBySymbolMap[symbolTarget] = { currency };
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await Promise.all(
|
|
|
|
|
quotes.map(({ symbol }) => {
|
|
|
|
|
return this.getAssetProfile({
|
|
|
|
|
requestTimeout,
|
|
|
|
|
symbol
|
|
|
|
|
}).then((assetProfile) => {
|
|
|
|
|
if (assetProfile?.currency) {
|
|
|
|
|
currencyBySymbolMap[symbol] = {
|
|
|
|
|
currency: assetProfile.currency
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const { price, symbol } of quotes) {
|
|
|
|
|
let marketState: MarketState = 'delayed';
|
|
|
|
|
@ -394,7 +412,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|
|
|
|
marketState,
|
|
|
|
|
currency: currencyBySymbolMap[symbol]?.currency,
|
|
|
|
|
dataProviderInfo: this.getDataProviderInfo(),
|
|
|
|
|
dataSource: DataSource.FINANCIAL_MODELING_PREP,
|
|
|
|
|
dataSource: this.getDataProviderInfo().dataSource,
|
|
|
|
|
marketPrice: price
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|