Browse Source

Include dividends in portfolio performance

pull/7961/head
Thomas Kaul 2 weeks ago
parent
commit
2f48aed7c3
  1. 17
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  3. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  4. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  5. 44
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  6. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  7. 11
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  8. 6
      apps/api/src/app/portfolio/portfolio.service.ts
  9. 1
      apps/api/src/app/portfolio/types/performance-percentages.type.ts
  10. 15
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
  11. 30
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
  12. 4
      apps/client/src/app/pages/faq/overview/faq-overview-page.html
  13. 30
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  14. 26
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  15. 1
      libs/common/src/lib/interfaces/historical-data-item.interface.ts
  16. 1
      libs/common/src/lib/interfaces/portfolio-performance.interface.ts

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

@ -946,7 +946,6 @@ export abstract class PortfolioCalculator {
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
const currentValues: { [date: string]: Big } = {};
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};
let dividends = new Big(0);
let fees = new Big(0);
let feesAtStartDate = new Big(0);
let feesAtStartDateWithCurrencyEffect = new Big(0);
@ -1124,16 +1123,6 @@ export abstract class PortfolioCalculator {
activity.feeInBaseCurrencyWithCurrencyEffect ?? 0
);
if (activity.type === 'DIVIDEND') {
// The amount of a dividend in the base currency is the same with and
// without currency effect, like the dividend of the holding
dividends = dividends.plus(
activity.quantity
.mul(activity.unitPrice)
.mul(exchangeRateAtActivityDate ?? 1)
);
}
totalQuantity = totalQuantity.plus(
activity.quantity.mul(getFactor(activity.type))
);
@ -1201,14 +1190,12 @@ export abstract class PortfolioCalculator {
const newGrossPerformance = valueOfInvestment
.minus(totalInvestment)
.plus(grossPerformanceFromSells)
.plus(dividends);
.plus(grossPerformanceFromSells);
const newGrossPerformanceWithCurrencyEffect =
valueOfInvestmentWithCurrencyEffect
.minus(totalInvestmentWithCurrencyEffect)
.plus(grossPerformanceFromSellsWithCurrencyEffect)
.plus(dividends);
.plus(grossPerformanceFromSellsWithCurrencyEffect);
grossPerformance = newGrossPerformance;

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

@ -143,7 +143,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -164,7 +163,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[1]).toEqual({
date: '2021-12-12',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 44558.42,
netPerformance: 5535.42, // 1 * (50098.3 - 44558.42) - 4.46 = 5535.42
@ -186,7 +184,6 @@ describe('PortfolioCalculator', () => {
).toEqual({
date: '2022-01-14',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: -1463.18,

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

@ -143,7 +143,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -164,7 +163,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[1]).toEqual({
date: '2021-12-12',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 44558.42,
netPerformance: 5535.42, // 1 * (50098.3 - 44558.42) - 4.46 = 5535.42
@ -186,7 +184,6 @@ describe('PortfolioCalculator', () => {
).toEqual({
date: '2022-01-14',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: -1463.18,

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

@ -322,7 +322,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -463,7 +462,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -608,7 +606,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,

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

@ -155,25 +155,25 @@ describe('PortfolioCalculator', () => {
'0.001144362748184'
),
fee: new Big('19'),
grossPerformance: new Big('33.87'),
grossPerformancePercentage: new Big('0.11343693482483756447'),
grossPerformance: new Big('33.25'),
grossPerformancePercentage: new Big('0.11136043941322258691'),
grossPerformancePercentageWithCurrencyEffect: new Big(
'0.11343693482483756447'
'0.11136043941322258691'
),
grossPerformanceWithCurrencyEffect: new Big('33.87'),
grossPerformanceWithCurrencyEffect: new Big('33.25'),
investment: new Big('298.58'),
investmentWithCurrencyEffect: new Big('298.58'),
marketPrice: 331.83,
marketPriceInBaseCurrency: 331.83,
netPerformance: new Big('14.87'),
netPerformancePercentage: new Big('0.04980239801728180052'),
netPerformance: new Big('14.25'),
netPerformancePercentage: new Big('0.04772590260566682296'),
netPerformancePercentageWithCurrencyEffectMap: {
max: new Big('0.04980239801728180052')
max: new Big('0.04772590260566682296')
},
netPerformanceWithCurrencyEffectMap: {
'1d': new Big('-5.39'),
'5y': new Big('14.87'),
max: new Big('14.87'),
'5y': new Big('14.25'),
max: new Big('14.25'),
wtd: new Big('-5.39')
},
quantity: new Big('1'),
@ -199,12 +199,11 @@ describe('PortfolioCalculator', () => {
'1d': {
date: '2023-07-10',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -5.390000000000002,
netPerformanceInPercentage: -0.015983630864124316,
netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124316,
netPerformanceWithCurrencyEffect: -5.390000000000002,
netPerformance: -5.390000000000001,
netPerformanceInPercentage: -0.015983630864124312,
netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124312,
netPerformanceWithCurrencyEffect: -5.390000000000001,
totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83
},
@ -212,22 +211,21 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10',
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0.002076495411614967,
netPerformance: 14.87,
netPerformanceInPercentage: 0.049802398017281556,
netPerformanceInPercentageWithCurrencyEffect: 0.049802398017281556,
netPerformanceWithCurrencyEffect: 14.87,
netPerformance: 14.25,
netPerformanceInPercentage: 0.04772590260566659,
netPerformanceInPercentageWithCurrencyEffect: 0.04772590260566659,
netPerformanceWithCurrencyEffect: 14.25,
totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83
},
ytd: {
date: '2023-07-10',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -7.6800000000000015,
netPerformanceInPercentage: -0.022620835910577015,
netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577015,
netPerformanceWithCurrencyEffect: -7.6800000000000015,
netPerformance: -7.68,
netPerformanceInPercentage: -0.022620835910577012,
netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577012,
netPerformanceWithCurrencyEffect: -7.68,
totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83
}

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

@ -140,7 +140,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2022-03-06',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -162,7 +161,6 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[1]).toEqual({
date: '2022-03-07',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 151.6,
netPerformance: 0, // 2 * (75.8 - 75.8) = 0
@ -184,7 +182,6 @@ describe('PortfolioCalculator', () => {
).toEqual({
date: '2022-04-11',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 19.86,

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

@ -192,13 +192,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
}
] of Object.entries(accumulatedValuesByDate)) {
performancePercentagesByDate[date] = {
dividendInPercentage:
hasDividendWithoutAverageInvestment ||
totalAverageInvestmentValue.eq(0)
? 0
: totalDividendValueWithCurrencyEffect
.div(totalAverageInvestmentValue)
.toNumber(),
dividendInPercentageWithCurrencyEffect:
hasDividendWithoutAverageInvestment ||
totalAverageInvestmentValueWithCurrencyEffect.eq(0)
@ -282,10 +275,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
: 0;
performancePercentagesByDate[historicalDataItem.date] = {
dividendInPercentage:
!hasDividendWithoutAverageInvestment && averageInvestmentValue > 0
? historicalDataItem.dividendInBaseCurrency / averageInvestmentValue
: 0,
dividendInPercentageWithCurrencyEffect:
!hasDividendWithoutAverageInvestment &&
averageInvestmentValueWithCurrencyEffect > 0

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

@ -1226,7 +1226,6 @@ export class PortfolioService {
currentNetWorth: 0,
currentValueInBaseCurrency: 0,
dividendInBaseCurrency: 0,
dividendPercentage: 0,
dividendPercentageWithCurrencyEffect: 0,
netPerformance: 0,
netPerformancePercentage: 0,
@ -1260,7 +1259,6 @@ export class PortfolioService {
const {
dividendInBaseCurrency,
dividendInPercentage,
dividendInPercentageWithCurrencyEffect,
netPerformance,
netPerformanceInPercentage,
@ -1272,7 +1270,6 @@ export class PortfolioService {
valueWithCurrencyEffect
} = chart?.at(-1) ?? {
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: 0,
netPerformanceInPercentage: 0,
@ -1296,7 +1293,6 @@ export class PortfolioService {
totalInvestmentValueWithCurrencyEffect,
currentNetWorth: netWorth,
currentValueInBaseCurrency: valueWithCurrencyEffect,
dividendPercentage: dividendInPercentage,
dividendPercentageWithCurrencyEffect:
dividendInPercentageWithCurrencyEffect,
netPerformancePercentage: netPerformanceInPercentage,
@ -2144,6 +2140,7 @@ export class PortfolioService {
const {
currentValueInBaseCurrency,
dividendPercentageWithCurrencyEffect,
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
@ -2240,6 +2237,7 @@ export class PortfolioService {
cash,
currentValueInBaseCurrency,
dateOfFirstActivity,
dividendPercentageWithCurrencyEffect,
excludedAccountsAndActivities,
netPerformance,
netPerformancePercentage,

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

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

15
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html

@ -94,13 +94,7 @@
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
[value]="
user?.settings?.isExperimentalFeatures &&
summary?.grossPerformanceWithCurrencyEffect !== null
? summary?.grossPerformanceWithCurrencyEffect -
summary?.dividendInBaseCurrency
: summary?.grossPerformanceWithCurrencyEffect
"
[value]="summary?.grossPerformanceWithCurrencyEffect"
/>
</div>
</div>
@ -153,7 +147,7 @@
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
[value]="summary?.netPerformanceWithCurrencyEffect"
[value]="netPerformanceWithCurrencyEffect"
/>
</div>
</div>
@ -166,8 +160,7 @@
) {
<abbr
class="d-none d-sm-inline-block initialism ml-2 text-muted"
i18n-title
title="Return on Average Investment (total return with dividends)"
title="Return on Average Investment"
>(ROAI)</abbr
>
}
@ -180,7 +173,7 @@
[isLoading]="isLoading"
[isPercent]="true"
[locale]="locale"
[value]="summary?.netPerformancePercentageWithCurrencyEffect"
[value]="netPerformancePercentageWithCurrencyEffect"
/>
</div>
</div>

30
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts

@ -137,6 +137,36 @@ export class GfPortfolioSummaryComponent implements OnChanges {
);
}
protected get netPerformancePercentageWithCurrencyEffect() {
if (
!this.user?.settings?.isExperimentalFeatures ||
!isNumber(this.summary?.netPerformancePercentageWithCurrencyEffect)
) {
return this.summary?.netPerformancePercentageWithCurrencyEffect;
}
// Include the dividends (total return)
return (
this.summary.netPerformancePercentageWithCurrencyEffect +
(this.summary.dividendPercentageWithCurrencyEffect ?? 0)
);
}
protected get netPerformanceWithCurrencyEffect() {
if (
!this.user?.settings?.isExperimentalFeatures ||
!isNumber(this.summary?.netPerformanceWithCurrencyEffect)
) {
return this.summary?.netPerformanceWithCurrencyEffect;
}
// Include the dividends (total return)
return (
this.summary.netPerformanceWithCurrencyEffect +
this.summary.dividendInBaseCurrency
);
}
public ngOnChanges() {
if (this.summary) {
if (

4
apps/client/src/app/pages/faq/overview/faq-overview-page.html

@ -52,8 +52,8 @@
calculation method based on the average amount of capital invested
over time. ROAI aims to provide a more insightful view of investment
performance than simpler approaches, especially when contributions are
made over time. Dividends are included in the calculation (total
return).</mat-card-content
made over time. Dividends are not part of the
calculation.</mat-card-content
>
</mat-card>
<mat-card appearance="outlined" class="mb-3">

30
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -133,6 +133,36 @@ export class GfAnalysisPageComponent implements OnInit {
addIcons({ copyOutline, ellipsisVertical });
}
protected get netPerformancePercentageWithCurrencyEffect() {
if (
!this.user?.settings?.isExperimentalFeatures ||
!isNumber(this.performance?.netPerformancePercentageWithCurrencyEffect)
) {
return this.performance?.netPerformancePercentageWithCurrencyEffect;
}
// Include the dividends (total return)
return (
this.performance.netPerformancePercentageWithCurrencyEffect +
(this.performance.dividendPercentageWithCurrencyEffect ?? 0)
);
}
protected get netPerformanceWithCurrencyEffect() {
if (
!this.user?.settings?.isExperimentalFeatures ||
!isNumber(this.performance?.netPerformanceWithCurrencyEffect)
) {
return this.performance?.netPerformanceWithCurrencyEffect;
}
// Include the dividends (total return)
return (
this.performance.netPerformanceWithCurrencyEffect +
this.performance.dividendInBaseCurrency
);
}
get savingsRate() {
if (!this.savingsRatePerMonth) {
return undefined;

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

@ -167,13 +167,7 @@
[isLoading]="isLoadingInvestmentChart"
[locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency"
[value]="
user?.settings?.isExperimentalFeatures &&
performance?.netPerformance !== null
? performance?.netPerformance -
performance?.dividendInBaseCurrency
: performance?.netPerformance
"
[value]="performance?.netPerformance"
/>
</div>
</div>
@ -189,12 +183,7 @@
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="
user?.settings?.isExperimentalFeatures
? performance?.netPerformancePercentage -
(performance?.dividendPercentage ?? 0)
: performance?.netPerformancePercentage
"
[value]="performance?.netPerformancePercentage"
/>
</div>
</div>
@ -232,7 +221,7 @@
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="performance?.dividendPercentage"
[value]="performance?.dividendPercentageWithCurrencyEffect"
/>
</div>
</div>
@ -297,7 +286,7 @@
[isLoading]="isLoadingInvestmentChart"
[locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency"
[value]="performance?.netPerformanceWithCurrencyEffect"
[value]="netPerformanceWithCurrencyEffect"
/>
</div>
</div>
@ -310,8 +299,7 @@
) {
<abbr
class="d-none d-sm-inline-block initialism ml-2 text-muted"
i18n-title
title="Return on Average Investment (total return with dividends)"
title="Return on Average Investment"
>(ROAI)</abbr
>
}
@ -324,9 +312,7 @@
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="
performance?.netPerformancePercentageWithCurrencyEffect
"
[value]="netPerformancePercentageWithCurrencyEffect"
/>
</div>
</div>

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

@ -2,7 +2,6 @@ export interface HistoricalDataItem {
averagePrice?: number;
date: string;
dividendInBaseCurrency?: number;
dividendInPercentage?: number;
dividendInPercentageWithCurrencyEffect?: number;
grossPerformancePercent?: number;
investmentValueWithCurrencyEffect?: number;

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

@ -3,7 +3,6 @@ export interface PortfolioPerformance {
currentNetWorth?: number;
currentValueInBaseCurrency: number;
dividendInBaseCurrency: number;
dividendPercentage?: number;
dividendPercentageWithCurrencyEffect?: number;
netPerformance: number;
netPerformancePercentage: number;

Loading…
Cancel
Save