Browse Source

Task/load activities of portfolio calculator tests from export files (#7992)

Load activities from export files
pull/7999/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
430f2fe06f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 40
      apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts
  2. 43
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts
  3. 43
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
  4. 94
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
  5. 43
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  6. 35
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts
  7. 35
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  8. 37
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts
  9. 43
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  10. 37
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  11. 37
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  12. 36
      test/import/ok/baln-buy-and-buy.json
  13. 36
      test/import/ok/baln-buy-and-sell.json
  14. 25
      test/import/ok/baln-buy-with-unit-price-lower-than-closing-price.json
  15. 25
      test/import/ok/baln-buy.json
  16. 36
      test/import/ok/btcusd-buy-and-sell-partially.json
  17. 36
      test/import/ok/msft-buy-with-dividend.json

40
apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts

@ -1,12 +1,15 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { parseDate } from '@ghostfolio/common/helper';
import {
Activity,
ExportResponse,
HistoricalDataItem
} from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
export const activityDummyData = {
accountId: undefined,
@ -71,6 +74,43 @@ export async function getPerformanceByDateRange({
return performanceByDateRange;
}
/**
* Loads the activities of an export file in test/import/ok for the portfolio
* calculator. The user currency of the file is the base currency of the test.
*
* The activity currency must be the asset profile currency. The helper does
* not convert the fee to the base currency and does not load the tags.
*/
export function loadActivitiesFromExportFile(fileName: string): {
activities: Activity[];
userCurrency: string;
} {
const exportResponse = loadExportFile(
join(__dirname, '../../../../../../test/import/ok', fileName)
);
const activities: Activity[] = exportResponse.activities.map((activity) => {
return {
...activityDummyData,
...activity,
assetProfile: {
...assetProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: activity.symbol,
symbol: activity.symbol
},
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
tags: [],
unitPriceInAssetProfileCurrency: activity.unitPrice
};
});
return { activities, userCurrency: exportResponse.user.settings.currency };
}
export function loadExportFile(filePath: string): ExportResponse {
return JSON.parse(readFileSync(filePath, 'utf8'));
}

43
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts

@ -1,6 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -13,7 +12,6 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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';
@ -85,45 +83,14 @@ describe('PortfolioCalculator', () => {
it.only('with BALN.SW buy and buy', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-22'),
feeInAssetProfileCurrency: 1.55,
feeInBaseCurrency: 1.55,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 142.9
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-30'),
feeInAssetProfileCurrency: 1.65,
feeInBaseCurrency: 1.65,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 136.6
}
];
const { activities, userCurrency } = loadActivitiesFromExportFile(
'baln-buy-and-buy.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
userId: userDummyData.id
});

43
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -1,7 +1,6 @@
import {
activityDummyData,
assetProfileDummyData,
getPerformanceByDateRange,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,7 +13,6 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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';
@ -86,45 +84,14 @@ describe('PortfolioCalculator', () => {
it.only('with BALN.SW buy and sell', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-22'),
feeInAssetProfileCurrency: 1.55,
feeInBaseCurrency: 1.55,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 142.9
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-30'),
feeInAssetProfileCurrency: 1.65,
feeInBaseCurrency: 1.65,
quantity: 2,
type: 'SELL',
unitPriceInAssetProfileCurrency: 136.6
}
];
const { activities, userCurrency } = loadActivitiesFromExportFile(
'baln-buy-and-sell.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
usePortfolioSnapshotCache: false,
userId: userDummyData.id
});

94
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts

@ -1,6 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -13,7 +12,6 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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';
@ -85,29 +83,13 @@ describe('PortfolioCalculator', () => {
it.only('with BALN.SW buy', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-30'),
feeInAssetProfileCurrency: 1.55,
feeInBaseCurrency: 1.55,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 136.6
}
];
const { activities, userCurrency } =
loadActivitiesFromExportFile('baln-buy.json');
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
userId: userDummyData.id
});
@ -217,29 +199,14 @@ describe('PortfolioCalculator', () => {
it.only('with BALN.SW buy (with unit price lower than closing price)', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-30'),
feeInAssetProfileCurrency: 1.55,
feeInBaseCurrency: 1.55,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 135.0
}
];
const { activities, userCurrency } = loadActivitiesFromExportFile(
'baln-buy-with-unit-price-lower-than-closing-price.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
userId: userDummyData.id
});
@ -257,29 +224,14 @@ describe('PortfolioCalculator', () => {
it.only('with BALN.SW buy (with unit price lower than closing price), calculated on buy date', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-11-30').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-30'),
feeInAssetProfileCurrency: 1.55,
feeInBaseCurrency: 1.55,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 135.0
}
];
const { activities, userCurrency } = loadActivitiesFromExportFile(
'baln-buy-with-unit-price-lower-than-closing-price.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
userId: userDummyData.id
});
@ -297,29 +249,13 @@ describe('PortfolioCalculator', () => {
it.only('with BALN.SW buy (on a date without historical market data)', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'CHF',
dataSource: 'YAHOO',
name: 'Bâloise Holding AG',
symbol: 'BALN.SW'
},
date: new Date('2021-11-30'),
feeInAssetProfileCurrency: 1.55,
feeInBaseCurrency: 1.55,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 136.6
}
];
const { activities, userCurrency } =
loadActivitiesFromExportFile('baln-buy.json');
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
userId: userDummyData.id
});

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

