Browse Source

Exclude cash in base currency from portfolio performance calculation

pull/7443/head
Thomas Kaul 1 month ago
parent
commit
278aca35c3
  1. 30
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 9
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  3. 12
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  4. 2
      apps/api/src/app/portfolio/portfolio.service.ts

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

@ -327,6 +327,7 @@ export abstract class PortfolioCalculator {
totalInvestmentValueWithCurrencyEffect: Big; totalInvestmentValueWithCurrencyEffect: Big;
totalNetPerformanceValue: Big; totalNetPerformanceValue: Big;
totalNetPerformanceValueWithCurrencyEffect: Big; totalNetPerformanceValueWithCurrencyEffect: Big;
totalNetWorthValueWithCurrencyEffect: Big;
totalTimeWeightedInvestmentValue: Big; totalTimeWeightedInvestmentValue: Big;
totalTimeWeightedInvestmentValueWithCurrencyEffect: Big; totalTimeWeightedInvestmentValueWithCurrencyEffect: Big;
}; };
@ -341,6 +342,7 @@ export abstract class PortfolioCalculator {
investmentValuesWithCurrencyEffect: { [date: string]: Big }; investmentValuesWithCurrencyEffect: { [date: string]: Big };
netPerformanceValues: { [date: string]: Big }; netPerformanceValues: { [date: string]: Big };
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big }; netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
netWorthValuesWithCurrencyEffect: { [date: string]: Big };
timeWeightedInvestmentValues: { [date: string]: Big }; timeWeightedInvestmentValues: { [date: string]: Big };
timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big }; timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big };
}; };
@ -359,7 +361,8 @@ export abstract class PortfolioCalculator {
const isCashInBaseCurrency = const isCashInBaseCurrency =
item.assetSubClass === AssetSubClass.CASH && item.assetSubClass === AssetSubClass.CASH &&
item.currency === this.currency; item.currency === this.currency &&
item.symbol === this.currency;
const { const {
currentValues, currentValues,
@ -402,16 +405,19 @@ export abstract class PortfolioCalculator {
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
// Cash in the base currency cannot generate a currency effect and thus // Cash in the base currency cannot generate a currency effect and thus
// contributes nothing but its balance to the performance calculation // contributes nothing but its balance to the performance calculation. It
// is therefore excluded from the value and the investment, while still
// contributing to the net worth.
valuesBySymbol[item.symbol] = isCashInBaseCurrency valuesBySymbol[item.symbol] = isCashInBaseCurrency
? { ? {
currentValues, currentValues: {},
currentValuesWithCurrencyEffect, currentValuesWithCurrencyEffect: {},
investmentValuesAccumulated: {}, investmentValuesAccumulated: {},
investmentValuesAccumulatedWithCurrencyEffect: {}, investmentValuesAccumulatedWithCurrencyEffect: {},
investmentValuesWithCurrencyEffect: {}, investmentValuesWithCurrencyEffect: {},
netPerformanceValues: {}, netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {}, netPerformanceValuesWithCurrencyEffect: {},
netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect,
timeWeightedInvestmentValues: {}, timeWeightedInvestmentValues: {},
timeWeightedInvestmentValuesWithCurrencyEffect: {} timeWeightedInvestmentValuesWithCurrencyEffect: {}
} }
@ -424,7 +430,8 @@ export abstract class PortfolioCalculator {
netPerformanceValues, netPerformanceValues,
netPerformanceValuesWithCurrencyEffect, netPerformanceValuesWithCurrencyEffect,
timeWeightedInvestmentValues, timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect timeWeightedInvestmentValuesWithCurrencyEffect,
netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect
}; };
positions.push({ positions.push({
@ -528,6 +535,10 @@ export abstract class PortfolioCalculator {
symbolValues.netPerformanceValuesWithCurrencyEffect?.[dateString] ?? symbolValues.netPerformanceValuesWithCurrencyEffect?.[dateString] ??
new Big(0); new Big(0);
const netWorthValueWithCurrencyEffect =
symbolValues.netWorthValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const timeWeightedInvestmentValue = const timeWeightedInvestmentValue =
symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0); symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0);
@ -546,7 +557,7 @@ export abstract class PortfolioCalculator {
?.totalCashValueWithCurrencyEffect ?? new Big(0) ?.totalCashValueWithCurrencyEffect ?? new Big(0)
).add( ).add(
cashSymbols.has(symbol) cashSymbols.has(symbol)
? currentValueWithCurrencyEffect ? netWorthValueWithCurrencyEffect
: new Big(0) : new Big(0)
), ),
totalCurrentValue: ( totalCurrentValue: (
@ -572,6 +583,10 @@ export abstract class PortfolioCalculator {
accumulatedValuesByDate[dateString] accumulatedValuesByDate[dateString]
?.totalNetPerformanceValueWithCurrencyEffect ?? new Big(0) ?.totalNetPerformanceValueWithCurrencyEffect ?? new Big(0)
).add(netPerformanceValueWithCurrencyEffect), ).add(netPerformanceValueWithCurrencyEffect),
totalNetWorthValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalNetWorthValueWithCurrencyEffect ?? new Big(0)
).add(netWorthValueWithCurrencyEffect),
totalTimeWeightedInvestmentValue: ( totalTimeWeightedInvestmentValue: (
accumulatedValuesByDate[dateString] accumulatedValuesByDate[dateString]
?.totalTimeWeightedInvestmentValue ?? new Big(0) ?.totalTimeWeightedInvestmentValue ?? new Big(0)
@ -596,6 +611,7 @@ export abstract class PortfolioCalculator {
totalInvestmentValueWithCurrencyEffect, totalInvestmentValueWithCurrencyEffect,
totalNetPerformanceValue, totalNetPerformanceValue,
totalNetPerformanceValueWithCurrencyEffect, totalNetPerformanceValueWithCurrencyEffect,
totalNetWorthValueWithCurrencyEffect,
totalTimeWeightedInvestmentValue, totalTimeWeightedInvestmentValue,
totalTimeWeightedInvestmentValueWithCurrencyEffect totalTimeWeightedInvestmentValueWithCurrencyEffect
} = values; } = values;
@ -622,7 +638,7 @@ export abstract class PortfolioCalculator {
netPerformance: totalNetPerformanceValue.toNumber(), netPerformance: totalNetPerformanceValue.toNumber(),
netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffect:
totalNetPerformanceValueWithCurrencyEffect.toNumber(), totalNetPerformanceValueWithCurrencyEffect.toNumber(),
netWorth: totalCurrentValueWithCurrencyEffect.toNumber(), netWorth: totalNetWorthValueWithCurrencyEffect.toNumber(),
totalCashInBaseCurrency: totalCashValueWithCurrencyEffect.toNumber(), totalCashInBaseCurrency: totalCashValueWithCurrencyEffect.toNumber(),
totalInvestment: totalInvestmentValue.toNumber(), totalInvestment: totalInvestmentValue.toNumber(),
totalInvestmentValueWithCurrencyEffect: totalInvestmentValueWithCurrencyEffect:

9
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -433,6 +433,11 @@ describe('PortfolioCalculator', () => {
totalLiabilitiesWithCurrencyEffect: new Big(0) totalLiabilitiesWithCurrencyEffect: new Big(0)
}); });
/**
* Value: 0 CHF (the cash is excluded from the performance calculation
* and therefore from the value it is measured against)
* Net worth: 2000 CHF (the cash still counts towards the net worth)
*/
expect(portfolioSnapshot.historicalData.at(-1)).toEqual({ expect(portfolioSnapshot.historicalData.at(-1)).toEqual({
date: '2025-01-01', date: '2025-01-01',
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
@ -444,8 +449,8 @@ describe('PortfolioCalculator', () => {
totalCashInBaseCurrency: 2000, totalCashInBaseCurrency: 2000,
totalInvestment: 0, totalInvestment: 0,
totalInvestmentValueWithCurrencyEffect: 0, totalInvestmentValueWithCurrencyEffect: 0,
value: 2000, value: 0,
valueWithCurrencyEffect: 2000 valueWithCurrencyEffect: 0
}); });
}); });
}); });

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

