Browse Source

Pass filters to portfolio calculator for caching

pull/3393/head
Thomas Kaul 1 year ago
parent
commit
ebc65ea631
  1. 14
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  2. 15
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  3. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  4. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts
  5. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts
  6. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  7. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts
  8. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts
  9. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts
  10. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts
  11. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts
  12. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts
  13. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  14. 1
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts
  15. 36
      apps/api/src/app/portfolio/portfolio.service.ts
  16. 43
      apps/api/src/app/redis-cache/redis-cache.service.ts
  17. 6
      apps/api/src/events/portfolio-changed.listener.ts

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

@ -3,7 +3,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
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 { 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 { Filter, HistoricalDataItem } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -30,27 +30,23 @@ export class PortfolioCalculatorFactory {
activities, activities,
calculationType, calculationType,
currency, currency,
hasFilters, filters = [],
isExperimentalFeatures = false,
userId userId
}: { }: {
accountBalanceItems?: HistoricalDataItem[]; accountBalanceItems?: HistoricalDataItem[];
activities: Activity[]; activities: Activity[];
calculationType: PerformanceCalculationType; calculationType: PerformanceCalculationType;
currency: string; currency: string;
hasFilters: boolean; filters?: Filter[];
isExperimentalFeatures?: boolean;
userId: string; userId: string;
}): PortfolioCalculator { }): PortfolioCalculator {
const useCache = true; // TODO
switch (calculationType) { switch (calculationType) {
case PerformanceCalculationType.MWR: case PerformanceCalculationType.MWR:
return new MWRPortfolioCalculator({ return new MWRPortfolioCalculator({
accountBalanceItems, accountBalanceItems,
activities, activities,
currency, currency,
useCache, filters,
userId, userId,
configurationService: this.configurationService, configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
@ -63,7 +59,7 @@ export class PortfolioCalculatorFactory {
activities, activities,
currency, currency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
useCache, filters,
userId, userId,
configurationService: this.configurationService, configurationService: this.configurationService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,

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

@ -19,6 +19,7 @@ import {
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
DataProviderInfo, DataProviderInfo,
Filter,
HistoricalDataItem, HistoricalDataItem,
InvestmentItem, InvestmentItem,
ResponseError, ResponseError,
@ -54,12 +55,12 @@ export abstract class PortfolioCalculator {
private dataProviderInfos: DataProviderInfo[]; private dataProviderInfos: DataProviderInfo[];
private endDate: Date; private endDate: Date;
private exchangeRateDataService: ExchangeRateDataService; private exchangeRateDataService: ExchangeRateDataService;
private filters: Filter[];
private redisCacheService: RedisCacheService; 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 useCache: boolean;
private userId: string; private userId: string;
public constructor({ public constructor({
@ -69,8 +70,8 @@ export abstract class PortfolioCalculator {
currency, currency,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
filters,
redisCacheService, redisCacheService,
useCache,
userId userId
}: { }: {
accountBalanceItems: HistoricalDataItem[]; accountBalanceItems: HistoricalDataItem[];
@ -79,8 +80,8 @@ export abstract class PortfolioCalculator {
currency: string; currency: string;
currentRateService: CurrentRateService; currentRateService: CurrentRateService;
exchangeRateDataService: ExchangeRateDataService; exchangeRateDataService: ExchangeRateDataService;
filters: Filter[];
redisCacheService: RedisCacheService; redisCacheService: RedisCacheService;
useCache: boolean;
userId: string; userId: string;
}) { }) {
this.accountBalanceItems = accountBalanceItems; this.accountBalanceItems = accountBalanceItems;
@ -88,6 +89,7 @@ export abstract class PortfolioCalculator {
this.currency = currency; this.currency = currency;
this.currentRateService = currentRateService; this.currentRateService = currentRateService;
this.exchangeRateDataService = exchangeRateDataService; this.exchangeRateDataService = exchangeRateDataService;
this.filters = filters;
let dateOfFirstActivity = new Date(); let dateOfFirstActivity = new Date();
@ -128,7 +130,6 @@ export abstract class PortfolioCalculator {
}); });
this.redisCacheService = redisCacheService; this.redisCacheService = redisCacheService;
this.useCache = useCache;
this.userId = userId; this.userId = userId;
const { endDate, startDate } = getIntervalFromDateRange( const { endDate, startDate } = getIntervalFromDateRange(
@ -1007,11 +1008,11 @@ export abstract class PortfolioCalculator {
} }
private async initialize() { private async initialize() {
if (this.useCache) {
const startTimeTotal = performance.now(); const startTimeTotal = performance.now();
const cachedSnapshot = await this.redisCacheService.get( const cachedSnapshot = await this.redisCacheService.get(
this.redisCacheService.getPortfolioSnapshotKey({ this.redisCacheService.getPortfolioSnapshotKey({
filters: this.filters,
userId: this.userId userId: this.userId
}) })
); );
@ -1034,6 +1035,7 @@ export abstract class PortfolioCalculator {
this.redisCacheService.set( this.redisCacheService.set(
this.redisCacheService.getPortfolioSnapshotKey({ this.redisCacheService.getPortfolioSnapshotKey({
filters: this.filters,
userId: this.userId userId: this.userId
}), }),
JSON.stringify(this.snapshot), JSON.stringify(this.snapshot),
@ -1048,8 +1050,5 @@ export abstract class PortfolioCalculator {
'PortfolioCalculator' 'PortfolioCalculator'
); );
} }
} else {
this.snapshot = await this.computeSnapshot();
}
} }
} }

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

@ -121,7 +121,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -106,7 +106,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -92,7 +92,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -120,7 +120,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -91,7 +91,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -104,7 +104,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -91,7 +91,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -91,7 +91,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -119,7 +119,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -69,7 +69,6 @@ describe('PortfolioCalculator', () => {
activities: [], activities: [],
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -106,7 +106,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -106,7 +106,6 @@ describe('PortfolioCalculator', () => {
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
hasFilters: false,
userId: userDummyData.id userId: userDummyData.id
}); });

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

@ -264,12 +264,10 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
filters,
userId, userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency, currency: this.request.user.Settings.settings.baseCurrency
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
}); });
const { historicalData } = await portfolioCalculator.getSnapshot(); const { historicalData } = await portfolioCalculator.getSnapshot();
@ -343,12 +341,10 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
filters,
userId, userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency, currency: userCurrency
hasFilters: filters?.length > 0, // TODO
isExperimentalFeatures:
this.request.user?.Settings.settings.isExperimentalFeatures
}); });
const { currentValueInBaseCurrency, hasErrors, positions } = const { currentValueInBaseCurrency, hasErrors, positions } =
@ -652,18 +648,13 @@ export class PortfolioService {
{ dataSource: aDataSource, symbol: aSymbol } { dataSource: aDataSource, symbol: aSymbol }
]); ]);
// TODO: Always use same parameters when calling
// this.calculatorFactory.createCalculator()
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
userId, 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);
}), }),
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency, currency: userCurrency
hasFilters: true,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
}); });
const portfolioStart = portfolioCalculator.getStartDate(); const portfolioStart = portfolioCalculator.getStartDate();
@ -933,12 +924,10 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
filters,
userId, userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency, currency: this.request.user.Settings.settings.baseCurrency
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
}); });
let { hasErrors, positions } = await portfolioCalculator.getSnapshot(); let { hasErrors, positions } = await portfolioCalculator.getSnapshot();
@ -1125,12 +1114,10 @@ export class PortfolioService {
this.calculatorFactory.createCalculator({ this.calculatorFactory.createCalculator({
accountBalanceItems, accountBalanceItems,
activities, activities,
filters,
userId, userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency, currency: userCurrency
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
}); });
const { errors, hasErrors, historicalData } = const { errors, hasErrors, historicalData } =
@ -1194,10 +1181,7 @@ export class PortfolioService {
activities, activities,
userId, userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency, currency: this.request.user.Settings.settings.baseCurrency
hasFilters: false,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
}); });
let { totalFeesWithCurrencyEffect, positions, totalInvestment } = let { totalFeesWithCurrencyEffect, positions, totalInvestment } =

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

