diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 185c1cd80..258dce4a0 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, @@ -898,70 +899,72 @@ export abstract class PortfolioCalculator { } of this.activities) { let currentTransactionPointItem: TransactionPointSymbol; - const currency = SymbolProfile.currency; - const dataSource = SymbolProfile.dataSource; - const factor = getFactor(type); - const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles - const symbol = SymbolProfile.symbol; - - const oldAccumulatedSymbol = symbols[symbol]; - - if (oldAccumulatedSymbol) { - let investment = oldAccumulatedSymbol.investment; - - const newQuantity = quantity - .mul(factor) - .plus(oldAccumulatedSymbol.quantity); + if (INVESTMENT_ACTIVITY_TYPES.includes(type)) { + const currency = SymbolProfile.currency; + const dataSource = SymbolProfile.dataSource; + const factor = getFactor(type); + const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles + const symbol = SymbolProfile.symbol; + + const oldAccumulatedSymbol = symbols[symbol]; + + if (oldAccumulatedSymbol) { + let investment = oldAccumulatedSymbol.investment; + + const newQuantity = quantity + .mul(factor) + .plus(oldAccumulatedSymbol.quantity); + + if (type === 'BUY') { + investment = oldAccumulatedSymbol.investment.plus( + quantity.mul(unitPrice) + ); + } else if (type === 'SELL') { + investment = oldAccumulatedSymbol.investment.minus( + quantity.mul(oldAccumulatedSymbol.averagePrice) + ); + } - if (type === 'BUY') { - investment = oldAccumulatedSymbol.investment.plus( - quantity.mul(unitPrice) - ); - } else if (type === 'SELL') { - investment = oldAccumulatedSymbol.investment.minus( - quantity.mul(oldAccumulatedSymbol.averagePrice) - ); + currentTransactionPointItem = { + currency, + dataSource, + investment, + skipErrors, + symbol, + averagePrice: newQuantity.gt(0) + ? investment.div(newQuantity) + : new Big(0), + dividend: new Big(0), + fee: oldAccumulatedSymbol.fee.plus(fee), + firstBuyDate: oldAccumulatedSymbol.firstBuyDate, + quantity: newQuantity, + tags: oldAccumulatedSymbol.tags.concat(tags), + transactionCount: oldAccumulatedSymbol.transactionCount + 1 + }; + } else { + currentTransactionPointItem = { + currency, + dataSource, + fee, + skipErrors, + symbol, + tags, + averagePrice: unitPrice, + dividend: new Big(0), + firstBuyDate: date, + investment: unitPrice.mul(quantity).mul(factor), + quantity: quantity.mul(factor), + transactionCount: 1 + }; } - currentTransactionPointItem = { - currency, - dataSource, - investment, - skipErrors, - symbol, - averagePrice: newQuantity.gt(0) - ? investment.div(newQuantity) - : new Big(0), - dividend: new Big(0), - fee: oldAccumulatedSymbol.fee.plus(fee), - firstBuyDate: oldAccumulatedSymbol.firstBuyDate, - quantity: newQuantity, - tags: oldAccumulatedSymbol.tags.concat(tags), - transactionCount: oldAccumulatedSymbol.transactionCount + 1 - }; - } else { - currentTransactionPointItem = { - currency, - dataSource, - fee, - skipErrors, - symbol, - tags, - averagePrice: unitPrice, - dividend: new Big(0), - firstBuyDate: date, - investment: unitPrice.mul(quantity).mul(factor), - quantity: quantity.mul(factor), - transactionCount: 1 - }; - } - - currentTransactionPointItem.tags = uniqBy( - currentTransactionPointItem.tags, - 'id' - ); + currentTransactionPointItem.tags = uniqBy( + currentTransactionPointItem.tags, + 'id' + ); - symbols[SymbolProfile.symbol] = currentTransactionPointItem; + symbols[SymbolProfile.symbol] = currentTransactionPointItem; + } const items = lastTransactionPoint?.items ?? []; @@ -969,7 +972,9 @@ export abstract class PortfolioCalculator { return symbol !== SymbolProfile.symbol; }); - newItems.push(currentTransactionPointItem); + if (currentTransactionPointItem) { + newItems.push(currentTransactionPointItem); + } newItems.sort((a, b) => { return a.symbol?.localeCompare(b.symbol); diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 5aee7e0e6..7d9520485 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'; @@ -128,6 +128,13 @@ export const HEADER_KEY_TIMEZONE = 'Timezone'; export const HEADER_KEY_TOKEN = 'Authorization'; export const HEADER_KEY_SKIP_INTERCEPTOR = 'X-Skip-Interceptor'; +export const INVESTMENT_ACTIVITY_TYPES = [ + Type.BUY, + Type.DIVIDEND, + Type.ITEM, + Type.SELL +] as Type[]; + export const MAX_TOP_HOLDINGS = 50; export const NUMERICAL_PRECISION_THRESHOLD = 100000;