From c489a1cc00d2d84fccee2acd01070ad0d2e232f8 Mon Sep 17 00:00:00 2001 From: Varun Chawla <34209028+veeceey@users.noreply.github.com> Date: Sat, 14 Feb 2026 03:33:55 -0800 Subject: [PATCH 1/3] Bugfix/integrate missing valueInBaseCurrency in import activities response (#6294) * Integrate missing valueInBaseCurrency in import activities response * Update changelog --- CHANGELOG.md | 6 ++++++ apps/api/src/app/import/import.service.ts | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90430f5c4..1f91e3c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Added the missing `valueInBaseCurrency` to the response of the import activities endpoint + ## 2.238.0 - 2026-02-12 ### Changed diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 7e8e333b9..a787927b5 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -5,6 +5,7 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.servic import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; @@ -48,6 +49,7 @@ export class ImportService { private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, + private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, private readonly orderService: OrderService, private readonly platformService: PlatformService, @@ -590,10 +592,18 @@ export class ImportService { const value = new Big(quantity).mul(unitPrice).toNumber(); + const valueInBaseCurrency = this.exchangeRateDataService.toCurrencyAtDate( + value, + currency ?? assetProfile.currency, + userCurrency, + date + ); + activities.push({ ...order, error, value, + valueInBaseCurrency: await valueInBaseCurrency, // @ts-ignore SymbolProfile: assetProfile }); From 8a98c0a3f0644f86b0cb1986eb9cc26b0dbce0e9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:37:55 +0100 Subject: [PATCH 2/3] Task/ignore nested ETFs when fetching top holdings in Yahoo Finance service (#6319) * Ignore nested ETFs in top holdings * Update changelog --- CHANGELOG.md | 4 ++++ .../yahoo-finance/yahoo-finance.service.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f91e3c77..3d3c55eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Ignored nested ETFs when fetching top holdings for ETF and mutual fund assets from _Yahoo Finance_ + ### Fixed - Added the missing `valueInBaseCurrency` to the response of the import activities endpoint diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts index c83e35503..72136dc04 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts @@ -207,14 +207,16 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { if (['ETF', 'MUTUALFUND'].includes(assetSubClass)) { response.holdings = - assetProfile.topHoldings?.holdings?.map( - ({ holdingName, holdingPercent }) => { + assetProfile.topHoldings?.holdings + ?.filter(({ holdingName }) => { + return !holdingName?.includes('ETF'); + }) + ?.map(({ holdingName, holdingPercent }) => { return { name: this.formatName({ longName: holdingName }), weight: holdingPercent }; - } - ) ?? []; + }) ?? []; response.sectors = ( assetProfile.topHoldings?.sectorWeightings ?? [] From 299bca27802222c5789bfba05bf912a1f7bcf4dd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:42:06 +0100 Subject: [PATCH 3/3] Task/extend error messages in scraper configuration (#6322) * Improve error handling * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/admin/admin.controller.ts | 7 ++-- .../data-provider/manual/manual.service.ts | 36 +++++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3c55eb3..7c41c75bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Ignored nested ETFs when fetching top holdings for ETF and mutual fund assets from _Yahoo Finance_ +- Improved the scraper configuration with more detailed error messages ### Fixed diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 24467c732..8a202a926 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -247,14 +247,17 @@ export class AdminController { @Param('symbol') symbol: string ): Promise<{ price: number }> { try { - const price = await this.manualService.test(data.scraperConfiguration); + const price = await this.manualService.test({ + symbol, + scraperConfiguration: data.scraperConfiguration + }); if (price) { return { price }; } throw new Error( - `Could not parse the current market price for ${symbol} (${dataSource})` + `Could not parse the market price for ${symbol} (${dataSource})` ); } catch (error) { Logger.error(error, 'AdminController'); diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 7392f0914..51e65e631 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -105,7 +105,10 @@ export class ManualService implements DataProviderInterface { return {}; } - const value = await this.scrape(symbolProfile.scraperConfiguration); + const value = await this.scrape({ + symbol, + scraperConfiguration: symbolProfile.scraperConfiguration + }); return { [symbol]: { @@ -170,7 +173,10 @@ export class ManualService implements DataProviderInterface { symbolProfilesWithScraperConfigurationAndInstantMode.map( async ({ scraperConfiguration, symbol }) => { try { - const marketPrice = await this.scrape(scraperConfiguration); + const marketPrice = await this.scrape({ + scraperConfiguration, + symbol + }); return { marketPrice, symbol }; } catch (error) { Logger.error( @@ -267,13 +273,23 @@ export class ManualService implements DataProviderInterface { }; } - public async test(scraperConfiguration: ScraperConfiguration) { - return this.scrape(scraperConfiguration); + public async test({ + scraperConfiguration, + symbol + }: { + scraperConfiguration: ScraperConfiguration; + symbol: string; + }) { + return this.scrape({ scraperConfiguration, symbol }); } - private async scrape( - scraperConfiguration: ScraperConfiguration - ): Promise { + private async scrape({ + scraperConfiguration, + symbol + }: { + scraperConfiguration: ScraperConfiguration; + symbol: string; + }): Promise { let locale = scraperConfiguration.locale; const response = await fetch(scraperConfiguration.url, { @@ -283,6 +299,12 @@ export class ManualService implements DataProviderInterface { ) }); + if (!response.ok) { + throw new Error( + `Failed to scrape the market price for ${symbol} (${this.getName()}): ${response.status} ${response.statusText} at ${scraperConfiguration.url}` + ); + } + let value: string; if (response.headers.get('content-type')?.includes('application/json')) {