From 1d003427022bed510985acd4f6e2d57e34f0aca9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:53:15 +0200 Subject: [PATCH] Bugfix/market price carry forward on dates with activities (#7702) * Fix market price carry forward on dates with activities * Update changelog --- CHANGELOG.md | 4 ++ .../portfolio-calculator-baln-buy.spec.ts | 43 +++++++++++++++++++ .../calculator/roai/portfolio-calculator.ts | 36 +++++++--------- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9bd6ca83..23346e159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the holdings table by the date of first activity in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) - Extended the holdings table by the date of first activity in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) +### Fixed + +- Fixed the performance calculation for dates without historical market data by carrying forward the market price from dates with activities + ## 3.58.0 - 2026-08-22 ### Changed diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index a2d576361..947935296 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -293,5 +293,48 @@ describe('PortfolioCalculator', () => { // Closing price on 2021-11-30: 136.6 expect(snapshotOnBuyDate?.netPerformanceWithCurrencyEffect).toEqual(1.65); // 2 * (136.6 - 135.0) - 1.55 = 1.65 }); + + it.only('with BALN.SW buy (on a date without historical market data)', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime()); + + const activities: Activity[] = [ + { + ...activityDummyData, + assetProfile: { + ...assetProfileDummyData, + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, + type: 'BUY', + unitPriceInAssetProfileCurrency: 136.6 + } + ]; + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'CHF', + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + const snapshotOnDateWithoutMarketData = + portfolioSnapshot.historicalData.find(({ date }) => { + return date === '2021-12-05'; + }); + + // No historical market data on 2021-12-05: carry forward the closing + // price of 2021-11-30, which is the date of the activity + expect(snapshotOnDateWithoutMarketData?.value).toEqual(273.2); // 2 * 136.6 + expect( + snapshotOnDateWithoutMarketData?.netPerformanceWithCurrencyEffect + ).toEqual(-1.55); // 2 * (136.6 - 136.6) - 1.55 + }); }); }); 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 26deba0b4..e49b3fd25 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -391,30 +391,26 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { break; } + const unitPrice = marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice; + if (ordersByDate[dateString]?.length > 0) { for (const order of ordersByDate[dateString]) { - order.unitPriceFromMarketData = - marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice; - } - } else { - const unitPrice = - marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice; - - if (dateString >= dateStringOfFirstActivity) { - orders.push({ - assetProfile, - unitPrice, - date: dateString, - fee: new Big(0), - feeInBaseCurrency: new Big(0), - quantity: new Big(0), - type: 'BUY', - unitPriceFromMarketData: unitPrice - }); + order.unitPriceFromMarketData = unitPrice; } - - lastUnitPrice = unitPrice; + } else if (dateString >= dateStringOfFirstActivity) { + orders.push({ + assetProfile, + unitPrice, + date: dateString, + fee: new Big(0), + feeInBaseCurrency: new Big(0), + quantity: new Big(0), + type: 'BUY', + unitPriceFromMarketData: unitPrice + }); } + + lastUnitPrice = unitPrice; } // Sort orders so that the start and end placeholder order are at the correct