Browse Source

Merge bc334c8220 into df5fa6a4b9

pull/7148/merge
Thomas Kaul 22 hours ago
committed by GitHub
parent
commit
7ba77fa80f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 6
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  3. 104
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Included cash in the performance calculation of the portfolio on the home page
## 3.26.0 - 2026-07-14
### Added

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

@ -392,7 +392,6 @@ export abstract class PortfolioCalculator {
const includeInTotalAssetValue =
item.assetSubClass !== AssetSubClass.CASH;
if (includeInTotalAssetValue) {
valuesBySymbol[item.symbol] = {
currentValues,
currentValuesWithCurrencyEffect,
@ -404,7 +403,6 @@ export abstract class PortfolioCalculator {
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect
};
}
positions.push({
includeInTotalAssetValue,
@ -610,9 +608,7 @@ export abstract class PortfolioCalculator {
netPerformance: totalNetPerformanceValue.toNumber(),
netPerformanceWithCurrencyEffect:
totalNetPerformanceValueWithCurrencyEffect.toNumber(),
netWorth: totalCurrentValueWithCurrencyEffect
.plus(totalAccountBalanceWithCurrencyEffect)
.toNumber(),
netWorth: totalCurrentValueWithCurrencyEffect.toNumber(),
totalAccountBalance: totalAccountBalanceWithCurrencyEffect.toNumber(),
totalInvestment: totalInvestmentValue.toNumber(),
totalInvestmentValueWithCurrencyEffect:

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

@ -287,5 +287,109 @@ describe('PortfolioCalculator', () => {
totalLiabilitiesWithCurrencyEffect: new Big(0)
});
});
it('should include cash in the net worth and performance without counting it twice', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2025-01-01').getTime());
const accountId = randomUUID();
jest
.spyOn(accountBalanceService, 'getAccountBalances')
.mockResolvedValue({
balances: [
{
accountId,
date: parseDate('2023-12-31'),
id: randomUUID(),
value: 1000,
valueInBaseCurrency: 850
},
{
accountId,
date: parseDate('2024-12-31'),
id: randomUUID(),
value: 2000,
valueInBaseCurrency: 1800
}
]
});
jest.spyOn(accountService, 'getCashDetails').mockResolvedValue({
accounts: [
{
balance: 2000,
comment: null,
createdAt: parseDate('2023-12-31'),
currency: 'USD',
id: accountId,
isExcluded: false,
name: 'USD',
platformId: null,
updatedAt: parseDate('2023-12-31'),
userId: userDummyData.id
}
],
balanceInBaseCurrency: 1820
});
jest
.spyOn(dataProviderService, 'getDataSourceForExchangeRates')
.mockReturnValue(DataSource.YAHOO);
jest.spyOn(activitiesService, 'getActivities').mockResolvedValue({
activities: [],
count: 0
});
const { activities } =
await activitiesService.getActivitiesForPortfolioCalculator({
userCurrency: 'CHF',
userId: userDummyData.id,
withCash: true
});
jest.spyOn(currentRateService, 'getValues').mockResolvedValue({
dataProviderInfos: [],
errors: [],
values: []
});
const accountBalanceItems =
await accountBalanceService.getAccountBalanceItems({
userCurrency: 'CHF',
userId: userDummyData.id
});
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
accountBalanceItems,
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
userId: userDummyData.id
});
const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const lastDataItem = portfolioSnapshot.historicalData.at(-1);
// The cash position is now reflected in the portfolio value (it used to
// be excluded, which left the value at 0 for a cash-only portfolio).
expect(lastDataItem.valueWithCurrencyEffect).toBeGreaterThan(0);
// The account balance is still tracked ...
expect(lastDataItem.totalAccountBalance).toBeGreaterThan(0);
// ... but it is no longer added on top of the cash position, so the net
// worth equals the portfolio value and cash is not counted twice.
expect(lastDataItem.netWorth).toBeCloseTo(
lastDataItem.valueWithCurrencyEffect
);
// Cash now contributes to the performance (here through the currency
// effect), so it is no longer flat at 0 %.
expect(
lastDataItem.netPerformanceInPercentageWithCurrencyEffect
).not.toBe(0);
});
});
});

Loading…
Cancel
Save