|
|
@ -44,6 +44,12 @@ import { |
|
|
|
|
|
|
|
|
@Injectable() |
|
|
@Injectable() |
|
|
export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
|
|
|
private static countriesMapping = { |
|
|
|
|
|
'Korea (the Republic of)': 'South Korea', |
|
|
|
|
|
'Russian Federation': 'Russia', |
|
|
|
|
|
'Taiwan (Province of China)': 'Taiwan' |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
private apiKey: string; |
|
|
private apiKey: string; |
|
|
|
|
|
|
|
|
public constructor( |
|
|
public constructor( |
|
|
@ -79,8 +85,13 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
symbol.length - DEFAULT_CURRENCY.length |
|
|
symbol.length - DEFAULT_CURRENCY.length |
|
|
); |
|
|
); |
|
|
} else if (this.cryptocurrencyService.isCryptocurrency(symbol)) { |
|
|
} else if (this.cryptocurrencyService.isCryptocurrency(symbol)) { |
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
symbol, |
|
|
|
|
|
apikey: this.apiKey |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [quote] = await fetch( |
|
|
const [quote] = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/quote?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/quote?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -93,8 +104,13 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
); |
|
|
); |
|
|
response.name = quote.name; |
|
|
response.name = quote.name; |
|
|
} else { |
|
|
} else { |
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
symbol, |
|
|
|
|
|
apikey: this.apiKey |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [assetProfile] = await fetch( |
|
|
const [assetProfile] = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/profile?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/profile?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -114,19 +130,31 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
assetSubClass === AssetSubClass.ETF || |
|
|
assetSubClass === AssetSubClass.ETF || |
|
|
assetSubClass === AssetSubClass.MUTUALFUND |
|
|
assetSubClass === AssetSubClass.MUTUALFUND |
|
|
) { |
|
|
) { |
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
symbol, |
|
|
|
|
|
apikey: this.apiKey |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const etfCountryWeightings = await fetch( |
|
|
const etfCountryWeightings = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
).then((res) => res.json()); |
|
|
|
|
|
|
|
|
response.countries = etfCountryWeightings.map( |
|
|
response.countries = etfCountryWeightings |
|
|
({ country: countryName, weightPercentage }) => { |
|
|
.filter(({ country: countryName }) => { |
|
|
|
|
|
return countryName.toLowerCase() !== 'other'; |
|
|
|
|
|
}) |
|
|
|
|
|
.map(({ country: countryName, weightPercentage }) => { |
|
|
let countryCode: string; |
|
|
let countryCode: string; |
|
|
|
|
|
|
|
|
for (const [code, country] of Object.entries(countries)) { |
|
|
for (const [code, country] of Object.entries(countries)) { |
|
|
if (country.name === countryName) { |
|
|
if ( |
|
|
|
|
|
country.name === countryName || |
|
|
|
|
|
country.name === |
|
|
|
|
|
FinancialModelingPrepService.countriesMapping[countryName] |
|
|
|
|
|
) { |
|
|
countryCode = code; |
|
|
countryCode = code; |
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
@ -136,11 +164,10 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
code: countryCode, |
|
|
code: countryCode, |
|
|
weight: parseFloat(weightPercentage.slice(0, -1)) / 100 |
|
|
weight: parseFloat(weightPercentage.slice(0, -1)) / 100 |
|
|
}; |
|
|
}; |
|
|
} |
|
|
}); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const etfHoldings = await fetch( |
|
|
const etfHoldings = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/holdings?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/holdings?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -159,7 +186,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const [etfInformation] = await fetch( |
|
|
const [etfInformation] = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/info?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/info?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -170,7 +197,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const etfSectorWeightings = await fetch( |
|
|
const etfSectorWeightings = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -242,12 +269,17 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
symbol, |
|
|
|
|
|
apikey: this.apiKey |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const response: { |
|
|
const response: { |
|
|
[date: string]: DataProviderHistoricalResponse; |
|
|
[date: string]: DataProviderHistoricalResponse; |
|
|
} = {}; |
|
|
} = {}; |
|
|
|
|
|
|
|
|
const dividends = await fetch( |
|
|
const dividends = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/dividends?symbol=${symbol}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/dividends?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -307,8 +339,15 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
? addYears(currentFrom, MAX_YEARS_PER_REQUEST) |
|
|
? addYears(currentFrom, MAX_YEARS_PER_REQUEST) |
|
|
: to; |
|
|
: to; |
|
|
|
|
|
|
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
symbol, |
|
|
|
|
|
apikey: this.apiKey, |
|
|
|
|
|
from: format(currentFrom, DATE_FORMAT), |
|
|
|
|
|
to: format(currentTo, DATE_FORMAT) |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const historical = await fetch( |
|
|
const historical = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?symbol=${symbol}&apikey=${this.apiKey}&from=${format(currentFrom, DATE_FORMAT)}&to=${format(currentTo, DATE_FORMAT)}`, |
|
|
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -363,6 +402,11 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
[symbol: string]: Pick<SymbolProfile, 'currency'>; |
|
|
[symbol: string]: Pick<SymbolProfile, 'currency'>; |
|
|
} = {}; |
|
|
} = {}; |
|
|
|
|
|
|
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
symbols: symbols.join(','), |
|
|
|
|
|
apikey: this.apiKey |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const [assetProfileResolutions, quotes] = await Promise.all([ |
|
|
const [assetProfileResolutions, quotes] = await Promise.all([ |
|
|
this.prismaService.assetProfileResolution.findMany({ |
|
|
this.prismaService.assetProfileResolution.findMany({ |
|
|
where: { |
|
|
where: { |
|
|
@ -371,7 +415,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
} |
|
|
} |
|
|
}), |
|
|
}), |
|
|
fetch( |
|
|
fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/batch-quote-short?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -463,12 +507,18 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
const assetProfileBySymbolMap: { |
|
|
const assetProfileBySymbolMap: { |
|
|
[symbol: string]: Partial<SymbolProfile>; |
|
|
[symbol: string]: Partial<SymbolProfile>; |
|
|
} = {}; |
|
|
} = {}; |
|
|
|
|
|
|
|
|
let items: LookupItem[] = []; |
|
|
let items: LookupItem[] = []; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
if (isISIN(query?.toUpperCase())) { |
|
|
if (isISIN(query?.toUpperCase())) { |
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
apikey: this.apiKey, |
|
|
|
|
|
isin: query.toUpperCase() |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const result = await fetch( |
|
|
const result = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/search-isin?isin=${query.toUpperCase()}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/search-isin?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
@ -494,8 +544,13 @@ export class FinancialModelingPrepService implements DataProviderInterface { |
|
|
}; |
|
|
}; |
|
|
}); |
|
|
}); |
|
|
} else { |
|
|
} else { |
|
|
|
|
|
const queryParams = new URLSearchParams({ |
|
|
|
|
|
query, |
|
|
|
|
|
apikey: this.apiKey |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const result = await fetch( |
|
|
const result = await fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/search-symbol?query=${query}&apikey=${this.apiKey}`, |
|
|
`${this.getUrl({ version: 'stable' })}/search-symbol?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout( |
|
|
signal: AbortSignal.timeout( |
|
|
this.configurationService.get('REQUEST_TIMEOUT') |
|
|
this.configurationService.get('REQUEST_TIMEOUT') |
|
|
|