Browse Source

Include dividend of holdings without average investment in dividend percentage

pull/7990/head
Thomas Kaul 1 week ago
parent
commit
c88592ec53
  1. 14
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 12
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts
  3. 38
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

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

@ -223,19 +223,15 @@ export abstract class PortfolioCalculator {
): PortfolioSnapshot; ): PortfolioSnapshot;
protected abstract calculatePerformancePercentages({ protected abstract calculatePerformancePercentages({
accumulatedValuesByDate, accumulatedValuesByDate
holdings
}: { }: {
accumulatedValuesByDate: { [date: string]: AccumulatedValues }; accumulatedValuesByDate: { [date: string]: AccumulatedValues };
holdings: PortfolioSnapshotHolding[];
}): { [date: string]: PerformancePercentages }; }): { [date: string]: PerformancePercentages };
protected abstract calculatePerformancePercentagesForDateRange({ protected abstract calculatePerformancePercentagesForDateRange({
historicalDataItems, historicalDataItems
holdings
}: { }: {
historicalDataItems: HistoricalDataItem[]; historicalDataItems: HistoricalDataItem[];
holdings: PortfolioSnapshotHolding[];
}): { [date: string]: PerformancePercentages }; }): { [date: string]: PerformancePercentages };
@LogPerformance @LogPerformance
@ -680,8 +676,7 @@ export abstract class PortfolioCalculator {
}); });
const performancePercentagesByDate = this.calculatePerformancePercentages({ const performancePercentagesByDate = this.calculatePerformancePercentages({
accumulatedValuesByDate, accumulatedValuesByDate
holdings: positionsIncludedInHoldings
}); });
const historicalData: HistoricalDataItem[] = Object.entries( const historicalData: HistoricalDataItem[] = Object.entries(
@ -1443,8 +1438,7 @@ export abstract class PortfolioCalculator {
const performancePercentagesByDate = const performancePercentagesByDate =
this.calculatePerformancePercentagesForDateRange({ this.calculatePerformancePercentagesForDateRange({
historicalDataItems: historicalDataItemsOfDateRange, historicalDataItems: historicalDataItemsOfDateRange
holdings: this.snapshot.positions
}); });
const chart = historicalDataItemsOfDateRange.map((historicalDataItem) => { const chart = historicalDataItemsOfDateRange.map((historicalDataItem) => {

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

@ -234,8 +234,8 @@ describe('PortfolioCalculator', () => {
unitPriceInAssetProfileCurrency: 0.62 unitPriceInAssetProfileCurrency: 0.62
}, },
{ {
// The holding has a dividend, but no average investment. Thus the // The holding has a dividend, but no average investment. Its dividend
// dividend percentage stays 0, like the dividend yield // is part of the net performance, and thus of the dividend percentage
...activityDummyData, ...activityDummyData,
assetProfile: { assetProfile: {
...assetProfileDummyData, ...assetProfileDummyData,
@ -274,7 +274,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({ expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
dividendInBaseCurrency: 5.62, dividendInBaseCurrency: 5.62,
dividendInPercentageWithCurrencyEffect: 0, dividendInPercentageWithCurrencyEffect: 0.01882242615044544,
netPerformance: 19.87, netPerformance: 19.87,
netPerformanceInPercentage: 0.06654832875611226 netPerformanceInPercentage: 0.06654832875611226
}); });
@ -282,7 +282,7 @@ describe('PortfolioCalculator', () => {
expect(performanceByDateRange).toMatchObject({ expect(performanceByDateRange).toMatchObject({
max: { max: {
dividendInBaseCurrency: 5.62, dividendInBaseCurrency: 5.62,
dividendInPercentageWithCurrencyEffect: 0, dividendInPercentageWithCurrencyEffect: 0.01882242615044535,
netPerformance: 19.87, netPerformance: 19.87,
netPerformanceInPercentage: 0.06654832875611194 netPerformanceInPercentage: 0.06654832875611194
} }
@ -365,7 +365,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({ expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
dividendInBaseCurrency: 0.62, dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0, dividendInPercentageWithCurrencyEffect: 0.0020764954116149776,
netPerformance: 14.87, netPerformance: 14.87,
netPerformanceInPercentage: 0.0498023980172818 netPerformanceInPercentage: 0.0498023980172818
}); });
@ -373,7 +373,7 @@ describe('PortfolioCalculator', () => {
expect(performanceByDateRange).toMatchObject({ expect(performanceByDateRange).toMatchObject({
max: { max: {
dividendInBaseCurrency: 0.62, dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0, dividendInPercentageWithCurrencyEffect: 0.002076495411614967,
netPerformance: 14.87, netPerformance: 14.87,
netPerformanceInPercentage: 0.049802398017281556 netPerformanceInPercentage: 0.049802398017281556
} }

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

@ -17,10 +17,7 @@ import {
AssetProfileIdentifier, AssetProfileIdentifier,
HistoricalDataItem HistoricalDataItem
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { import { PortfolioSnapshot } from '@ghostfolio/common/models';
PortfolioSnapshot,
PortfolioSnapshotHolding
} from '@ghostfolio/common/models';
import { DateRange } from '@ghostfolio/common/types'; import { DateRange } from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -168,15 +165,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
} }
protected calculatePerformancePercentages({ protected calculatePerformancePercentages({
accumulatedValuesByDate, accumulatedValuesByDate
holdings
}: { }: {
accumulatedValuesByDate: { [date: string]: AccumulatedValues }; accumulatedValuesByDate: { [date: string]: AccumulatedValues };
holdings: PortfolioSnapshotHolding[];
}): { [date: string]: PerformancePercentages } { }): { [date: string]: PerformancePercentages } {
const hasDividendWithoutAverageInvestment =
this.hasDividendWithoutAverageInvestment(holdings);
const performancePercentagesByDate: { const performancePercentagesByDate: {
[date: string]: PerformancePercentages; [date: string]: PerformancePercentages;
} = {}; } = {};
@ -193,7 +185,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
] of Object.entries(accumulatedValuesByDate)) { ] of Object.entries(accumulatedValuesByDate)) {
performancePercentagesByDate[date] = { performancePercentagesByDate[date] = {
dividendInPercentageWithCurrencyEffect: dividendInPercentageWithCurrencyEffect:
hasDividendWithoutAverageInvestment ||
totalAverageInvestmentValueWithCurrencyEffect.eq(0) totalAverageInvestmentValueWithCurrencyEffect.eq(0)
? 0 ? 0
: totalDividendValueWithCurrencyEffect : totalDividendValueWithCurrencyEffect
@ -217,15 +208,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
} }
protected calculatePerformancePercentagesForDateRange({ protected calculatePerformancePercentagesForDateRange({
historicalDataItems, historicalDataItems
holdings
}: { }: {
historicalDataItems: HistoricalDataItem[]; historicalDataItems: HistoricalDataItem[];
holdings: PortfolioSnapshotHolding[];
}): { [date: string]: PerformancePercentages } { }): { [date: string]: PerformancePercentages } {
const hasDividendWithoutAverageInvestment =
this.hasDividendWithoutAverageInvestment(holdings);
const averageInvestmentValues: number[] = []; const averageInvestmentValues: number[] = [];
const averageInvestmentValuesWithCurrencyEffect: number[] = []; const averageInvestmentValuesWithCurrencyEffect: number[] = [];
let grossPerformanceAtStartDate: number; let grossPerformanceAtStartDate: number;
@ -276,7 +262,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
performancePercentagesByDate[historicalDataItem.date] = { performancePercentagesByDate[historicalDataItem.date] = {
dividendInPercentageWithCurrencyEffect: dividendInPercentageWithCurrencyEffect:
!hasDividendWithoutAverageInvestment &&
averageInvestmentValueWithCurrencyEffect > 0 averageInvestmentValueWithCurrencyEffect > 0
? historicalDataItem.dividendInBaseCurrency / ? historicalDataItem.dividendInBaseCurrency /
averageInvestmentValueWithCurrencyEffect averageInvestmentValueWithCurrencyEffect
@ -770,21 +755,4 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
protected getPerformanceCalculationType() { protected getPerformanceCalculationType() {
return PerformanceCalculationType.ROAI; return PerformanceCalculationType.ROAI;
} }
private hasDividendWithoutAverageInvestment(
holdings: PortfolioSnapshotHolding[]
) {
// A holding with a dividend only (without a buy activity), and a holding
// without a market price, gives a dividend but no average investment.
// Such a holding makes the dividend percentage too high. Therefore the
// dividend percentage stays 0 in this case, like the dividend yield.
return holdings.some(
({ averageInvestmentWithCurrencyEffect, dividendInBaseCurrency }) => {
return (
!dividendInBaseCurrency.eq(0) &&
averageInvestmentWithCurrencyEffect.eq(0)
);
}
);
}
} }

Loading…
Cancel
Save