Browse Source

Move total return from experimental to general availability

pull/7990/head
Thomas Kaul 2 weeks ago
parent
commit
8e3418481b
  1. 2
      README.md
  2. 17
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  3. 42
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  4. 54
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
  5. 30
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
  6. 4
      apps/client/src/app/pages/faq/overview/faq-overview-page.html
  7. 30
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  8. 82
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html

2
README.md

@ -47,7 +47,7 @@ Ghostfolio is for you if you are...
- ✅ Create, update and delete transactions
- ✅ Multi account management
- ✅ Portfolio performance: Return on Average Investment (ROAI) for `Today`, `WTD`, `MTD`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Portfolio performance: Return on Average Investment (ROAI) including dividends (total return) for `Today`, `WTD`, `MTD`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Various charts
- ✅ Static analysis to identify potential risks in your portfolio
- ✅ Import and export transactions

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);
@ -1188,14 +1189,26 @@ export abstract class PortfolioCalculator {
);
}
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)
);
}
const newGrossPerformance = valueOfInvestment
.minus(totalInvestment)
.plus(grossPerformanceFromSells);
.plus(grossPerformanceFromSells)
.plus(dividends);
const newGrossPerformanceWithCurrencyEffect =
valueOfInvestmentWithCurrencyEffect
.minus(totalInvestmentWithCurrencyEffect)
.plus(grossPerformanceFromSellsWithCurrencyEffect);
.plus(grossPerformanceFromSellsWithCurrencyEffect)
.plus(dividends);
grossPerformance = newGrossPerformance;

42
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'),
@ -200,10 +200,10 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10',
dividendInBaseCurrency: 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,10 +211,10 @@ 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
},
@ -222,10 +222,10 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10',
dividendInBaseCurrency: 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
}

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

@ -98,25 +98,20 @@
/>
</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 class="flex-nowrap px-3 py-1 row text-muted">
<div class="flex-grow-1 indent-1 text-truncate" i18n>thereof Dividend</div>
<div class="justify-content-end">
<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">
@ -147,7 +142,7 @@
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
[value]="netPerformanceWithCurrencyEffect"
[value]="summary?.netPerformanceWithCurrencyEffect"
/>
</div>
</div>
@ -160,7 +155,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
>
}
@ -173,7 +169,7 @@
[isLoading]="isLoading"
[isPercent]="true"
[locale]="locale"
[value]="netPerformancePercentageWithCurrencyEffect"
[value]="summary?.netPerformancePercentageWithCurrencyEffect"
/>
</div>
</div>
@ -506,20 +502,4 @@
/>
</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">
<gf-value
class="justify-content-end"
[isCurrency]="true"
[isLoading]="isLoading"
[locale]="locale"
[precision]="precision"
[unit]="displayedCurrency"
[value]="summary?.dividendInBaseCurrency"
/>
</div>
</div>
}
</div>

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

@ -137,36 +137,6 @@ 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 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">

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

@ -133,36 +133,6 @@ 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;

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

@ -171,7 +171,7 @@
/>
</div>
</div>
<div class="d-flex mb-3 ml-3 py-1">
<div class="d-flex ml-3 py-1">
<div class="flex-grow-1 mr-2 text-truncate">
<ng-container i18n>Asset Performance</ng-container>
</div>
@ -187,45 +187,42 @@
/>
</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 class="d-flex ml-3 py-1 text-muted">
<div
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
>
<span i18n>thereof Dividend</span>
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="ml-1" />
}
</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?.dividendPercentageWithCurrencyEffect"
/>
</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 text-muted">
<div class="flex-grow-1 mr-2 text-truncate">
<ng-container i18n>thereof Dividend Performance</ng-container>
</div>
<div class="d-flex justify-content-end">
<gf-value
class="justify-content-end"
position="end"
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="performance?.dividendPercentageWithCurrencyEffect"
/>
</div>
</div>
<div class="d-flex py-1">
<div
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
@ -286,7 +283,7 @@
[isLoading]="isLoadingInvestmentChart"
[locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency"
[value]="netPerformanceWithCurrencyEffect"
[value]="performance?.netPerformanceWithCurrencyEffect"
/>
</div>
</div>
@ -299,7 +296,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
>
}
@ -312,7 +310,9 @@
[isLoading]="isLoadingInvestmentChart"
[isPercent]="true"
[locale]="user?.settings?.locale"
[value]="netPerformancePercentageWithCurrencyEffect"
[value]="
performance?.netPerformancePercentageWithCurrencyEffect
"
/>
</div>
</div>

Loading…
Cancel
Save