From 21e78b39820672c138c9097765a5448722d3758f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:44:04 +0200 Subject: [PATCH] Bugfix/handling of indices in FMP service (#7738) * Improve handling of indices * Update changelog --- CHANGELOG.md | 4 + .../financial-modeling-prep.service.ts | 108 +++++++++++++----- 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf6a6da9..9cc46088e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Consolidated the duplicated translations of the asset classes and asset sub classes +### Fixed + +- Improved the handling of indices in the _Financial Modeling Prep_ service + ## 3.62.0 - 2026-08-27 ### Added diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 648ec5fe1..da3d20a07 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -142,9 +142,18 @@ export class FinancialModelingPrepService .then((res) => res.json()); if (!assetProfile) { - throw new AssetProfileDelistedError( - `No data found, ${symbol} (${this.getName()}) may be delisted` - ); + const indexProfile = await this.getIndexProfile({ + requestTimeout, + symbol + }); + + if (!indexProfile) { + throw new AssetProfileDelistedError( + `No data found, ${symbol} (${this.getName()}) may be delisted` + ); + } + + return { ...response, ...indexProfile }; } const { assetClass, assetSubClass } = @@ -582,28 +591,17 @@ export class FinancialModelingPrepService }; }); } else { - const queryParams = new URLSearchParams({ - query, - apikey: this.apiKey - }); - const [nameResults, symbolResults] = await Promise.all([ - this.fetchService - .fetch( - `${this.getUrl({ version: 'stable' })}/search-name?${queryParams.toString()}`, - { - signal: AbortSignal.timeout(requestTimeout) - } - ) - .then((res) => res.json()), - this.fetchService - .fetch( - `${this.getUrl({ version: 'stable' })}/search-symbol?${queryParams.toString()}`, - { - signal: AbortSignal.timeout(requestTimeout) - } - ) - .then((res) => res.json()) + this.getSearchResults({ + query, + requestTimeout, + endpoint: 'search-name' + }), + this.getSearchResults({ + query, + requestTimeout, + endpoint: 'search-symbol' + }) ]); const result = uniqBy( @@ -615,10 +613,9 @@ export class FinancialModelingPrepService items = result .filter(({ exchange, symbol }) => { - if ( - exchange === 'FOREX' || - (includeIndices === false && symbol.startsWith('^')) - ) { + const isIndex = exchange === 'INDEX' || symbol.startsWith('^'); + + if (exchange === 'FOREX' || (includeIndices === false && isIndex)) { return false; } @@ -663,6 +660,61 @@ export class FinancialModelingPrepService return name; } + /** + * Financial Modeling Prep has no profile for an index, thus the currency and + * the name are taken from the search endpoint. + */ + private async getIndexProfile({ + requestTimeout, + symbol + }: { + requestTimeout: number; + symbol: string; + }): Promise> { + const results = await this.getSearchResults({ + requestTimeout, + endpoint: 'search-symbol', + query: symbol + }); + + const index = results?.find(({ exchange, symbol: symbolOfResult }) => { + return exchange === 'INDEX' && symbolOfResult === symbol; + }); + + if (!index) { + return undefined; + } + + return { + currency: index.currency, + name: this.formatName({ name: index.name }) + }; + } + + private async getSearchResults({ + endpoint, + query, + requestTimeout + }: { + endpoint: 'search-name' | 'search-symbol'; + query: string; + requestTimeout: number; + }) { + const queryParams = new URLSearchParams({ + query, + apikey: this.apiKey + }); + + return this.fetchService + .fetch( + `${this.getUrl({ version: 'stable' })}/${endpoint}?${queryParams.toString()}`, + { + signal: AbortSignal.timeout(requestTimeout) + } + ) + .then((res) => res.json()); + } + private getUrl({ version }: { version: number | 'stable' }) { const baseUrl = 'https://financialmodelingprep.com';