From ac0735557ec830be1afa2e32b3c71e02139eba20 Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Fri, 18 Jul 2025 16:33:28 +0200 Subject: [PATCH] filter non-investment positions --- .../calculator/portfolio-calculator.ts | 148 ++++++++++-------- .../roai/portfolio-calculator-fee.spec.ts | 32 +--- .../transaction-point-symbol.interface.ts | 1 + libs/common/src/lib/config.ts | 14 +- 4 files changed, 89 insertions(+), 106 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 258dce4a0..cdfd7adbf 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -288,7 +288,9 @@ export abstract class PortfolioCalculator { firstIndex--; } - const positions: TimelinePosition[] = []; + const positions: (TimelinePosition & { + isInvestmentAssetProfilePosition: boolean; + })[] = []; let hasAnySymbolMetricsErrors = false; const errors: ResponseError['errors'] = []; @@ -412,6 +414,7 @@ export abstract class PortfolioCalculator { : null, investment: totalInvestment, investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect, + isInvestmentAssetProfilePosition: item.isInvestmentAssetProfileItem, marketPrice: marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null, marketPriceInBaseCurrency: @@ -606,14 +609,23 @@ export abstract class PortfolioCalculator { const overall = this.calculateOverallPerformance(positions); + const investmentPositions = positions + .filter(({ isInvestmentAssetProfilePosition }) => { + return isInvestmentAssetProfilePosition; + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(({ isInvestmentAssetProfilePosition, ...rest }) => { + return rest; + }); + return { ...overall, errors, historicalData, - positions, totalInterestWithCurrencyEffect, totalLiabilitiesWithCurrencyEffect, - hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors + hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors, + positions: investmentPositions }; } @@ -899,82 +911,82 @@ export abstract class PortfolioCalculator { } of this.activities) { let currentTransactionPointItem: TransactionPointSymbol; - 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) - ); - } + 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; - 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 - }; - } + const oldAccumulatedSymbol = symbols[symbol]; - currentTransactionPointItem.tags = uniqBy( - currentTransactionPointItem.tags, - 'id' - ); + if (oldAccumulatedSymbol) { + let investment = oldAccumulatedSymbol.investment; - symbols[SymbolProfile.symbol] = currentTransactionPointItem; + 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) + ); + } + + 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, + isInvestmentAssetProfileItem: + oldAccumulatedSymbol.isInvestmentAssetProfileItem, + 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), + isInvestmentAssetProfileItem: + INVESTMENT_ACTIVITY_TYPES.includes(type), + quantity: quantity.mul(factor), + transactionCount: 1 + }; } + currentTransactionPointItem.tags = uniqBy( + currentTransactionPointItem.tags, + 'id' + ); + + symbols[SymbolProfile.symbol] = currentTransactionPointItem; + const items = lastTransactionPoint?.items ?? []; const newItems = items.filter(({ symbol }) => { return symbol !== SymbolProfile.symbol; }); - if (currentTransactionPointItem) { - newItems.push(currentTransactionPointItem); - } + newItems.push(currentTransactionPointItem); newItems.sort((a, b) => { return a.symbol?.localeCompare(b.symbol); 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..04d867b2a 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 @@ -9,6 +9,7 @@ export interface TransactionPointSymbol { fee: Big; firstBuyDate: string; investment: Big; + isInvestmentAssetProfileItem: boolean; quantity: Big; skipErrors: boolean; symbol: string; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 7d9520485..91460e373 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -118,6 +118,13 @@ export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_OPTIONS: JobOptions = { removeOnComplete: true }; +export const INVESTMENT_ACTIVITY_TYPES = [ + Type.BUY, + Type.DIVIDEND, + Type.ITEM, + Type.SELL +] as Type[]; + export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME = 'PORTFOLIO'; export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS: JobOptions = { removeOnComplete: true @@ -128,13 +135,6 @@ 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;