@ -41,12 +41,6 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0); let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
for (const currentPosition of positions) { for (const currentPosition of positions) {
if (currentPosition.feeInBaseCurrency) {
totalFeesWithCurrencyEffect = totalFeesWithCurrencyEffect.plus(
currentPosition.feeInBaseCurrency
);
}
if (currentPosition.valueInBaseCurrency) { if (currentPosition.valueInBaseCurrency) {
currentValueInBaseCurrency = currentValueInBaseCurrency.plus( currentValueInBaseCurrency = currentValueInBaseCurrency.plus(
currentPosition.valueInBaseCurrency currentPosition.valueInBaseCurrency
@ -59,6 +53,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
continue; continue;
} }
if (currentPosition.feeInBaseCurrency) {
totalFeesWithCurrencyEffect = totalFeesWithCurrencyEffect.plus(
currentPosition.feeInBaseCurrency
);
}
if (currentPosition.investment) { if (currentPosition.investment) {
totalInvestment = totalInvestment.plus(currentPosition.investment); totalInvestment = totalInvestment.plus(currentPosition.investment);

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

@ -2040,7 +2040,7 @@ export class PortfolioService {
.toNumber(), .toNumber(),
interestInBaseCurrency: interest.toNumber(), interestInBaseCurrency: interest.toNumber(),
liabilitiesInBaseCurrency: liabilities.toNumber(), liabilitiesInBaseCurrency: liabilities.toNumber(),
totalCashInBaseCurrency: totalCashInBaseCurrency?.toNumber() ?? 0, totalCashInBaseCurrency: balanceInBaseCurrency,
totalInvestment: totalInvestment.toNumber(), totalInvestment: totalInvestment.toNumber(),
totalInvestmentValueWithCurrencyEffect: totalInvestmentValueWithCurrencyEffect:
totalInvestmentWithCurrencyEffect.toNumber(), totalInvestmentWithCurrencyEffect.toNumber(),

Loading…
Cancel
Save