Browse Source

Move total return from experimental to general availability

pull/7990/head
Thomas Kaul 2 weeks ago
parent
commit
01c8d667a2
  1. 123
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 166
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts
  3. 22
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts
  4. 2
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  5. 3
      apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts
  6. 1
      apps/api/src/app/portfolio/interfaces/holding-valuation.interface.ts
  7. 1
      apps/api/src/app/portfolio/portfolio.service.spec.ts
  8. 6
      apps/api/src/app/portfolio/portfolio.service.ts
  9. 5
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  10. 74
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html

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

@ -371,6 +371,7 @@ export abstract class PortfolioCalculator {
averageInvestmentValuesWithCurrencyEffect: { [date: string]: Big };
currentValues: { [date: string]: Big };
currentValuesWithCurrencyEffect: { [date: string]: Big };
dividendValuesWithCurrencyEffect: { [date: string]: Big };
investmentValuesAccumulated: { [date: string]: Big };
investmentValuesAccumulatedWithCurrencyEffect: { [date: string]: Big };
investmentValuesWithCurrencyEffect: { [date: string]: Big };
@ -406,6 +407,7 @@ export abstract class PortfolioCalculator {
averageInvestmentWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
dividendYieldPercent,
dividendYieldPercentWithCurrencyEffect,
grossPerformance,
@ -453,6 +455,7 @@ export abstract class PortfolioCalculator {
averageInvestmentValuesWithCurrencyEffect: {},
currentValues: {},
currentValuesWithCurrencyEffect: {},
dividendValuesWithCurrencyEffect: {},
investmentValuesAccumulated: {},
investmentValuesAccumulatedWithCurrencyEffect: {},
investmentValuesWithCurrencyEffect: {},
@ -465,6 +468,7 @@ export abstract class PortfolioCalculator {
averageInvestmentValuesWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
investmentValuesWithCurrencyEffect,
@ -548,13 +552,6 @@ export abstract class PortfolioCalculator {
}
}
const totalDividendValueWithCurrencyEffectByDate =
this.getTotalDividendValueWithCurrencyEffectByDate({
chartDates,
exchangeRatesByCurrency,
holdings: positions
});
const assetProfileIdentifiers = Object.keys(valuesByAssetProfileIdentifier);
for (const dateString of chartDates) {
@ -569,6 +566,10 @@ export abstract class PortfolioCalculator {
assetProfileValues.currentValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const dividendValueWithCurrencyEffect =
assetProfileValues.dividendValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const investmentValueAccumulated =
assetProfileValues.investmentValuesAccumulated?.[dateString] ??
new Big(0);
@ -631,8 +632,10 @@ export abstract class PortfolioCalculator {
accumulatedValuesByDate[dateString]
?.totalCurrentValueWithCurrencyEffect ?? new Big(0)
).add(currentValueWithCurrencyEffect),
totalDividendValueWithCurrencyEffect:
totalDividendValueWithCurrencyEffectByDate[dateString],
totalDividendValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalDividendValueWithCurrencyEffect ?? new Big(0)
).add(dividendValueWithCurrencyEffect),
totalInvestmentValue: (
accumulatedValuesByDate[dateString]?.totalInvestmentValue ??
new Big(0)
@ -870,12 +873,6 @@ export abstract class PortfolioCalculator {
return this.dataProviderInfos;
}
public async getDividendInBaseCurrency() {
await this.snapshotPromise;
return this.getDividendInBaseCurrencyOfHoldings(this.snapshot.positions);
}
protected getDividendInBaseCurrencyOfHoldings(
holdings: PortfolioSnapshotHolding[]
) {
@ -894,6 +891,7 @@ export abstract class PortfolioCalculator {
averageInvestmentWithCurrencyEffect: new Big(0),
currentValues: {},
currentValuesWithCurrencyEffect: {},
dividendValuesWithCurrencyEffect: {},
dividendYieldPercent: new Big(0),
dividendYieldPercentWithCurrencyEffect: new Big(0),
grossPerformance: new Big(0),
@ -959,7 +957,10 @@ export abstract class PortfolioCalculator {
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
const currentValues: { [date: string]: Big } = {};
const currentValuesWithCurrencyEffect: { [date: string]: Big } = {};
const dividendValuesWithCurrencyEffect: { [date: string]: Big } = {};
let dividends = new Big(0);
let dividendsAtStartDateWithCurrencyEffect = new Big(0);
let dividendsWithCurrencyEffect = new Big(0);
let fees = new Big(0);
let feesAtStartDate = new Big(0);
let feesAtStartDateWithCurrencyEffect = new Big(0);
@ -1203,12 +1204,12 @@ 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 dividend = activity.quantity.mul(activity.unitPrice);
dividends = dividends.plus(dividend.mul(currentExchangeRate ?? 1));
dividendsWithCurrencyEffect = dividendsWithCurrencyEffect.plus(
dividend.mul(exchangeRateAtActivityDate ?? 1)
);
}
@ -1221,7 +1222,7 @@ export abstract class PortfolioCalculator {
valueOfInvestmentWithCurrencyEffect
.minus(totalInvestmentWithCurrencyEffect)
.plus(grossPerformanceFromSellsWithCurrencyEffect)
.plus(dividends);
.plus(dividendsWithCurrencyEffect);
grossPerformance = newGrossPerformance;
@ -1229,6 +1230,7 @@ export abstract class PortfolioCalculator {
newGrossPerformanceWithCurrencyEffect;
if (activity.itemType === 'start') {
dividendsAtStartDateWithCurrencyEffect = dividendsWithCurrencyEffect;
feesAtStartDate = fees;
feesAtStartDateWithCurrencyEffect = feesWithCurrencyEffect;
grossPerformanceAtStartDate = grossPerformance;
@ -1243,6 +1245,11 @@ export abstract class PortfolioCalculator {
currentValuesWithCurrencyEffect[activity.date] =
valueOfInvestmentWithCurrencyEffect;
dividendValuesWithCurrencyEffect[activity.date] =
dividendsWithCurrencyEffect.minus(
dividendsAtStartDateWithCurrencyEffect
);
netPerformanceValues[activity.date] = grossPerformance
.minus(grossPerformanceAtStartDate)
.minus(fees.minus(feesAtStartDate));
@ -1316,6 +1323,7 @@ export abstract class PortfolioCalculator {
return {
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
initialValue,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
@ -1788,77 +1796,6 @@ export abstract class PortfolioCalculator {
return chartDateMap;
}
private getTotalDividendValueWithCurrencyEffectByDate({
chartDates,
exchangeRatesByCurrency,
holdings
}: {
chartDates: string[];
exchangeRatesByCurrency: {
[currencyPair: string]: { [dateString: string]: number };
};
holdings: PortfolioCalculatorHolding[];
}): { [date: string]: Big } {
// Take the dividend from the same holdings as the portfolio summary, so
// that the response shows one dividend only
const assetProfileIdentifiersIncludedInHoldings = new Set(
holdings
.filter(({ includeInHoldings }) => {
return includeInHoldings;
})
.map(({ dataSource, symbol }) => {
return getAssetProfileIdentifier({ dataSource, symbol });
})
);
const dividendActivities = this.activities.filter(
({ assetProfile, type }) => {
return (
type === 'DIVIDEND' &&
assetProfileIdentifiersIncludedInHoldings.has(
getAssetProfileIdentifier(assetProfile)
)
);
}
);
const totalDividendValueWithCurrencyEffectByDate: { [date: string]: Big } =
{};
let index = 0;
let totalDividendValueWithCurrencyEffect = new Big(0);
// The activities and the chart dates are sorted by date, so one pass over
// both gives the dividends received up to each chart date
for (const chartDate of chartDates) {
while (
index < dividendActivities.length &&
dividendActivities[index].date <= chartDate
) {
const { assetProfile, date, quantity, unitPrice } =
dividendActivities[index];
totalDividendValueWithCurrencyEffect =
totalDividendValueWithCurrencyEffect.plus(
quantity
.mul(unitPrice)
.mul(
exchangeRatesByCurrency[
`${assetProfile.currency}${this.currency}`
]?.[date] ?? 1
)
);
index++;
}
totalDividendValueWithCurrencyEffectByDate[chartDate] =
totalDividendValueWithCurrencyEffect;
}
return totalDividendValueWithCurrencyEffectByDate;
}
@LogPerformance
private async initialize(attempt = 1) {
const startTimeTotal = performance.now();

166
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts

@ -0,0 +1,166 @@
import {
activityDummyData,
assetProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
CurrentRateService: jest.fn().mockImplementation(() => {
return CurrentRateServiceMock;
})
};
});
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;
})
};
});
jest.mock(
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
() => {
return {
ExchangeRateDataService: jest.fn().mockImplementation(() => {
return ExchangeRateDataServiceMock;
})
};
}
);
describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null);
exchangeRateDataService = new ExchangeRateDataService(
null,
null,
null,
null
);
portfolioSnapshotService = new PortfolioSnapshotService(null, null);
redisCacheService = new RedisCacheService(null, null);
portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService,
currentRateService,
exchangeRateDataService,
portfolioSnapshotService,
redisCacheService
);
});
describe('get current positions', () => {
it('with GOOGL buy and dividend', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Alphabet Inc.',
symbol: 'GOOGL'
},
date: new Date('2023-01-03'),
feeInAssetProfileCurrency: 1,
feeInBaseCurrency: 0.9238,
quantity: 1,
type: 'BUY',
unitPriceInAssetProfileCurrency: 89.12
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Alphabet Inc.',
symbol: 'GOOGL'
},
date: new Date('2023-01-03'),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
quantity: 1,
type: 'DIVIDEND',
unitPriceInAssetProfileCurrency: 5
}
];
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
// The asset performance takes the dividend at the current exchange rate
// (0.8854), the net performance at the exchange rate of the payment date
// (0.9238)
expect(portfolioSnapshot.positions).toMatchObject([
{
dividendInBaseCurrency: new Big('5').mul(0.9238),
grossPerformance: new Big('32.33').mul(0.8854),
grossPerformanceWithCurrencyEffect: new Big('25.394774'),
netPerformance: new Big('31.33').mul(0.8854),
netPerformanceWithCurrencyEffectMap: {
max: new Big('24.470974')
}
}
]);
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
dividendInBaseCurrency: 4.619,
netPerformance: new Big('31.33').mul(0.8854).toNumber(),
netPerformanceWithCurrencyEffect: 24.470974
});
});
});
});

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

