From 430f2fe06ff5fb3f89693643a890d42d41314727 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:06:59 +0200 Subject: [PATCH] Task/load activities of portfolio calculator tests from export files (#7992) Load activities from export files --- .../portfolio-calculator-test-utils.ts | 40 ++++++++ ...tfolio-calculator-baln-buy-and-buy.spec.ts | 43 +-------- ...folio-calculator-baln-buy-and-sell.spec.ts | 43 +-------- .../portfolio-calculator-baln-buy.spec.ts | 94 +++---------------- ...ator-btcusd-buy-and-sell-partially.spec.ts | 43 +-------- .../portfolio-calculator-btcusd-short.spec.ts | 35 +------ .../roai/portfolio-calculator-btcusd.spec.ts | 35 +------ ...jnug-buy-and-sell-and-buy-and-sell.spec.ts | 37 +------- ...-calculator-msft-buy-with-dividend.spec.ts | 43 +-------- ...ulator-novn-buy-and-sell-partially.spec.ts | 37 +------- ...folio-calculator-novn-buy-and-sell.spec.ts | 37 +------- test/import/ok/baln-buy-and-buy.json | 36 +++++++ test/import/ok/baln-buy-and-sell.json | 36 +++++++ ...h-unit-price-lower-than-closing-price.json | 25 +++++ test/import/ok/baln-buy.json | 25 +++++ .../ok/btcusd-buy-and-sell-partially.json | 36 +++++++ test/import/ok/msft-buy-with-dividend.json | 36 +++++++ 17 files changed, 289 insertions(+), 392 deletions(-) create mode 100644 test/import/ok/baln-buy-and-buy.json create mode 100644 test/import/ok/baln-buy-and-sell.json create mode 100644 test/import/ok/baln-buy-with-unit-price-lower-than-closing-price.json create mode 100644 test/import/ok/baln-buy.json create mode 100644 test/import/ok/btcusd-buy-and-sell-partially.json create mode 100644 test/import/ok/msft-buy-with-dividend.json diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts index 87474e806c..fb88c8a7f1 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts +++ b/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')); } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index 1b2fee98d9..5d3cbcf320 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index 408076717f..9ee4877180 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index 19f3c64369..afae22c1c6 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index 6a9ed1f71c..15474a0236 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts index 58dc545e49..356fdebd03 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index e0cfeb8227..0d0f6cfc3b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts index ba2b7d05c6..b2e0ffab6b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index 2c6ff79b6d..649542e325 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 92d078d811..e85a053e61 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/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 }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index 46e1b14aa4..a0174b048e 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/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 }); diff --git a/test/import/ok/baln-buy-and-buy.json b/test/import/ok/baln-buy-and-buy.json new file mode 100644 index 0000000000..c5c33764b1 --- /dev/null +++ b/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" + } + } +} diff --git a/test/import/ok/baln-buy-and-sell.json b/test/import/ok/baln-buy-and-sell.json new file mode 100644 index 0000000000..7bb096e51c --- /dev/null +++ b/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" + } + } +} diff --git a/test/import/ok/baln-buy-with-unit-price-lower-than-closing-price.json b/test/import/ok/baln-buy-with-unit-price-lower-than-closing-price.json new file mode 100644 index 0000000000..bef2f1eaa8 --- /dev/null +++ b/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" + } + } +} diff --git a/test/import/ok/baln-buy.json b/test/import/ok/baln-buy.json new file mode 100644 index 0000000000..8435b79137 --- /dev/null +++ b/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" + } + } +} diff --git a/test/import/ok/btcusd-buy-and-sell-partially.json b/test/import/ok/btcusd-buy-and-sell-partially.json new file mode 100644 index 0000000000..b91741245d --- /dev/null +++ b/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" + } + } +} diff --git a/test/import/ok/msft-buy-with-dividend.json b/test/import/ok/msft-buy-with-dividend.json new file mode 100644 index 0000000000..c4f20e2898 --- /dev/null +++ b/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" + } + } +}