Browse Source

test(api): add tests for performance grouped by year in ROAI calculator

Add test cases that verify getChartByYear correctly groups portfolio
performance chart data by year. Tests use getPerformance() output
with getChartByYear to validate yearly grouping:

- BTCUSD: Tests multi-year scenario (2021-2022)
- GOOGL: Tests single-year scenario (2023)
- NOVN: Tests buy-and-sell within single year (2022)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
pull/6223/head
John Costa 2 weeks ago
parent
commit
041a854c65
  1. 65
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  2. 57
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
  3. 57
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

65
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts

@ -7,6 +7,7 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { getChartByYear } from '@ghostfolio/api/app/portfolio/portfolio-chart.helper';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -88,6 +89,10 @@ describe('PortfolioCalculator', () => {
); );
}); });
afterEach(() => {
jest.useRealTimers();
});
describe('get current positions', () => { describe('get current positions', () => {
it.only('with BTCUSD buy (in USD)', async () => { it.only('with BTCUSD buy (in USD)', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime()); jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime());
@ -258,5 +263,65 @@ describe('PortfolioCalculator', () => {
{ date: '2022-01-01', investment: 0 } { date: '2022-01-01', investment: 0 }
]); ]);
}); });
it.only('with BTCUSD buy - performance grouped by year', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
date: parseDate(activity.date),
feeInAssetProfileCurrency: 4.46,
feeInBaseCurrency: 4.46,
SymbolProfile: {
...symbolProfileDummyData,
currency: 'USD',
dataSource: activity.dataSource,
name: 'Bitcoin',
symbol: activity.symbol
},
unitPriceInAssetProfileCurrency: 44558.42
})
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
userId: userDummyData.id
});
await portfolioCalculator.computeSnapshot();
const { chart } = await portfolioCalculator.getPerformance({
end: parseDate('2022-01-14'),
start: parseDate('2021-12-11')
});
const chartByYear = getChartByYear(chart);
// Chart spans from 2021-12-11 to 2022-01-14, covering two years
expect(chartByYear).toHaveLength(2);
// First year (2021) - should have the last data point from 2021-12-31
expect(chartByYear[0].date).toEqual('2021-01-01');
expect(chartByYear[0]).toMatchObject(
expect.objectContaining({
date: '2021-01-01',
totalInvestmentValueWithCurrencyEffect: 44558.42
})
);
// Second year (2022) - should have the last data point from 2022-01-14
expect(chartByYear[1].date).toEqual('2022-01-01');
expect(chartByYear[1]).toMatchObject(
expect.objectContaining({
date: '2022-01-01',
netPerformance: -1463.18,
totalInvestmentValueWithCurrencyEffect: 44558.42
})
);
});
}); });
}); });

57
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts

@ -6,6 +6,7 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { getChartByYear } from '@ghostfolio/api/app/portfolio/portfolio-chart.helper';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -90,6 +91,10 @@ describe('PortfolioCalculator', () => {
); );
}); });
afterEach(() => {
jest.useRealTimers();
});
describe('get current positions', () => { describe('get current positions', () => {
it.only('with GOOGL buy', async () => { it.only('with GOOGL buy', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime()); jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime());
@ -230,5 +235,57 @@ describe('PortfolioCalculator', () => {
{ date: '2023-01-01', investment: 82.329056 } { date: '2023-01-01', investment: 82.329056 }
]); ]);
}); });
it.only('with GOOGL buy - performance grouped by year', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime());
const activities: Activity[] = [
{
...activityDummyData,
date: new Date('2023-01-03'),
feeInAssetProfileCurrency: 1,
feeInBaseCurrency: 0.9238,
quantity: 1,
SymbolProfile: {
...symbolProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Alphabet Inc.',
symbol: 'GOOGL'
},
type: 'BUY',
unitPriceInAssetProfileCurrency: 89.12
}
];
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
userId: userDummyData.id
});
await portfolioCalculator.computeSnapshot();
const { chart } = await portfolioCalculator.getPerformance({
end: parseDate('2023-07-10'),
start: parseDate('2023-01-03')
});
const chartByYear = getChartByYear(chart);
// All data is within 2023, so should only have one year entry
expect(chartByYear).toHaveLength(1);
// Should have the last data point of 2023 (2023-07-10) with normalized date
expect(chartByYear[0].date).toEqual('2023-01-01');
expect(chartByYear[0]).toMatchObject(
expect.objectContaining({
date: '2023-01-01',
netPerformance: new Big('26.33').mul(0.8854).toNumber(),
totalInvestmentValueWithCurrencyEffect: 82.329056
})
);
});
}); });
}); });

57
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -7,6 +7,7 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { getChartByYear } from '@ghostfolio/api/app/portfolio/portfolio-chart.helper';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -91,6 +92,10 @@ describe('PortfolioCalculator', () => {
); );
}); });
afterEach(() => {
jest.useRealTimers();
});
describe('get current positions', () => { describe('get current positions', () => {
it.only('with NOVN.SW buy and sell', async () => { it.only('with NOVN.SW buy and sell', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime()); jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime());
@ -261,5 +266,57 @@ describe('PortfolioCalculator', () => {
{ date: '2022-01-01', investment: 0 } { date: '2022-01-01', investment: 0 }
]); ]);
}); });
it.only('with NOVN.SW buy and sell - performance grouped by year', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
SymbolProfile: {
...symbolProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Novartis AG',
symbol: activity.symbol
},
unitPriceInAssetProfileCurrency: activity.unitPrice
})
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
userId: userDummyData.id
});
await portfolioCalculator.computeSnapshot();
const { chart } = await portfolioCalculator.getPerformance({
end: parseDate('2022-04-11'),
start: parseDate('2022-03-06')
});
const chartByYear = getChartByYear(chart);
// All data is within 2022, so should only have one year entry
expect(chartByYear).toHaveLength(1);
// Should have the last data point of 2022 (2022-04-11) with normalized date
expect(chartByYear[0].date).toEqual('2022-01-01');
expect(chartByYear[0]).toMatchObject(
expect.objectContaining({
date: '2022-01-01',
netPerformance: 19.86,
netPerformanceInPercentage: 0.13100263852242744,
totalInvestmentValueWithCurrencyEffect: 0
})
);
});
}); });
}); });

Loading…
Cancel
Save