From 92d3079f2d361df94ee7828ed02cb0587d4e4af5 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:13:01 +0700 Subject: [PATCH] Bugfix/improve portfolio calculation for activity with no market data (#5130) * Improve portfolio calculation for activity with no market data * Update changelog --- CHANGELOG.md | 1 + .../portfolio-calculator-valuable.spec.ts | 44 +++++++++---------- .../calculator/roai/portfolio-calculator.ts | 20 +++++++-- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47042b173..7308e73f4 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 +- Improved the portfolio calculations for activities without historical market data - Improved the asset profile dialog’s data gathering checkbox of the admin control panel to reflect the global settings - Improved the language localization for Catalan (`ca`) - Improved the language localization for Portuguese (`pt`) 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 f883e67a2..5e9949dd2 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 @@ -114,14 +114,8 @@ describe('PortfolioCalculator', () => { expect(portfolioSnapshot).toMatchObject({ currentValueInBaseCurrency: new Big('500000'), - // TODO: [] - errors: [ - { - dataSource: 'MANUAL', - symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde' - } - ], - hasErrors: true, // TODO: false + errors: [], + hasErrors: false, positions: [ { averagePrice: new Big('500000'), @@ -132,31 +126,35 @@ describe('PortfolioCalculator', () => { fee: new Big('0'), feeInBaseCurrency: new Big('0'), firstBuyDate: '2022-01-01', - grossPerformance: null, - grossPerformancePercentage: null, - grossPerformancePercentageWithCurrencyEffect: null, - grossPerformanceWithCurrencyEffect: null, - investment: new Big('0'), // TODO: new Big('500000') - investmentWithCurrencyEffect: new Big('0'), // TODO: new Big('500000') + grossPerformance: new Big('0'), + grossPerformancePercentage: new Big('0'), + grossPerformancePercentageWithCurrencyEffect: new Big('0'), + grossPerformanceWithCurrencyEffect: new Big('0'), + investment: new Big('500000'), + investmentWithCurrencyEffect: new Big('500000'), marketPrice: null, marketPriceInBaseCurrency: 500000, - netPerformance: null, - netPerformancePercentage: null, - netPerformancePercentageWithCurrencyEffectMap: null, - netPerformanceWithCurrencyEffectMap: null, + netPerformance: new Big('0'), + netPerformancePercentage: new Big('0'), + netPerformancePercentageWithCurrencyEffectMap: { + max: new Big('0') + }, + netPerformanceWithCurrencyEffectMap: { + max: new Big('0') + }, quantity: new Big('1'), symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde', tags: [], - timeWeightedInvestment: new Big('0'), - timeWeightedInvestmentWithCurrencyEffect: new Big('0'), + timeWeightedInvestment: new Big('500000'), + timeWeightedInvestmentWithCurrencyEffect: new Big('500000'), transactionCount: 1, valueInBaseCurrency: new Big('500000') } ], totalFeesWithCurrencyEffect: new Big('0'), totalInterestWithCurrencyEffect: new Big('0'), - totalInvestment: new Big('0'), // TODO: new Big('500000') - totalInvestmentWithCurrencyEffect: new Big('0'), // TODO: new Big('500000') + totalInvestment: new Big('500000'), + totalInvestmentWithCurrencyEffect: new Big('500000'), totalLiabilitiesWithCurrencyEffect: new Big('0') }); @@ -166,7 +164,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0, netPerformanceInPercentageWithCurrencyEffect: 0, netPerformanceWithCurrencyEffect: 0, - totalInvestmentValueWithCurrencyEffect: 0 // TODO: 500000 + totalInvestmentValueWithCurrencyEffect: 500000 }) ); }); 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 d697c7c69..d4fad7d93 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -231,7 +231,20 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { const startDateString = format(start, DATE_FORMAT); const unitPriceAtStartDate = marketSymbolMap[startDateString]?.[symbol]; - const unitPriceAtEndDate = marketSymbolMap[endDateString]?.[symbol]; + let unitPriceAtEndDate = marketSymbolMap[endDateString]?.[symbol]; + + let latestActivity = orders.at(-1); + + if ( + dataSource === 'MANUAL' && + ['BUY', 'SELL'].includes(latestActivity?.type) && + latestActivity?.unitPrice && + !unitPriceAtEndDate + ) { + // For BUY / SELL activities with a MANUAL data source where no historical market price is available, + // the calculation should fall back to using the activity’s unit price. + unitPriceAtEndDate = latestActivity.unitPrice; + } if ( !unitPriceAtEndDate || @@ -344,9 +357,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { }); } - const lastOrder = orders.at(-1); + latestActivity = orders.at(-1); - lastUnitPrice = lastOrder.unitPriceFromMarketData ?? lastOrder.unitPrice; + lastUnitPrice = + latestActivity.unitPriceFromMarketData ?? latestActivity.unitPrice; } // Sort orders so that the start and end placeholder order are at the correct