Feature/improve caching of benchmarks in markets overview (#3640)
* Improve caching * Update changelog
This commit is contained in:
parent
42fe653e1e
commit
b2ed0b2c80
@ -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);
|
||||||
|
|
||||||
|
if (isAfter(new Date(), new Date(expiration))) {
|
||||||
|
this.calculateAndCacheBenchmarks({
|
||||||
|
enableSharing
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return benchmarks;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const benchmarkAssetProfiles = await this.getBenchmarkAssetProfiles({
|
return 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([
|
|
||||||
Promise.all(promisesAllTimeHighs),
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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…
x
Reference in New Issue
Block a user