Browse Source

Set up caching

pull/3335/head
Thomas Kaul 1 year ago
parent
commit
2a3b617716
  1. 3
      apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts
  2. 4
      apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts
  3. 12
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  4. 76
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  5. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  6. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts
  7. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts
  8. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  9. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts
  10. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts
  11. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts
  12. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts
  13. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts
  14. 9
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts
  15. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  16. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts
  17. 5
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts
  18. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts
  19. 25
      apps/api/src/app/portfolio/interfaces/portfolio-snapshot.interface.ts
  20. 6
      apps/api/src/app/portfolio/portfolio.service.ts
  21. 14
      apps/api/src/app/redis-cache/redis-cache.service.mock.ts
  22. 4
      apps/api/src/app/redis-cache/redis-cache.service.ts
  23. 3
      apps/api/src/events/events.module.ts
  24. 8
      apps/api/src/events/portfolio-changed.listener.ts
  25. 3
      libs/common/src/lib/models/index.ts
  26. 89
      libs/common/src/lib/models/portfolio-snapshot.ts

3
apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts

@ -1,7 +1,6 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces'; import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
export class MWRPortfolioCalculator extends PortfolioCalculator { export class MWRPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance( protected calculateOverallPerformance(

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

@ -24,3 +24,7 @@ export const symbolProfileDummyData = {
sectors: [], sectors: [],
updatedAt: undefined updatedAt: undefined
}; };
export const userDummyData = {
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
};

12
apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts

@ -1,9 +1,10 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; import { HistoricalDataItem } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types'; import { DateRange, UserWithSettings } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -19,6 +20,7 @@ export enum PerformanceCalculationType {
@Injectable() @Injectable()
export class PortfolioCalculatorFactory { export class PortfolioCalculatorFactory {
public constructor( public constructor(
private readonly configurationService: ConfigurationService,
private readonly currentRateService: CurrentRateService, private readonly currentRateService: CurrentRateService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly redisCacheService: RedisCacheService private readonly redisCacheService: RedisCacheService
@ -29,13 +31,15 @@ export class PortfolioCalculatorFactory {
activities, activities,
calculationType, calculationType,
currency, currency,
dateRange = 'max' dateRange = 'max',
userId
}: { }: {
accountBalanceItems?: HistoricalDataItem[]; accountBalanceItems?: HistoricalDataItem[];
activities: Activity[]; activities: Activity[];
calculationType: PerformanceCalculationType; calculationType: PerformanceCalculationType;
currency: string; currency: string;
dateRange?: DateRange; dateRange?: DateRange;
userId: string;
}): PortfolioCalculator { }): PortfolioCalculator {
switch (calculationType) { switch (calculationType) {
case PerformanceCalculationType.MWR: case PerformanceCalculationType.MWR:
@ -44,6 +48,8 @@ export class PortfolioCalculatorFactory {
activities, activities,
currency, currency,
dateRange, dateRange,
userId,
configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
redisCacheService: this.redisCacheService redisCacheService: this.redisCacheService
@ -55,6 +61,8 @@ export class PortfolioCalculatorFactory {
currency, currency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
dateRange, dateRange,
userId,
configurationService: this.configurationService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
redisCacheService: this.redisCacheService redisCacheService: this.redisCacheService
}); });

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

@ -1,7 +1,6 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface'; import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -9,6 +8,7 @@ import {
getFactor, getFactor,
getInterval getInterval
} from '@ghostfolio/api/helper/portfolio.helper'; } from '@ghostfolio/api/helper/portfolio.helper';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config'; import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
@ -26,10 +26,11 @@ import {
SymbolMetrics, SymbolMetrics,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { DateRange, GroupBy } from '@ghostfolio/common/types'; import { DateRange, GroupBy } from '@ghostfolio/common/types';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { plainToClass } from 'class-transformer';
import { import {
differenceInDays, differenceInDays,
eachDayOfInterval, eachDayOfInterval,
@ -42,6 +43,7 @@ import {
subDays subDays
} from 'date-fns'; } from 'date-fns';
import { first, last, uniq, uniqBy } from 'lodash'; import { first, last, uniq, uniqBy } from 'lodash';
import ms from 'ms';
export abstract class PortfolioCalculator { export abstract class PortfolioCalculator {
protected static readonly ENABLE_LOGGING = false; protected static readonly ENABLE_LOGGING = false;
@ -49,53 +51,74 @@ export abstract class PortfolioCalculator {
protected accountBalanceItems: HistoricalDataItem[]; protected accountBalanceItems: HistoricalDataItem[];
protected orders: PortfolioOrder[]; protected orders: PortfolioOrder[];
private configurationService: ConfigurationService;
private currency: string; private currency: string;
private currentRateService: CurrentRateService; private currentRateService: CurrentRateService;
private dataProviderInfos: DataProviderInfo[]; private dataProviderInfos: DataProviderInfo[];
private endDate: Date; private endDate: Date;
private exchangeRateDataService: ExchangeRateDataService; private exchangeRateDataService: ExchangeRateDataService;
private redisCacheService: RedisCacheService;
private snapshot: PortfolioSnapshot; private snapshot: PortfolioSnapshot;
private snapshotPromise: Promise<void>; private snapshotPromise: Promise<void>;
private startDate: Date; private startDate: Date;
private transactionPoints: TransactionPoint[]; private transactionPoints: TransactionPoint[];
private userId: string;
public constructor({ public constructor({
accountBalanceItems, accountBalanceItems,
activities, activities,
configurationService,
currency, currency,
currentRateService, currentRateService,
dateRange, dateRange,
exchangeRateDataService exchangeRateDataService,
redisCacheService,
userId
}: { }: {
accountBalanceItems: HistoricalDataItem[]; accountBalanceItems: HistoricalDataItem[];
activities: Activity[]; activities: Activity[];
configurationService: ConfigurationService;
currency: string; currency: string;
currentRateService: CurrentRateService; currentRateService: CurrentRateService;
dateRange: DateRange; dateRange: DateRange;
exchangeRateDataService: ExchangeRateDataService; exchangeRateDataService: ExchangeRateDataService;
redisCacheService: RedisCacheService; redisCacheService: RedisCacheService;
userId: string;
}) { }) {
this.accountBalanceItems = accountBalanceItems; this.accountBalanceItems = accountBalanceItems;
this.configurationService = configurationService;
this.currency = currency; this.currency = currency;
this.currentRateService = currentRateService; this.currentRateService = currentRateService;
this.exchangeRateDataService = exchangeRateDataService; this.exchangeRateDataService = exchangeRateDataService;
this.orders = activities.map(
({ date, fee, quantity, SymbolProfile, tags = [], type, unitPrice }) => { this.orders = activities
return { .map(
({
date,
fee,
quantity,
SymbolProfile, SymbolProfile,
tags, tags = [],
type, type,
date: format(date, DATE_FORMAT), unitPrice
fee: new Big(fee), }) => {
quantity: new Big(quantity), return {
unitPrice: new Big(unitPrice) SymbolProfile,
}; tags,
} type,
); date: format(date, DATE_FORMAT),
fee: new Big(fee),
quantity: new Big(quantity),
unitPrice: new Big(unitPrice)
};
}
)
.sort((a, b) => {
return a.date?.localeCompare(b.date);
});
this.orders.sort((a, b) => { this.redisCacheService = redisCacheService;
return a.date?.localeCompare(b.date); this.userId = userId;
});
const { endDate, startDate } = getInterval(dateRange); const { endDate, startDate } = getInterval(dateRange);
@ -1013,6 +1036,23 @@ export abstract class PortfolioCalculator {
} }
private async initialize() { private async initialize() {
this.snapshot = await this.computeSnapshot(this.startDate, this.endDate); const cachedSnapshot = await this.redisCacheService.get(
this.redisCacheService.getPortfolioSnapshotKey(this.userId)
);
if (cachedSnapshot) {
this.snapshot = plainToClass(
PortfolioSnapshot,
JSON.parse(cachedSnapshot)
);
} else {
this.snapshot = await this.computeSnapshot(this.startDate, this.endDate);
this.redisCacheService.set(
this.redisCacheService.getPortfolioSnapshotKey(this.userId),
JSON.stringify(this.snapshot),
this.configurationService.get('CACHE_QUOTES_TTL')
);
}
} }
} }

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -116,7 +122,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PerformanceCalculationType, PerformanceCalculationType,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -101,7 +107,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -86,7 +92,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

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

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
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 { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -48,12 +50,15 @@ jest.mock(
); );
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -66,6 +71,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -114,7 +120,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -86,7 +92,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD' currency: 'USD',
userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.computeSnapshot( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
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 { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -48,12 +50,15 @@ jest.mock(
); );
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -66,6 +71,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -99,7 +105,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -86,7 +92,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD' currency: 'USD',
userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.computeSnapshot( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PortfolioCalculatorFactory, PortfolioCalculatorFactory,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -86,7 +92,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD' currency: 'USD',
userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.computeSnapshot( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PerformanceCalculationType, PerformanceCalculationType,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
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 { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -48,12 +50,15 @@ jest.mock(
); );
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -66,6 +71,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -114,7 +120,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD' currency: 'USD',
userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.computeSnapshot( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(

9
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts

@ -1,3 +1,4 @@
import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PerformanceCalculationType, PerformanceCalculationType,
PortfolioCalculatorFactory PortfolioCalculatorFactory
@ -6,6 +7,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -31,12 +33,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -49,6 +54,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -64,7 +70,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities: [], activities: [],
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const start = subDays(new Date(Date.now()), 10); const start = subDays(new Date(Date.now()), 10);

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

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PerformanceCalculationType, PerformanceCalculationType,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -101,7 +107,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

11
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -1,7 +1,8 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { import {
activityDummyData, activityDummyData,
symbolProfileDummyData symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import { import {
PerformanceCalculationType, PerformanceCalculationType,
@ -11,6 +12,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
@ -35,12 +37,15 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
}); });
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -53,6 +58,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService
@ -101,7 +107,8 @@ describe('PortfolioCalculator', () => {
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF',
userId: userDummyData.id
}); });
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({

5
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts

@ -1,15 +1,19 @@
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 { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let factory: PortfolioCalculatorFactory;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
configurationService = new ConfigurationService();
currentRateService = new CurrentRateService(null, null, null, null); currentRateService = new CurrentRateService(null, null, null, null);
exchangeRateDataService = new ExchangeRateDataService( exchangeRateDataService = new ExchangeRateDataService(
@ -22,6 +26,7 @@ describe('PortfolioCalculator', () => {
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( factory = new PortfolioCalculatorFactory(
configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
redisCacheService redisCacheService

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -1,10 +1,9 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface'; import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces'; import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { Big } from 'big.js'; import { Big } from 'big.js';

25
apps/api/src/app/portfolio/interfaces/portfolio-snapshot.interface.ts

@ -1,25 +0,0 @@
import { ResponseError } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import { Big } from 'big.js';
export interface PortfolioSnapshot extends ResponseError {
currentValueInBaseCurrency: Big;
grossPerformance: Big;
grossPerformanceWithCurrencyEffect: Big;
grossPerformancePercentage: Big;
grossPerformancePercentageWithCurrencyEffect: Big;
netAnnualizedPerformance?: Big;
netAnnualizedPerformanceWithCurrencyEffect?: Big;
netPerformance: Big;
netPerformanceWithCurrencyEffect: Big;
netPerformancePercentage: Big;
netPerformancePercentageWithCurrencyEffect: Big;
positions: TimelinePosition[];
totalFeesWithCurrencyEffect: Big;
totalInterestWithCurrencyEffect: Big;
totalInvestment: Big;
totalInvestmentWithCurrencyEffect: Big;
totalLiabilitiesWithCurrencyEffect: Big;
totalValuablesWithCurrencyEffect: Big;
}

6
apps/api/src/app/portfolio/portfolio.service.ts

@ -277,6 +277,7 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency currency: this.request.user.Settings.settings.baseCurrency
}); });
@ -352,6 +353,7 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
dateRange, dateRange,
userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency currency: userCurrency
}); });
@ -648,6 +650,7 @@ export class PortfolioService {
]); ]);
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
userId,
activities: orders.filter((order) => { activities: orders.filter((order) => {
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type); return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
}), }),
@ -919,6 +922,7 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
dateRange, dateRange,
userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency currency: this.request.user.Settings.settings.baseCurrency
}); });
@ -1108,6 +1112,7 @@ export class PortfolioService {
accountBalanceItems, accountBalanceItems,
activities, activities,
dateRange, dateRange,
userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency currency: userCurrency
}); });
@ -1202,6 +1207,7 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency currency: this.request.user.Settings.settings.baseCurrency
}); });

