From 71902e39d1e90f88000031e2fb1f90b924582fe4 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 21 Jan 2026 18:49:20 +0100 Subject: [PATCH] Task/deprecate transactionCount in portfolio calculator and service (#6228) * Deprecate transactionCount in favor of activitiesCount * Update changelog --- CHANGELOG.md | 1 + .../calculator/portfolio-calculator.ts | 3 +++ ...tfolio-calculator-baln-buy-and-buy.spec.ts | 1 + ...aln-buy-and-sell-in-two-activities.spec.ts | 1 + ...folio-calculator-baln-buy-and-sell.spec.ts | 1 + .../portfolio-calculator-baln-buy.spec.ts | 1 + .../roai/portfolio-calculator-btceur.spec.ts | 1 + ...ator-btcusd-buy-and-sell-partially.spec.ts | 1 + .../roai/portfolio-calculator-btcusd.spec.ts | 1 + .../roai/portfolio-calculator-cash.spec.ts | 1 + .../portfolio-calculator-googl-buy.spec.ts | 1 + ...-calculator-msft-buy-with-dividend.spec.ts | 1 + ...ulator-novn-buy-and-sell-partially.spec.ts | 1 + ...folio-calculator-novn-buy-and-sell.spec.ts | 1 + .../portfolio-calculator-valuable.spec.ts | 1 + .../transaction-point-symbol.interface.ts | 3 +++ .../src/app/portfolio/portfolio.service.ts | 22 ++++++++++++++----- .../portfolio-position.interface.ts | 4 ++++ .../responses/accounts-response.interface.ts | 3 +++ .../src/lib/models/timeline-position.ts | 3 +++ .../src/lib/types/account-with-value.type.ts | 4 ++++ libs/ui/src/lib/mocks/holdings.ts | 7 ++++++ 22 files changed, 57 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98e4a9380..1c06b0e64 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 ### Changed +- Deprecated `transactionCount` in favor of `activitiesCount` in the portfolio calculator and service - Removed the deprecated `firstBuyDate` from the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` ## 2.232.0 - 2026-01-19 diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 8fee1957c..22d9e09ae 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -407,6 +407,7 @@ export abstract class PortfolioCalculator { includeInTotalAssetValue, timeWeightedInvestment, timeWeightedInvestmentWithCurrencyEffect, + activitiesCount: item.activitiesCount, averagePrice: item.averagePrice, currency: item.currency, dataSource: item.dataSource, @@ -993,6 +994,7 @@ export abstract class PortfolioCalculator { investment, skipErrors, symbol, + activitiesCount: oldAccumulatedSymbol.activitiesCount + 1, averagePrice: newQuantity.eq(0) ? new Big(0) : investment.div(newQuantity).abs(), @@ -1016,6 +1018,7 @@ export abstract class PortfolioCalculator { skipErrors, symbol, tags, + activitiesCount: 1, averagePrice: unitPrice, dividend: new Big(0), firstBuyDate: date, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index 6b56b39a2..b715d0437 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts @@ -139,6 +139,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 2, averagePrice: new Big('139.75'), currency: 'CHF', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 2aad0cb26..5e1593fbb 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -155,6 +155,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 3, averagePrice: new Big('0'), currency: 'CHF', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index 35e4309eb..65d2a93a6 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -139,6 +139,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 2, averagePrice: new Big('0'), currency: 'CHF', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index ebce2ac1c..ed002317b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -129,6 +129,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 1, averagePrice: new Big('136.6'), currency: 'CHF', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index 9ca8b2d36..af4f17611 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -190,6 +190,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 1, averagePrice: new Big('44558.42'), currency: 'USD', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index 3648eb84b..5dd99dbf4 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -153,6 +153,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 2, averagePrice: new Big('320.43'), currency: 'USD', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index 5179fdaa6..929ebc76d 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -190,6 +190,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 1, averagePrice: new Big('44558.42'), currency: 'USD', dataSource: 'YAHOO', 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 c9adebc44..ba22c565d 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 @@ -230,6 +230,7 @@ describe('PortfolioCalculator', () => { * Value in base currency: 2000 USD * 0.91 = 1820 CHF */ expect(position).toMatchObject({ + activitiesCount: 2, averagePrice: new Big(1), currency: 'USD', dataSource: DataSource.YAHOO, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index fa38d0030..a14e28dff 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -135,6 +135,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 1, averagePrice: new Big('89.12'), currency: 'USD', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index ab7480bbf..4b2698ccb 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -131,6 +131,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 2, averagePrice: new Big('298.58'), currency: 'USD', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 32a1b02f3..852c3c13a 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -135,6 +135,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 2, averagePrice: new Big('75.80'), currency: 'CHF', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index f2903f3cd..8764da3ee 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -188,6 +188,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 2, averagePrice: new Big('0'), currency: 'CHF', dataSource: 'YAHOO', diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index 3a00c022c..554f74046 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -116,6 +116,7 @@ describe('PortfolioCalculator', () => { hasErrors: false, positions: [ { + activitiesCount: 1, averagePrice: new Big('500000'), currency: 'USD', dataSource: 'MANUAL', diff --git a/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts b/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts index 1c43508dd..6e0bb4ac3 100644 --- a/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts @@ -2,6 +2,7 @@ import { AssetSubClass, DataSource, Tag } from '@prisma/client'; import { Big } from 'big.js'; export interface TransactionPointSymbol { + activitiesCount: number; assetSubClass: AssetSubClass; averagePrice: Big; currency: string; @@ -16,5 +17,7 @@ export interface TransactionPointSymbol { skipErrors: boolean; symbol: string; tags?: Tag[]; + + /** @deprecated use activitiesCount instead */ transactionCount: number; } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 6ffd9fe0b..20016e67f 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -179,9 +179,9 @@ export class PortfolioService { return Promise.all( accounts.map(async (account) => { + let activitiesCount = 0; let dividendInBaseCurrency = 0; let interestInBaseCurrency = 0; - let transactionCount = 0; for (const { currency, @@ -214,7 +214,7 @@ export class PortfolioService { } if (!isDraft) { - transactionCount += 1; + activitiesCount += 1; } } @@ -223,9 +223,9 @@ export class PortfolioService { const result = { ...account, + activitiesCount, dividendInBaseCurrency, interestInBaseCurrency, - transactionCount, valueInBaseCurrency, allocationInPercentage: 0, balanceInBaseCurrency: this.exchangeRateDataService.toCurrency( @@ -233,6 +233,7 @@ export class PortfolioService { account.currency, userCurrency ), + transactionCount: activitiesCount, value: this.exchangeRateDataService.toCurrency( valueInBaseCurrency, userCurrency, @@ -262,6 +263,8 @@ export class PortfolioService { withExcludedAccounts }); + let activitiesCount = 0; + const searchQuery = filters.find(({ type }) => { return type === 'SEARCH_QUERY'; })?.id; @@ -284,6 +287,8 @@ export class PortfolioService { let transactionCount = 0; for (const account of accounts) { + activitiesCount += account.activitiesCount; + totalBalanceInBaseCurrency = totalBalanceInBaseCurrency.plus( account.balanceInBaseCurrency ); @@ -296,6 +301,7 @@ export class PortfolioService { totalValueInBaseCurrency = totalValueInBaseCurrency.plus( account.valueInBaseCurrency ); + transactionCount += account.transactionCount; } @@ -310,6 +316,7 @@ export class PortfolioService { return { accounts, + activitiesCount, transactionCount, totalBalanceInBaseCurrency: totalBalanceInBaseCurrency.toNumber(), totalDividendInBaseCurrency: totalDividendInBaseCurrency.toNumber(), @@ -567,6 +574,7 @@ export class PortfolioService { } for (const { + activitiesCount, currency, dividend, firstBuyDate, @@ -610,6 +618,7 @@ export class PortfolioService { } holdings[symbol] = { + activitiesCount, currency, markets, marketsAdvanced, @@ -789,6 +798,7 @@ export class PortfolioService { } const { + activitiesCount, averagePrice, currency, dividendInBaseCurrency, @@ -807,8 +817,7 @@ export class PortfolioService { quantity, tags, timeWeightedInvestment, - timeWeightedInvestmentWithCurrencyEffect, - transactionCount + timeWeightedInvestmentWithCurrencyEffect } = holding; const activitiesOfHolding = activities.filter(({ SymbolProfile }) => { @@ -914,12 +923,12 @@ export class PortfolioService { ); return { + activitiesCount, marketPrice, marketPriceMax, marketPriceMin, SymbolProfile, tags, - activitiesCount: transactionCount, averagePrice: averagePrice.toNumber(), dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], dateOfFirstActivity: firstBuyDate, @@ -1657,6 +1666,7 @@ export class PortfolioService { }): PortfolioPosition { return { currency, + activitiesCount: 0, allocationInPercentage: 0, assetClass: AssetClass.LIQUIDITY, assetSubClass: AssetSubClass.CASH, diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index e277ba468..67a2f3e77 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -7,6 +7,7 @@ import { Holding } from './holding.interface'; import { Sector } from './sector.interface'; export interface PortfolioPosition { + activitiesCount: number; allocationInPercentage: number; assetClass?: AssetClass; assetClassLabel?: string; @@ -38,7 +39,10 @@ export interface PortfolioPosition { sectors: Sector[]; symbol: string; tags?: Tag[]; + + /** @deprecated use activitiesCount instead */ transactionCount: number; + type?: string; url?: string; valueInBaseCurrency?: number; diff --git a/libs/common/src/lib/interfaces/responses/accounts-response.interface.ts b/libs/common/src/lib/interfaces/responses/accounts-response.interface.ts index 0a6af978f..1891b9cbb 100644 --- a/libs/common/src/lib/interfaces/responses/accounts-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/accounts-response.interface.ts @@ -2,9 +2,12 @@ import { AccountWithValue } from '@ghostfolio/common/types'; export interface AccountsResponse { accounts: AccountWithValue[]; + activitiesCount: number; totalBalanceInBaseCurrency: number; totalDividendInBaseCurrency: number; totalInterestInBaseCurrency: number; totalValueInBaseCurrency: number; + + /** @deprecated use activitiesCount instead */ transactionCount: number; } diff --git a/libs/common/src/lib/models/timeline-position.ts b/libs/common/src/lib/models/timeline-position.ts index 8eae56cf7..4144f349d 100644 --- a/libs/common/src/lib/models/timeline-position.ts +++ b/libs/common/src/lib/models/timeline-position.ts @@ -9,6 +9,8 @@ import { Big } from 'big.js'; import { Transform, Type } from 'class-transformer'; export class TimelinePosition { + activitiesCount: number; + @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) averagePrice: Big; @@ -92,6 +94,7 @@ export class TimelinePosition { @Type(() => Big) timeWeightedInvestmentWithCurrencyEffect: Big; + /** @deprecated use activitiesCount instead */ transactionCount: number; @Transform(transformToBig, { toClassOnly: true }) diff --git a/libs/common/src/lib/types/account-with-value.type.ts b/libs/common/src/lib/types/account-with-value.type.ts index 08af86454..7f5fe79ba 100644 --- a/libs/common/src/lib/types/account-with-value.type.ts +++ b/libs/common/src/lib/types/account-with-value.type.ts @@ -1,12 +1,16 @@ import { Account as AccountModel, Platform } from '@prisma/client'; export type AccountWithValue = AccountModel & { + activitiesCount: number; allocationInPercentage: number; balanceInBaseCurrency: number; dividendInBaseCurrency: number; interestInBaseCurrency: number; platform?: Platform; + + /** @deprecated use activitiesCount instead */ transactionCount: number; + value: number; valueInBaseCurrency: number; }; diff --git a/libs/ui/src/lib/mocks/holdings.ts b/libs/ui/src/lib/mocks/holdings.ts index 6e8485c82..0f30b3e6f 100644 --- a/libs/ui/src/lib/mocks/holdings.ts +++ b/libs/ui/src/lib/mocks/holdings.ts @@ -2,6 +2,7 @@ import { PortfolioPosition } from '@ghostfolio/common/interfaces'; export const holdings: PortfolioPosition[] = [ { + activitiesCount: 1, allocationInPercentage: 0.042990776363386086, assetClass: 'EQUITY' as any, assetClassLabel: 'Equity', @@ -45,6 +46,7 @@ export const holdings: PortfolioPosition[] = [ valueInBaseCurrency: 12230 }, { + activitiesCount: 2, allocationInPercentage: 0.02377401948293552, assetClass: 'EQUITY' as any, assetClassLabel: 'Equity', @@ -88,6 +90,7 @@ export const holdings: PortfolioPosition[] = [ valueInBaseCurrency: 6763.224181360202 }, { + activitiesCount: 1, allocationInPercentage: 0.08038536990007467, assetClass: 'EQUITY' as any, assetClassLabel: 'Equity', @@ -131,6 +134,7 @@ export const holdings: PortfolioPosition[] = [ valueInBaseCurrency: 22868 }, { + activitiesCount: 1, allocationInPercentage: 0.19216416482928922, assetClass: 'LIQUIDITY' as any, assetClassLabel: 'Liquidity', @@ -162,6 +166,7 @@ export const holdings: PortfolioPosition[] = [ valueInBaseCurrency: 54666.7898248 }, { + activitiesCount: 1, allocationInPercentage: 0.04307127421937313, assetClass: 'EQUITY' as any, assetClassLabel: 'Equity', @@ -205,6 +210,7 @@ export const holdings: PortfolioPosition[] = [ valueInBaseCurrency: 12252.9 }, { + activitiesCount: 1, allocationInPercentage: 0.18762679306394897, assetClass: 'EQUITY' as any, assetClassLabel: 'Equity', @@ -248,6 +254,7 @@ export const holdings: PortfolioPosition[] = [ valueInBaseCurrency: 53376 }, { + activitiesCount: 5, allocationInPercentage: 0.053051250766657634, assetClass: 'EQUITY' as any, assetClassLabel: 'Equity',