diff --git a/CHANGELOG.md b/CHANGELOG.md index ec236ed90..083fd2d88 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 + +- Excluded the holdings originated of `FEE`, `INTEREST` and `LIABILITY` activities from the closed holdings on the portfolio holdings page + ## 2.185.0 - 2025-07-26 ### Added diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 185c1cd80..78323a332 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -13,6 +13,7 @@ import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfac import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { + INVESTMENT_ACTIVITY_TYPES, PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME, PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS, PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH, @@ -287,10 +288,12 @@ export abstract class PortfolioCalculator { firstIndex--; } - const positions: TimelinePosition[] = []; + const errors: ResponseError['errors'] = []; let hasAnySymbolMetricsErrors = false; - const errors: ResponseError['errors'] = []; + const positions: (TimelinePosition & { + includeInHoldings: boolean; + })[] = []; const accumulatedValuesByDate: { [date: string]: { @@ -409,6 +412,7 @@ export abstract class PortfolioCalculator { grossPerformanceWithCurrencyEffect: !hasErrors ? (grossPerformanceWithCurrencyEffect ?? null) : null, + includeInHoldings: item.includeInHoldings, investment: totalInvestment, investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect, marketPrice: @@ -605,14 +609,23 @@ export abstract class PortfolioCalculator { const overall = this.calculateOverallPerformance(positions); + const positionsIncludedInHoldings = positions + .filter(({ includeInHoldings }) => { + return includeInHoldings; + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(({ includeInHoldings, ...rest }) => { + return rest; + }); + return { ...overall, errors, historicalData, - positions, totalInterestWithCurrencyEffect, totalLiabilitiesWithCurrencyEffect, - hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors + hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors, + positions: positionsIncludedInHoldings }; } @@ -935,6 +948,7 @@ export abstract class PortfolioCalculator { dividend: new Big(0), fee: oldAccumulatedSymbol.fee.plus(fee), firstBuyDate: oldAccumulatedSymbol.firstBuyDate, + includeInHoldings: oldAccumulatedSymbol.includeInHoldings, quantity: newQuantity, tags: oldAccumulatedSymbol.tags.concat(tags), transactionCount: oldAccumulatedSymbol.transactionCount + 1 @@ -950,6 +964,7 @@ export abstract class PortfolioCalculator { averagePrice: unitPrice, dividend: new Big(0), firstBuyDate: date, + includeInHoldings: INVESTMENT_ACTIVITY_TYPES.includes(type), investment: unitPrice.mul(quantity).mul(factor), quantity: quantity.mul(factor), transactionCount: 1 diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts index 31c6afe66..aaf2c4302 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts @@ -116,37 +116,7 @@ describe('PortfolioCalculator', () => { currentValueInBaseCurrency: new Big('0'), errors: [], hasErrors: true, - positions: [ - { - averagePrice: new Big('0'), - currency: 'USD', - dataSource: 'MANUAL', - dividend: new Big('0'), - dividendInBaseCurrency: new Big('0'), - fee: new Big('49'), - feeInBaseCurrency: new Big('49'), - firstBuyDate: '2021-09-01', - grossPerformance: null, - grossPerformancePercentage: null, - grossPerformancePercentageWithCurrencyEffect: null, - grossPerformanceWithCurrencyEffect: null, - investment: new Big('0'), - investmentWithCurrencyEffect: new Big('0'), - marketPrice: null, - marketPriceInBaseCurrency: 0, - netPerformance: null, - netPerformancePercentage: null, - netPerformancePercentageWithCurrencyEffectMap: null, - netPerformanceWithCurrencyEffectMap: null, - quantity: new Big('0'), - symbol: '2c463fb3-af07-486e-adb0-8301b3d72141', - tags: [], - timeWeightedInvestment: new Big('0'), - timeWeightedInvestmentWithCurrencyEffect: new Big('0'), - transactionCount: 1, - valueInBaseCurrency: new Big('0') - } - ], + positions: [], totalFeesWithCurrencyEffect: new Big('49'), totalInterestWithCurrencyEffect: new Big('0'), totalInvestment: new Big('0'), 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 0d648322f..f4ceadf3b 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 @@ -8,6 +8,7 @@ export interface TransactionPointSymbol { dividend: Big; fee: Big; firstBuyDate: string; + includeInHoldings: boolean; investment: Big; quantity: Big; skipErrors: boolean; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 5aee7e0e6..467716575 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -1,4 +1,4 @@ -import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; +import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; import { JobOptions, JobStatus } from 'bull'; import ms from 'ms'; @@ -118,6 +118,12 @@ export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_OPTIONS: JobOptions = { removeOnComplete: true }; +export const INVESTMENT_ACTIVITY_TYPES = [ + Type.BUY, + Type.DIVIDEND, + Type.SELL +] as Type[]; + export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME = 'PORTFOLIO'; export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS: JobOptions = { removeOnComplete: true