From 2b5d2701bd00acb4fff83e1ebf2f5b19b42dfeb9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 00:06:01 +0200 Subject: [PATCH 01/15] Task/improve exchange rate and market data gathering robustness (#7119) * Improve exchange rate robustness * Improve market data gathering robustness * Update changelog --- CHANGELOG.md | 2 + .../src/app/activities/activities.service.ts | 8 +- apps/api/src/app/import/import.service.ts | 15 ++-- .../src/app/portfolio/portfolio.service.ts | 8 +- .../market-data/market-data.service.ts | 74 ++++++++++--------- libs/common/src/lib/config.ts | 1 + 6 files changed, 58 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f175878fc..c8d6a7244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed an issue where symbols with special characters caused API request failures by URL encoding the symbol - Fixed the disabled state of the delete action in the asset profiles actions menu of the historical market data table in the admin control panel - Fixed the persistence of an empty `locale` string in the scraper configuration +- Fixed a transaction timeout that prevented gathering historical market data for symbols with a long history +- Fixed an exception in various portfolio endpoints when historical exchange rate data is missing ## 3.14.0 - 2026-06-22 diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index f57507e3d..44ab67e45 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -746,10 +746,10 @@ export class ActivitiesService { const value = new Big(order.quantity).mul(order.unitPrice).toNumber(); const [ - feeInAssetProfileCurrency, - feeInBaseCurrency, - unitPriceInAssetProfileCurrency, - valueInBaseCurrency + feeInAssetProfileCurrency = 0, + feeInBaseCurrency = 0, + unitPriceInAssetProfileCurrency = 0, + valueInBaseCurrency = 0 ] = await Promise.all([ this.exchangeRateDataService.toCurrencyAtDate( order.fee, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 2ecc4d3a5..ba704d5ad 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -592,18 +592,19 @@ export class ImportService { const value = new Big(quantity).mul(unitPrice).toNumber(); - const valueInBaseCurrency = this.exchangeRateDataService.toCurrencyAtDate( - value, - currency ?? assetProfile.currency, - userCurrency, - date - ); + const valueInBaseCurrency = + (await this.exchangeRateDataService.toCurrencyAtDate( + value, + currency ?? assetProfile.currency, + userCurrency, + date + )) ?? 0; activities.push({ ...order, error, value, - valueInBaseCurrency: await valueInBaseCurrency, + valueInBaseCurrency, // @ts-ignore SymbolProfile: assetProfile }); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 418e60401..77cbb5b49 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -205,21 +205,21 @@ export class PortfolioService { switch (type) { case ActivityType.DIVIDEND: dividendInBaseCurrency += - await this.exchangeRateDataService.toCurrencyAtDate( + (await this.exchangeRateDataService.toCurrencyAtDate( new Big(quantity).mul(unitPrice).toNumber(), currency ?? SymbolProfile.currency, userCurrency, date - ); + )) ?? 0; break; case ActivityType.INTEREST: interestInBaseCurrency += - await this.exchangeRateDataService.toCurrencyAtDate( + (await this.exchangeRateDataService.toCurrencyAtDate( unitPrice, currency ?? SymbolProfile.currency, userCurrency, date - ); + )) ?? 0; break; } diff --git a/apps/api/src/services/market-data/market-data.service.ts b/apps/api/src/services/market-data/market-data.service.ts index 27c741055..086434724 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -1,6 +1,7 @@ import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface'; import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT } from '@ghostfolio/common/config'; import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; import { resetHours } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; @@ -155,49 +156,52 @@ export class MarketDataService { dataSource, symbol }: AssetProfileIdentifier & { data: Prisma.MarketDataUpdateInput[] }) { - await this.prismaService.$transaction(async (prisma) => { - if (data.length > 0) { - let minTime = Infinity; - let maxTime = -Infinity; + await this.prismaService.$transaction( + async (prisma) => { + if (data.length > 0) { + let minTime = Infinity; + let maxTime = -Infinity; - for (const { date } of data) { - const time = (date as Date).getTime(); + for (const { date } of data) { + const time = (date as Date).getTime(); - if (time < minTime) { - minTime = time; - } + if (time < minTime) { + minTime = time; + } - if (time > maxTime) { - maxTime = time; + if (time > maxTime) { + maxTime = time; + } } - } - const minDate = new Date(minTime); - const maxDate = new Date(maxTime); + const minDate = new Date(minTime); + const maxDate = new Date(maxTime); - await prisma.marketData.deleteMany({ - where: { - dataSource, - symbol, - date: { - gte: minDate, - lte: maxDate + await prisma.marketData.deleteMany({ + where: { + dataSource, + symbol, + date: { + gte: minDate, + lte: maxDate + } } - } - }); + }); - await prisma.marketData.createMany({ - data: data.map(({ date, marketPrice, state }) => ({ - dataSource, - symbol, - date: date as Date, - marketPrice: marketPrice as number, - state: state as MarketDataState - })), - skipDuplicates: true - }); - } - }); + await prisma.marketData.createMany({ + data: data.map(({ date, marketPrice, state }) => ({ + dataSource, + symbol, + date: date as Date, + marketPrice: marketPrice as number, + state: state as MarketDataState + })), + skipDuplicates: true + }); + } + }, + { timeout: DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT } + ); } public async updateAssetProfileIdentifier( diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index e6d717c7b..f96a934e7 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -90,6 +90,7 @@ export const DEFAULT_PAGE_SIZE = 50; export const DEFAULT_PORT = 3333; export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY = 1; +export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT = 60000; export const DEFAULT_PROCESSOR_GATHER_STATISTICS_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; From 5cd81192ee09e83f4f64ab2298ce9cff4e6faeea Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 00:07:34 +0200 Subject: [PATCH 02/15] Release 3.15.1 (#7120) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d6a7244..a903122d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## 3.15.0 - 2026-06-23 +## 3.15.1 - 2026-06-23 ### Changed diff --git a/package-lock.json b/package-lock.json index d3250d5d0..e40bcbe1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.15.0", + "version": "3.15.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.15.0", + "version": "3.15.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 423b64cb9..df644f017 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.15.0", + "version": "3.15.1", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 020c982e73c706c7ec0a81fabe75ef0c73dad7fe Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:05:11 +0200 Subject: [PATCH 03/15] Bugfix/hourly market data updates not refreshing prices for asset profiles with manual data source (#7122) * Fix hourly market data updates not refreshing prices for MANUAL asset profiles * Update changelog --- CHANGELOG.md | 6 +++ .../data-provider/data-provider.service.ts | 6 ++- .../interfaces/data-provider.interface.ts | 1 + .../data-provider/manual/manual.service.ts | 45 ++++++++++--------- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a903122d4..4ec1539da 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 + +- Fixed an issue with hourly market data updates not refreshing prices for asset profiles with `MANUAL` data source + ## 3.15.1 - 2026-06-23 ### Changed diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 5d49848fa..10b0e6fd8 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -662,7 +662,11 @@ export class DataProviderService implements OnModuleInit { ); const promise = Promise.resolve( - dataProvider.getQuotes({ requestTimeout, symbols: symbolsChunk }) + dataProvider.getQuotes({ + requestTimeout, + useCache, + symbols: symbolsChunk + }) ); promises.push( diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index a55c9f328..5002fa87e 100644 --- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -75,6 +75,7 @@ export interface GetHistoricalParams { export interface GetQuotesParams { requestTimeout?: number; symbols: string[]; + useCache?: boolean; } export interface GetSearchParams { 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 87e116dda..66571c239 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -136,7 +136,8 @@ export class ManualService implements DataProviderInterface { } public async getQuotes({ - symbols + symbols, + useCache = true }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { const response: { [symbol: string]: DataProviderResponse } = {}; @@ -164,32 +165,32 @@ export class ManualService implements DataProviderInterface { } }); - const symbolProfilesWithScraperConfigurationAndInstantMode = - symbolProfiles.filter(({ scraperConfiguration }) => { + const symbolProfilesToScrape = symbolProfiles.filter( + ({ scraperConfiguration }) => { return ( - scraperConfiguration?.mode === 'instant' && + (scraperConfiguration?.mode === 'instant' || !useCache) && scraperConfiguration?.selector && scraperConfiguration?.url ); - }); - - const scraperResultPromises = - symbolProfilesWithScraperConfigurationAndInstantMode.map( - async ({ scraperConfiguration, symbol }) => { - try { - const marketPrice = await this.scrape({ - scraperConfiguration, - symbol - }); - return { marketPrice, symbol }; - } catch (error) { - this.logger.error( - `Could not get quote for ${symbol} (${this.getName()}): [${error.name}] ${error.message}` - ); - return { symbol, marketPrice: undefined }; - } + } + ); + + const scraperResultPromises = symbolProfilesToScrape.map( + async ({ scraperConfiguration, symbol }) => { + try { + const marketPrice = await this.scrape({ + scraperConfiguration, + symbol + }); + return { marketPrice, symbol }; + } catch (error) { + this.logger.error( + `Could not get quote for ${symbol} (${this.getName()}): [${error.name}] ${error.message}` + ); + return { symbol, marketPrice: undefined }; } - ); + } + ); // Wait for all scraping requests to complete concurrently const scraperResults = await Promise.all(scraperResultPromises); From 11f37f8f7ddae1eedd91c361ea8d5d7f697249ba Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:06:26 +0200 Subject: [PATCH 04/15] Task/improve style of Ghostfolio Premium card in admin settings (#7127) Improve style --- .../admin-settings.component.scss | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.scss b/apps/client/src/app/components/admin-settings/admin-settings.component.scss index 1c0a17624..29340ea0d 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.scss +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.scss @@ -10,7 +10,33 @@ } .mat-mdc-card { - --mat-card-outlined-container-color: whitesmoke; + --gradient-opacity: 50%; + --mat-card-outlined-outline-color: rgb(193, 219, 254); + + background-color: white; + background-image: linear-gradient( + 109.6deg, + color-mix(in srgb, rgba(255, 255, 255, 1) 100%, transparent) 11.2%, + color-mix( + in srgb, + rgba(221, 108, 241, 0.26) var(--gradient-opacity), + transparent + ) + 42%, + color-mix( + in srgb, + rgba(229, 106, 253, 0.71) var(--gradient-opacity), + transparent + ) + 71.5%, + color-mix( + in srgb, + rgba(123, 183, 253, 1) var(--gradient-opacity), + transparent + ) + 100% + ); + color: rgb(var(--dark-primary-text)); .mat-mdc-card-actions { min-height: 0; @@ -32,6 +58,6 @@ :host-context(.theme-dark) { .mat-mdc-card { - --mat-card-outlined-container-color: #222222; + --mat-card-outlined-outline-color: white; } } From a2dbfcf8e301cd8b4ed5a70c13ba019d536820e9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:07:04 +0200 Subject: [PATCH 05/15] Task/refactor timeouts to use ms() (#7123) Refactor to use ms() --- libs/common/src/lib/config.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index f96a934e7..03600088f 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -90,10 +90,12 @@ export const DEFAULT_PAGE_SIZE = 50; export const DEFAULT_PORT = 3333; export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY = 1; -export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT = 60000; +export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT = + ms('1 minute'); export const DEFAULT_PROCESSOR_GATHER_STATISTICS_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY = 1; -export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; +export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = + ms('30 seconds'); export const DEFAULT_REDACTED_PATHS = [ 'accounts[*].balance', From f64edb6705aa86cedb968567603fc669f1cb9371 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:16:54 +0200 Subject: [PATCH 06/15] Feature/add pagination to platform and tag management of admin control panel (#7126) * Add pagination * Update changelog --- CHANGELOG.md | 5 +++++ .../components/admin-platform/admin-platform.component.html | 6 ++++++ .../components/admin-platform/admin-platform.component.ts | 6 ++++++ .../src/app/components/admin-tag/admin-tag.component.html | 6 ++++++ .../src/app/components/admin-tag/admin-tag.component.ts | 6 ++++++ 5 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec1539da..47c43d38f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added pagination to the platform management of the admin control panel +- Added pagination to the tag management of the admin control panel + ### Fixed - Fixed an issue with hourly market data updates not refreshing prices for asset profiles with `MANUAL` data source diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.html b/apps/client/src/app/components/admin-platform/admin-platform.component.html index 44f5a6eab..19682bdc0 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.html +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -96,3 +96,9 @@ + + diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 26e6c2b1e..727585478 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,4 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale, getLowercase } from '@ghostfolio/common/helper'; @@ -22,6 +23,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -46,6 +48,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- IonIcon, MatButtonModule, MatMenuModule, + MatPaginatorModule, MatSortModule, MatTableModule, RouterModule @@ -59,11 +62,13 @@ export class GfAdminPlatformComponent implements OnInit { protected dataSource = new MatTableDataSource(); protected readonly displayedColumns = ['name', 'url', 'accounts', 'actions']; + protected readonly pageSize = DEFAULT_PAGE_SIZE; protected platforms: Platform[]; private readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); + private readonly paginator = viewChild.required(MatPaginator); private readonly sort = viewChild.required(MatSort); private readonly adminService = inject(AdminService); @@ -145,6 +150,7 @@ export class GfAdminPlatformComponent implements OnInit { this.platforms = platforms; this.dataSource = new MatTableDataSource(platforms); + this.dataSource.paginator = this.paginator(); this.dataSource.sort = this.sort(); this.dataSource.sortingDataAccessor = getLowercase; diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.html b/apps/client/src/app/components/admin-tag/admin-tag.component.html index 3c125d5c0..a84cbb283 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.html +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -89,3 +89,9 @@ + + diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index fcb901707..7bd69c964 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,4 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale, getLowercase } from '@ghostfolio/common/helper'; @@ -21,6 +22,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -44,6 +46,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int IonIcon, MatButtonModule, MatMenuModule, + MatPaginatorModule, MatSortModule, MatTableModule, RouterModule @@ -62,11 +65,13 @@ export class GfAdminTagComponent implements OnInit { 'activities', 'actions' ]; + protected readonly pageSize = DEFAULT_PAGE_SIZE; protected tags: Tag[]; private readonly deviceType = computed( () => this.deviceDetectorService.deviceInfo().deviceType ); + private readonly paginator = viewChild.required(MatPaginator); private readonly sort = viewChild.required(MatSort); private readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -147,6 +152,7 @@ export class GfAdminTagComponent implements OnInit { this.tags = tags; this.dataSource = new MatTableDataSource(this.tags); + this.dataSource.paginator = this.paginator(); this.dataSource.sort = this.sort(); this.dataSource.sortingDataAccessor = getLowercase; From 0d5c20622647fcdecabee2680b3b6a71a494b41b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:19:33 +0200 Subject: [PATCH 07/15] Bugfix/log context formatting in performance logging service (#7121) * Fix log context formatting * Update changelog --- CHANGELOG.md | 1 + .../performance-logging/performance-logging.service.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c43d38f..4f019b7f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed an issue with hourly market data updates not refreshing prices for asset profiles with `MANUAL` data source +- Fixed an issue with the log context formatting in the performance logging service ## 3.15.1 - 2026-06-23 diff --git a/apps/api/src/interceptors/performance-logging/performance-logging.service.ts b/apps/api/src/interceptors/performance-logging/performance-logging.service.ts index a07783cd9..b2ea03c37 100644 --- a/apps/api/src/interceptors/performance-logging/performance-logging.service.ts +++ b/apps/api/src/interceptors/performance-logging/performance-logging.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; @Injectable() export class PerformanceLoggingService { - private readonly logger = new Logger(PerformanceLoggingService.name); + private readonly logger = new Logger(); public logPerformance({ className, From 6d96f75aca218338e150ad4170bdd32e0d63d09c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:00:10 +0200 Subject: [PATCH 08/15] Feature/extend asset profile details dialog with copy to clipboard buttons for ISIN and symbol (#7124) * Extend dialog with copy-to-clipboard button for ISIN number and symbol * Update changelog --- CHANGELOG.md | 2 ++ .../asset-profile-dialog/asset-profile-dialog.html | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f019b7f3..00c5a90fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added pagination to the platform management of the admin control panel - Added pagination to the tag management of the admin control panel +- Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the ISIN number +- Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the symbol ### Fixed diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index f00c279a0..9b69ef6fc 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -165,7 +165,11 @@ } @else {
- Symbol
@@ -202,6 +206,7 @@
ISIN Date: Wed, 24 Jun 2026 19:01:15 +0200 Subject: [PATCH 09/15] Feature/extend user account settings with copy-to-clipboard button for user id (#7125) * Extend settings with copy-to-clipboard button for user id * Update changelog --- CHANGELOG.md | 1 + .../user-account-settings.component.ts | 2 ++ .../user-account-settings/user-account-settings.html | 8 +++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00c5a90fc..04a4d896a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Extended the user account settings with a copy-to-clipboard button for the user id - Added pagination to the platform management of the admin control panel - Added pagination to the tag management of the admin control panel - Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the ISIN number diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts index 72bbfc2c6..0101bb90a 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -12,6 +12,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; +import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, @@ -51,6 +52,7 @@ import { catchError } from 'rxjs/operators'; changeDetection: ChangeDetectionStrategy.OnPush, imports: [ FormsModule, + GfValueComponent, IonIcon, MatButtonModule, MatCardModule, diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html index f646ef0fd..e4e9777de 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.html +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -260,7 +260,13 @@
Ghostfolio User ID
-
{{ user?.id }}
+
+ +
From 95b0318f8e73370b459e761aa333cf8a9c0eb003 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:04:36 +0200 Subject: [PATCH 10/15] Task/improve throughput of market data gathering queue by applying rate limit per data source (#7128) * Improve throughput by applying rate limit per data source * Update changelog --- CHANGELOG.md | 4 ++++ .../services/queues/data-gathering/data-gathering.module.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a4d896a..df2f6ba9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the ISIN number - Extended the asset profile details dialog of the admin control panel with a copy-to-clipboard button for the symbol +### Changed + +- Improved the throughput of the market data gathering queue by applying the rate limit per data source + ### Fixed - Fixed an issue with hourly market data updates not refreshing prices for asset profiles with `MANUAL` data source diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts index 5ac6c40c0..7258c034c 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts @@ -29,6 +29,7 @@ import { DataGatheringProcessor } from './data-gathering.processor'; BullModule.registerQueue({ limiter: { duration: ms('4 seconds'), + groupKey: 'dataSource', max: 1 }, name: DATA_GATHERING_QUEUE From 3c758b90e2e81fa5e1ca4294c75bd0aff57340fa Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:17:00 +0200 Subject: [PATCH 11/15] Task/decrease rate limiter duration of market data gathering queue (#7129) * Decrease rate limiter duration * Update changelog --- CHANGELOG.md | 1 + .../src/services/queues/data-gathering/data-gathering.module.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df2f6ba9e..88ea7dd26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved the throughput of the market data gathering queue by applying the rate limit per data source +- Decreased the rate limiter duration of the market data gathering queue jobs from 4 to 3 seconds ### Fixed diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts index 7258c034c..d66411797 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts @@ -28,7 +28,7 @@ import { DataGatheringProcessor } from './data-gathering.processor'; }), BullModule.registerQueue({ limiter: { - duration: ms('4 seconds'), + duration: ms('3 seconds'), groupKey: 'dataSource', max: 1 }, From e8c0e62dbb63cdfce6d426db96f5f0a591be5d1f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:17:23 +0200 Subject: [PATCH 12/15] Task/upgrade simplewebauthn to version 13.3 (#7115) * Upgrade simplewebauthn to version 13.3 * Update changelog --- CHANGELOG.md | 4 ++++ package-lock.json | 28 ++++++++++++++-------------- package.json | 4 ++-- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88ea7dd26..f8a0ca796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the throughput of the market data gathering queue by applying the rate limit per data source - Decreased the rate limiter duration of the market data gathering queue jobs from 4 to 3 seconds +### Changed + +- Upgraded `@simplewebauthn/browser` and `@simplewebauthn/server` from version `13.2.2` to `13.3` + ### Fixed - Fixed an issue with hourly market data updates not refreshing prices for asset profiles with `MANUAL` data source diff --git a/package-lock.json b/package-lock.json index e40bcbe1c..bb9e62dbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,8 +43,8 @@ "@openrouter/ai-sdk-provider": "2.9.1", "@prisma/adapter-pg": "7.8.0", "@prisma/client": "7.8.0", - "@simplewebauthn/browser": "13.2.2", - "@simplewebauthn/server": "13.2.2", + "@simplewebauthn/browser": "13.3.0", + "@simplewebauthn/server": "13.3.1", "ai": "6.0.174", "alphavantage": "2.2.0", "big.js": "7.0.1", @@ -13296,25 +13296,25 @@ } }, "node_modules/@simplewebauthn/browser": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.2.tgz", - "integrity": "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz", + "integrity": "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==", "license": "MIT" }, "node_modules/@simplewebauthn/server": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.2.2.tgz", - "integrity": "sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.3.1.tgz", + "integrity": "sha512-GV/oM/qeycWn8p42JZIMJBsXWQcNFg+nJFzeQTnMA4gN8mXg0+HZFWJerHg8ZN/zlveMS3iV1wzuFpOVWS/46w==", "license": "MIT", "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", - "@peculiar/asn1-android": "^2.3.10", - "@peculiar/asn1-ecc": "^2.3.8", - "@peculiar/asn1-rsa": "^2.3.8", - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/asn1-x509": "^2.3.8", - "@peculiar/x509": "^1.13.0" + "@peculiar/asn1-android": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/x509": "^1.14.3" }, "engines": { "node": ">=20.0.0" diff --git a/package.json b/package.json index df644f017..7371a9187 100644 --- a/package.json +++ b/package.json @@ -87,8 +87,8 @@ "@openrouter/ai-sdk-provider": "2.9.1", "@prisma/adapter-pg": "7.8.0", "@prisma/client": "7.8.0", - "@simplewebauthn/browser": "13.2.2", - "@simplewebauthn/server": "13.2.2", + "@simplewebauthn/browser": "13.3.0", + "@simplewebauthn/server": "13.3.1", "ai": "6.0.174", "alphavantage": "2.2.0", "big.js": "7.0.1", From 981f21425f4e565c7a4215a5bf606a378558e680 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:43:34 +0200 Subject: [PATCH 13/15] Task/upgrade @types/big.js to version 7.0.0 (#7098) Upgrade @types/big.js to version 7.0.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb9e62dbd..7cc890099 100644 --- a/package-lock.json +++ b/package-lock.json @@ -129,7 +129,7 @@ "@storybook/addon-themes": "10.1.10", "@storybook/angular": "10.1.10", "@trivago/prettier-plugin-sort-imports": "6.0.2", - "@types/big.js": "6.2.2", + "@types/big.js": "7.0.0", "@types/cookie-parser": "1.4.10", "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", @@ -14020,9 +14020,9 @@ } }, "node_modules/@types/big.js": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.2.2.tgz", - "integrity": "sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-7.0.0.tgz", + "integrity": "sha512-WfAGp7IbJvyB8EmWK4tJD24rJRAL6uVbw3LV/hJntFNam+os9KWKj0PzXo8rRRpjupYK8U0M8FoBB8dBhWF2dg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 7371a9187..fb1976570 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,7 @@ "@storybook/addon-themes": "10.1.10", "@storybook/angular": "10.1.10", "@trivago/prettier-plugin-sort-imports": "6.0.2", - "@types/big.js": "6.2.2", + "@types/big.js": "7.0.0", "@types/cookie-parser": "1.4.10", "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", From 9b86a9a73f2fe00509de2d74a51f3081a36cfd6a Mon Sep 17 00:00:00 2001 From: Akash Negi <95514575+AkashNegi1@users.noreply.github.com> Date: Wed, 24 Jun 2026 23:44:14 +0530 Subject: [PATCH 14/15] Task/remove deprecated SymbolProfile from PortfolioHoldingResponse (#7094) * Remove deprecated SymbolProfile * Update changelog --- CHANGELOG.md | 4 +- .../src/app/portfolio/portfolio.service.ts | 1 - .../holding-detail-dialog.component.ts | 52 +++++++++++------- .../holding-detail-dialog.html | 54 +++++++++---------- .../portfolio-holding-response.interface.ts | 4 -- 5 files changed, 60 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a0ca796..76dbe113a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the throughput of the market data gathering queue by applying the rate limit per data source - Decreased the rate limiter duration of the market data gathering queue jobs from 4 to 3 seconds - -### Changed - +- Removed the deprecated `SymbolProfile` field from the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Upgraded `@simplewebauthn/browser` and `@simplewebauthn/server` from version `13.2.2` to `13.3` ### Fixed diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 77cbb5b49..41697b346 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -957,7 +957,6 @@ export class PortfolioService { marketPrice, marketPriceMax, marketPriceMin, - SymbolProfile, tags, assetProfile: { assetClass: SymbolProfile.assetClass, diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index e14c638be..6fa3a4853 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -116,6 +116,19 @@ export class GfHoldingDetailDialogComponent implements OnInit { protected accounts: Account[]; protected activitiesCount: number; protected assetClass: string; + protected assetProfile: Pick< + EnhancedSymbolProfile, + | 'assetClass' + | 'assetSubClass' + | 'countries' + | 'currency' + | 'dataSource' + | 'isin' + | 'name' + | 'sectors' + | 'symbol' + | 'userId' + >; protected assetSubClass: string; protected averagePrice: number; protected averagePricePrecision = 2; @@ -164,7 +177,6 @@ export class GfHoldingDetailDialogComponent implements OnInit { }; protected sortColumn = 'date'; protected sortDirection: SortDirection = 'desc'; - protected SymbolProfile: EnhancedSymbolProfile; protected tagsAvailable: Tag[]; protected readonly translate = translate; protected user: User; @@ -266,6 +278,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { .subscribe( ({ activitiesCount, + assetProfile, averagePrice, dataProviderInfo, dateOfFirstActivity, @@ -280,11 +293,11 @@ export class GfHoldingDetailDialogComponent implements OnInit { netPerformancePercentWithCurrencyEffect, netPerformanceWithCurrencyEffect, quantity, - SymbolProfile, tags, value }) => { this.activitiesCount = activitiesCount; + this.assetProfile = assetProfile; this.averagePrice = averagePrice; if ( @@ -322,8 +335,8 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.user?.permissions, permissions.readMarketDataOfOwnAssetProfile ) && - SymbolProfile?.dataSource === 'MANUAL' && - SymbolProfile?.userId === this.user?.id; + assetProfile?.dataSource === 'MANUAL' && + assetProfile?.userId === this.user?.id; this.historicalDataItems = historicalData.map( ({ averagePrice, date, marketPrice }) => { @@ -402,7 +415,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { if (Number.isInteger(this.quantity)) { this.quantityPrecision = 0; - } else if (SymbolProfile?.assetSubClass === 'CRYPTOCURRENCY') { + } else if (assetProfile?.assetSubClass === 'CRYPTOCURRENCY') { if (this.quantity < 10) { this.quantityPrecision = 8; } else if (this.quantity < 1000) { @@ -413,7 +426,6 @@ export class GfHoldingDetailDialogComponent implements OnInit { } this.sectors = {}; - this.SymbolProfile = SymbolProfile; this.tags = tags.map((tag) => { return { @@ -427,21 +439,21 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.value = value; const reportDataGlitchSubject = `Ghostfolio Data Glitch Report${ - this.SymbolProfile?.symbol ? ` (${this.SymbolProfile.symbol})` : '' + this.assetProfile?.symbol ? ` (${this.assetProfile.symbol})` : '' }`; - this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=${reportDataGlitchSubject}&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${this.SymbolProfile?.symbol}%0DData Source: ${this.SymbolProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; + this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=${reportDataGlitchSubject}&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${this.assetProfile?.symbol}%0DData Source: ${this.assetProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; - if (this.SymbolProfile?.assetClass) { - this.assetClass = translate(this.SymbolProfile?.assetClass); + if (this.assetProfile?.assetClass) { + this.assetClass = translate(this.assetProfile?.assetClass); } - if (this.SymbolProfile?.assetSubClass) { - this.assetSubClass = translate(this.SymbolProfile?.assetSubClass); + if (this.assetProfile?.assetSubClass) { + this.assetSubClass = translate(this.assetProfile?.assetSubClass); } - if (this.SymbolProfile?.countries?.length > 0) { - for (const country of this.SymbolProfile.countries) { + if (this.assetProfile?.countries?.length > 0) { + for (const country of this.assetProfile.countries) { this.countries[country.code] = { name: getCountryName({ code: country.code }), value: country.weight @@ -449,8 +461,8 @@ export class GfHoldingDetailDialogComponent implements OnInit { } } - if (this.SymbolProfile?.sectors?.length > 0) { - for (const sector of this.SymbolProfile.sectors) { + if (this.assetProfile?.sectors?.length > 0) { + for (const sector of this.assetProfile.sectors) { this.sectors[sector.name] = { name: translate(sector.name), value: sector.weight @@ -570,12 +582,12 @@ export class GfHoldingDetailDialogComponent implements OnInit { const activity: CreateOrderDto = { accountId: this.accounts.length === 1 ? this.accounts[0].id : undefined, comment: undefined, - currency: this.SymbolProfile?.currency ?? '', - dataSource: this.SymbolProfile?.dataSource, + currency: this.assetProfile?.currency ?? '', + dataSource: this.assetProfile?.dataSource, date: today.toISOString(), fee: 0, quantity: this.quantity, - symbol: this.SymbolProfile?.symbol ?? '', + symbol: this.assetProfile?.symbol ?? '', tags: this.tags.map(({ id }) => { return id; }), @@ -606,7 +618,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { .subscribe((data) => { downloadAsFile({ content: data, - fileName: `ghostfolio-export-${this.SymbolProfile?.symbol}-${format( + fileName: `ghostfolio-export-${this.assetProfile?.symbol}-${format( parseISO(data.meta.date), 'yyyyMMddHHmm' )}.json`, diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 0d5691874..ff1d19cc3 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1,7 +1,7 @@ @@ -24,11 +24,11 @@ [benchmarkDataItems]="benchmarkDataItems" [benchmarkLabel]="benchmarkLabel" [colorScheme]="data.colorScheme" - [currency]="SymbolProfile?.currency" + [currency]="assetProfile?.currency" [historicalDataItems]="historicalDataItems" [isAnimated]="true" [label]=" - isUUID(data.symbol) ? (SymbolProfile?.name ?? data.symbol) : data.symbol + isUUID(data.symbol) ? (assetProfile?.name ?? data.symbol) : data.symbol " [locale]="data.locale" [showGradient]="true" @@ -60,8 +60,8 @@ [value]="netPerformanceWithCurrencyEffect" > @if ( - SymbolProfile?.currency && - data.baseCurrency !== SymbolProfile?.currency + assetProfile?.currency && + data.baseCurrency !== assetProfile?.currency ) { Change with currency effect } @else { @@ -80,8 +80,8 @@ [value]="netPerformancePercentWithCurrencyEffect" > @if ( - SymbolProfile?.currency && - data.baseCurrency !== SymbolProfile?.currency + assetProfile?.currency && + data.baseCurrency !== assetProfile?.currency ) { Performance with currency effect } @else { @@ -96,7 +96,7 @@ [isCurrency]="true" [locale]="data.locale" [precision]="averagePricePrecision" - [unit]="SymbolProfile?.currency" + [unit]="assetProfile?.currency" [value]="averagePrice" >Average Unit Price @@ -108,7 +108,7 @@ [isCurrency]="true" [locale]="data.locale" [precision]="marketPricePrecision" - [unit]="SymbolProfile?.currency" + [unit]="assetProfile?.currency" [value]="marketPrice" >Market Price @@ -124,7 +124,7 @@ [isCurrency]="true" [locale]="data.locale" [precision]="marketPriceMinPrecision" - [unit]="SymbolProfile?.currency" + [unit]="assetProfile?.currency" [value]="marketPriceMin" >Minimum Price @@ -140,7 +140,7 @@ [isCurrency]="true" [locale]="data.locale" [precision]="marketPriceMaxPrecision" - [unit]="SymbolProfile?.currency" + [unit]="assetProfile?.currency" [value]="marketPriceMax" >Maximum Price @@ -250,23 +250,23 @@ >
@if ( - SymbolProfile?.countries?.length > 0 || - SymbolProfile?.sectors?.length > 0 + assetProfile?.countries?.length > 0 || + assetProfile?.sectors?.length > 0 ) { @if ( - SymbolProfile?.countries?.length === 1 && - SymbolProfile?.sectors?.length === 1 + assetProfile?.countries?.length === 1 && + assetProfile?.sectors?.length === 1 ) {
Sector
- @if (SymbolProfile?.countries?.length === 1) { + @if (assetProfile?.countries?.length === 1) {
CountrySymbol
@@ -322,8 +322,8 @@ ISIN @@ -404,12 +404,12 @@
Market Data
@@ -462,8 +462,8 @@ mat-stroked-button [queryParams]="{ assetProfileDialog: true, - dataSource: SymbolProfile?.dataSource, - symbol: SymbolProfile?.symbol + dataSource: assetProfile?.dataSource, + symbol: assetProfile?.symbol }" [routerLink]="routerLinkAdminControlMarketData" (click)="onClose()" diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts index 3b07666c9..075234e6a 100644 --- a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts @@ -44,10 +44,6 @@ export interface PortfolioHoldingResponse { netPerformanceWithCurrencyEffect: number; performances: Benchmark['performances']; quantity: number; - - /* @deprecated */ - SymbolProfile: EnhancedSymbolProfile; - tags: Tag[]; value: number; } From cb66e63ed4d1812c6f4dc6607e42d578425697ed Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:15:50 +0200 Subject: [PATCH 15/15] Release 3.16.0 (#7131) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76dbe113a..e220aa0ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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 +## 3.16.0 - 2026-06-24 ### Added diff --git a/package-lock.json b/package-lock.json index 7cc890099..49ac987ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.15.1", + "version": "3.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.15.1", + "version": "3.16.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index fb1976570..39f2ec046 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.15.1", + "version": "3.16.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio",