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 - ✅ Create, update and delete transactions
- ✅ Multi account management - ✅ 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 - ✅ Various charts
- ✅ Static analysis to identify potential risks in your portfolio - ✅ Static analysis to identify potential risks in your portfolio
- ✅ Import and export transactions - ✅ 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 currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
const currentValues: { [date: string]: Big } = {}; const currentValues: { [date: string]: Big } = {};
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {}; const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};
let dividends = new Big(0);
let fees = new Big(0); let fees = new Big(0);
let feesAtStartDate = new Big(0); let feesAtStartDate = new Big(0);
let feesAtStartDateWithCurrencyEffect = 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 const newGrossPerformance = valueOfInvestment
.minus(totalInvestment) .minus(totalInvestment)
.plus(grossPerformanceFromSells); .plus(grossPerformanceFromSells)
.plus(dividends);
const newGrossPerformanceWithCurrencyEffect = const newGrossPerformanceWithCurrencyEffect =
valueOfInvestmentWithCurrencyEffect valueOfInvestmentWithCurrencyEffect
.minus(totalInvestmentWithCurrencyEffect) .minus(totalInvestmentWithCurrencyEffect)
.plus(grossPerformanceFromSellsWithCurrencyEffect); .plus(grossPerformanceFromSellsWithCurrencyEffect)
.plus(dividends);
grossPerformance = newGrossPerformance; 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' '0.001144362748184'
), ),
fee: new Big('19'), fee: new Big('19'),
grossPerformance: new Big('33.25'), grossPerformance: new Big('33.87'),
grossPerformancePercentage: new Big('0.11136043941322258691'), grossPerformancePercentage: new Big('0.11343693482483756447'),
grossPerformancePercentageWithCurrencyEffect: new Big( grossPerformancePercentageWithCurrencyEffect: new Big(
'0.11136043941322258691' '0.11343693482483756447'
), ),
grossPerformanceWithCurrencyEffect: new Big('33.25'), grossPerformanceWithCurrencyEffect: new Big('33.87'),
investment: new Big('298.58'), investment: new Big('298.58'),
investmentWithCurrencyEffect: new Big('298.58'), investmentWithCurrencyEffect: new Big('298.58'),
marketPrice: 331.83, marketPrice: 331.83,
marketPriceInBaseCurrency: 331.83, marketPriceInBaseCurrency: 331.83,
netPerformance: new Big('14.25'), netPerformance: new Big('14.87'),
netPerformancePercentage: new Big('0.04772590260566682296'), netPerformancePercentage: new Big('0.04980239801728180052'),
netPerformancePercentageWithCurrencyEffectMap: { netPerformancePercentageWithCurrencyEffectMap: {
max: new Big('0.04772590260566682296') max: new Big('0.04980239801728180052')
}, },
netPerformanceWithCurrencyEffectMap: { netPerformanceWithCurrencyEffectMap: {
'1d': new Big('-5.39'), '1d': new Big('-5.39'),
'5y': new Big('14.25'), '5y': new Big('14.87'),
max: new Big('14.25'), max: new Big('14.87'),
wtd: new Big('-5.39') wtd: new Big('-5.39')
}, },
quantity: new Big('1'), quantity: new Big('1'),
@ -200,10 +200,10 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10', date: '2023-07-10',
dividendInBaseCurrency: 0, dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0, dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -5.390000000000001, netPerformance: -5.390000000000002,
netPerformanceInPercentage: -0.015983630864124312, netPerformanceInPercentage: -0.015983630864124316,
netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124312, netPerformanceInPercentageWithCurrencyEffect: -0.015983630864124316,
netPerformanceWithCurrencyEffect: -5.390000000000001, netPerformanceWithCurrencyEffect: -5.390000000000002,
totalInvestmentValueWithCurrencyEffect: 298.58, totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83 valueWithCurrencyEffect: 331.83
}, },
@ -211,10 +211,10 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10', date: '2023-07-10',
dividendInBaseCurrency: 0.62, dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0.002076495411614967, dividendInPercentageWithCurrencyEffect: 0.002076495411614967,
netPerformance: 14.25, netPerformance: 14.87,
netPerformanceInPercentage: 0.04772590260566659, netPerformanceInPercentage: 0.049802398017281556,
netPerformanceInPercentageWithCurrencyEffect: 0.04772590260566659, netPerformanceInPercentageWithCurrencyEffect: 0.049802398017281556,
netPerformanceWithCurrencyEffect: 14.25, netPerformanceWithCurrencyEffect: 14.87,
totalInvestmentValueWithCurrencyEffect: 298.58, totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83 valueWithCurrencyEffect: 331.83
}, },
@ -222,10 +222,10 @@ describe('PortfolioCalculator', () => {
date: '2023-07-10', date: '2023-07-10',
dividendInBaseCurrency: 0, dividendInBaseCurrency: 0,
dividendInPercentageWithCurrencyEffect: 0, dividendInPercentageWithCurrencyEffect: 0,
netPerformance: -7.68, netPerformance: -7.6800000000000015,
netPerformanceInPercentage: -0.022620835910577012, netPerformanceInPercentage: -0.022620835910577015,
netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577012, netPerformanceInPercentageWithCurrencyEffect: -0.022620835910577015,
netPerformanceWithCurrencyEffect: -7.68, netPerformanceWithCurrencyEffect: -7.6800000000000015,
totalInvestmentValueWithCurrencyEffect: 298.58, totalInvestmentValueWithCurrencyEffect: 298.58,
valueWithCurrencyEffect: 331.83 valueWithCurrencyEffect: 331.83
} }

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

@ -98,25 +98,20 @@
/> />
</div> </div>
</div> </div>
@if (user?.settings?.isExperimentalFeatures) { <div class="flex-nowrap px-3 py-1 row text-muted">
<div class="flex-nowrap px-3 py-1 row"> <div class="flex-grow-1 indent-1 text-truncate" i18n>thereof Dividend</div>
<div class="flex-grow-1 text-truncate" i18n>Dividend</div> <div class="justify-content-end">
<div class="d-flex justify-content-end"> <gf-value
@if (isNumber(summary?.dividendInBaseCurrency)) { class="justify-content-end"
<span class="mr-1">+</span> [isCurrency]="true"
} [isLoading]="isLoading"
<gf-value [locale]="locale"
class="justify-content-end" [precision]="precision"
[isCurrency]="true" [unit]="displayedCurrency"
[isLoading]="isLoading" [value]="summary?.dividendInBaseCurrency"
[locale]="locale" />
[precision]="precision"
[unit]="displayedCurrency"
[value]="summary?.dividendInBaseCurrency"
/>
</div>
</div> </div>
} </div>
<div class="flex-nowrap px-3 py-1 row"> <div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate" i18n>Fees</div> <div class="flex-grow-1 text-truncate" i18n>Fees</div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
@ -147,7 +142,7 @@
[locale]="locale" [locale]="locale"
[precision]="precision" [precision]="precision"
[unit]="displayedCurrency" [unit]="displayedCurrency"
[value]="netPerformanceWithCurrencyEffect" [value]="summary?.netPerformanceWithCurrencyEffect"
/> />
</div> </div>
</div> </div>
@ -160,7 +155,8 @@
) { ) {
<abbr <abbr
class="d-none d-sm-inline-block initialism ml-2 text-muted" 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 >(ROAI)</abbr
> >
} }
@ -173,7 +169,7 @@
[isLoading]="isLoading" [isLoading]="isLoading"
[isPercent]="true" [isPercent]="true"
[locale]="locale" [locale]="locale"
[value]="netPerformancePercentageWithCurrencyEffect" [value]="summary?.netPerformancePercentageWithCurrencyEffect"
/> />
</div> </div>
</div> </div>
@ -506,20 +502,4 @@
/> />
</div> </div>
</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> </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() { public ngOnChanges() {
if (this.summary) { if (this.summary) {
if ( 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 calculation method based on the average amount of capital invested
over time. ROAI aims to provide a more insightful view of investment over time. ROAI aims to provide a more insightful view of investment
performance than simpler approaches, especially when contributions are performance than simpler approaches, especially when contributions are
made over time. Dividends are not part of the made over time. Dividends are included in the calculation (total
calculation.</mat-card-content return).</mat-card-content
> >
</mat-card> </mat-card>
<mat-card appearance="outlined" class="mb-3"> <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 }); 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() { get savingsRate() {
if (!this.savingsRatePerMonth) { if (!this.savingsRatePerMonth) {
return undefined; return undefined;

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

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

Loading…
Cancel
Save