Browse Source

Bugfix/start date of calendar year date ranges in portfolio performance (#8077)

* Fix start date of calendar year date ranges in portfolio performance

* Update changelog
pull/8078/head
Thomas Kaul 2 days ago
committed by GitHub
parent
commit
a0b3d1f71a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 4
      apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts
  3. 7
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  4. 21
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

1
CHANGELOG.md

@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed the visibility of the asset class, asset sub class, fee and quantity fields of a valuable in the create activity dialog - Fixed the visibility of the asset class, asset sub class, fee and quantity fields of a valuable in the create activity dialog
- Fixed the missing mapping for Aland Islands in the country weightings of the _Financial Modeling Prep_ service - Fixed the missing mapping for Aland Islands in the country weightings of the _Financial Modeling Prep_ service
- Fixed the net performance percentage of date ranges in the portfolio performance calculation by weighting the average investment by the number of days between the chart dates - Fixed the net performance percentage of date ranges in the portfolio performance calculation by weighting the average investment by the number of days between the chart dates
- Fixed the start date of calendar year date ranges in the portfolio performance calculation
## 3.80.2 - 2026-10-06 ## 3.80.2 - 2026-10-06

4
apps/api/src/app/endpoints/benchmarks/benchmarks.service.ts

@ -13,7 +13,7 @@ import {
import { DateRange } from '@ghostfolio/common/types'; import { DateRange } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { format, isSameDay } from 'date-fns'; import { format, isSameDay, min } from 'date-fns';
import { isNumber } from 'lodash-es'; import { isNumber } from 'lodash-es';
@Injectable() @Injectable()
@ -96,8 +96,8 @@ export class BenchmarksService {
const exchangeRates = const exchangeRates =
await this.exchangeRateDataService.getExchangeRatesByCurrency({ await this.exchangeRateDataService.getExchangeRatesByCurrency({
startDate,
currencies: [currentSymbolItem.currency], currencies: [currentSymbolItem.currency],
startDate: min([baselineDate, startDate]),
targetCurrency: userCurrency targetCurrency: userCurrency
}); });

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

@ -64,6 +64,7 @@ import {
format, format,
isAfter, isAfter,
isBefore, isBefore,
isEqual,
isFuture, isFuture,
isPast, isPast,
isWithinInterval, isWithinInterval,
@ -1417,10 +1418,14 @@ export abstract class PortfolioCalculator {
let netPerformanceAtStartDate: number; let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number; let netPerformanceWithCurrencyEffectAtStartDate: number;
const startDate = isEqual(start, endOfDay(start))
? resetHours(start)
: start;
for (const historicalDataItem of historicalData) { for (const historicalDataItem of historicalData) {
const date = resetHours(parseDate(historicalDataItem.date)); const date = resetHours(parseDate(historicalDataItem.date));
if (!isBefore(date, start) && !isAfter(date, end)) { if (!isBefore(date, startDate) && !isAfter(date, end)) {
// Take the values at the start date from the first day of the date // Take the values at the start date from the first day of the date
// range // range
if (historicalDataItemsOfDateRange.length === 0) { if (historicalDataItemsOfDateRange.length === 0) {

21
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -13,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; 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 { 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 { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -168,6 +169,7 @@ describe('PortfolioCalculator', () => {
}, },
netPerformanceWithCurrencyEffectMap: { netPerformanceWithCurrencyEffectMap: {
'1d': new Big('-556.443324'), '1d': new Big('-556.443324'),
'2017': new Big('27081.23736'),
max: new Big('26516.208701400000064086'), max: new Big('26516.208701400000064086'),
ytd: new Big('-556.443324') ytd: new Big('-556.443324')
}, },
@ -262,8 +264,8 @@ describe('PortfolioCalculator', () => {
'2017': { '2017': {
date: '2017-12-31', date: '2017-12-31',
netPerformance: 26957.033439, netPerformance: 26957.033439,
netPerformanceInPercentage: 44.109822737327406, netPerformanceInPercentage: 44.10965416175681,
netPerformanceInPercentageWithCurrencyEffect: 43.14859823223876, netPerformanceInPercentageWithCurrencyEffect: 43.148434302822835,
netPerformanceWithCurrencyEffect: 27081.23736, netPerformanceWithCurrencyEffect: 27081.23736,
totalInvestmentValueWithCurrencyEffect: 318.54266729999995, totalInvestmentValueWithCurrencyEffect: 318.54266729999995,
valueWithCurrencyEffect: 13854.86868 valueWithCurrencyEffect: 13854.86868
@ -287,6 +289,17 @@ describe('PortfolioCalculator', () => {
valueWithCurrencyEffect: 13298.425356 valueWithCurrencyEffect: 13298.425356
} }
}); });
const { endDate, startDate } = getIntervalFromDateRange({
dateRange: '2017'
});
const { chart } = await portfolioCalculator.getPerformance({
end: endDate,
start: startDate
});
expect(chart[0].date).toBe('2016-12-31');
}); });
it.only('with BTCUSD buy and sell partially and fewer chart items', async () => { it.only('with BTCUSD buy and sell partially and fewer chart items', async () => {
@ -333,12 +346,12 @@ describe('PortfolioCalculator', () => {
// Other weights give another rounding of the floating point numbers // Other weights give another rounding of the floating point numbers
expect( expect(
performanceByDateRange['2017'].netPerformanceInPercentage performanceByDateRange['2017'].netPerformanceInPercentage
).toBeCloseTo(44.109822737327406, 10); ).toBeCloseTo(44.10965416175681, 10);
expect( expect(
performanceByDateRange['2017'] performanceByDateRange['2017']
.netPerformanceInPercentageWithCurrencyEffect .netPerformanceInPercentageWithCurrencyEffect
).toBeCloseTo(43.14859823223876, 10); ).toBeCloseTo(43.148434302822835, 10);
expect(performanceByDateRange.max.netPerformanceInPercentage).toBeCloseTo( expect(performanceByDateRange.max.netPerformanceInPercentage).toBeCloseTo(
42.43911719562077, 42.43911719562077,

Loading…
Cancel
Save