|
|
@ -9,6 +9,7 @@ import { |
|
|
GetQuotesParams, |
|
|
GetQuotesParams, |
|
|
GetSearchParams |
|
|
GetSearchParams |
|
|
} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; |
|
|
} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; |
|
|
|
|
|
import { FetchService } from '@ghostfolio/api/services/fetch/fetch.service'; |
|
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
|
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
|
|
import { |
|
|
import { |
|
|
DEFAULT_CURRENCY, |
|
|
DEFAULT_CURRENCY, |
|
|
@ -59,6 +60,7 @@ export class FinancialModelingPrepService |
|
|
public constructor( |
|
|
public constructor( |
|
|
private readonly configurationService: ConfigurationService, |
|
|
private readonly configurationService: ConfigurationService, |
|
|
private readonly cryptocurrencyService: CryptocurrencyService, |
|
|
private readonly cryptocurrencyService: CryptocurrencyService, |
|
|
|
|
|
private readonly fetchService: FetchService, |
|
|
private readonly prismaService: PrismaService |
|
|
private readonly prismaService: PrismaService |
|
|
) {} |
|
|
) {} |
|
|
|
|
|
|
|
|
@ -96,12 +98,14 @@ export class FinancialModelingPrepService |
|
|
apikey: this.apiKey |
|
|
apikey: this.apiKey |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const [quote] = await fetch( |
|
|
const [quote] = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/quote?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/quote?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
response.assetClass = AssetClass.LIQUIDITY; |
|
|
response.assetClass = AssetClass.LIQUIDITY; |
|
|
response.assetSubClass = AssetSubClass.CRYPTOCURRENCY; |
|
|
response.assetSubClass = AssetSubClass.CRYPTOCURRENCY; |
|
|
@ -115,12 +119,14 @@ export class FinancialModelingPrepService |
|
|
apikey: this.apiKey |
|
|
apikey: this.apiKey |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const [assetProfile] = await fetch( |
|
|
const [assetProfile] = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/profile?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/profile?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
if (!assetProfile) { |
|
|
if (!assetProfile) { |
|
|
throw new AssetProfileDelistedError( |
|
|
throw new AssetProfileDelistedError( |
|
|
@ -143,12 +149,14 @@ export class FinancialModelingPrepService |
|
|
apikey: this.apiKey |
|
|
apikey: this.apiKey |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const etfCountryWeightings = await fetch( |
|
|
const etfCountryWeightings = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?${queryParams.toString()}`, |
|
|
`${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 |
|
|
response.countries = etfCountryWeightings |
|
|
.filter(({ country: countryName }) => { |
|
|
.filter(({ country: countryName }) => { |
|
|
@ -174,12 +182,14 @@ export class FinancialModelingPrepService |
|
|
}; |
|
|
}; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const etfHoldings = await fetch( |
|
|
const etfHoldings = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/holdings?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/holdings?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
const sortedTopHoldings = etfHoldings |
|
|
const sortedTopHoldings = etfHoldings |
|
|
.sort((a, b) => { |
|
|
.sort((a, b) => { |
|
|
@ -193,23 +203,27 @@ export class FinancialModelingPrepService |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const [etfInformation] = await fetch( |
|
|
const [etfInformation] = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/info?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/info?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
if (etfInformation?.website) { |
|
|
if (etfInformation?.website) { |
|
|
response.url = etfInformation.website; |
|
|
response.url = etfInformation.website; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const etfSectorWeightings = await fetch( |
|
|
const etfSectorWeightings = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
response.sectors = etfSectorWeightings.map( |
|
|
response.sectors = etfSectorWeightings.map( |
|
|
({ sector, weightPercentage }) => { |
|
|
({ sector, weightPercentage }) => { |
|
|
@ -286,12 +300,14 @@ export class FinancialModelingPrepService |
|
|
[date: string]: DataProviderHistoricalResponse; |
|
|
[date: string]: DataProviderHistoricalResponse; |
|
|
} = {}; |
|
|
} = {}; |
|
|
|
|
|
|
|
|
const dividends = await fetch( |
|
|
const dividends = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/dividends?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/dividends?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
dividends |
|
|
dividends |
|
|
.filter(({ date }) => { |
|
|
.filter(({ date }) => { |
|
|
@ -354,12 +370,14 @@ export class FinancialModelingPrepService |
|
|
to: format(currentTo, DATE_FORMAT) |
|
|
to: format(currentTo, DATE_FORMAT) |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const historical = await fetch( |
|
|
const historical = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
for (const { close, date } of historical) { |
|
|
for (const { close, date } of historical) { |
|
|
if ( |
|
|
if ( |
|
|
@ -422,13 +440,16 @@ export class FinancialModelingPrepService |
|
|
symbolTarget: { in: symbols } |
|
|
symbolTarget: { in: symbols } |
|
|
} |
|
|
} |
|
|
}), |
|
|
}), |
|
|
fetch( |
|
|
this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/batch-quote-short?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/batch-quote-short?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then( |
|
|
) |
|
|
(res) => res.json() as unknown as { price: number; symbol: string }[] |
|
|
.then( |
|
|
|
|
|
(res) => |
|
|
|
|
|
res.json() as unknown as { price: number; symbol: string }[] |
|
|
) |
|
|
) |
|
|
]); |
|
|
]); |
|
|
|
|
|
|
|
|
@ -525,12 +546,14 @@ export class FinancialModelingPrepService |
|
|
isin: query.toUpperCase() |
|
|
isin: query.toUpperCase() |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const result = await fetch( |
|
|
const result = await this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/search-isin?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/search-isin?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()); |
|
|
) |
|
|
|
|
|
.then((res) => res.json()); |
|
|
|
|
|
|
|
|
await Promise.all( |
|
|
await Promise.all( |
|
|
result.map(({ symbol }) => { |
|
|
result.map(({ symbol }) => { |
|
|
@ -558,18 +581,22 @@ export class FinancialModelingPrepService |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const [nameResults, symbolResults] = await Promise.all([ |
|
|
const [nameResults, symbolResults] = await Promise.all([ |
|
|
fetch( |
|
|
this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/search-name?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/search-name?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()), |
|
|
) |
|
|
fetch( |
|
|
.then((res) => res.json()), |
|
|
|
|
|
this.fetchService |
|
|
|
|
|
.fetch( |
|
|
`${this.getUrl({ version: 'stable' })}/search-symbol?${queryParams.toString()}`, |
|
|
`${this.getUrl({ version: 'stable' })}/search-symbol?${queryParams.toString()}`, |
|
|
{ |
|
|
{ |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
signal: AbortSignal.timeout(requestTimeout) |
|
|
} |
|
|
} |
|
|
).then((res) => res.json()) |
|
|
) |
|
|
|
|
|
.then((res) => res.json()) |
|
|
]); |
|
|
]); |
|
|
|
|
|
|
|
|
const result = uniqBy( |
|
|
const result = uniqBy( |
|
|
|