@ -1,9 +1,10 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { AssetProfileIdentifier, Filter } from '@ghostfolio/common/interfaces';
import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable, Logger } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { createHash } from 'crypto';
import type { RedisCache } from './interfaces/redis-cache.interface'; import type { RedisCache } from './interfaces/redis-cache.interface';
@ -24,8 +25,28 @@ export class RedisCacheService {
return this.cache.get(key); return this.cache.get(key);
} }
public getPortfolioSnapshotKey({ userId }: { userId: string }) { public async getKeys(aPrefix?: string): Promise<string[]> {
return `portfolio-snapshot-${userId}`; return this.cache.store.keys(aPrefix);
}
public getPortfolioSnapshotKey({
filters,
userId
}: {
filters?: Filter[];
userId: string;
}) {
let portfolioSnapshotKey = `portfolio-snapshot-${userId}`;
if (filters?.length > 0) {
const filtersHash = createHash('sha256')
.update(JSON.stringify(filters))
.digest('hex');
portfolioSnapshotKey = `${portfolioSnapshotKey}-${filtersHash}`;
}
return portfolioSnapshotKey;
} }
public getQuoteKey({ dataSource, symbol }: AssetProfileIdentifier) { public getQuoteKey({ dataSource, symbol }: AssetProfileIdentifier) {
@ -36,6 +57,22 @@ export class RedisCacheService {
return this.cache.del(key); return this.cache.del(key);
} }
public async removePortfolioSnapshotsByUserId({
userId
}: {
userId: string;
}) {
const keys = await this.getKeys(
`${this.getPortfolioSnapshotKey({ userId })}`
);
console.log(keys);
for (const key of keys) {
this.remove(key);
}
}
public async reset() { public async reset() {
return this.cache.reset(); return this.cache.reset();
} }

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

@ -16,10 +16,8 @@ export class PortfolioChangedListener {
'PortfolioChangedListener' 'PortfolioChangedListener'
); );
this.redisCacheService.remove( this.redisCacheService.removePortfolioSnapshotsByUserId({
this.redisCacheService.getPortfolioSnapshotKey({
userId: event.getUserId() userId: event.getUserId()
}) });
);
} }
} }

Loading…
Cancel
Save