Browse Source

Add dividend performance

pull/7924/head
Thomas Kaul 1 week ago
committed by Thomas Kaul
parent
commit
a08fa85ab1
  1. 86
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  3. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  4. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  5. 186
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts
  6. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  7. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  8. 12
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  9. 1
      apps/api/src/app/portfolio/interfaces/accumulated-values.interface.ts
  10. 1
      apps/api/src/app/portfolio/portfolio.controller.ts
  11. 11
      apps/api/src/app/portfolio/portfolio.service.ts
  12. 1
      apps/api/src/app/portfolio/types/performance-percentages.type.ts
  13. 44
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  14. 2
      libs/common/src/lib/interfaces/historical-data-item.interface.ts
  15. 2
      libs/common/src/lib/interfaces/portfolio-performance.interface.ts
  16. 1
      libs/common/src/lib/interfaces/portfolio-summary.interface.ts

86
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -544,6 +544,12 @@ export abstract class PortfolioCalculator {
} }
} }
const totalDividendValueByDate = this.getTotalDividendValueByDate({
chartDates,
exchangeRatesByCurrency,
holdings: positions
});
const assetProfileIdentifiers = Object.keys(valuesByAssetProfileIdentifier); const assetProfileIdentifiers = Object.keys(valuesByAssetProfileIdentifier);
for (const dateString of chartDates) { for (const dateString of chartDates) {
@ -620,6 +626,7 @@ export abstract class PortfolioCalculator {
accumulatedValuesByDate[dateString] accumulatedValuesByDate[dateString]
?.totalCurrentValueWithCurrencyEffect ?? new Big(0) ?.totalCurrentValueWithCurrencyEffect ?? new Big(0)
).add(currentValueWithCurrencyEffect), ).add(currentValueWithCurrencyEffect),
totalDividendValue: totalDividendValueByDate[dateString],
totalInvestmentValue: ( totalInvestmentValue: (
accumulatedValuesByDate[dateString]?.totalInvestmentValue ?? accumulatedValuesByDate[dateString]?.totalInvestmentValue ??
new Big(0) new Big(0)
@ -656,6 +663,7 @@ export abstract class PortfolioCalculator {
totalCashValueWithCurrencyEffect, totalCashValueWithCurrencyEffect,
totalCurrentValue, totalCurrentValue,
totalCurrentValueWithCurrencyEffect, totalCurrentValueWithCurrencyEffect,
totalDividendValue,
totalInvestmentValue, totalInvestmentValue,
totalInvestmentValueWithCurrencyEffect, totalInvestmentValueWithCurrencyEffect,
totalNetPerformanceValue, totalNetPerformanceValue,
@ -666,6 +674,7 @@ export abstract class PortfolioCalculator {
return { return {
...performancePercentagesByDate[date], ...performancePercentagesByDate[date],
date, date,
dividendInBaseCurrency: totalDividendValue.toNumber(),
investmentValueWithCurrencyEffect: investmentValueWithCurrencyEffect:
investmentValueWithCurrencyEffect.toNumber(), investmentValueWithCurrencyEffect.toNumber(),
netPerformance: totalNetPerformanceValue.toNumber(), netPerformance: totalNetPerformanceValue.toNumber(),
@ -1343,6 +1352,7 @@ export abstract class PortfolioCalculator {
const historicalDataItemsOfDateRange: HistoricalDataItem[] = []; const historicalDataItemsOfDateRange: HistoricalDataItem[] = [];
let dividendInBaseCurrencyAtStartDate: number;
let netPerformanceAtStartDate: number; let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number; let netPerformanceWithCurrencyEffectAtStartDate: number;
@ -1353,6 +1363,9 @@ export abstract class PortfolioCalculator {
// Take the values at the start date from the first day of the date // Take the values at the start date from the first day of the date
// range // range
if (historicalDataItemsOfDateRange.length === 0) { if (historicalDataItemsOfDateRange.length === 0) {
dividendInBaseCurrencyAtStartDate =
historicalDataItem.dividendInBaseCurrency ?? 0;
netPerformanceAtStartDate = historicalDataItem.netPerformance; netPerformanceAtStartDate = historicalDataItem.netPerformance;
netPerformanceWithCurrencyEffectAtStartDate = netPerformanceWithCurrencyEffectAtStartDate =
@ -1361,6 +1374,11 @@ export abstract class PortfolioCalculator {
historicalDataItemsOfDateRange.push({ historicalDataItemsOfDateRange.push({
...historicalDataItem, ...historicalDataItem,
// TODO: Remove the fallback to 0 with the next release, when each
// cached portfolio snapshot contains the dividend
dividendInBaseCurrency:
(historicalDataItem.dividendInBaseCurrency ?? 0) -
dividendInBaseCurrencyAtStartDate,
netPerformance: netPerformance:
historicalDataItem.netPerformance - netPerformanceAtStartDate, historicalDataItem.netPerformance - netPerformanceAtStartDate,
netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffect:
@ -1734,6 +1752,74 @@ export abstract class PortfolioCalculator {
return chartDateMap; return chartDateMap;
} }
private getTotalDividendValueByDate({
chartDates,
exchangeRatesByCurrency,
holdings
}: {
chartDates: string[];
exchangeRatesByCurrency: {
[currencyPair: string]: { [dateString: string]: number };
};
holdings: PortfolioCalculatorHolding[];
}): { [date: string]: Big } {
// Take the dividend from the same holdings as the portfolio summary, so
// that the response shows one dividend only
const assetProfileIdentifiersIncludedInHoldings = new Set(
holdings
.filter(({ includeInHoldings }) => {
return includeInHoldings;
})
.map(({ dataSource, symbol }) => {
return getAssetProfileIdentifier({ dataSource, symbol });
})
);
const dividendActivities = this.activities.filter(
({ assetProfile, type }) => {
return (
type === 'DIVIDEND' &&
assetProfileIdentifiersIncludedInHoldings.has(
getAssetProfileIdentifier(assetProfile)
)
);
}
);
const totalDividendValueByDate: { [date: string]: Big } = {};
let index = 0;
let totalDividendValue = new Big(0);
// The activities and the chart dates are sorted by date, so one pass over
// both gives the dividends received up to each chart date
for (const chartDate of chartDates) {
while (
index < dividendActivities.length &&
dividendActivities[index].date <= chartDate
) {
const { assetProfile, date, quantity, unitPrice } =
dividendActivities[index];
totalDividendValue = totalDividendValue.plus(
quantity
.mul(unitPrice)
.mul(
exchangeRatesByCurrency[
`${assetProfile.currency}${this.currency}`
]?.[date] ?? 1
)
);
index++;
}
totalDividendValueByDate[chartDate] = totalDividendValue;
}
return totalDividendValueByDate;
}
@LogPerformance @LogPerformance
private async initialize(attempt = 1) { private async initialize(attempt = 1) {
const startTimeTotal = performance.now(); const startTimeTotal = performance.now();

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts

@ -142,6 +142,8 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({ expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11', date: '2021-12-11',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,
@ -160,6 +162,8 @@ describe('PortfolioCalculator', () => {
*/ */
expect(portfolioSnapshot.historicalData[1]).toEqual({ expect(portfolioSnapshot.historicalData[1]).toEqual({
date: '2021-12-12', date: '2021-12-12',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 44558.42, investmentValueWithCurrencyEffect: 44558.42,
netPerformance: 5535.42, // 1 * (50098.3 - 44558.42) - 4.46 = 5535.42 netPerformance: 5535.42, // 1 * (50098.3 - 44558.42) - 4.46 = 5535.42
netPerformanceInPercentage: 0.12422837255001412, // 5535.42 ÷ 44558.42 = 0.12422837255001412 netPerformanceInPercentage: 0.12422837255001412, // 5535.42 ÷ 44558.42 = 0.12422837255001412
@ -179,6 +183,8 @@ describe('PortfolioCalculator', () => {
] ]
).toEqual({ ).toEqual({
date: '2022-01-14', date: '2022-01-14',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: -1463.18, netPerformance: -1463.18,
netPerformanceInPercentage: -0.032837340282712, netPerformanceInPercentage: -0.032837340282712,

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts

@ -142,6 +142,8 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({ expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11', date: '2021-12-11',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,
@ -160,6 +162,8 @@ describe('PortfolioCalculator', () => {
*/ */
expect(portfolioSnapshot.historicalData[1]).toEqual({ expect(portfolioSnapshot.historicalData[1]).toEqual({
date: '2021-12-12', date: '2021-12-12',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 44558.42, investmentValueWithCurrencyEffect: 44558.42,
netPerformance: 5535.42, // 1 * (50098.3 - 44558.42) - 4.46 = 5535.42 netPerformance: 5535.42, // 1 * (50098.3 - 44558.42) - 4.46 = 5535.42
netPerformanceInPercentage: 0.12422837255001412, // 5535.42 ÷ 44558.42 = 0.12422837255001412 netPerformanceInPercentage: 0.12422837255001412, // 5535.42 ÷ 44558.42 = 0.12422837255001412
@ -179,6 +183,8 @@ describe('PortfolioCalculator', () => {
] ]
).toEqual({ ).toEqual({
date: '2022-01-14', date: '2022-01-14',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: -1463.18, netPerformance: -1463.18,
netPerformanceInPercentage: -0.032837340282712, netPerformanceInPercentage: -0.032837340282712,

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -321,6 +321,8 @@ describe('PortfolioCalculator', () => {
*/ */
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({ expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01', date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,
@ -459,6 +461,8 @@ describe('PortfolioCalculator', () => {
*/ */
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({ expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01', date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,
@ -601,6 +605,8 @@ describe('PortfolioCalculator', () => {
*/ */
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({ expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01', date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,

186
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts

@ -0,0 +1,186 @@
import {
activityDummyData,
assetProfileDummyData,
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(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,
null,
null,
null
);
portfolioSnapshotService = new PortfolioSnapshotService(null, null);
redisCacheService = new RedisCacheService(null, null);
portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService,
currentRateService,
exchangeRateDataService,
portfolioSnapshotService,
redisCacheService
);
});
describe('get current positions', () => {
it('with GOOGL fee and dividend', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Inc.',
symbol: 'MSFT'
},
date: new Date('2021-09-16'),
feeInAssetProfileCurrency: 19,
feeInBaseCurrency: 19,
quantity: 1,
type: 'BUY',
unitPriceInAssetProfileCurrency: 298.58
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Inc.',
symbol: 'MSFT'
},
date: new Date('2021-11-16'),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
quantity: 1,
type: 'DIVIDEND',
unitPriceInAssetProfileCurrency: 0.62
},
{
// The first activity of this asset profile is a fee, which is not an
// investment activity. Thus the holding is not included in the
// holdings of the portfolio summary, and its dividend must not be
// included in the chart
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Alphabet Inc.',
symbol: 'GOOGL'
},
date: new Date('2023-01-03'),
feeInAssetProfileCurrency: 1,
feeInBaseCurrency: 1,
quantity: 0,
type: 'FEE',
unitPriceInAssetProfileCurrency: 0
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Alphabet Inc.',
symbol: 'GOOGL'
},
date: new Date('2023-07-10'),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
quantity: 1,
type: 'DIVIDEND',
unitPriceInAssetProfileCurrency: 5
}
];
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'USD',
usePortfolioSnapshotCache: false,
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const dividendInBaseCurrency =
await portfolioCalculator.getDividendInBaseCurrency();
expect(
portfolioSnapshot.positions.map(({ symbol }) => {
return symbol;
})
).toEqual(['MSFT']);
// The chart and the portfolio summary must show the same dividend
expect(dividendInBaseCurrency).toEqual(new Big('0.62'));
expect(
portfolioSnapshot.historicalData.at(-1).dividendInBaseCurrency
).toEqual(0.62);
});
});
});

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -198,6 +198,8 @@ describe('PortfolioCalculator', () => {
expect(performanceByDateRange).toMatchObject({ expect(performanceByDateRange).toMatchObject({
'1d': { '1d': {
date: '2023-07-10', date: '2023-07-10',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -5.390000000000001, netPerformance: -5.390000000000001,
netPerformanceInPercentage: -0.015983630864124312, netPerformanceInPercentage: -0.015983630864124312,
netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124312, netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124312,
@ -207,6 +209,8 @@ describe('PortfolioCalculator', () => {
}, },
max: { max: {
date: '2023-07-10', date: '2023-07-10',
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0.002076495411614967,
netPerformance: 14.25, netPerformance: 14.25,
netPerformanceInPercentage: 0.04772590260566659, netPerformanceInPercentage: 0.04772590260566659,
netPerformanceInPercentageWithCurrencyEffect: 0.04772590260566659, netPerformanceInPercentageWithCurrencyEffect: 0.04772590260566659,
@ -216,6 +220,8 @@ describe('PortfolioCalculator', () => {
}, },
ytd: { ytd: {
date: '2023-07-10', date: '2023-07-10',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -7.68, netPerformance: -7.68,
netPerformanceInPercentage: -0.022620835910577012, netPerformanceInPercentage: -0.022620835910577012,
netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577012, netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577012,

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -139,6 +139,8 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({ expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2022-03-06', date: '2022-03-06',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,
@ -158,6 +160,8 @@ describe('PortfolioCalculator', () => {
*/ */
expect(portfolioSnapshot.historicalData[1]).toEqual({ expect(portfolioSnapshot.historicalData[1]).toEqual({
date: '2022-03-07', date: '2022-03-07',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 151.6, investmentValueWithCurrencyEffect: 151.6,
netPerformance: 0, // 2 * (75.8 - 75.8) = 0 netPerformance: 0, // 2 * (75.8 - 75.8) = 0
netPerformanceInPercentage: 0, // 0 ÷ 151.6 = 0 netPerformanceInPercentage: 0, // 0 ÷ 151.6 = 0
@ -177,6 +181,8 @@ describe('PortfolioCalculator', () => {
] ]
).toEqual({ ).toEqual({
date: '2022-04-11', date: '2022-04-11',
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
netPerformance: 19.86, netPerformance: 19.86,
netPerformanceInPercentage: 0.13100263852242744, netPerformanceInPercentage: 0.13100263852242744,

12
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

@ -178,11 +178,18 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
{ {
totalAverageInvestmentValue, totalAverageInvestmentValue,
totalAverageInvestmentValueWithCurrencyEffect, totalAverageInvestmentValueWithCurrencyEffect,
totalDividendValue,
totalNetPerformanceValue, totalNetPerformanceValue,
totalNetPerformanceValueWithCurrencyEffect totalNetPerformanceValueWithCurrencyEffect
} }
] of Object.entries(accumulatedValuesByDate)) { ] of Object.entries(accumulatedValuesByDate)) {
performancePercentagesByDate[date] = { performancePercentagesByDate[date] = {
dividendInPercentageWithCurrencyEffect:
totalAverageInvestmentValueWithCurrencyEffect.eq(0)
? 0
: totalDividendValue
.div(totalAverageInvestmentValueWithCurrencyEffect)
.toNumber(),
netPerformanceInPercentage: totalAverageInvestmentValue.eq(0) netPerformanceInPercentage: totalAverageInvestmentValue.eq(0)
? 0 ? 0
: totalNetPerformanceValue : totalNetPerformanceValue
@ -254,6 +261,11 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
: 0; : 0;
performancePercentagesByDate[historicalDataItem.date] = { performancePercentagesByDate[historicalDataItem.date] = {
dividendInPercentageWithCurrencyEffect:
averageInvestmentValueWithCurrencyEffect > 0
? historicalDataItem.dividendInBaseCurrency /
averageInvestmentValueWithCurrencyEffect
: 0,
netPerformanceInPercentage: netPerformanceInPercentage:
averageInvestmentValue > 0 averageInvestmentValue > 0
? historicalDataItem.netPerformance / averageInvestmentValue ? historicalDataItem.netPerformance / averageInvestmentValue

1
apps/api/src/app/portfolio/interfaces/accumulated-values.interface.ts

@ -7,6 +7,7 @@ export interface AccumulatedValues {
totalCashValueWithCurrencyEffect: Big; totalCashValueWithCurrencyEffect: Big;
totalCurrentValue: Big; totalCurrentValue: Big;
totalCurrentValueWithCurrencyEffect: Big; totalCurrentValueWithCurrencyEffect: Big;
totalDividendValue: Big;
totalInvestmentValue: Big; totalInvestmentValue: Big;
totalInvestmentValueWithCurrencyEffect: Big; totalInvestmentValueWithCurrencyEffect: Big;
totalNetPerformanceValue: Big; totalNetPerformanceValue: Big;

1
apps/api/src/app/portfolio/portfolio.controller.ts

@ -565,6 +565,7 @@ export class PortfolioController {
[ [
'currentNetWorth', 'currentNetWorth',
'currentValueInBaseCurrency', 'currentValueInBaseCurrency',
'dividendInBaseCurrency',
'grossPerformance', 'grossPerformance',
'grossPerformanceWithCurrencyEffect', 'grossPerformanceWithCurrencyEffect',
'netPerformance', 'netPerformance',

11
apps/api/src/app/portfolio/portfolio.service.ts

@ -1220,6 +1220,8 @@ export class PortfolioService {
performance: { performance: {
currentNetWorth: 0, currentNetWorth: 0,
currentValueInBaseCurrency: 0, currentValueInBaseCurrency: 0,
dividendInBaseCurrency: 0,
dividendPercentageWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformancePercentage: 0, netPerformancePercentage: 0,
netPerformancePercentageWithCurrencyEffect: 0, netPerformancePercentageWithCurrencyEffect: 0,
@ -1251,6 +1253,8 @@ export class PortfolioService {
}); });
const { const {
dividendInBaseCurrency,
dividendInPercentageWithCurrencyEffect,
netPerformance, netPerformance,
netPerformanceInPercentage, netPerformanceInPercentage,
netPerformanceInPercentageWithCurrencyEffect, netPerformanceInPercentageWithCurrencyEffect,
@ -1260,6 +1264,8 @@ export class PortfolioService {
totalInvestmentValueWithCurrencyEffect, totalInvestmentValueWithCurrencyEffect,
valueWithCurrencyEffect valueWithCurrencyEffect
} = chart?.at(-1) ?? { } = chart?.at(-1) ?? {
dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: 0, netPerformance: 0,
netPerformanceInPercentage: 0, netPerformanceInPercentage: 0,
netPerformanceInPercentageWithCurrencyEffect: 0, netPerformanceInPercentageWithCurrencyEffect: 0,
@ -1275,12 +1281,15 @@ export class PortfolioService {
hasErrors, hasErrors,
dateOfFirstActivity: parseDate(historicalData[0]?.date), dateOfFirstActivity: parseDate(historicalData[0]?.date),
performance: { performance: {
dividendInBaseCurrency,
netPerformance, netPerformance,
netPerformanceWithCurrencyEffect, netPerformanceWithCurrencyEffect,
totalInvestment, totalInvestment,
totalInvestmentValueWithCurrencyEffect, totalInvestmentValueWithCurrencyEffect,
currentNetWorth: netWorth, currentNetWorth: netWorth,
currentValueInBaseCurrency: valueWithCurrencyEffect, currentValueInBaseCurrency: valueWithCurrencyEffect,
dividendPercentageWithCurrencyEffect:
dividendInPercentageWithCurrencyEffect,
netPerformancePercentage: netPerformanceInPercentage, netPerformancePercentage: netPerformanceInPercentage,
netPerformancePercentageWithCurrencyEffect: netPerformancePercentageWithCurrencyEffect:
netPerformanceInPercentageWithCurrencyEffect netPerformanceInPercentageWithCurrencyEffect
@ -2125,6 +2134,7 @@ export class PortfolioService {
const { const {
currentValueInBaseCurrency, currentValueInBaseCurrency,
dividendPercentageWithCurrencyEffect,
netPerformance, netPerformance,
netPerformancePercentage, netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect, netPerformancePercentageWithCurrencyEffect,
@ -2224,6 +2234,7 @@ export class PortfolioService {
cash, cash,
currentValueInBaseCurrency, currentValueInBaseCurrency,
dateOfFirstActivity, dateOfFirstActivity,
dividendPercentageWithCurrencyEffect,
excludedAccountsAndActivities, excludedAccountsAndActivities,
netPerformance, netPerformance,
netPerformancePercentage, netPerformancePercentage,

1
apps/api/src/app/portfolio/types/performance-percentages.type.ts

@ -3,6 +3,7 @@ import { HistoricalDataItem } from '@ghostfolio/common/interfaces';
export type PerformancePercentages = Required< export type PerformancePercentages = Required<
Pick< Pick<
HistoricalDataItem, HistoricalDataItem,
| 'dividendInPercentageWithCurrencyEffect'
| 'netPerformanceInPercentage' | 'netPerformanceInPercentage'
| 'netPerformanceInPercentageWithCurrencyEffect' | 'netPerformanceInPercentageWithCurrencyEffect'
> >

44
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -279,6 +279,50 @@
/> />
</div> </div>
</div> </div>
@if (user?.settings?.isExperimentalFeatures) {
<div><hr /></div>
<div class="d-flex py-1">
<div
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
>
<span i18n>Absolute Dividend Performance</span>
<small
class="d-none d-sm-inline-block ml-2 text-muted"
i18n
i18n-title
title="The dividends are not part of the net performance"
>(not included)</small
>
</div>
<div class="d-flex justify-content-end">
<gf-value
class="justify-content-end"
position="end"
[isCurrency]="true"
[isLoading]="isLoadingInvestmentChart"
[locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency"
[value]="performance?.dividendInBaseCurrency"
/>
</div>
</div>
<div class="d-flex ml-3 py-1">
<div class="flex-grow-1 mr-2 text-truncate">
<ng-container i18n>Dividend Performance</ng-container>
</div>
<div class="d-flex justify-content-end">
<gf-value
class="justify-content-end"
position="end"
[colorizeSign]="true"
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="performance?.dividendPercentageWithCurrencyEffect"
/>
</div>
</div>
}
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>

2
libs/common/src/lib/interfaces/historical-data-item.interface.ts

@ -1,6 +1,8 @@
export interface HistoricalDataItem { export interface HistoricalDataItem {
averagePrice?: number; averagePrice?: number;
date: string; date: string;
dividendInBaseCurrency?: number;
dividendInPercentageWithCurrencyEffect?: number;
grossPerformancePercent?: number; grossPerformancePercent?: number;
investmentValueWithCurrencyEffect?: number; investmentValueWithCurrencyEffect?: number;
marketPrice?: number; marketPrice?: number;

2
libs/common/src/lib/interfaces/portfolio-performance.interface.ts

@ -2,6 +2,8 @@ export interface PortfolioPerformance {
annualizedPerformancePercent?: number; annualizedPerformancePercent?: number;
currentNetWorth?: number; currentNetWorth?: number;
currentValueInBaseCurrency: number; currentValueInBaseCurrency: number;
dividendInBaseCurrency: number;
dividendPercentageWithCurrencyEffect: number;
netPerformance: number; netPerformance: number;
netPerformancePercentage: number; netPerformancePercentage: number;
netPerformancePercentageWithCurrencyEffect: number; netPerformancePercentageWithCurrencyEffect: number;

1
libs/common/src/lib/interfaces/portfolio-summary.interface.ts

@ -7,7 +7,6 @@ export interface PortfolioSummary extends PortfolioPerformance {
annualizedPerformancePercentWithCurrencyEffect: number; annualizedPerformancePercentWithCurrencyEffect: number;
cash: number; cash: number;
dateOfFirstActivity?: Date; dateOfFirstActivity?: Date;
dividendInBaseCurrency: number;
dividendYieldPercent: number; dividendYieldPercent: number;
dividendYieldPercentWithCurrencyEffect: number; dividendYieldPercentWithCurrencyEffect: number;
emergencyFund: { emergencyFund: {

Loading…
Cancel
Save