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. 83
      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. 8
      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 { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.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';
@ -30,27 +30,23 @@ export class PortfolioCalculatorFactory {
activities,
calculationType,
currency,
hasFilters,
isExperimentalFeatures = false,
filters = [],
userId
}: {
accountBalanceItems?: HistoricalDataItem[];
activities: Activity[];
calculationType: PerformanceCalculationType;
currency: string;
hasFilters: boolean;
isExperimentalFeatures?: boolean;
filters?: Filter[];
userId: string;
}): PortfolioCalculator {
const useCache = true; // TODO
switch (calculationType) {
case PerformanceCalculationType.MWR:
return new MWRPortfolioCalculator({
accountBalanceItems,
activities,
currency,
useCache,
filters,
userId,
configurationService: this.configurationService,
currentRateService: this.currentRateService,
@ -63,7 +59,7 @@ export class PortfolioCalculatorFactory {
activities,
currency,
currentRateService: this.currentRateService,
useCache,
filters,
userId,
configurationService: this.configurationService,
exchangeRateDataService: this.exchangeRateDataService,

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

@ -19,6 +19,7 @@ import {
import {
AssetProfileIdentifier,
DataProviderInfo,
Filter,
HistoricalDataItem,
InvestmentItem,
ResponseError,
@ -54,12 +55,12 @@ export abstract class PortfolioCalculator {
private dataProviderInfos: DataProviderInfo[];
private endDate: Date;
private exchangeRateDataService: ExchangeRateDataService;
private filters: Filter[];
private redisCacheService: RedisCacheService;
private snapshot: PortfolioSnapshot;
private snapshotPromise: Promise<void>;
private startDate: Date;
private transactionPoints: TransactionPoint[];
private useCache: boolean;
private userId: string;
public constructor({
@ -69,8 +70,8 @@ export abstract class PortfolioCalculator {
currency,
currentRateService,
exchangeRateDataService,
filters,
redisCacheService,
useCache,
userId
}: {
accountBalanceItems: HistoricalDataItem[];
@ -79,8 +80,8 @@ export abstract class PortfolioCalculator {
currency: string;
currentRateService: CurrentRateService;
exchangeRateDataService: ExchangeRateDataService;
filters: Filter[];
redisCacheService: RedisCacheService;
useCache: boolean;
userId: string;
}) {
this.accountBalanceItems = accountBalanceItems;
@ -88,6 +89,7 @@ export abstract class PortfolioCalculator {
this.currency = currency;
this.currentRateService = currentRateService;
this.exchangeRateDataService = exchangeRateDataService;
this.filters = filters;
let dateOfFirstActivity = new Date();
@ -128,7 +130,6 @@ export abstract class PortfolioCalculator {
});
this.redisCacheService = redisCacheService;
this.useCache = useCache;
this.userId = userId;
const { endDate, startDate } = getIntervalFromDateRange(
@ -1007,49 +1008,47 @@ export abstract class PortfolioCalculator {
}
private async initialize() {
if (this.useCache) {
const startTimeTotal = performance.now();
const startTimeTotal = performance.now();
const cachedSnapshot = await this.redisCacheService.get(
this.redisCacheService.getPortfolioSnapshotKey({
userId: this.userId
})
);
if (cachedSnapshot) {
this.snapshot = plainToClass(
PortfolioSnapshot,
JSON.parse(cachedSnapshot)
);
const cachedSnapshot = await this.redisCacheService.get(
this.redisCacheService.getPortfolioSnapshotKey({
filters: this.filters,
userId: this.userId
})
);
Logger.debug(
`Fetched portfolio snapshot from cache in ${(
(performance.now() - startTimeTotal) /
1000
).toFixed(3)} seconds`,
'PortfolioCalculator'
);
} else {
this.snapshot = await this.computeSnapshot();
this.redisCacheService.set(
this.redisCacheService.getPortfolioSnapshotKey({
userId: this.userId
}),
JSON.stringify(this.snapshot),
this.configurationService.get('CACHE_QUOTES_TTL')
);
if (cachedSnapshot) {
this.snapshot = plainToClass(
PortfolioSnapshot,
JSON.parse(cachedSnapshot)
);
Logger.debug(
`Computed portfolio snapshot in ${(
(performance.now() - startTimeTotal) /
1000
).toFixed(3)} seconds`,
'PortfolioCalculator'
);
}
Logger.debug(
`Fetched portfolio snapshot from cache in ${(
(performance.now() - startTimeTotal) /
1000
).toFixed(3)} seconds`,
'PortfolioCalculator'
);
} else {
this.snapshot = await this.computeSnapshot();
this.redisCacheService.set(
this.redisCacheService.getPortfolioSnapshotKey({
filters: this.filters,
userId: this.userId
}),
JSON.stringify(this.snapshot),
this.configurationService.get('CACHE_QUOTES_TTL')
);
Logger.debug(
`Computed portfolio snapshot in ${(
(performance.now() - startTimeTotal) /
1000
).toFixed(3)} seconds`,
'PortfolioCalculator'
);
}
}
}

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,
calculationType: PerformanceCalculationType.TWR,
currency: 'CHF',
hasFilters: false,
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,
calculationType: PerformanceCalculationType.TWR,
currency: 'CHF',
hasFilters: false,
userId: userDummyData.id
});

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -264,12 +264,10 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({
activities,
filters,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency,
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
currency: this.request.user.Settings.settings.baseCurrency
});
const { historicalData } = await portfolioCalculator.getSnapshot();
@ -343,12 +341,10 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({
activities,
filters,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: userCurrency,
hasFilters: filters?.length > 0, // TODO
isExperimentalFeatures:
this.request.user?.Settings.settings.isExperimentalFeatures
currency: userCurrency
});
const { currentValueInBaseCurrency, hasErrors, positions } =
@ -652,18 +648,13 @@ export class PortfolioService {
{ dataSource: aDataSource, symbol: aSymbol }
]);
// TODO: Always use same parameters when calling
// this.calculatorFactory.createCalculator()
const portfolioCalculator = this.calculatorFactory.createCalculator({
userId,
activities: orders.filter((order) => {
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
}),
calculationType: PerformanceCalculationType.TWR,
currency: userCurrency,
hasFilters: true,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
currency: userCurrency
});
const portfolioStart = portfolioCalculator.getStartDate();
@ -933,12 +924,10 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({
activities,
filters,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency,
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
currency: this.request.user.Settings.settings.baseCurrency
});
let { hasErrors, positions } = await portfolioCalculator.getSnapshot();
@ -1125,12 +1114,10 @@ export class PortfolioService {
this.calculatorFactory.createCalculator({
accountBalanceItems,
activities,
filters,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: userCurrency,
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
currency: userCurrency
});
const { errors, hasErrors, historicalData } =
@ -1194,10 +1181,7 @@ export class PortfolioService {
activities,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency,
hasFilters: false,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
currency: this.request.user.Settings.settings.baseCurrency
});
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 { 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 { Inject, Injectable, Logger } from '@nestjs/common';
import { createHash } from 'crypto';
import type { RedisCache } from './interfaces/redis-cache.interface';
@ -24,8 +25,28 @@ export class RedisCacheService {
return this.cache.get(key);
}
public getPortfolioSnapshotKey({ userId }: { userId: string }) {
return `portfolio-snapshot-${userId}`;
public async getKeys(aPrefix?: string): Promise<string[]> {
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) {
@ -36,6 +57,22 @@ export class RedisCacheService {
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() {
return this.cache.reset();
}

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

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

Loading…
Cancel
Save