14
apps/api/src/app/redis-cache/redis-cache.service.mock.ts

@ -1 +1,13 @@
export const RedisCacheServiceMock = {}; import { RedisCacheService } from './redis-cache.service';
export const RedisCacheServiceMock = {
get: (key: string): Promise<string> => {
return Promise.resolve(null);
},
getPortfolioSnapshotKey: (userId: string): string => {
return `portfolio-snapshot-${userId}`;
},
set: (key: string, value: string, ttlInSeconds?: number): Promise<string> => {
return Promise.resolve(value);
}
};

4
apps/api/src/app/redis-cache/redis-cache.service.ts

@ -24,6 +24,10 @@ export class RedisCacheService {
return this.cache.get(key); return this.cache.get(key);
} }
public getPortfolioSnapshotKey(userId: string) {
return `portfolio-snapshot-${userId}`;
}
public getQuoteKey({ dataSource, symbol }: UniqueAsset) { public getQuoteKey({ dataSource, symbol }: UniqueAsset) {
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`; return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
} }

3
apps/api/src/events/events.module.ts

@ -1,8 +1,11 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PortfolioChangedListener } from './portfolio-changed.listener'; import { PortfolioChangedListener } from './portfolio-changed.listener';
@Module({ @Module({
imports: [RedisCacheModule],
providers: [PortfolioChangedListener] providers: [PortfolioChangedListener]
}) })
export class EventsModule {} export class EventsModule {}

8
apps/api/src/events/portfolio-changed.listener.ts

@ -1,3 +1,5 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
@ -5,11 +7,17 @@ import { PortfolioChangedEvent } from './portfolio-changed.event';
@Injectable() @Injectable()
export class PortfolioChangedListener { export class PortfolioChangedListener {
public constructor(private readonly redisCacheService: RedisCacheService) {}
@OnEvent(PortfolioChangedEvent.getName()) @OnEvent(PortfolioChangedEvent.getName())
handlePortfolioChangedEvent(event: PortfolioChangedEvent) { handlePortfolioChangedEvent(event: PortfolioChangedEvent) {
Logger.log( Logger.log(
`Portfolio of user with id ${event.getUserId()} has changed`, `Portfolio of user with id ${event.getUserId()} has changed`,
'PortfolioChangedListener' 'PortfolioChangedListener'
); );
this.redisCacheService.remove(
this.redisCacheService.getPortfolioSnapshotKey(event.getUserId())
);
} }
} }

3
libs/common/src/lib/models/index.ts

@ -1,3 +1,4 @@
import { PortfolioSnapshot } from './portfolio-snapshot';
import { TimelinePosition } from './timeline-position'; import { TimelinePosition } from './timeline-position';
export { TimelinePosition }; export { PortfolioSnapshot, TimelinePosition };

89
libs/common/src/lib/models/portfolio-snapshot.ts

@ -0,0 +1,89 @@
import { transformToBig } from '@ghostfolio/common/class-transformer';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import { Big } from 'big.js';
import { Transform, Type, plainToClass } from 'class-transformer';
export class PortfolioSnapshot {
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
currentValueInBaseCurrency: Big;
errors?: UniqueAsset[];
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
grossPerformance: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
grossPerformanceWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
grossPerformancePercentage: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
grossPerformancePercentageWithCurrencyEffect: Big;
hasErrors: boolean;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netAnnualizedPerformance?: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netAnnualizedPerformanceWithCurrencyEffect?: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netPerformance: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netPerformanceWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netPerformancePercentage: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netPerformancePercentageWithCurrencyEffect: Big;
/*@Transform(
({ value }) =>
value.map((position: any) => {
return plainToClass(TimelinePosition, position);
}),
{ toClassOnly: true }
)*/
@Type(() => TimelinePosition)
positions: TimelinePosition[];
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalFeesWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalInterestWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalInvestment: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalInvestmentWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalLiabilitiesWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
totalValuablesWithCurrencyEffect: Big;
}
Loading…
Cancel
Save