From 1f76580b88b5039632bdc9ce64a3c10ee99341be Mon Sep 17 00:00:00 2001 From: KenTandrian Date: Wed, 7 Jan 2026 12:14:38 +0700 Subject: [PATCH] fix(api): do not include cash positions in overall performance calculation --- .../calculator/portfolio-calculator.ts | 33 +++++++++++-------- .../calculator/roai/portfolio-calculator.ts | 4 ++- .../src/lib/models/timeline-position.ts | 2 ++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index d2b3c0625..8f6cb0efc 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -39,6 +39,7 @@ import { GroupBy } from '@ghostfolio/common/types'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Logger } from '@nestjs/common'; +import { AssetSubClass } from '@prisma/client'; import { Big } from 'big.js'; import { plainToClass } from 'class-transformer'; import { @@ -389,27 +390,33 @@ export abstract class PortfolioCalculator { hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; - valuesBySymbol[item.symbol] = { - currentValues, - currentValuesWithCurrencyEffect, - investmentValuesAccumulated, - investmentValuesAccumulatedWithCurrencyEffect, - investmentValuesWithCurrencyEffect, - netPerformanceValues, - netPerformanceValuesWithCurrencyEffect, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect - }; + const includeInTotalAssetValue = + item.assetSubClass !== AssetSubClass.CASH; + + if (includeInTotalAssetValue) { + valuesBySymbol[item.symbol] = { + currentValues, + currentValuesWithCurrencyEffect, + investmentValuesAccumulated, + investmentValuesAccumulatedWithCurrencyEffect, + investmentValuesWithCurrencyEffect, + netPerformanceValues, + netPerformanceValuesWithCurrencyEffect, + timeWeightedInvestmentValues, + timeWeightedInvestmentValuesWithCurrencyEffect + }; + } positions.push({ feeInBaseCurrency, + includeInTotalAssetValue, timeWeightedInvestment, timeWeightedInvestmentWithCurrencyEffect, - dividend: totalDividend, - dividendInBaseCurrency: totalDividendInBaseCurrency, averagePrice: item.averagePrice, currency: item.currency, dataSource: item.dataSource, + dividend: totalDividend, + dividendInBaseCurrency: totalDividendInBaseCurrency, fee: item.fee, firstBuyDate: item.firstBuyDate, grossPerformance: !hasErrors ? (grossPerformance ?? null) : null, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts index 070d7543b..40e1ba449 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -34,7 +34,9 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { let totalTimeWeightedInvestment = new Big(0); let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0); - for (const currentPosition of positions) { + for (const currentPosition of positions.filter((position) => { + return position.includeInTotalAssetValue; + })) { if (currentPosition.feeInBaseCurrency) { totalFeesWithCurrencyEffect = totalFeesWithCurrencyEffect.plus( currentPosition.feeInBaseCurrency diff --git a/libs/common/src/lib/models/timeline-position.ts b/libs/common/src/lib/models/timeline-position.ts index f683c0951..8eae56cf7 100644 --- a/libs/common/src/lib/models/timeline-position.ts +++ b/libs/common/src/lib/models/timeline-position.ts @@ -50,6 +50,8 @@ export class TimelinePosition { @Type(() => Big) grossPerformanceWithCurrencyEffect: Big; + includeInTotalAssetValue?: boolean; + @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) investment: Big;