@ -1,7 +1,6 @@
import {
activityDummyData,
assetProfileDummyData,
getPerformanceByDateRange,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -15,7 +14,6 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r
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';
@ -98,45 +96,14 @@ describe('PortfolioCalculator', () => {
it.only('with BTCUSD buy and sell partially', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2018-01-01').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Bitcoin USD',
symbol: 'BTCUSD'
},
date: new Date('2015-01-01'),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
quantity: 2,
type: 'BUY',
unitPriceInAssetProfileCurrency: 320.43
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Bitcoin USD',
symbol: 'BTCUSD'
},
date: new Date('2017-12-31'),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
quantity: 1,
type: 'SELL',
unitPriceInAssetProfileCurrency: 14156.4
}
];
const { activities, userCurrency } = loadActivitiesFromExportFile(
'btcusd-buy-and-sell-partially.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'CHF',
currency: userCurrency,
usePortfolioSnapshotCache: false,
userId: userDummyData.id
});

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

@ -1,7 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadExportFile,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,11 +12,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'node:path';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
@ -48,8 +44,6 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
});
describe('PortfolioCalculator', () => {
let exportResponse: ExportResponse;
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
@ -57,12 +51,6 @@ describe('PortfolioCalculator', () => {
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeAll(() => {
exportResponse = loadExportFile(
join(__dirname, '../../../../../../../test/import/ok/btcusd-short.json')
);
});
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
@ -95,28 +83,13 @@ describe('PortfolioCalculator', () => {
it.only('with BTCUSD short sell (in USD)', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: activity.dataSource,
name: 'Bitcoin',
symbol: activity.symbol
},
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
unitPriceInAssetProfileCurrency: activity.unitPrice
})
);
const { activities, userCurrency } =
loadActivitiesFromExportFile('btcusd-short.json');
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
currency: userCurrency,
userId: userDummyData.id
});

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

@ -1,7 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadExportFile,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,11 +12,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'node:path';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
@ -48,8 +44,6 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
});
describe('PortfolioCalculator', () => {
let exportResponse: ExportResponse;
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
@ -57,12 +51,6 @@ describe('PortfolioCalculator', () => {
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeAll(() => {
exportResponse = loadExportFile(
join(__dirname, '../../../../../../../test/import/ok/btcusd.json')
);
});
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
@ -95,28 +83,13 @@ describe('PortfolioCalculator', () => {
it.only('with BTCUSD buy (in USD)', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: activity.dataSource,
name: 'Bitcoin',
symbol: activity.symbol
},
date: parseDate(activity.date),
feeInAssetProfileCurrency: 4.46,
feeInBaseCurrency: 4.46,
unitPriceInAssetProfileCurrency: 44558.42
})
);
const { activities, userCurrency } =
loadActivitiesFromExportFile('btcusd.json');
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
currency: userCurrency,
userId: userDummyData.id
});

37
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts

@ -1,7 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadExportFile,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,11 +12,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'node:path';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
@ -48,8 +44,6 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
});
describe('PortfolioCalculator', () => {
let exportResponse: ExportResponse;
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
@ -57,15 +51,6 @@ describe('PortfolioCalculator', () => {
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeAll(() => {
exportResponse = loadExportFile(
join(
__dirname,
'../../../../../../../test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json'
)
);
});
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
@ -98,28 +83,14 @@ describe('PortfolioCalculator', () => {
it.only('with JNUG buy and sell', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2025-12-28').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
assetProfile: {
...assetProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Direxion Daily Junior Gold Miners Index Bull 2X Shares',
symbol: activity.symbol
},
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
unitPriceInAssetProfileCurrency: activity.unitPrice
})
const { activities, userCurrency } = loadActivitiesFromExportFile(
'jnug-buy-and-sell-and-buy-and-sell.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
currency: userCurrency,
userId: userDummyData.id
});

43
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -1,7 +1,6 @@
import {
activityDummyData,
assetProfileDummyData,
getPerformanceByDateRange,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,7 +13,6 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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';
@ -86,45 +84,14 @@ describe('PortfolioCalculator', () => {
it.only('with MSFT buy', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2023-07-10').getTime());
const activities: Activity[] = [
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Inc.',
symbol: 'MSFT'
},
date: new Date('2021-09-16'),
feeInAssetProfileCurrency: 19,
feeInBaseCurrency: 19,
quantity: 1,
type: 'BUY',
unitPriceInAssetProfileCurrency: 298.58
},
{
...activityDummyData,
assetProfile: {
...assetProfileDummyData,
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Inc.',
symbol: 'MSFT'
},
date: new Date('2021-11-16'),
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
quantity: 1,
type: 'DIVIDEND',
unitPriceInAssetProfileCurrency: 0.62
}
];
const { activities, userCurrency } = loadActivitiesFromExportFile(
'msft-buy-with-dividend.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: 'USD',
currency: userCurrency,
usePortfolioSnapshotCache: false,
userId: userDummyData.id
});

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

@ -1,7 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadExportFile,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,11 +12,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'node:path';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
@ -48,8 +44,6 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
});
describe('PortfolioCalculator', () => {
let exportResponse: ExportResponse;
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
@ -57,15 +51,6 @@ describe('PortfolioCalculator', () => {
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeAll(() => {
exportResponse = loadExportFile(
join(
__dirname,
'../../../../../../../test/import/ok/novn-buy-and-sell-partially.json'
)
);
});
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
@ -98,28 +83,14 @@ describe('PortfolioCalculator', () => {
it.only('with NOVN.SW buy and sell partially', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
assetProfile: {
...assetProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Novartis AG',
symbol: activity.symbol
},
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
unitPriceInAssetProfileCurrency: activity.unitPrice
})
const { activities, userCurrency } = loadActivitiesFromExportFile(
'novn-buy-and-sell-partially.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
currency: userCurrency,
userId: userDummyData.id
});

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

@ -1,7 +1,5 @@
import {
activityDummyData,
assetProfileDummyData,
loadExportFile,
loadActivitiesFromExportFile,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
@ -14,11 +12,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
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, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'node:path';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return {
@ -48,8 +44,6 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
});
describe('PortfolioCalculator', () => {
let exportResponse: ExportResponse;
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService;
@ -57,15 +51,6 @@ describe('PortfolioCalculator', () => {
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService;
beforeAll(() => {
exportResponse = loadExportFile(
join(
__dirname,
'../../../../../../../test/import/ok/novn-buy-and-sell.json'
)
);
});
beforeEach(() => {
PortfolioSnapshotServiceMock.reset();
RedisCacheServiceMock.reset();
@ -98,28 +83,14 @@ describe('PortfolioCalculator', () => {
it.only('with NOVN.SW buy and sell', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime());
const activities: Activity[] = exportResponse.activities.map(
(activity) => ({
...activityDummyData,
...activity,
assetProfile: {
...assetProfileDummyData,
currency: activity.currency,
dataSource: activity.dataSource,
name: 'Novartis AG',
symbol: activity.symbol
},
date: parseDate(activity.date),
feeInAssetProfileCurrency: activity.fee,
feeInBaseCurrency: activity.fee,
unitPriceInAssetProfileCurrency: activity.unitPrice
})
const { activities, userCurrency } = loadActivitiesFromExportFile(
'novn-buy-and-sell.json'
);
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.ROAI,
currency: exportResponse.user.settings.currency,
currency: userCurrency,
userId: userDummyData.id
});

36
test/import/ok/baln-buy-and-buy.json

@ -0,0 +1,36 @@
{
"meta": {
"date": "2026-10-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"currency": "CHF",
"dataSource": "YAHOO",
"date": "2021-11-22T00:00:00.000Z",
"fee": 1.55,
"id": "fe7d42ee-e607-58b7-a183-9f2f894fe204",
"quantity": 2,
"symbol": "BALN.SW",
"type": "BUY",
"unitPrice": 142.9
},
{
"currency": "CHF",
"dataSource": "YAHOO",
"date": "2021-11-30T00:00:00.000Z",
"fee": 1.65,
"id": "716b3742-6326-5fb1-a420-5f21b97316ea",
"quantity": 2,
"symbol": "BALN.SW",
"type": "BUY",
"unitPrice": 136.6
}
],
"user": {
"settings": {
"currency": "CHF",
"performanceCalculationType": "ROAI"
}
}
}

36
test/import/ok/baln-buy-and-sell.json

@ -0,0 +1,36 @@
{
"meta": {
"date": "2026-10-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"currency": "CHF",
"dataSource": "YAHOO",
"date": "2021-11-22T00:00:00.000Z",
"fee": 1.55,
"id": "9269a332-dec6-51b5-9c9d-60dec1cf3d25",
"quantity": 2,
"symbol": "BALN.SW",
"type": "BUY",
"unitPrice": 142.9
},
{
"currency": "CHF",
"dataSource": "YAHOO",
"date": "2021-11-30T00:00:00.000Z",
"fee": 1.65,
"id": "aafb6d62-3486-5106-bff5-9e06b91b8d89",
"quantity": 2,
"symbol": "BALN.SW",
"type": "SELL",
"unitPrice": 136.6
}
],
"user": {
"settings": {
"currency": "CHF",
"performanceCalculationType": "ROAI"
}
}
}

25
test/import/ok/baln-buy-with-unit-price-lower-than-closing-price.json

@ -0,0 +1,25 @@
{
"meta": {
"date": "2026-10-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"currency": "CHF",
"dataSource": "YAHOO",
"date": "2021-11-30T00:00:00.000Z",
"fee": 1.55,
"id": "99a0844c-53ff-58b9-b120-e9339ea3ba22",
"quantity": 2,
"symbol": "BALN.SW",
"type": "BUY",
"unitPrice": 135.0
}
],
"user": {
"settings": {
"currency": "CHF",
"performanceCalculationType": "ROAI"
}
}
}

25
test/import/ok/baln-buy.json

@ -0,0 +1,25 @@
{
"meta": {
"date": "2026-10-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"currency": "CHF",
"dataSource": "YAHOO",
"date": "2021-11-30T00:00:00.000Z",
"fee": 1.55,
"id": "d82cae72-04b1-5beb-9d84-8e7740cd1b44",
"quantity": 2,
"symbol": "BALN.SW",
"type": "BUY",
"unitPrice": 136.6
}
],
"user": {
"settings": {
"currency": "CHF",
"performanceCalculationType": "ROAI"
}
}
}

36
test/import/ok/btcusd-buy-and-sell-partially.json

@ -0,0 +1,36 @@
{
"meta": {
"date": "2026-10-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2015-01-01T00:00:00.000Z",
"fee": 0,
"id": "0d9ac134-2329-5234-8b59-8aaa6424e79d",
"quantity": 2,
"symbol": "BTCUSD",
"type": "BUY",
"unitPrice": 320.43
},
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2017-12-31T00:00:00.000Z",
"fee": 0,
"id": "f7a55970-57b0-52b3-9846-c7c306645050",
"quantity": 1,
"symbol": "BTCUSD",
"type": "SELL",
"unitPrice": 14156.4
}
],
"user": {
"settings": {
"currency": "CHF",
"performanceCalculationType": "ROAI"
}
}
}

36
test/import/ok/msft-buy-with-dividend.json

@ -0,0 +1,36 @@
{
"meta": {
"date": "2026-10-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-09-16T00:00:00.000Z",
"fee": 19,
"id": "dcec53cf-61d7-522b-985f-fd94b1f074a0",
"quantity": 1,
"symbol": "MSFT",
"type": "BUY",
"unitPrice": 298.58
},
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-11-16T00:00:00.000Z",
"fee": 0,
"id": "ac5970cf-4bb9-5385-8faf-9dd1efaca883",
"quantity": 1,
"symbol": "MSFT",
"type": "DIVIDEND",
"unitPrice": 0.62
}
],
"user": {
"settings": {
"currency": "USD",
"performanceCalculationType": "ROAI"
}
}
}
Loading…
Cancel
Save