@ -122,9 +122,9 @@ describe('PortfolioCalculator', () => {
{
// The first activity of this asset profile is a fee, which is not an
// investment activity. Thus the holding is not included in the
// holdings of the portfolio summary, and its dividend is not part of
// the dividend of the chart. But its fee and its dividend are part of
// the net performance
// holdings of the portfolio summary. But its fee and its dividend are
// part of the net performance, and thus its dividend is part of the
// dividend of the chart
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
@ -180,8 +180,8 @@ describe('PortfolioCalculator', () => {
).toEqual(['MSFT']);
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0.0020764954116149776,
dividendInBaseCurrency: 5.62,
dividendInPercentageWithCurrencyEffect: 0.01882242615044544,
netPerformance: 18.87,
netPerformanceInPercentage: 0.06319914260834618
});
@ -189,8 +189,8 @@ describe('PortfolioCalculator', () => {
// The portfolio summary takes the dividend of the date range max
expect(performanceByDateRange).toMatchObject({
max: {
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0.002076495411614967,
dividendInBaseCurrency: 5.62,
dividendInPercentageWithCurrencyEffect: 0.01882242615044535,
netPerformance: 18.87,
netPerformanceInPercentage: 0.06319914260834586
}
@ -327,8 +327,8 @@ describe('PortfolioCalculator', () => {
},
{
// The holding has no market price, thus it has no value. Its dividend
// is part of the dividend of the chart, but not part of the net
// performance (unlike the dividend in the scenario above)
// is not part of the net performance (unlike the dividend in the
// scenario above), and thus not part of the dividend of the chart
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
@ -364,7 +364,7 @@ describe('PortfolioCalculator', () => {
expect(portfolioSnapshot.hasErrors).toBe(false);
expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject({
dividendInBaseCurrency: 5.62,
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: 14.87,
netPerformanceInPercentage: 0.0498023980172818
@ -372,7 +372,7 @@ describe('PortfolioCalculator', () => {
expect(performanceByDateRange).toMatchObject({
max: {
dividendInBaseCurrency: 5.62,
dividendInBaseCurrency: 0.62,
dividendInPercentageWithCurrencyEffect: 0,
netPerformance: 14.87,
netPerformanceInPercentage: 0.049802398017281556

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

@ -403,6 +403,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
const {
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
initialValue,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
@ -736,6 +737,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
averageInvestmentValuesWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
dividendValuesWithCurrencyEffect,
dividendYieldPercent,
dividendYieldPercentWithCurrencyEffect,
grossPerformancePercentage,

3
apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts

@ -17,6 +17,9 @@ export interface HoldingPerformance {
currentValuesWithCurrencyEffect: {
[date: string]: Big;
};
dividendValuesWithCurrencyEffect: {
[date: string]: Big;
};
dividendYieldPercent: Big;
dividendYieldPercentWithCurrencyEffect: Big;
grossPerformance: Big;

1
apps/api/src/app/portfolio/interfaces/holding-valuation.interface.ts

@ -5,6 +5,7 @@ import { HoldingValuationItem } from './holding-valuation-item.interface';
export interface HoldingValuation {
currentValues: { [date: string]: Big };
currentValuesWithCurrencyEffect: { [date: string]: Big };
dividendValuesWithCurrencyEffect: { [date: string]: Big };
initialValue?: Big;
investmentValuesAccumulated: { [date: string]: Big };
investmentValuesAccumulatedWithCurrencyEffect: { [date: string]: Big };

1
apps/api/src/app/portfolio/portfolio.service.spec.ts

@ -865,7 +865,6 @@ describe('PortfolioService', () => {
function createPortfolioCalculator() {
return {
getDividendInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)),
getFeesInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)),
getInterestInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)),
getLiabilitiesInBaseCurrency: jest.fn().mockResolvedValue(new Big(0)),

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

@ -2140,6 +2140,7 @@ export class PortfolioService {
const {
currentValueInBaseCurrency,
dividendInBaseCurrency,
dividendPercentageWithCurrencyEffect,
netPerformance,
netPerformancePercentage,
@ -2154,9 +2155,6 @@ export class PortfolioService {
const dateOfFirstActivity = portfolioCalculator.getStartDate();
const dividendInBaseCurrency =
await portfolioCalculator.getDividendInBaseCurrency();
const fees = await portfolioCalculator.getFeesInBaseCurrency();
const interest = await portfolioCalculator.getInterestInBaseCurrency();
@ -2237,6 +2235,7 @@ export class PortfolioService {
cash,
currentValueInBaseCurrency,
dateOfFirstActivity,
dividendInBaseCurrency,
dividendPercentageWithCurrencyEffect,
excludedAccountsAndActivities,
netPerformance,
@ -2248,7 +2247,6 @@ export class PortfolioService {
activityCount: activities.filter(({ type }) => {
return ['BUY', 'SELL'].includes(type);
}).length,
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
// TODO: Remove the fallback to 0 with the next release, when each
// cached portfolio snapshot contains the dividend yield
dividendYieldPercent: dividendYieldPercent?.toNumber() ?? 0,

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

@ -333,6 +333,7 @@ export class GfAnalysisPageComponent implements OnInit {
index,
{
date,
dividendInPercentageWithCurrencyEffect,
netPerformanceInPercentageWithCurrencyEffect,
totalInvestmentValueWithCurrencyEffect,
valueInPercentage,
@ -363,7 +364,9 @@ export class GfAnalysisPageComponent implements OnInit {
if (isNumber(netPerformanceInPercentageWithCurrencyEffect)) {
this.performanceDataItemsInPercentage.push({
date,
value: netPerformanceInPercentageWithCurrencyEffect
value:
netPerformanceInPercentageWithCurrencyEffect -
(dividendInPercentageWithCurrencyEffect ?? 0)
});
}
}

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

@ -171,7 +171,7 @@
/>
</div>
</div>
<div class="d-flex ml-3 py-1">
<div class="d-flex mb-3 ml-3 py-1">
<div class="flex-grow-1 mr-2 text-truncate">
<ng-container i18n>Asset Performance</ng-container>
</div>
@ -187,42 +187,6 @@
/>
</div>
</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 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"
@ -316,6 +280,42 @@
/>
</div>
</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 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 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>
</mat-card-content>
</mat-card>
</div>

Loading…
Cancel
Save