From 17f0fe87b19c2a4de2ce871d1ffd1578fe971eab Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 29 Jul 2026 17:49:11 +0200
Subject: [PATCH 1/2] Task/move tags to overview tab of account and holding
detail dialogs (#7465)
* Move tags to overview tab
* Update changelog
---
CHANGELOG.md | 7 +++++++
.../account-detail-dialog.html | 9 ++++----
.../holding-detail-dialog.html | 21 ++++++++++---------
3 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 335743775f..2b33b14059 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ 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
+
+### Changed
+
+- Moved the tags to the overview tab of the account detail dialog (experimental)
+- Moved the tags to the overview tab of the holding detail dialog
+
## 3.36.0 - 2026-07-29
### Added
diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
index b7deae6e85..32a11717f8 100644
--- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
+++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
@@ -112,6 +112,11 @@
>Platform
+ @if (user?.settings?.isExperimentalFeatures) {
+
+
+
+ }
@@ -179,10 +184,6 @@
/>
-
- @if (user?.settings?.isExperimentalFeatures) {
-
- }
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 30222de65e..464f0b6b2b 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
@@ -331,6 +331,17 @@
>ISIN
+
+
+
@if (dataProviderInfo) {
@@ -419,16 +430,6 @@
}
-
-
@if (
data.hasPermissionToAccessAdminControl ||
(dataSource?.data.length > 0 &&
From b8f6037f8c0612bf719ad1a3c7d84312407f57b1 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 29 Jul 2026 18:04:12 +0200
Subject: [PATCH 2/2] Bugfix/ignore future-dated account balances in portfolio
calculation (#7436)
* Ignore future-dated account balances in the portfolio calculation
* Update changelog
---
CHANGELOG.md | 4 +++
.../account-balance.service.ts | 12 +++++--
apps/api/src/app/account/account.service.ts | 32 ++++++++++++++++---
.../src/app/activities/activities.service.ts | 15 ++++++++-
.../roai/portfolio-calculator-cash.spec.ts | 8 +++++
apps/api/src/helper/account.helper.ts | 17 ++++++++++
6 files changed, 80 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b33b14059..f78b9dad5e 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
- Moved the tags to the overview tab of the account detail dialog (experimental)
- Moved the tags to the overview tab of the holding detail dialog
+### Fixed
+
+- Ignored future-dated account balances in the portfolio calculation
+
## 3.36.0 - 2026-07-29
### Added
diff --git a/apps/api/src/app/account-balance/account-balance.service.ts b/apps/api/src/app/account-balance/account-balance.service.ts
index 84932f4295..29c7f28872 100644
--- a/apps/api/src/app/account-balance/account-balance.service.ts
+++ b/apps/api/src/app/account-balance/account-balance.service.ts
@@ -1,5 +1,8 @@
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
-import { WHERE_ACCOUNT_NOT_EXCLUDED } from '@ghostfolio/api/helper/account.helper';
+import {
+ isAccountBalanceInFuture,
+ WHERE_ACCOUNT_NOT_EXCLUDED
+} from '@ghostfolio/api/helper/account.helper';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
@@ -15,7 +18,7 @@ import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { AccountBalance, Prisma } from '@prisma/client';
import { Big } from 'big.js';
-import { format, parseISO } from 'date-fns';
+import { endOfToday, format, parseISO } from 'date-fns';
import { groupBy } from 'lodash';
@Injectable()
@@ -114,8 +117,13 @@ export class AccountBalanceService {
const accumulatedBalancesByDate: { [date: string]: HistoricalDataItem } =
{};
const lastBalancesByAccount: { [accountId: string]: Big } = {};
+ const endOfTodayDate = endOfToday();
for (const { accountId, date, valueInBaseCurrency } of balances) {
+ if (isAccountBalanceInFuture({ date, endOfTodayDate })) {
+ continue;
+ }
+
const formattedDate = format(date, DATE_FORMAT);
lastBalancesByAccount[accountId] = new Big(valueInBaseCurrency);
diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts
index 2098062340..7f0451101c 100644
--- a/apps/api/src/app/account/account.service.ts
+++ b/apps/api/src/app/account/account.service.ts
@@ -1,6 +1,10 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
-import { WHERE_ACCOUNT_NOT_EXCLUDED } from '@ghostfolio/api/helper/account.helper';
+import {
+ getWhereAccountBalanceNotInFuture,
+ isAccountBalanceInFuture,
+ WHERE_ACCOUNT_NOT_EXCLUDED
+} from '@ghostfolio/api/helper/account.helper';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
@@ -19,7 +23,7 @@ import {
Tag
} from '@prisma/client';
import { Big } from 'big.js';
-import { format } from 'date-fns';
+import { endOfToday, format } from 'date-fns';
import { groupBy } from 'lodash';
import { CashDetails } from './interfaces/cash-details.interface';
@@ -41,7 +45,9 @@ export class AccountService {
include: {
balances: {
orderBy: { date: 'desc' },
- take: 1
+ take: 1,
+ // Ignore account balances in the future
+ where: getWhereAccountBalanceNotInFuture()
}
},
where: { id_userId }
@@ -95,7 +101,16 @@ export class AccountService {
include.balances = {
orderBy: { date: 'desc' },
- ...(isBalancesIncluded ? {} : { take: 1 })
+ // If the balances are included, they are returned as-is (including the
+ // ones in the future) because the client renders the full history. The
+ // balance is derived below and skips the account balances in the future.
+ ...(isBalancesIncluded
+ ? {}
+ : {
+ take: 1,
+ // Ignore account balances in the future
+ where: getWhereAccountBalanceNotInFuture()
+ })
};
if (isTagsIncluded) {
@@ -115,10 +130,17 @@ export class AccountService {
where
});
+ const endOfTodayDate = endOfToday();
+
return accounts.map((account) => {
const result = {
...account,
- balance: account.balances[0]?.value ?? 0,
+ balance:
+ // The balances are ordered by date descending, hence the first account
+ // balance which is not in the future reflects the current balance
+ account.balances.find(({ date }) => {
+ return !isAccountBalanceInFuture({ date, endOfTodayDate });
+ })?.value ?? 0,
tags: isTagsIncluded
? (account.tags as unknown as { tag: Tag }[]).map(({ tag }) => {
return tag;
diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts
index 0a390d4ec3..7d6e822c52 100644
--- a/apps/api/src/app/activities/activities.service.ts
+++ b/apps/api/src/app/activities/activities.service.ts
@@ -3,7 +3,10 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface';
import { AssetProfileChangedEvent } from '@ghostfolio/api/events/asset-profile-changed.event';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
-import { WHERE_ACCOUNT_NOT_EXCLUDED } from '@ghostfolio/api/helper/account.helper';
+import {
+ isAccountBalanceInFuture,
+ WHERE_ACCOUNT_NOT_EXCLUDED
+} from '@ghostfolio/api/helper/account.helper';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
@@ -456,6 +459,7 @@ export class ActivitiesService {
}
const activities: Activity[] = [];
+ const endOfTodayDate = endOfToday();
for (const account of cashDetails.accounts) {
const { balances } = await this.accountBalanceService.getAccountBalances({
@@ -468,6 +472,15 @@ export class ActivitiesService {
let currentBalanceInBaseCurrency = 0;
for (const balanceItem of balances) {
+ if (
+ isAccountBalanceInFuture({
+ endOfTodayDate,
+ date: balanceItem.date
+ })
+ ) {
+ continue;
+ }
+
const syntheticActivityTemplate: Activity = {
userId,
accountId: account.id,
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
index 9bef6ad394..1fc705b467 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
+++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
@@ -166,6 +166,14 @@ describe('PortfolioCalculator', () => {
id: randomUUID(),
value: 2000,
valueInBaseCurrency: 1800
+ },
+ {
+ // Ignored future account balance
+ accountId,
+ date: parseDate('2050-12-31'),
+ id: randomUUID(),
+ value: 0,
+ valueInBaseCurrency: 0
}
]
});
diff --git a/apps/api/src/helper/account.helper.ts b/apps/api/src/helper/account.helper.ts
index 0800c022e2..8e7520d0a9 100644
--- a/apps/api/src/helper/account.helper.ts
+++ b/apps/api/src/helper/account.helper.ts
@@ -1,6 +1,7 @@
import { TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config';
import { Prisma } from '@prisma/client';
+import { endOfToday, isAfter } from 'date-fns';
export const WHERE_ACCOUNT_NOT_EXCLUDED: Prisma.AccountWhereInput = {
isExcluded: false,
@@ -10,3 +11,19 @@ export const WHERE_ACCOUNT_NOT_EXCLUDED: Prisma.AccountWhereInput = {
}
}
};
+
+export function getWhereAccountBalanceNotInFuture(): Prisma.AccountBalanceWhereInput {
+ return {
+ date: { lte: endOfToday() }
+ };
+}
+
+export function isAccountBalanceInFuture({
+ date,
+ endOfTodayDate = endOfToday()
+}: {
+ date: Date;
+ endOfTodayDate?: Date;
+}) {
+ return isAfter(date, endOfTodayDate);
+}