Browse Source

Merge 08dfdb8507 into 82cee20f2d

pull/7961/merge
Thomas Kaul 22 hours ago
committed by GitHub
parent
commit
db305f482b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 17
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  3. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  4. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  5. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  6. 44
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  7. 3
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  8. 11
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  9. 4
      apps/api/src/app/portfolio/portfolio.service.ts
  10. 1
      apps/api/src/app/portfolio/types/performance-percentages.type.ts
  11. 39
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
  12. 1
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
  13. 4
      apps/client/src/app/pages/faq/overview/faq-overview-page.html
  14. 97
      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

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Changed the performance calculation to include the dividends (total return)
## 3.74.0 - 2026-09-27
### Added

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

@ -946,6 +946,7 @@ 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);
@ -1123,6 +1124,16 @@ 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))
);
@ -1190,12 +1201,14 @@ export abstract class PortfolioCalculator {
const newGrossPerformance = valueOfInvestment
.minus(totalInvestment)
.plus(grossPerformanceFromSells);
.plus(grossPerformanceFromSells)
.plus(dividends);
const newGrossPerformanceWithCurrencyEffect =
valueOfInvestmentWithCurrencyEffect
.minus(totalInvestmentWithCurrencyEffect)
.plus(grossPerformanceFromSellsWithCurrencyEffect);
.plus(grossPerformanceFromSellsWithCurrencyEffect)
.plus(dividends);
grossPerformance = newGrossPerformance;

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

@ -143,6 +143,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -163,6 +164,7 @@ 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
@ -184,6 +186,7 @@ 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,6 +143,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -163,6 +164,7 @@ 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
@ -184,6 +186,7 @@ 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,6 +322,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -462,6 +463,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -606,6 +608,7 @@ 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.25'),
grossPerformancePercentage: new Big('0.11136043941322258691'),
grossPerformance: new Big('33.87'),
grossPerformancePercentage: new Big('0.11343693482483756447'),
grossPerformancePercentageWithCurrencyEffect: new Big(
'0.11136043941322258691'
'0.11343693482483756447'
),
grossPerformanceWithCurrencyEffect: new Big('33.25'),
grossPerformanceWithCurrencyEffect: new Big('33.87'),
investment: new Big('298.58'),
investmentWithCurrencyEffect: new Big('298.58'),
marketPrice: 331.83,
marketPriceInBaseCurrency: 331.83,
netPerformance: new Big('14.25'),
netPerformancePercentage: new Big('0.04772590260566682296'),
netPerformance: new Big('14.87'),
netPerformancePercentage: new Big('0.04980239801728180052'),
netPerformancePercentageWithCurrencyEffectMap: {
max: new Big('0.04772590260566682296')
max: new Big('0.04980239801728180052')
},
netPerformanceWithCurrencyEffectMap: {
'1d': new Big('-5.39'),
'5y': new Big('14.25'),
max: new Big('14.25'),
'5y': new Big('14.87'),
max: new Big('14.87'),
wtd: new Big('-5.39')
},
quantity: new Big('1'),
@ -199,11 +199,12 @@ describe('PortfolioCalculator', () => {
'1d': {
date: '2023-07-10',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -5.390000000000001,
netPerformanceInPercentage: -0.015983630864124312,
netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124312,
netPerformanceWithCurrencyEffect: -5.390000000000001,
netPerformance: -5.390000000000002,
netPerformanceInPercentage: -0.015983630864124316,
netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124316,
netPerformanceWithCurrencyEffect: -5.390000000000002,
totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83
},
@ -211,21 +212,22 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10',
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0.002076495411614967,
netPerformance: 14.25,
netPerformanceInPercentage: 0.04772590260566659,
netPerformanceInPercentageWithCurrencyEffect: 0.04772590260566659,
netPerformanceWithCurrencyEffect: 14.25,
netPerformance: 14.87,
netPerformanceInPercentage: 0.049802398017281556,
netPerformanceInPercentageWithCurrencyEffect: 0.049802398017281556,
netPerformanceWithCurrencyEffect: 14.87,
totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83
},
ytd: {
date: '2023-07-10',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -7.68,
netPerformanceInPercentage: -0.022620835910577012,
netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577012,
netPerformanceWithCurrencyEffect: -7.68,
netPerformance: -7.6800000000000015,
netPerformanceInPercentage: -0.022620835910577015,
netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577015,
netPerformanceWithCurrencyEffect: -7.6800000000000015,
totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83
}

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

@ -140,6 +140,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2022-03-06',
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
investmentValueWithCurrencyEffect: 0,
netPerformance: 0,
@ -161,6 +162,7 @@ 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
@ -182,6 +184,7 @@ 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,6 +192,13 @@ 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)
@ -275,6 +282,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
: 0;
performancePercentagesByDate[historicalDataItem.date] = {
dividendInPercentage:
!hasDividendWithoutAverageInvestment && averageInvestmentValue > 0
? historicalDataItem.dividendInBaseCurrency / averageInvestmentValue
: 0,
dividendInPercentageWithCurrencyEffect:
!hasDividendWithoutAverageInvestment &&
averageInvestmentValueWithCurrencyEffect > 0

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

@ -1222,6 +1222,7 @@ export class PortfolioService {
currentNetWorth: 0,
currentValueInBaseCurrency: 0,
dividendInBaseCurrency: 0,
dividendPercentage: 0,
dividendPercentageWithCurrencyEffect: 0,
netPerformance: 0,
netPerformancePercentage: 0,
@ -1255,6 +1256,7 @@ export class PortfolioService {
const {
dividendInBaseCurrency,
dividendInPercentage,
dividendInPercentageWithCurrencyEffect,
netPerformance,
netPerformanceInPercentage,
@ -1266,6 +1268,7 @@ export class PortfolioService {
valueWithCurrencyEffect
} = chart?.at(-1) ?? {
dividendInBaseCurrency: 0,
dividendInPercentage: 0,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: 0,
netPerformanceInPercentage: 0,
@ -1289,6 +1292,7 @@ export class PortfolioService {
totalInvestmentValueWithCurrencyEffect,
currentNetWorth: netWorth,
currentValueInBaseCurrency: valueWithCurrencyEffect,
dividendPercentage: dividendInPercentage,
dividendPercentageWithCurrencyEffect:
dividendInPercentageWithCurrencyEffect,
netPerformancePercentage: netPerformanceInPercentage,

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<
Pick<
HistoricalDataItem,
| 'dividendInPercentage'
| 'dividendInPercentageWithCurrencyEffect'
| 'netPerformanceInPercentage'
| 'netPerformanceInPercentageWithCurrencyEffect'

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

@ -94,14 +94,39 @@
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
[value]="summary?.grossPerformanceWithCurrencyEffect"
[value]="
user?.settings?.isExperimentalFeatures &&
summary?.grossPerformanceWithCurrencyEffect !== null
? summary?.grossPerformanceWithCurrencyEffect -
summary?.dividendInBaseCurrency
: summary?.grossPerformanceWithCurrencyEffect
"
/>
</div>
</div>
@if (user?.settings?.isExperimentalFeatures) {
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Dividend</div>
<div class="d-flex justify-content-end">
@if (isNumber(summary?.dividendInBaseCurrency)) {
<span class="mr-1">+</span>
}
<gf-value
class="justify-content-end"
[isCurrency]="true"
[isLoading]="isLoading"
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
[value]="summary?.dividendInBaseCurrency"
/>
</div>
</div>
}
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Fees</div>
<div class="d-flex justify-content-end">
@if (summary?.fees || summary?.fees === 0) {
@if (isNumber(summary?.fees)) {
<span class="mr-1">-</span>
}
<gf-value
@ -141,7 +166,8 @@
) {
<abbr
class="d-none d-sm-inline-block initialism ml-2 text-muted"
title="Return on Average Investment"
i18n-title
title="Return on Average Investment (total return with dividends)"
>(ROAI)</abbr
>
}
@ -367,10 +393,7 @@
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Liabilities</div>
<div class="d-flex justify-content-end">
@if (
summary?.liabilitiesInBaseCurrency ||
summary?.liabilitiesInBaseCurrency === 0
) {
@if (isNumber(summary?.liabilitiesInBaseCurrency)) {
<span class="mr-1">-</span>
}
<gf-value
@ -488,6 +511,7 @@
/>
</div>
</div>
@if (!user?.settings?.isExperimentalFeatures) {
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Dividend</div>
<div class="justify-content-end">
@ -502,4 +526,5 @@
/>
</div>
</div>
}
</div>

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

@ -55,6 +55,7 @@ export class GfPortfolioSummaryComponent implements OnChanges {
'BUY_AND_SELL_ACTIVITIES_TOOLTIP'
);
protected readonly isNumber = isNumber;
protected readonly PerformanceCalculationType = PerformanceCalculationType;
protected isCashExpanded = false;

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 not part of the
calculation.</mat-card-content
made over time. Dividends are included in the calculation (total
return).</mat-card-content
>
</mat-card>
<mat-card appearance="outlined" class="mb-3">

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

@ -167,7 +167,13 @@
[isLoading]="isLoadingInvestmentChart"
[locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency"
[value]="performance?.netPerformance"
[value]="
user?.settings?.isExperimentalFeatures &&
performance?.netPerformance !== null
? performance?.netPerformance -
performance?.dividendInBaseCurrency
: performance?.netPerformance
"
/>
</div>
</div>
@ -183,10 +189,54 @@
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="performance?.netPerformancePercentage"
[value]="
user?.settings?.isExperimentalFeatures
? performance?.netPerformancePercentage -
(performance?.dividendPercentage ?? 0)
: performance?.netPerformancePercentage
"
/>
</div>
</div>
@if (user?.settings?.isExperimentalFeatures) {
<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>
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="ml-1" />
}
</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 mb-3 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?.dividendPercentage"
/>
</div>
</div>
}
<div class="d-flex py-1">
<div
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
@ -260,7 +310,8 @@
) {
<abbr
class="d-none d-sm-inline-block initialism ml-2 text-muted"
title="Return on Average Investment"
i18n-title
title="Return on Average Investment (total return with dividends)"
>(ROAI)</abbr
>
}
@ -279,46 +330,6 @@
/>
</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>
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="ml-1" />
}
</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>
</div>

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

@ -2,6 +2,7 @@ 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,6 +3,7 @@ export interface PortfolioPerformance {
currentNetWorth?: number;
currentValueInBaseCurrency: number;
dividendInBaseCurrency: number;
dividendPercentage?: number;
dividendPercentageWithCurrencyEffect?: number;
netPerformance: number;
netPerformancePercentage: number;

Loading…
Cancel
Save