Feature/improve caching of benchmarks in markets overview (#3640)

* Improve caching

* Update changelog
pull/3621/merge
Thomas Kaul 6 months ago committed by GitHub
parent 42fe653e1e
commit b2ed0b2c80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Improved the caching of the benchmarks in the markets overview by returning cached data and recalculating in the background when it expires
- Improved the language localization for German (`de`) - Improved the language localization for German (`de`)
- Improved the language localization for Polish (`pl`) - Improved the language localization for Polish (`pl`)
- Upgraded `Nx` from version `19.5.1` to `19.5.6` - Upgraded `Nx` from version `19.5.1` to `19.5.6`

@ -29,15 +29,19 @@ import { Injectable, Logger } from '@nestjs/common';
import { SymbolProfile } from '@prisma/client'; import { SymbolProfile } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { import {
addHours,
differenceInDays, differenceInDays,
eachDayOfInterval, eachDayOfInterval,
format, format,
isAfter,
isSameDay, isSameDay,
subDays subDays
} from 'date-fns'; } from 'date-fns';
import { isNumber, last, uniqBy } from 'lodash'; import { isNumber, last, uniqBy } from 'lodash';
import ms from 'ms'; import ms from 'ms';
import { BenchmarkValue } from './interfaces/benchmark-value.interface';
@Injectable() @Injectable()
export class BenchmarkService { export class BenchmarkService {
private readonly CACHE_KEY_BENCHMARKS = 'BENCHMARKS'; private readonly CACHE_KEY_BENCHMARKS = 'BENCHMARKS';
@ -92,99 +96,26 @@ export class BenchmarkService {
enableSharing = false, enableSharing = false,
useCache = true useCache = true
} = {}): Promise<BenchmarkResponse['benchmarks']> { } = {}): Promise<BenchmarkResponse['benchmarks']> {
let benchmarks: BenchmarkResponse['benchmarks'];
if (useCache) { if (useCache) {
try { try {
benchmarks = JSON.parse( const cachedBenchmarkValue = await this.redisCacheService.get(
await this.redisCacheService.get(this.CACHE_KEY_BENCHMARKS) this.CACHE_KEY_BENCHMARKS
); );
if (benchmarks) { const { benchmarks, expiration }: BenchmarkValue =
return benchmarks; JSON.parse(cachedBenchmarkValue);
}
} catch {}
}
const benchmarkAssetProfiles = await this.getBenchmarkAssetProfiles({ if (isAfter(new Date(), new Date(expiration))) {
this.calculateAndCacheBenchmarks({
enableSharing enableSharing
}); });
const promisesAllTimeHighs: Promise<{ date: Date; marketPrice: number }>[] =
[];
const promisesBenchmarkTrends: Promise<{
trend50d: BenchmarkTrend;
trend200d: BenchmarkTrend;
}>[] = [];
const quotes = await this.dataProviderService.getQuotes({
items: benchmarkAssetProfiles.map(({ dataSource, symbol }) => {
return { dataSource, symbol };
}),
requestTimeout: ms('30 seconds'),
useCache: false
});
for (const { dataSource, symbol } of benchmarkAssetProfiles) {
promisesAllTimeHighs.push(
this.marketDataService.getMax({ dataSource, symbol })
);
promisesBenchmarkTrends.push(
this.getBenchmarkTrends({ dataSource, symbol })
);
} }
const [allTimeHighs, benchmarkTrends] = await Promise.all([ return benchmarks;
Promise.all(promisesAllTimeHighs), } catch {}
Promise.all(promisesBenchmarkTrends)
]);
let storeInCache = useCache;
benchmarks = allTimeHighs.map((allTimeHigh, index) => {
const { marketPrice } =
quotes[benchmarkAssetProfiles[index].symbol] ?? {};
let performancePercentFromAllTimeHigh = 0;
if (allTimeHigh?.marketPrice && marketPrice) {
performancePercentFromAllTimeHigh = this.calculateChangeInPercentage(
allTimeHigh.marketPrice,
marketPrice
);
} else {
storeInCache = false;
}
return {
dataSource: benchmarkAssetProfiles[index].dataSource,
marketCondition: this.getMarketCondition(
performancePercentFromAllTimeHigh
),
name: benchmarkAssetProfiles[index].name,
performances: {
allTimeHigh: {
date: allTimeHigh?.date,
performancePercent:
performancePercentFromAllTimeHigh >= 0
? 0
: performancePercentFromAllTimeHigh
}
},
symbol: benchmarkAssetProfiles[index].symbol,
trend50d: benchmarkTrends[index].trend50d,
trend200d: benchmarkTrends[index].trend200d
};
});
if (storeInCache) {
await this.redisCacheService.set(
this.CACHE_KEY_BENCHMARKS,
JSON.stringify(benchmarks),
ms('2 hours') / 1000
);
} }
return benchmarks; return this.calculateAndCacheBenchmarks({ enableSharing });
} }
public async getBenchmarkAssetProfiles({ public async getBenchmarkAssetProfiles({
@ -422,6 +353,95 @@ export class BenchmarkService {
}; };
} }
private async calculateAndCacheBenchmarks({
enableSharing = false
}): Promise<BenchmarkResponse['benchmarks']> {
const benchmarkAssetProfiles = await this.getBenchmarkAssetProfiles({
enableSharing
});
const promisesAllTimeHighs: Promise<{ date: Date; marketPrice: number }>[] =
[];
const promisesBenchmarkTrends: Promise<{
trend50d: BenchmarkTrend;
trend200d: BenchmarkTrend;
}>[] = [];
const quotes = await this.dataProviderService.getQuotes({
items: benchmarkAssetProfiles.map(({ dataSource, symbol }) => {
return { dataSource, symbol };
}),
requestTimeout: ms('30 seconds'),
useCache: false
});
for (const { dataSource, symbol } of benchmarkAssetProfiles) {
promisesAllTimeHighs.push(
this.marketDataService.getMax({ dataSource, symbol })
);
promisesBenchmarkTrends.push(
this.getBenchmarkTrends({ dataSource, symbol })
);
}
const [allTimeHighs, benchmarkTrends] = await Promise.all([
Promise.all(promisesAllTimeHighs),
Promise.all(promisesBenchmarkTrends)
]);
let storeInCache = true;
const benchmarks = allTimeHighs.map((allTimeHigh, index) => {
const { marketPrice } =
quotes[benchmarkAssetProfiles[index].symbol] ?? {};
let performancePercentFromAllTimeHigh = 0;
if (allTimeHigh?.marketPrice && marketPrice) {
performancePercentFromAllTimeHigh = this.calculateChangeInPercentage(
allTimeHigh.marketPrice,
marketPrice
);
} else {
storeInCache = false;
}
return {
dataSource: benchmarkAssetProfiles[index].dataSource,
marketCondition: this.getMarketCondition(
performancePercentFromAllTimeHigh
),
name: benchmarkAssetProfiles[index].name,
performances: {
allTimeHigh: {
date: allTimeHigh?.date,
performancePercent:
performancePercentFromAllTimeHigh >= 0
? 0
: performancePercentFromAllTimeHigh
}
},
symbol: benchmarkAssetProfiles[index].symbol,
trend50d: benchmarkTrends[index].trend50d,
trend200d: benchmarkTrends[index].trend200d
};
});
if (storeInCache) {
const expiration = addHours(new Date(), 2);
await this.redisCacheService.set(
this.CACHE_KEY_BENCHMARKS,
JSON.stringify(<BenchmarkValue>{
benchmarks,
expiration: expiration.getTime()
}),
ms('12 hours') / 1000
);
}
return benchmarks;
}
private getMarketCondition( private getMarketCondition(
aPerformanceInPercent: number aPerformanceInPercent: number
): Benchmark['marketCondition'] { ): Benchmark['marketCondition'] {

@ -0,0 +1,6 @@
import { BenchmarkResponse } from '@ghostfolio/common/interfaces';
export interface BenchmarkValue {
benchmarks: BenchmarkResponse['benchmarks'];
expiration: number;
}
Loading…
Cancel
Save