From 955b8177952d744001fe988a7cb33320911305d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Fri, 6 Feb 2026 17:13:37 +0100 Subject: [PATCH] efactor test file --- ...culator-dividend-yield-multi-asset.spec.ts | 468 ++++++++++++++++++ ...-calculator-msft-buy-with-dividend.spec.ts | 402 +-------------- 2 files changed, 485 insertions(+), 385 deletions(-) create mode 100644 apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-dividend-yield-multi-asset.spec.ts diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-dividend-yield-multi-asset.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-dividend-yield-multi-asset.spec.ts new file mode 100644 index 000000000..87bb40c61 --- /dev/null +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-dividend-yield-multi-asset.spec.ts @@ -0,0 +1,468 @@ +import { + activityDummyData, + symbolProfileDummyData, + userDummyData +} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; +import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; +import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { Big } from 'big.js'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', + () => { + return { + PortfolioSnapshotService: jest.fn().mockImplementation(() => { + return PortfolioSnapshotServiceMock; + }) + }; + } +); + +jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { + return { + RedisCacheService: jest.fn().mockImplementation(() => { + return RedisCacheServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let exchangeRateDataService: ExchangeRateDataService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeEach(() => { + configurationService = new ConfigurationService(); + + currentRateService = new CurrentRateService(null, null, null, null); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null); + + redisCacheService = new RedisCacheService(null, null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('Multi-asset dividend yield', () => { + it('with MSFT and IBM positions verifies portfolio-wide dividend yield aggregation', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime()); + + const activities: Activity[] = [ + // MSFT: 1 share @ 300, 4 quarterly dividends = 2.60 total + { + ...activityDummyData, + date: new Date('2021-09-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'BUY', + unitPriceInAssetProfileCurrency: 300 + }, + { + ...activityDummyData, + date: new Date('2022-08-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.65 + }, + { + ...activityDummyData, + date: new Date('2022-11-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.65 + }, + { + ...activityDummyData, + date: new Date('2023-02-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.65 + }, + { + ...activityDummyData, + date: new Date('2023-05-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.65 + }, + // IBM: 1 share @ 200, 4 quarterly dividends = 6.60 total + { + ...activityDummyData, + date: new Date('2021-10-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'BUY', + unitPriceInAssetProfileCurrency: 200 + }, + { + ...activityDummyData, + date: new Date('2022-09-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 1.65 + }, + { + ...activityDummyData, + date: new Date('2022-12-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 1.65 + }, + { + ...activityDummyData, + date: new Date('2023-03-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 1.65 + }, + { + ...activityDummyData, + date: new Date('2023-06-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 1.65 + } + ]; + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'USD', + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + expect(portfolioSnapshot.positions).toHaveLength(2); + + const msftPosition = portfolioSnapshot.positions.find( + ({ symbol }) => symbol === 'MSFT' + ); + const ibmPosition = portfolioSnapshot.positions.find( + ({ symbol }) => symbol === 'IBM' + ); + + // MSFT: 2.60 dividends / 300 investment = 0.00867 (0.867%) + expect(msftPosition.dividendInBaseCurrency).toEqual(new Big('2.6')); + expect(msftPosition.investmentWithCurrencyEffect).toEqual(new Big('300')); + expect(msftPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( + 2.6 / 300, + 5 + ); + + // IBM: 6.60 dividends / 200 investment = 0.033 (3.3%) + expect(ibmPosition.dividendInBaseCurrency).toEqual(new Big('6.6')); + expect(ibmPosition.investmentWithCurrencyEffect).toEqual(new Big('200')); + expect(ibmPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( + 6.6 / 200, + 5 + ); + + // Portfolio-wide: (2.60 + 6.60) / (300 + 200) = 9.20 / 500 = 0.0184 (1.84%) + const totalDividends = new Big(msftPosition.dividendInBaseCurrency).plus( + ibmPosition.dividendInBaseCurrency + ); + const totalInvestment = new Big( + msftPosition.investmentWithCurrencyEffect + ).plus(ibmPosition.investmentWithCurrencyEffect); + + expect(totalDividends.toNumber()).toBe(9.2); + expect(totalInvestment.toNumber()).toBe(500); + + // Verify portfolio-level dividend yield aggregation + expect(portfolioSnapshot).toHaveProperty( + 'dividendYieldTrailingTwelveMonths' + ); + expect(portfolioSnapshot.dividendYieldTrailingTwelveMonths).toBeCloseTo( + 0.0184, + 4 + ); + }); + + it('ignores dividends older than 12 months when aggregating portfolio yield', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime()); + + const activities: Activity[] = [ + // MSFT: 1 share @ 300, 3 dividends total (one older than 12 months) + { + ...activityDummyData, + date: new Date('2021-09-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'BUY', + unitPriceInAssetProfileCurrency: 300 + }, + { + ...activityDummyData, + date: new Date('2021-11-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.62 + }, + { + ...activityDummyData, + date: new Date('2022-08-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.65 + }, + { + ...activityDummyData, + date: new Date('2023-05-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Inc.', + symbol: 'MSFT' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 0.65 + }, + // IBM: 1 share @ 200, 2 dividends total (one older than 12 months) + { + ...activityDummyData, + date: new Date('2021-10-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'BUY', + unitPriceInAssetProfileCurrency: 200 + }, + { + ...activityDummyData, + date: new Date('2022-06-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 1.65 + }, + { + ...activityDummyData, + date: new Date('2023-06-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: 'YAHOO', + name: 'IBM', + symbol: 'IBM' + }, + type: 'DIVIDEND', + unitPriceInAssetProfileCurrency: 1.65 + } + ]; + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'USD', + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + const msftPosition = portfolioSnapshot.positions.find( + ({ symbol }) => symbol === 'MSFT' + ); + const ibmPosition = portfolioSnapshot.positions.find( + ({ symbol }) => symbol === 'IBM' + ); + + expect(msftPosition.dividendInBaseCurrency).toEqual(new Big('1.92')); + expect(ibmPosition.dividendInBaseCurrency).toEqual(new Big('3.3')); + + const msftDividendLast12Months = new Big('1.3'); + const ibmDividendLast12Months = new Big('1.65'); + const totalInvestment = new Big('500'); + + expect(msftPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( + msftDividendLast12Months.div(new Big('300')).toNumber(), + 6 + ); + expect(ibmPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( + ibmDividendLast12Months.div(new Big('200')).toNumber(), + 6 + ); + + const expectedDividendYield = msftDividendLast12Months + .plus(ibmDividendLast12Months) + .div(totalInvestment) + .toNumber(); + + expect(portfolioSnapshot.dividendYieldTrailingTwelveMonths).toBeCloseTo( + expectedDividendYield, + 6 + ); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index 1725ef046..e82f9ee4f 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -292,409 +292,41 @@ describe('PortfolioCalculator', () => { tags: [], transactionCount: 5 } - ] + ], + totalFeesWithCurrencyEffect: new Big('19'), + totalInterestWithCurrencyEffect: new Big('0'), + totalInvestment: new Big('298.58'), + totalInvestmentWithCurrencyEffect: new Big('298.58'), + totalLiabilitiesWithCurrencyEffect: new Big('0') }); + // Verify position-level dividend yield const position = portfolioSnapshot.positions[0]; expect(position).toHaveProperty('dividendYieldTrailingTwelveMonths'); expect(position.dividendYieldTrailingTwelveMonths).toBeGreaterThan(0); - // Verify that the snapshot data is sufficient for portfolio summary calculation - // Portfolio summary annualized dividend yield = totalDividend / totalInvestment - const expectedPortfolioYield = new Big(position.dividendInBaseCurrency) + const expectedPositionYield = new Big(position.dividendInBaseCurrency) .div(position.investmentWithCurrencyEffect) .toNumber(); - expect(position.dividendYieldTrailingTwelveMonths).toBeCloseTo( - expectedPortfolioYield, + expectedPositionYield, 10 ); - expect(expectedPortfolioYield).toBeCloseTo(0.00891, 3); // ~0.89% yield on cost - }); - - it('with MSFT and IBM positions to verify portfolio-wide dividend yield aggregation', async () => { - jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime()); - - const activities: Activity[] = [ - // MSFT: 1 share @ 300, 4 quarterly dividends = 2.60 total - { - ...activityDummyData, - date: new Date('2021-09-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'BUY', - unitPriceInAssetProfileCurrency: 300 - }, - { - ...activityDummyData, - date: new Date('2022-08-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.65 - }, - { - ...activityDummyData, - date: new Date('2022-11-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.65 - }, - { - ...activityDummyData, - date: new Date('2023-02-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.65 - }, - { - ...activityDummyData, - date: new Date('2023-05-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.65 - }, - // IBM: 1 share @ 200, 4 quarterly dividends = 6.60 total - { - ...activityDummyData, - date: new Date('2021-10-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'BUY', - unitPriceInAssetProfileCurrency: 200 - }, - { - ...activityDummyData, - date: new Date('2022-09-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 1.65 - }, - { - ...activityDummyData, - date: new Date('2022-12-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 1.65 - }, - { - ...activityDummyData, - date: new Date('2023-03-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 1.65 - }, - { - ...activityDummyData, - date: new Date('2023-06-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 1.65 - } - ]; - - const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ - activities, - calculationType: PerformanceCalculationType.ROAI, - currency: 'USD', - userId: userDummyData.id - }); - - const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - - expect(portfolioSnapshot.positions).toHaveLength(2); - - const msftPosition = portfolioSnapshot.positions.find( - ({ symbol }) => symbol === 'MSFT' - ); - const ibmPosition = portfolioSnapshot.positions.find( - ({ symbol }) => symbol === 'IBM' - ); - - // MSFT: 2.60 dividends / 300 investment = 0.00867 (0.867%) - expect(msftPosition.dividendInBaseCurrency).toEqual(new Big('2.6')); - expect(msftPosition.investmentWithCurrencyEffect).toEqual(new Big('300')); - expect(msftPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( - 2.6 / 300, - 5 - ); - - // IBM: 6.60 dividends / 200 investment = 0.033 (3.3%) - expect(ibmPosition.dividendInBaseCurrency).toEqual(new Big('6.6')); - expect(ibmPosition.investmentWithCurrencyEffect).toEqual(new Big('200')); - expect(ibmPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( - 6.6 / 200, - 5 - ); - - // Portfolio-wide: (2.60 + 6.60) / (300 + 200) = 9.20 / 500 = 0.0184 (1.84%) - const totalDividends = new Big(msftPosition.dividendInBaseCurrency).plus( - ibmPosition.dividendInBaseCurrency - ); - const totalInvestment = new Big( - msftPosition.investmentWithCurrencyEffect - ).plus(ibmPosition.investmentWithCurrencyEffect); + expect(expectedPositionYield).toBeCloseTo(0.00891, 3); // ~0.89% yield on cost - expect(totalDividends.toNumber()).toBe(9.2); - expect(totalInvestment.toNumber()).toBe(500); - - // Test that portfolioSnapshot has aggregated dividendYieldTrailingTwelveMonths + // Verify portfolio-level dividend yield expect(portfolioSnapshot).toHaveProperty( 'dividendYieldTrailingTwelveMonths' ); expect(portfolioSnapshot.dividendYieldTrailingTwelveMonths).toBeCloseTo( - 0.0184, - 4 - ); - }); - - it('ignores dividends older than 12 months when aggregating portfolio yield', async () => { - jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime()); - - const activities: Activity[] = [ - // MSFT: 1 share @ 300, 3 dividends total (one older than 12 months) - { - ...activityDummyData, - date: new Date('2021-09-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'BUY', - unitPriceInAssetProfileCurrency: 300 - }, - { - ...activityDummyData, - date: new Date('2021-11-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.62 - }, - { - ...activityDummyData, - date: new Date('2022-08-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.65 - }, - { - ...activityDummyData, - date: new Date('2023-05-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'Microsoft Inc.', - symbol: 'MSFT' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 0.65 - }, - // IBM: 1 share @ 200, 2 dividends total (one older than 12 months) - { - ...activityDummyData, - date: new Date('2021-10-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'BUY', - unitPriceInAssetProfileCurrency: 200 - }, - { - ...activityDummyData, - date: new Date('2022-06-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 1.65 - }, - { - ...activityDummyData, - date: new Date('2023-06-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: 'YAHOO', - name: 'IBM', - symbol: 'IBM' - }, - type: 'DIVIDEND', - unitPriceInAssetProfileCurrency: 1.65 - } - ]; - - const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ - activities, - calculationType: PerformanceCalculationType.ROAI, - currency: 'USD', - userId: userDummyData.id - }); - - const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - - const msftPosition = portfolioSnapshot.positions.find( - ({ symbol }) => symbol === 'MSFT' - ); - const ibmPosition = portfolioSnapshot.positions.find( - ({ symbol }) => symbol === 'IBM' - ); - - expect(msftPosition.dividendInBaseCurrency).toEqual(new Big('1.92')); - expect(ibmPosition.dividendInBaseCurrency).toEqual(new Big('3.3')); - - const msftDividendLast12Months = new Big('1.3'); - const ibmDividendLast12Months = new Big('1.65'); - const totalInvestment = new Big('500'); - - expect(msftPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( - msftDividendLast12Months.div(new Big('300')).toNumber(), - 6 - ); - expect(ibmPosition.dividendYieldTrailingTwelveMonths).toBeCloseTo( - ibmDividendLast12Months.div(new Big('200')).toNumber(), - 6 + expectedPositionYield, + 10 ); - const expectedAnnualizedDividendYield = msftDividendLast12Months - .plus(ibmDividendLast12Months) - .div(totalInvestment) - .toNumber(); - - expect(portfolioSnapshot.dividendYieldTrailingTwelveMonths).toBeCloseTo( - expectedAnnualizedDividendYield, - 6 + expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( + expect.objectContaining({ + totalInvestmentValueWithCurrencyEffect: 298.58 + }) ); }); });