From 73f576a997a3d2c1d6833dde349c8454ab2e6923 Mon Sep 17 00:00:00 2001 From: GabeSilvaDev Date: Tue, 22 Sep 2026 23:06:12 -0300 Subject: [PATCH 1/3] Fix alignment of average price in chart of holding detail dialog --- .../holding-detail-dialog.component.ts | 8 +++ libs/common/src/lib/helper.spec.ts | 61 +++++++++++++++++++ libs/common/src/lib/helper.ts | 22 +++++++ 3 files changed, 91 insertions(+) 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 d8385d70e2..1da85101d9 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 @@ -9,6 +9,7 @@ import { } from '@ghostfolio/common/config'; import { CreateOrderDto } from '@ghostfolio/common/dtos'; import { + alignLineChartItemsToDates, DATE_FORMAT, downloadAsFile, getCountryName @@ -725,6 +726,13 @@ export class GfHoldingDetailDialogComponent implements OnInit { } ); + this.benchmarkDataItems = alignLineChartItemsToDates({ + dates: this.historicalDataItems.map(({ date }) => { + return date; + }), + items: this.benchmarkDataItems + }); + this.changeDetectorRef.markForCheck(); }); } diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index d1130f0d75..cbf2bd1e99 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -3,6 +3,7 @@ import { TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config'; import { + alignLineChartItemsToDates, canApplyFiltersToAccess, extractNumberFromString, getCountryCodeFromCurrency, @@ -23,6 +24,66 @@ import { import { UserSettings } from '@ghostfolio/common/interfaces'; describe('Helper', () => { + describe('Align line chart items to dates', () => { + it('Dates before the first item', () => { + expect( + alignLineChartItemsToDates({ + dates: ['2025-01-02', '2025-01-03'], + items: [{ date: '2026-01-09', value: 100 }] + }) + ).toEqual([ + { date: '2025-01-02', value: null }, + { date: '2025-01-03', value: null } + ]); + }); + + it('Dates on and after the items', () => { + expect( + alignLineChartItemsToDates({ + dates: [ + '2026-01-08', + '2026-01-09', + '2026-01-10', + '2026-01-12', + '2026-01-13' + ], + items: [ + { date: '2026-01-09', value: 100 }, + { date: '2026-01-12', value: 110 } + ] + }) + ).toEqual([ + { date: '2026-01-08', value: null }, + { date: '2026-01-09', value: 100 }, + { date: '2026-01-10', value: 100 }, + { date: '2026-01-12', value: 110 }, + { date: '2026-01-13', value: 110 } + ]); + }); + + it('Null values', () => { + expect( + alignLineChartItemsToDates({ + dates: ['2026-01-09', '2026-01-10', '2026-01-11'], + items: [ + { date: '2026-01-09', value: 100 }, + { date: '2026-01-10', value: null } + ] + }) + ).toEqual([ + { date: '2026-01-09', value: 100 }, + { date: '2026-01-10', value: null }, + { date: '2026-01-11', value: null } + ]); + }); + + it('No items', () => { + expect( + alignLineChartItemsToDates({ dates: ['2026-01-09'], items: [] }) + ).toEqual([{ date: '2026-01-09', value: null }]); + }); + }); + describe('Can apply filters to access', () => { it('An access of the model context protocol', () => { expect(canApplyFiltersToAccess({ type: 'MCP' })).toEqual(false); diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 6e50911322..769d693572 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -57,6 +57,7 @@ import { AssetProfileIdentifier, AssetProfileItem, Benchmark, + NullableLineChartItem, PortfolioPosition, UserSettings } from './interfaces'; @@ -91,6 +92,27 @@ const USER_SETTINGS_KEYS_OF_AUTHENTICATED_USER: (keyof UserSettings)[] = [ const VALID_COUNTRY_CODES = new Set([...Object.keys(countries), 'EU']); +export function alignLineChartItemsToDates({ + dates, + items +}: { + dates: string[]; + items: NullableLineChartItem[]; +}): NullableLineChartItem[] { + let index = -1; + + return dates.map((date) => { + while ( + index + 1 < items.length && + !isAfter(parseISO(items[index + 1].date), parseISO(date)) + ) { + index++; + } + + return { date, value: items[index]?.value ?? null }; + }); +} + export function applyAssetProfileOverrides>( assetProfile: T, assetProfileOverrides: AssetProfileOverrides | null From 3cc6316d57954ec50f730b8ef2d59b4cd2f2b176 Mon Sep 17 00:00:00 2001 From: GabeSilvaDev Date: Tue, 22 Sep 2026 23:07:17 -0300 Subject: [PATCH 2/3] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7acd3e068..6253e83882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `bull-board` from version `9.9.0` to `9.10.1` - Upgraded `zod` from version `4.5.4` to `4.6.5` +### Fixed + +- Fixed the alignment of the average price in the chart of the holding detail dialog for custom asset profiles + ## 3.72.0 - 2026-09-20 ### Added From ec0252be4071666cc1b7ccd2b5b10da8a3f06906 Mon Sep 17 00:00:00 2001 From: GabeSilvaDev Date: Tue, 22 Sep 2026 23:34:09 -0300 Subject: [PATCH 3/3] Format market data dates in UTC in holding detail dialog --- .../holding-detail-dialog/holding-detail-dialog.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 1da85101d9..02bfd25e5c 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 @@ -69,6 +69,7 @@ import { SortDirection } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { MatTabsModule } from '@angular/material/tabs'; import { NavigationStart, Router, RouterModule } from '@angular/router'; +import { utc } from '@date-fns/utc'; import { IonIcon } from '@ionic/angular/standalone'; import { MarketData, Tag } from '@prisma/client'; import { isUUID } from 'class-validator'; @@ -720,7 +721,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.historicalDataItems = this.marketDataItems.map( ({ date, marketPrice }) => { return { - date: format(date, DATE_FORMAT), + date: format(date, DATE_FORMAT, { in: utc }), value: marketPrice }; }